def run_weis(fname_wt_input, fname_modeling_options, fname_opt_options, overridden_values=None): # Load all yaml inputs and validate (also fills in defaults) wt_initial = WindTurbineOntologyPythonWEIS(fname_wt_input, fname_modeling_options, fname_opt_options) wt_init, modeling_options, opt_options = wt_initial.get_input_data() # Initialize openmdao problem. If running with multiple processors in MPI, use parallel finite differencing equal to the number of cores used. # Otherwise, initialize the WindPark system normally. Get the rank number for parallelization. We only print output files using the root processor. blade_opt_options = opt_options['optimization_variables']['blade'] tower_opt_options = opt_options['optimization_variables']['tower'] control_opt_options = opt_options['optimization_variables']['control'] if MPI: # Determine the number of design variables n_DV = 0 if blade_opt_options['aero_shape']['twist']['flag']: n_DV += blade_opt_options['aero_shape']['twist']['n_opt'] - 2 if blade_opt_options['aero_shape']['chord']['flag']: n_DV += blade_opt_options['aero_shape']['chord']['n_opt'] - 3 if blade_opt_options['aero_shape']['af_positions']['flag']: n_DV += modeling_options['blade']['n_af_span'] - blade_opt_options[ 'aero_shape']['af_positions']['af_start'] - 1 if blade_opt_options['structure']['spar_cap_ss']['flag']: n_DV += blade_opt_options['structure']['spar_cap_ss']['n_opt'] - 2 if blade_opt_options['structure']['spar_cap_ps'][ 'flag'] and not blade_opt_options['structure']['spar_cap_ps'][ 'equal_to_suction']: n_DV += blade_opt_options['structure']['spar_cap_ps']['n_opt'] - 2 if opt_options['optimization_variables']['control']['tsr']['flag']: n_DV += 1 if opt_options['optimization_variables']['control']['servo'][ 'pitch_control']['flag']: n_DV += 2 if opt_options['optimization_variables']['control']['servo'][ 'torque_control']['flag']: n_DV += 2 if opt_options['optimization_variables']['control']['servo'][ 'flap_control']['flag']: n_DV += 2 if 'dac' in blade_opt_options: if blade_opt_options['dac']['te_flap_end']['flag']: n_DV += modeling_options['blade']['n_te_flaps'] if blade_opt_options['dac']['te_flap_ext']['flag']: n_DV += modeling_options['blade']['n_te_flaps'] if tower_opt_options['outer_diameter']['flag']: n_DV += modeling_options['tower']['n_height'] if tower_opt_options['layer_thickness']['flag']: n_DV += (modeling_options['tower']['n_height'] - 1) * modeling_options['tower']['n_layers'] if opt_options['driver']['form'] == 'central': n_DV *= 2 # Extract the number of cores available max_cores = MPI.COMM_WORLD.Get_size() if max_cores / 2. != np.round(max_cores / 2.): exit( 'ERROR: the parallelization logic only works for an even number of cores available' ) # Define the color map for the parallelization, determining the maximum number of parallel finite difference (FD) evaluations based on the number of design variables (DV). OpenFAST on/off changes things. if modeling_options['Analysis_Flags']['OpenFAST']: # If openfast is called, the maximum number of FD is the number of DV, if we have the number of cores available that doubles the number of DVs, otherwise it is half of the number of DV (rounded to the lower integer). We need this because a top layer of cores calls a bottom set of cores where OpenFAST runs. if max_cores > 2. * n_DV: n_FD = n_DV else: n_FD = int(np.floor(max_cores / 2)) # The number of OpenFAST runs is the minimum between the actual number of requested OpenFAST simulations, and the number of cores available (minus the number of DV, which sit and wait for OF to complete) # need to calculate the number of OpenFAST runs from the user input n_OF_runs = 0 if modeling_options['openfast']['dlc_settings']['run_power_curve']: if modeling_options['openfast']['dlc_settings']['Power_Curve'][ 'turbulent_power_curve']: n_OF_runs += len( modeling_options['openfast']['dlc_settings'] ['Power_Curve']['U']) * len( modeling_options['openfast']['dlc_settings'] ['Power_Curve']['Seeds']) else: n_OF_runs += len(modeling_options['openfast'] ['dlc_settings']['Power_Curve']['U']) if modeling_options['openfast']['dlc_settings'][ 'run_IEC'] or modeling_options['openfast']['dlc_settings'][ 'run_blade_fatigue']: for dlc in modeling_options['openfast']['dlc_settings']['IEC']: dlc_vars = list(dlc.keys()) # Number of wind speeds if 'U' not in dlc_vars: if dlc['DLC'] == 1.4: # assuming 1.4 is run at [V_rated-2, V_rated, V_rated] and +/- direction change n_U = 6 elif dlc[ 'DLC'] == 5.1: # assuming 1.4 is run at [V_rated-2, V_rated, V_rated] n_U = 3 elif dlc['DLC'] in [ 6.1, 6.3 ]: # assuming V_50 for [-8, 8] deg yaw error n_U = 2 else: print( 'Warning: for OpenFAST DLC %1.1f specified in the Analysis Options, wind speeds "U" must be provided' % dlc['DLC']) else: n_U = len(dlc['U']) # Number of seeds if 'Seeds' not in dlc_vars: if dlc['DLC'] == 1.4: # not turbulent n_Seeds = 1 else: print( 'Warning: for OpenFAST DLC %1.1f specified in the Analysis Options, turbulent seeds "Seeds" must be provided' % dlc['DLC']) else: n_Seeds = len(dlc['Seeds']) n_OF_runs += n_U * n_Seeds n_DV = max([n_DV, 1]) max_parallel_OF_runs = max( [int(np.floor((max_cores - n_DV) / n_DV)), 1]) n_OF_runs_parallel = min([int(n_OF_runs), max_parallel_OF_runs]) modeling_options['openfast']['dlc_settings'][ 'n_OF_runs'] = n_OF_runs else: # If OpenFAST is not called, the number of parallel calls to compute the FDs is just equal to the minimum of cores available and DV n_FD = min([max_cores, n_DV]) n_OF_runs_parallel = 1 # Define the color map for the cores (how these are distributed between finite differencing and openfast runs) n_FD = max([n_FD, 1]) comm_map_down, comm_map_up, color_map = map_comm_heirarchical( n_FD, n_OF_runs_parallel) rank = MPI.COMM_WORLD.Get_rank() color_i = color_map[rank] comm_i = MPI.COMM_WORLD.Split(color_i, 1) else: color_i = 0 rank = 0 folder_output = opt_options['general']['folder_output'] if rank == 0 and not os.path.isdir(folder_output): os.mkdir(folder_output) if color_i == 0: # the top layer of cores enters, the others sit and wait to run openfast simulations if MPI: if modeling_options['Analysis_Flags']['OpenFAST']: # Parallel settings for OpenFAST modeling_options['openfast']['analysis_settings'][ 'mpi_run'] = True modeling_options['openfast']['analysis_settings'][ 'mpi_comm_map_down'] = comm_map_down modeling_options['openfast']['analysis_settings'][ 'cores'] = n_OF_runs_parallel # Parallel settings for OpenMDAO wt_opt = om.Problem(model=om.Group(num_par_fd=n_FD), comm=comm_i) wt_opt.model.add_subsystem('comp', WindPark( modeling_options=modeling_options, opt_options=opt_options), promotes=['*']) else: # Sequential finite differencing and openfast simulations modeling_options['openfast']['analysis_settings']['cores'] = 1 wt_opt = om.Problem(model=WindPark( modeling_options=modeling_options, opt_options=opt_options)) # If at least one of the design variables is active, setup an optimization if opt_options['opt_flag']: # If a step size for the driver-level finite differencing is provided, use that step size. Otherwise use a default value. if 'step_size' in opt_options['driver']: step_size = opt_options['driver']['step_size'] else: step_size = 1.e-6 # Solver has specific meaning in OpenMDAO wt_opt.model.approx_totals(method='fd', step=step_size, form=opt_options['driver']['form']) # Set optimization solver and options. First, Scipy's SLSQP if opt_options['driver']['solver'] == 'SLSQP': wt_opt.driver = om.ScipyOptimizeDriver() wt_opt.driver.options['optimizer'] = opt_options['driver'][ 'solver'] wt_opt.driver.options['tol'] = opt_options['driver']['tol'] wt_opt.driver.options['maxiter'] = opt_options['driver'][ 'max_iter'] # The next two optimization methods require pyOptSparse. elif opt_options['driver']['solver'] == 'CONMIN': try: from openmdao.api import pyOptSparseDriver except: exit( 'You requested the optimization solver CONMIN, but you have not installed the pyOptSparseDriver. Please do so and rerun.' ) wt_opt.driver = pyOptSparseDriver() wt_opt.driver.options['optimizer'] = opt_options['driver'][ 'solver'] wt_opt.driver.opt_settings['ITMAX'] = opt_options['driver'][ 'max_iter'] elif opt_options['driver']['solver'] == 'SNOPT': try: from openmdao.api import pyOptSparseDriver except: exit( 'You requested the optimization solver SNOPT which requires pyOptSparse to be installed, but it cannot be found. Please install pyOptSparse and rerun.' ) wt_opt.driver = pyOptSparseDriver() try: wt_opt.driver.options['optimizer'] = opt_options['driver'][ 'solver'] except: exit( 'You requested the optimization solver SNOPT, but you have not installed it within the pyOptSparseDriver. Please do so and rerun.' ) wt_opt.driver.opt_settings[ 'Major optimality tolerance'] = float( opt_options['driver']['tol']) wt_opt.driver.opt_settings['Major iterations limit'] = int( opt_options['driver']['max_major_iter']) wt_opt.driver.opt_settings['Iterations limit'] = int( opt_options['driver']['max_minor_iter']) wt_opt.driver.opt_settings[ 'Major feasibility tolerance'] = float( opt_options['driver']['tol']) wt_opt.driver.opt_settings['Summary file'] = os.path.join( folder_output, 'SNOPT_Summary_file.txt') wt_opt.driver.opt_settings['Print file'] = os.path.join( folder_output, 'SNOPT_Print_file.txt') if 'hist_file_name' in opt_options['driver']: wt_opt.driver.hist_file = opt_options['driver'][ 'hist_file_name'] if 'verify_level' in opt_options['driver']: wt_opt.driver.opt_settings['Verify level'] = opt_options[ 'driver']['verify_level'] # wt_opt.driver.declare_coloring() if 'hotstart_file' in opt_options['driver']: wt_opt.driver.hotstart_file = opt_options['driver'][ 'hotstart_file'] else: exit('The optimizer ' + opt_options['driver']['solver'] + 'is not yet supported!') # Set merit figure. Each objective has its own scaling. if opt_options['merit_figure'] == 'AEP': wt_opt.model.add_objective('sse.AEP', ref=-1.e6) elif opt_options['merit_figure'] == 'blade_mass': wt_opt.model.add_objective('elastic.precomp.blade_mass', ref=1.e4) elif opt_options['merit_figure'] == 'LCOE': wt_opt.model.add_objective('financese.lcoe', ref=0.1) elif opt_options['merit_figure'] == 'blade_tip_deflection': wt_opt.model.add_objective('tcons.tip_deflection_ratio') elif opt_options['merit_figure'] == 'tower_mass': wt_opt.model.add_objective('towerse.tower_mass') elif opt_options['merit_figure'] == 'tower_cost': wt_opt.model.add_objective('tcc.tower_cost') elif opt_options['merit_figure'] == 'Cp': if modeling_options['Analysis_Flags']['ServoSE']: wt_opt.model.add_objective('sse.powercurve.Cp_regII', ref=-1.) else: wt_opt.model.add_objective('ccblade.CP', ref=-1.) elif opt_options[ 'merit_figure'] == 'My_std': # for DAC optimization on root-flap-bending moments wt_opt.model.add_objective('aeroelastic.My_std', ref=1.e6) elif opt_options[ 'merit_figure'] == 'DEL_RootMyb': # for DAC optimization on root-flap-bending moments wt_opt.model.add_objective('aeroelastic.DEL_RootMyb', ref=1.e5) elif opt_options[ 'merit_figure'] == 'flp1_std': # for DAC optimization on flap angles - TORQUE 2020 paper (need to define time constant in ROSCO) wt_opt.model.add_objective('aeroelastic.flp1_std') #1.e-8) else: exit('The merit figure ' + opt_options['merit_figure'] + ' is not supported.') # Set optimization design variables. if blade_opt_options['aero_shape']['twist']['flag']: indices = range( 2, blade_opt_options['aero_shape']['twist']['n_opt']) wt_opt.model.add_design_var('blade.opt_var.twist_opt_gain', indices=indices, lower=0., upper=1.) chord_options = blade_opt_options['aero_shape']['chord'] if chord_options['flag']: indices = range(3, chord_options['n_opt'] - 1) wt_opt.model.add_design_var('blade.opt_var.chord_opt_gain', indices=indices, lower=chord_options['min_gain'], upper=chord_options['max_gain']) if blade_opt_options['aero_shape']['af_positions']['flag']: n_af = modeling_options['blade']['n_af_span'] indices = range( blade_opt_options['aero_shape']['af_positions'] ['af_start'], n_af - 1) af_pos_init = wt_init['components']['blade'][ 'outer_shape_bem']['airfoil_position']['grid'] lb_af = np.zeros(n_af) ub_af = np.zeros(n_af) for i in range(1, indices[0]): lb_af[i] = ub_af[i] = af_pos_init[i] for i in indices: lb_af[i] = 0.5 * (af_pos_init[i - 1] + af_pos_init[i]) + step_size ub_af[i] = 0.5 * (af_pos_init[i + 1] + af_pos_init[i]) - step_size lb_af[-1] = ub_af[-1] = 1. wt_opt.model.add_design_var('blade.opt_var.af_position', indices=indices, lower=lb_af[indices], upper=ub_af[indices]) spar_cap_ss_options = blade_opt_options['structure']['spar_cap_ss'] if spar_cap_ss_options['flag']: indices = range(1, spar_cap_ss_options['n_opt'] - 1) wt_opt.model.add_design_var( 'blade.opt_var.spar_cap_ss_opt_gain', indices=indices, lower=spar_cap_ss_options['min_gain'], upper=spar_cap_ss_options['max_gain']) # Only add the pressure side design variables if we do set # `equal_to_suction` as False in the optimization yaml. spar_cap_ps_options = blade_opt_options['structure']['spar_cap_ps'] if spar_cap_ps_options[ 'flag'] and not spar_cap_ps_options['equal_to_suction']: indices = range(1, spar_cap_ps_options['n_opt'] - 1) wt_opt.model.add_design_var( 'blade.opt_var.spar_cap_ps_opt_gain', indices=indices, lower=spar_cap_ps_options['min_gain'], upper=spar_cap_ps_options['max_gain']) if 'dac' in blade_opt_options: if blade_opt_options['dac']['te_flap_end']['flag']: wt_opt.model.add_design_var('blade.opt_var.te_flap_end', lower=blade_opt_options['dac'] ['te_flap_end']['min_end'], upper=blade_opt_options['dac'] ['te_flap_end']['max_end']) if blade_opt_options['dac']['te_flap_ext']['flag']: wt_opt.model.add_design_var('blade.opt_var.te_flap_ext', lower=blade_opt_options['dac'] ['te_flap_ext']['min_ext'], upper=blade_opt_options['dac'] ['te_flap_ext']['max_ext']) if tower_opt_options['outer_diameter']['flag']: wt_opt.model.add_design_var( 'tower.diameter', lower=tower_opt_options['outer_diameter']['lower_bound'], upper=tower_opt_options['outer_diameter']['upper_bound'], ref=5.) if tower_opt_options['layer_thickness']['flag']: wt_opt.model.add_design_var( 'tower.layer_thickness', lower=tower_opt_options['layer_thickness']['lower_bound'], upper=tower_opt_options['layer_thickness']['upper_bound'], ref=1e-2) # -- Control -- if control_opt_options['tsr']['flag']: wt_opt.model.add_design_var( 'opt_var.tsr_opt_gain', lower=control_opt_options['tsr']['min_gain'], upper=control_opt_options['tsr']['max_gain']) if control_opt_options['servo']['pitch_control']['flag']: wt_opt.model.add_design_var('control.PC_omega', lower=control_opt_options['servo'] ['pitch_control']['omega_min'], upper=control_opt_options['servo'] ['pitch_control']['omega_max']) wt_opt.model.add_design_var('control.PC_zeta', lower=control_opt_options['servo'] ['pitch_control']['zeta_min'], upper=control_opt_options['servo'] ['pitch_control']['zeta_max']) if control_opt_options['servo']['torque_control']['flag']: wt_opt.model.add_design_var('control.VS_omega', lower=control_opt_options['servo'] ['torque_control']['omega_min'], upper=control_opt_options['servo'] ['torque_control']['omega_max']) wt_opt.model.add_design_var('control.VS_zeta', lower=control_opt_options['servo'] ['torque_control']['zeta_min'], upper=control_opt_options['servo'] ['torque_control']['zeta_max']) if 'flap_control' in control_opt_options['servo']: if control_opt_options['servo']['flap_control']['flag']: wt_opt.model.add_design_var( 'control.Flp_omega', lower=control_opt_options['servo']['flap_control'] ['omega_min'], upper=control_opt_options['servo']['flap_control'] ['omega_max']) wt_opt.model.add_design_var( 'control.Flp_zeta', lower=control_opt_options['servo']['flap_control'] ['zeta_min'], upper=control_opt_options['servo']['flap_control'] ['zeta_max']) # Set non-linear constraints blade_constraints = opt_options['constraints']['blade'] if blade_constraints['strains_spar_cap_ss']['flag']: if blade_opt_options['structure']['spar_cap_ss']['flag']: wt_opt.model.add_constraint( 'rlds.constr.constr_max_strainU_spar', upper=1.0) else: print( 'WARNING: the strains of the suction-side spar cap are set to be constrained, but spar cap thickness is not an active design variable. The constraint is not enforced.' ) if blade_constraints['strains_spar_cap_ps']['flag']: if blade_opt_options['structure']['spar_cap_ps'][ 'flag'] or blade_opt_options['structure'][ 'spar_cap_ps']['equal_to_suction']: wt_opt.model.add_constraint( 'rlds.constr.constr_max_strainL_spar', upper=1.0) else: print( 'WARNING: the strains of the pressure-side spar cap are set to be constrained, but spar cap thickness is not an active design variable. The constraint is not enforced.' ) if blade_constraints['stall']['flag']: if blade_opt_options['aero_shape']['twist']['flag']: wt_opt.model.add_constraint( 'stall_check.no_stall_constraint', upper=1.0) else: print( 'WARNING: the margin to stall is set to be constrained, but twist is not an active design variable. The constraint is not enforced.' ) if blade_constraints['tip_deflection']['flag']: if blade_opt_options['structure']['spar_cap_ss'][ 'flag'] or blade_opt_options['structure'][ 'spar_cap_ps']['flag']: wt_opt.model.add_constraint('tcons.tip_deflection_ratio', upper=1.0) else: print( 'WARNING: the tip deflection is set to be constrained, but spar caps thickness is not an active design variable. The constraint is not enforced.' ) if blade_constraints['chord']['flag']: if blade_opt_options['aero_shape']['chord']['flag']: wt_opt.model.add_constraint('blade.pa.max_chord_constr', upper=1.0) else: print( 'WARNING: the max chord is set to be constrained, but chord is not an active design variable. The constraint is not enforced.' ) if blade_constraints['frequency']['flap_above_3P']: if blade_opt_options['structure']['spar_cap_ss'][ 'flag'] or blade_opt_options['structure'][ 'spar_cap_ps']['flag']: wt_opt.model.add_constraint( 'rlds.constr.constr_flap_f_margin', upper=0.0) else: print( 'WARNING: the blade flap frequencies are set to be constrained, but spar caps thickness is not an active design variable. The constraint is not enforced.' ) if blade_constraints['frequency']['edge_above_3P']: wt_opt.model.add_constraint('rlds.constr.constr_edge_f_margin', upper=0.0) # if blade_constraints['frequency']['flap_below_3P']: # wt_opt.model.add_constraint('rlds.constr.constr_flap_f_below_3P', upper= 1.0) # if blade_constraints['frequency']['edge_below_3P']: # wt_opt.model.add_constraint('rlds.constr.constr_edge_f_below_3P', upper= 1.0) # if blade_constraints['frequency']['flap_above_3P'] and blade_constraints['frequency']['flap_below_3P']: # exit('The blade flap frequency is constrained to be both above and below 3P. Please check the constraint flags.') # if blade_constraints['frequency']['edge_above_3P'] and blade_constraints['frequency']['edge_below_3P']: # exit('The blade edge frequency is constrained to be both above and below 3P. Please check the constraint flags.') if blade_constraints['rail_transport']['flag']: if blade_constraints['rail_transport']['8_axle']: wt_opt.model.add_constraint( 'elastic.rail.constr_LV_8axle_horiz', lower=0.8, upper=1.0) wt_opt.model.add_constraint('elastic.rail.constr_strainPS', upper=1.0) wt_opt.model.add_constraint('elastic.rail.constr_strainSS', upper=1.0) elif blade_constraints['rail_transport']['4_axle']: wt_opt.model.add_constraint( 'elastic.rail.constr_LV_4axle_horiz', upper=1.0) else: exit( 'You have activated the rail transport constraint module. Please define whether you want to model 4- or 8-axle flatcars.' ) if opt_options['constraints']['blade']['moment_coefficient'][ 'flag']: wt_opt.model.add_constraint( 'ccblade.CM', lower=opt_options['constraints']['blade'] ['moment_coefficient']['min'], upper=opt_options['constraints']['blade'] ['moment_coefficient']['max']) if opt_options['constraints']['blade']['match_cl_cd'][ 'flag_cl'] or opt_options['constraints']['blade'][ 'match_cl_cd']['flag_cd']: data_target = np.loadtxt(opt_options['constraints']['blade'] ['match_cl_cd']['filename']) eta_opt = np.linspace( 0., 1., opt_options['optimization_variables']['blade'] ['aero_shape']['twist']['n_opt']) target_cl = np.interp(eta_opt, data_target[:, 0], data_target[:, 3]) target_cd = np.interp(eta_opt, data_target[:, 0], data_target[:, 4]) eps_cl = 1.e-2 if opt_options['constraints']['blade']['match_cl_cd'][ 'flag_cl']: wt_opt.model.add_constraint('ccblade.cl_n_opt', lower=target_cl - eps_cl, upper=target_cl + eps_cl) if opt_options['constraints']['blade']['match_cl_cd'][ 'flag_cd']: wt_opt.model.add_constraint('ccblade.cd_n_opt', lower=target_cd - eps_cl, upper=target_cd + eps_cl) if opt_options['constraints']['blade']['match_L_D'][ 'flag_L'] or opt_options['constraints']['blade'][ 'match_L_D']['flag_D']: data_target = np.loadtxt(opt_options['constraints']['blade'] ['match_L_D']['filename']) eta_opt = np.linspace( 0., 1., opt_options['optimization_variables']['blade'] ['aero_shape']['twist']['n_opt']) target_L = np.interp(eta_opt, data_target[:, 0], data_target[:, 7]) target_D = np.interp(eta_opt, data_target[:, 0], data_target[:, 8]) eps_L = 1.e+2 if opt_options['constraints']['blade']['match_L_D']['flag_L']: wt_opt.model.add_constraint('ccblade.L_n_opt', lower=target_L - eps_L, upper=target_L + eps_L) if opt_options['constraints']['blade']['match_L_D']['flag_D']: wt_opt.model.add_constraint('ccblade.D_n_opt', lower=target_D - eps_L, upper=target_D + eps_L) tower_constraints = opt_options['constraints']['tower'] if tower_constraints['height_constraint']['flag']: wt_opt.model.add_constraint( 'towerse.height_constraint', lower=tower_constraints['height_constraint'] ['lower_bound'], upper=tower_constraints['height_constraint'] ['upper_bound']) if tower_constraints['stress']['flag']: wt_opt.model.add_constraint('towerse.post.stress', upper=1.0) if tower_constraints['global_buckling']['flag']: wt_opt.model.add_constraint('towerse.post.global_buckling', upper=1.0) if tower_constraints['shell_buckling']['flag']: wt_opt.model.add_constraint('towerse.post.shell_buckling', upper=1.0) if tower_constraints['constr_d_to_t']['flag']: wt_opt.model.add_constraint('towerse.constr_d_to_t', upper=0.0) if tower_constraints['constr_taper']['flag']: wt_opt.model.add_constraint('towerse.constr_taper', lower=0.0) if tower_constraints['slope']['flag']: wt_opt.model.add_constraint('towerse.slope', upper=1.0) if tower_constraints['frequency_1']['flag']: wt_opt.model.add_constraint( 'towerse.tower.f1', lower=tower_constraints['frequency_1']['lower_bound'], upper=tower_constraints['frequency_1']['upper_bound']) control_constraints = opt_options['constraints']['control'] if control_constraints['flap_control']['flag']: if modeling_options['Analysis_Flags']['OpenFAST'] != True: exit( 'Please turn on the call to OpenFAST if you are trying to optimize trailing edge flaps.' ) wt_opt.model.add_constraint( 'sse_tune.tune_rosco.Flp_Kp', lower=control_constraints['flap_control']['min'], upper=control_constraints['flap_control']['max']) wt_opt.model.add_constraint( 'sse_tune.tune_rosco.Flp_Ki', lower=control_constraints['flap_control']['min'], upper=control_constraints['flap_control']['max']) # Set recorder on the OpenMDAO driver level using the `optimization_log` # filename supplied in the optimization yaml if opt_options['recorder']['flag']: recorder = om.SqliteRecorder( os.path.join(folder_output, opt_options['recorder']['file_name'])) wt_opt.driver.add_recorder(recorder) wt_opt.add_recorder(recorder) wt_opt.driver.recording_options['excludes'] = ['*_df'] wt_opt.driver.recording_options['record_constraints'] = True wt_opt.driver.recording_options['record_desvars'] = True wt_opt.driver.recording_options['record_objectives'] = True # Setup openmdao problem if opt_options['opt_flag']: wt_opt.setup() else: # If we're not performing optimization, we don't need to allocate # memory for the derivative arrays. wt_opt.setup(derivatives=False) # Load initial wind turbine data from wt_initial to the openmdao problem wt_opt = yaml2openmdao(wt_opt, modeling_options, wt_init) wt_opt = assign_ROSCO_values(wt_opt, modeling_options, wt_init['control']) wt_opt['blade.opt_var.s_opt_twist'] = np.linspace( 0., 1., blade_opt_options['aero_shape']['twist']['n_opt']) if blade_opt_options['aero_shape']['twist']['flag']: init_twist_opt = np.interp( wt_opt['blade.opt_var.s_opt_twist'], wt_init['components'] ['blade']['outer_shape_bem']['twist']['grid'], wt_init['components']['blade']['outer_shape_bem']['twist'] ['values']) lb_twist = np.array( blade_opt_options['aero_shape']['twist']['lower_bound']) ub_twist = np.array( blade_opt_options['aero_shape']['twist']['upper_bound']) wt_opt['blade.opt_var.twist_opt_gain'] = ( init_twist_opt - lb_twist) / (ub_twist - lb_twist) if max(wt_opt['blade.opt_var.twist_opt_gain']) > 1. or min( wt_opt['blade.opt_var.twist_opt_gain']) < 0.: print( 'Warning: the initial twist violates the upper or lower bounds of the twist design variables.' ) blade_constraints = opt_options['constraints']['blade'] wt_opt['blade.opt_var.s_opt_chord'] = np.linspace( 0., 1., blade_opt_options['aero_shape']['chord']['n_opt']) wt_opt['blade.ps.s_opt_spar_cap_ss'] = np.linspace( 0., 1., blade_opt_options['structure']['spar_cap_ss']['n_opt']) wt_opt['blade.ps.s_opt_spar_cap_ps'] = np.linspace( 0., 1., blade_opt_options['structure']['spar_cap_ps']['n_opt']) wt_opt['rlds.constr.max_strainU_spar'] = blade_constraints[ 'strains_spar_cap_ss']['max'] wt_opt['rlds.constr.max_strainL_spar'] = blade_constraints[ 'strains_spar_cap_ps']['max'] wt_opt['stall_check.stall_margin'] = blade_constraints['stall'][ 'margin'] * 180. / np.pi # If the user provides values in this dict, they overwrite # whatever values have been set by the yaml files. # This is useful for performing black-box wrapped optimization without # needing to modify the yaml files. if overridden_values is not None: for key in overridden_values: num_values = np.array(overridden_values[key]).size wt_opt[key][:] = overridden_values[key] # Place the last design variables from a previous run into the problem. # This needs to occur after the above setup() and yaml2openmdao() calls # so these values are correctly placed in the problem. if 'warmstart_file' in opt_options['driver']: # Directly read the pyoptsparse sqlite db file from pyoptsparse import SqliteDict db = SqliteDict(opt_options['driver']['warmstart_file']) # Grab the last iteration's design variables last_key = db['last'] desvars = db[last_key]['xuser'] # Obtain the already-setup OM problem's design variables if wt_opt.model._static_mode: design_vars = wt_opt.model._static_design_vars else: design_vars = wt_opt.model._design_vars # Get the absolute names from the promoted names within the OM model. # We need this because the pyoptsparse db has the absolute names for # variables but the OM model uses the promoted names. prom2abs = wt_opt.model._var_allprocs_prom2abs_list['output'] abs2prom = {} for key in design_vars: abs2prom[prom2abs[key][0]] = key # Loop through each design variable for key in desvars: prom_key = abs2prom[key] # Scale each DV based on the OM scaling from the problem. # This assumes we're running the same problem with the same scaling scaler = design_vars[prom_key]['scaler'] adder = design_vars[prom_key]['adder'] if scaler is None: scaler = 1.0 if adder is None: adder = 0.0 scaled_dv = desvars[key] / scaler - adder # Special handling for blade twist as we only have the # last few control points as design variables if 'twist_opt_gain' in key: wt_opt[key][2:] = scaled_dv else: wt_opt[key][:] = scaled_dv if 'check_totals' in opt_options['driver']: if opt_options['driver']['check_totals']: wt_opt.run_model() totals = wt_opt.compute_totals() if 'check_partials' in opt_options['driver']: if opt_options['driver']['check_partials']: wt_opt.run_model() checks = wt_opt.check_partials(compact_print=True) sys.stdout.flush() # Run openmdao problem if opt_options['opt_flag']: wt_opt.run_driver() else: wt_opt.run_model() if (not MPI) or (MPI and rank == 0): # Save data coming from openmdao to an output yaml file froot_out = os.path.join(folder_output, opt_options['general']['fname_output']) wt_initial.update_ontology_control(wt_opt) wt_initial.write_ontology(wt_opt, froot_out) # Save data to numpy and matlab arrays fileIO.save_data(froot_out, wt_opt) if MPI and modeling_options['Analysis_Flags']['OpenFAST']: # subprocessor ranks spin, waiting for FAST simulations to run sys.stdout.flush() if rank in comm_map_up.keys(): subprocessor_loop(comm_map_up) sys.stdout.flush() # close signal to subprocessors if rank == 0: subprocessor_stop(comm_map_down) sys.stdout.flush() if rank == 0: return wt_opt, modeling_options, opt_options else: return [], [], []
def run_weis(fname_wt_input, fname_modeling_options, fname_opt_options, overridden_values=None): # Load all yaml inputs and validate (also fills in defaults) wt_initial = WindTurbineOntologyPythonWEIS(fname_wt_input, fname_modeling_options, fname_opt_options) wt_init, modeling_options, opt_options = wt_initial.get_input_data() # Initialize openmdao problem. If running with multiple processors in MPI, use parallel finite differencing equal to the number of cores used. # Otherwise, initialize the WindPark system normally. Get the rank number for parallelization. We only print output files using the root processor. myopt = PoseOptimizationWEIS(modeling_options, opt_options) if MPI: n_DV = myopt.get_number_design_variables() # Extract the number of cores available max_cores = MPI.COMM_WORLD.Get_size() # Define the color map for the parallelization, determining the maximum number of parallel finite difference (FD) evaluations based on the number of design variables (DV). OpenFAST on/off changes things. if modeling_options['Level3']['flag']: # If openfast is called, the maximum number of FD is the number of DV, if we have the number of cores available that doubles the number of DVs, otherwise it is half of the number of DV (rounded to the lower integer). We need this because a top layer of cores calls a bottom set of cores where OpenFAST runs. if max_cores > 2. * n_DV: n_FD = n_DV else: n_FD = int(np.floor(max_cores / 2)) # The number of OpenFAST runs is the minimum between the actual number of requested OpenFAST simulations, and the number of cores available (minus the number of DV, which sit and wait for OF to complete) # need to calculate the number of OpenFAST runs from the user input n_OF_runs = 0 if modeling_options['openfast']['dlc_settings']['run_power_curve']: if modeling_options['openfast']['dlc_settings']['Power_Curve'][ 'turbulent_power_curve']: n_OF_runs += len( modeling_options['openfast']['dlc_settings'] ['Power_Curve']['U']) * len( modeling_options['openfast']['dlc_settings'] ['Power_Curve']['Seeds']) else: n_OF_runs += len(modeling_options['openfast'] ['dlc_settings']['Power_Curve']['U']) if modeling_options['openfast']['dlc_settings'][ 'run_IEC'] or modeling_options['openfast']['dlc_settings'][ 'run_blade_fatigue']: for dlc in modeling_options['openfast']['dlc_settings']['IEC']: dlc_vars = list(dlc.keys()) # Number of wind speeds if 'U' not in dlc_vars: if dlc['DLC'] == 1.4: # assuming 1.4 is run at [V_rated-2, V_rated, V_rated] and +/- direction change n_U = 6 elif dlc[ 'DLC'] == 5.1: # assuming 1.4 is run at [V_rated-2, V_rated, V_rated] n_U = 3 elif dlc['DLC'] in [ 6.1, 6.3 ]: # assuming V_50 for [-8, 8] deg yaw error n_U = 2 else: print( 'Warning: for OpenFAST DLC %1.1f specified in the Analysis Options, wind speeds "U" must be provided' % dlc['DLC']) else: n_U = len(dlc['U']) # Number of seeds if 'Seeds' not in dlc_vars: if dlc['DLC'] == 1.4: # not turbulent n_Seeds = 1 else: print( 'Warning: for OpenFAST DLC %1.1f specified in the Analysis Options, turbulent seeds "Seeds" must be provided' % dlc['DLC']) else: n_Seeds = len(dlc['Seeds']) n_OF_runs += n_U * n_Seeds n_DV = max([n_DV, 1]) max_parallel_OF_runs = max( [int(np.floor((max_cores - n_DV) / n_DV)), 1]) n_OF_runs_parallel = min([int(n_OF_runs), max_parallel_OF_runs]) modeling_options['openfast']['dlc_settings'][ 'n_OF_runs'] = n_OF_runs else: # If OpenFAST is not called, the number of parallel calls to compute the FDs is just equal to the minimum of cores available and DV n_FD = min([max_cores, n_DV]) n_OF_runs_parallel = 1 # Define the color map for the cores (how these are distributed between finite differencing and openfast runs) if opt_options['driver']['design_of_experiments']['flag']: n_FD = MPI.COMM_WORLD.Get_size() n_OF_runs_parallel = 1 rank = MPI.COMM_WORLD.Get_rank() comm_map_up = comm_map_down = {} for r in range(MPI.COMM_WORLD.Get_size()): comm_map_up[r] = [r] color_i = 0 else: n_FD = max([n_FD, 1]) comm_map_down, comm_map_up, color_map = map_comm_heirarchical( n_FD, n_OF_runs_parallel) rank = MPI.COMM_WORLD.Get_rank() color_i = color_map[rank] comm_i = MPI.COMM_WORLD.Split(color_i, 1) else: color_i = 0 rank = 0 folder_output = opt_options['general']['folder_output'] if rank == 0 and not os.path.isdir(folder_output): os.mkdir(folder_output) if color_i == 0: # the top layer of cores enters, the others sit and wait to run openfast simulations # if MPI and opt_options['driver']['optimization']['flag']: if MPI: if modeling_options['Level3']['flag']: # Parallel settings for OpenFAST modeling_options['openfast']['analysis_settings'][ 'mpi_run'] = True modeling_options['openfast']['analysis_settings'][ 'mpi_comm_map_down'] = comm_map_down if opt_options['driver']['design_of_experiments']['flag']: modeling_options['openfast']['analysis_settings'][ 'cores'] = 1 else: modeling_options['openfast']['analysis_settings'][ 'cores'] = n_OF_runs_parallel # Parallel settings for OpenMDAO if opt_options['driver']['design_of_experiments']['flag']: wt_opt = om.Problem( model=WindPark(modeling_options=modeling_options, opt_options=opt_options)) else: wt_opt = om.Problem(model=om.Group(num_par_fd=n_FD), comm=comm_i) wt_opt.model.add_subsystem( 'comp', WindPark(modeling_options=modeling_options, opt_options=opt_options), promotes=['*']) else: # Sequential finite differencing and openfast simulations modeling_options['openfast']['analysis_settings'][ 'mpi_run'] = False modeling_options['openfast']['analysis_settings']['cores'] = 1 wt_opt = om.Problem(model=WindPark( modeling_options=modeling_options, opt_options=opt_options)) # If at least one of the design variables is active, setup an optimization if opt_options['opt_flag']: wt_opt = myopt.set_driver(wt_opt) wt_opt = myopt.set_objective(wt_opt) wt_opt = myopt.set_design_variables(wt_opt, wt_init) wt_opt = myopt.set_constraints(wt_opt) if opt_options['driver']['design_of_experiments']['flag']: wt_opt.driver.options['debug_print'] = [ 'desvars', 'ln_cons', 'nl_cons', 'objs' ] wt_opt.driver.options[ 'procs_per_model'] = 1 # n_OF_runs_parallel # int(max_cores / np.floor(max_cores/n_OF_runs)) wt_opt = myopt.set_recorders(wt_opt) wt_opt.driver.options['debug_print'] = [ 'desvars', 'ln_cons', 'nl_cons', 'objs', 'totals' ] # Setup openmdao problem if opt_options['opt_flag']: wt_opt.setup() else: # If we're not performing optimization, we don't need to allocate # memory for the derivative arrays. wt_opt.setup(derivatives=False) # Load initial wind turbine data from wt_initial to the openmdao problem wt_opt = yaml2openmdao(wt_opt, modeling_options, wt_init, opt_options) wt_opt = assign_ROSCO_values(wt_opt, modeling_options, wt_init['control']) wt_opt = myopt.set_initial(wt_opt, wt_init) if modeling_options['Level3']['flag']: wt_opt = myopt.set_initial_weis(wt_opt) # If the user provides values in this dict, they overwrite # whatever values have been set by the yaml files. # This is useful for performing black-box wrapped optimization without # needing to modify the yaml files. # Some logic is used here if the user gives a smalller size for the # design variable than expected to input the values into the end # of the array. # This is useful when optimizing twist, where the first few indices # do not need to be optimized as they correspond to a circular cross-section. if overridden_values is not None: for key in overridden_values: num_values = np.array(overridden_values[key]).size key_size = wt_opt[key].size idx_start = key_size - num_values wt_opt[key][idx_start:] = overridden_values[key] # Place the last design variables from a previous run into the problem. # This needs to occur after the above setup() and yaml2openmdao() calls # so these values are correctly placed in the problem. wt_opt = myopt.set_restart(wt_opt) if 'check_totals' in opt_options['driver']['optimization']: if opt_options['driver']['optimization']['check_totals']: wt_opt.run_model() totals = wt_opt.compute_totals() if 'check_partials' in opt_options['driver']['optimization']: if opt_options['driver']['optimization']['check_partials']: wt_opt.run_model() checks = wt_opt.check_partials(compact_print=True) sys.stdout.flush() # Run openmdao problem if opt_options['opt_flag']: wt_opt.run_driver() else: wt_opt.run_model() if (not MPI) or (MPI and rank == 0): # Save data coming from openmdao to an output yaml file froot_out = os.path.join(folder_output, opt_options['general']['fname_output']) wt_initial.update_ontology_control(wt_opt) # Remove the fst_vt key from the dictionary and write out the modeling options modeling_options['openfast']['fst_vt'] = {} wt_initial.write_ontology(wt_opt, froot_out) wt_initial.write_options(froot_out) # Save data to numpy and matlab arrays fileIO.save_data(froot_out, wt_opt) if MPI and modeling_options['Level3']['flag'] and opt_options['driver'][ 'optimization']['flag']: # subprocessor ranks spin, waiting for FAST simulations to run sys.stdout.flush() if rank in comm_map_up.keys(): subprocessor_loop(comm_map_up) sys.stdout.flush() # close signal to subprocessors subprocessor_stop(comm_map_down) sys.stdout.flush() if rank == 0: return wt_opt, modeling_options, opt_options else: return [], [], []
fastBatch = runFAST_pywrapper_batch(FAST_ver='OpenFAST',dev_branch = True) if eagle: fastBatch.FAST_exe = '/home/pbortolo/OpenFAST/build/glue-codes/openfast/openfast' # Path to executable fastBatch.FAST_InputFile = 'OpenFAST_BAR_30.fst' # FAST input file (ext=.fst) fastBatch.FAST_directory = '/home/pbortolo/wisdem_1_0_0/BAR/SONATA/temp_inflatable_blades/BeamDyn_analysis/BAR_30_full_model' # Path to fst directory files else: fastBatch.FAST_exe = '/Users/pbortolo/work/2_openfast/openfast/build/glue-codes/openfast/openfast' # Path to executable fastBatch.FAST_InputFile = 'OpenFAST_BAR_05.fst' # FAST input file (ext=.fst) fastBatch.FAST_directory = '/Users/pbortolo/work/2_openfast/BAR/OpenFAST_Models/BAR_05' # Path to fst directory files fastBatch.FAST_runDirectory = iec.run_dir fastBatch.case_list = case_list fastBatch.case_name_list = case_name_list fastBatch.debug_level = 2 if MPI: fastBatch.run_mpi(comm_map_down) else: fastBatch.run_serial() if MPI: sys.stdout.flush() if rank in comm_map_up.keys(): subprocessor_loop(comm_map_up) sys.stdout.flush() # close signal to subprocessors if rank == 0 and MPI: subprocessor_stop(comm_map_down) sys.stdout.flush()