def setup_aerostruct(self): """ Specific method to add the necessary components to the problem for an aerostructural problem. Because this code has been extended to work for multiple aerostructural surfaces, a good portion of it is spent doing the bookkeeping for parameter passing and ensuring that each component modifies the correct data. """ # Set the problem name if the user doesn't if 'prob_name' not in self.prob_dict.keys(): self.prob_dict['prob_name'] = 'aerostruct' # Create the base root-level group root = Group() coupled = Group() # Create the problem and assign the root group self.prob = Problem() self.prob.root = root # Loop over each surface in the surfaces list for surface in self.surfaces: # Get the surface name and create a group to contain components # only for this surface name = surface['name'] tmp_group = Group() # Strip the surface names from the desvars list and save this # modified list as self.desvars desvar_names = [] for desvar in self.desvars.keys(): # Check to make sure that the surface's name is in the design # variable and only add the desvar to the list if it corresponds # to this surface. if name[:-1] in desvar: desvar_names.append(''.join(desvar.split('.')[1:])) # Add independent variables that do not belong to a specific component indep_vars = [] for var in surface['geo_vars']: if var in desvar_names or var in surface['initial_geo'] or 'thickness' in var: indep_vars.append((var, surface[var])) # Add components to include in the surface's group tmp_group.add('indep_vars', IndepVarComp(indep_vars), promotes=['*']) tmp_group.add('tube', MaterialsTube(surface), promotes=['*']) tmp_group.add('mesh', GeometryMesh(surface, self.desvars), promotes=['*']) tmp_group.add('struct_setup', SpatialBeamSetup(surface), promotes=['*']) # Add bspline components for active bspline geometric variables. # We only add the component if the corresponding variable is a desvar, # a special parameter (radius), or if the user or geometry provided # an initial distribution. for var in surface['bsp_vars']: if var in desvar_names or var in surface['initial_geo'] or 'thickness' in var: n_pts = surface['num_y'] if var in ['thickness_cp', 'radius_cp']: n_pts -= 1 trunc_var = var.split('_')[0] tmp_group.add(trunc_var + '_bsp', Bspline(var, trunc_var, surface['num_'+var], n_pts), promotes=['*']) # Add monotonic constraints for selected variables if surface['monotonic_con'] is not None: if type(surface['monotonic_con']) is not list: surface['monotonic_con'] = [surface['monotonic_con']] for var in surface['monotonic_con']: tmp_group.add('monotonic_' + var, MonotonicConstraint(var, surface), promotes=['*']) # Add tmp_group to the problem with the name of the surface. name_orig = name name = name[:-1] root.add(name, tmp_group, promotes=[]) # Add components to the 'coupled' group for each surface. # The 'coupled' group must contain all components and parameters # needed to converge the aerostructural system. tmp_group = Group() tmp_group.add('def_mesh', TransferDisplacements(surface), promotes=['*']) tmp_group.add('aero_geom', VLMGeometry(surface), promotes=['*']) tmp_group.add('struct_states', SpatialBeamStates(surface), promotes=['*']) tmp_group.struct_states.ln_solver = LinearGaussSeidel() tmp_group.struct_states.ln_solver.options['atol'] = 1e-20 name = name_orig coupled.add(name[:-1], tmp_group, promotes=[]) # Add a loads component to the coupled group coupled.add(name_orig + 'loads', TransferLoads(surface), promotes=[]) # Add a performance group which evaluates the data after solving # the coupled system tmp_group = Group() tmp_group.add('struct_funcs', SpatialBeamFunctionals(surface), promotes=['*']) tmp_group.add('aero_funcs', VLMFunctionals(surface, self.prob_dict), promotes=['*']) root.add(name_orig + 'perf', tmp_group, promotes=["rho", "v", "alpha", "re", "M"]) root.add_metadata(surface['name'] + 'yield_stress', surface['yield']) root.add_metadata(surface['name'] + 'fem_origin', surface['fem_origin']) # Add a single 'aero_states' component for the whole system within the # coupled group. coupled.add('aero_states', VLMStates(self.surfaces), promotes=['v', 'alpha', 'rho']) # Explicitly connect parameters from each surface's group and the common # 'aero_states' group. for surface in self.surfaces: name = surface['name'] root.connect(name[:-1] + '.K', 'coupled.' + name[:-1] + '.K') # Perform the connections with the modified names within the # 'aero_states' group. root.connect('coupled.' + name[:-1] + '.def_mesh', 'coupled.aero_states.' + name + 'def_mesh') root.connect('coupled.' + name[:-1] + '.b_pts', 'coupled.aero_states.' + name + 'b_pts') root.connect('coupled.' + name[:-1] + '.c_pts', 'coupled.aero_states.' + name + 'c_pts') root.connect('coupled.' + name[:-1] + '.normals', 'coupled.aero_states.' + name + 'normals') # Connect the results from 'aero_states' to the performance groups root.connect('coupled.aero_states.' + name + 'sec_forces', name + 'perf' + '.sec_forces') # Connect the results from 'coupled' to the performance groups root.connect('coupled.' + name[:-1] + '.def_mesh', 'coupled.' + name + 'loads.def_mesh') root.connect('coupled.aero_states.' + name + 'sec_forces', 'coupled.' + name + 'loads.sec_forces') # Connect the output of the loads component with the FEM # displacement parameter. This links the coupling within the coupled # group that necessitates the subgroup solver. root.connect('coupled.' + name + 'loads.loads', 'coupled.' + name[:-1] + '.loads') # Connect aerodyamic mesh to coupled group mesh root.connect(name[:-1] + '.mesh', 'coupled.' + name[:-1] + '.mesh') # Connect performance calculation variables root.connect(name[:-1] + '.radius', name + 'perf.radius') root.connect(name[:-1] + '.A', name + 'perf.A') root.connect(name[:-1] + '.thickness', name + 'perf.thickness') # Connection performance functional variables root.connect(name + 'perf.structural_weight', 'total_perf.' + name + 'structural_weight') root.connect(name + 'perf.L', 'total_perf.' + name + 'L') root.connect(name + 'perf.CL', 'total_perf.' + name + 'CL') root.connect(name + 'perf.CD', 'total_perf.' + name + 'CD') root.connect('coupled.aero_states.' + name + 'sec_forces', 'total_perf.' + name + 'sec_forces') # Connect parameters from the 'coupled' group to the performance # groups for the individual surfaces. root.connect(name[:-1] + '.nodes', name + 'perf.nodes') root.connect('coupled.' + name[:-1] + '.disp', name + 'perf.disp') root.connect('coupled.' + name[:-1] + '.S_ref', name + 'perf.S_ref') root.connect('coupled.' + name[:-1] + '.widths', name + 'perf.widths') root.connect('coupled.' + name[:-1] + '.lengths', name + 'perf.lengths') root.connect('coupled.' + name[:-1] + '.cos_sweep', name + 'perf.cos_sweep') # Connect parameters from the 'coupled' group to the total performance group. root.connect('coupled.' + name[:-1] + '.S_ref', 'total_perf.' + name + 'S_ref') root.connect('coupled.' + name[:-1] + '.widths', 'total_perf.' + name + 'widths') root.connect('coupled.' + name[:-1] + '.chords', 'total_perf.' + name + 'chords') root.connect('coupled.' + name[:-1] + '.b_pts', 'total_perf.' + name + 'b_pts') root.connect(name + 'perf.cg_location', 'total_perf.' + name + 'cg_location') # Set solver properties for the coupled group coupled.ln_solver = ScipyGMRES() coupled.ln_solver.preconditioner = LinearGaussSeidel() coupled.aero_states.ln_solver = LinearGaussSeidel() coupled.nl_solver = NLGaussSeidel() # This is only available in the most recent version of OpenMDAO. # It may help converge tightly coupled systems when using NLGS. try: coupled.nl_solver.options['use_aitken'] = True coupled.nl_solver.options['aitken_alpha_min'] = 0.01 # coupled.nl_solver.options['aitken_alpha_max'] = 0.5 except: pass if self.prob_dict['print_level'] == 2: coupled.ln_solver.options['iprint'] = 1 if self.prob_dict['print_level']: coupled.nl_solver.options['iprint'] = 1 # Add the coupled group to the root problem root.add('coupled', coupled, promotes=['v', 'alpha', 'rho']) # Add problem information as an independent variables component prob_vars = [('v', self.prob_dict['v']), ('alpha', self.prob_dict['alpha']), ('M', self.prob_dict['M']), ('re', self.prob_dict['Re']/self.prob_dict['reynolds_length']), ('rho', self.prob_dict['rho'])] root.add('prob_vars', IndepVarComp(prob_vars), promotes=['*']) # Add functionals to evaluate performance of the system. # Note that only the interesting results are promoted here; not all # of the parameters. root.add('total_perf', TotalPerformance(self.surfaces, self.prob_dict), promotes=['L_equals_W', 'fuelburn', 'CM', 'CL', 'CD', 'v', 'rho', 'cg', 'weighted_obj', 'total_weight']) # Actually set up the system self.setup_prob()
def setup_struct(self): """ Specific method to add the necessary components to the problem for a structural problem. """ # Set the problem name if the user doesn't if 'prob_name' not in self.prob_dict.keys(): self.prob_dict['prob_name'] = 'struct' # Create the base root-level group root = Group() # Create the problem and assign the root group self.prob = Problem() self.prob.root = root # Loop over each surface in the surfaces list for surface in self.surfaces: # Get the surface name and create a group to contain components # only for this surface. # This group's name is whatever the surface's name is. # The default is 'wing'. name = surface['name'] tmp_group = Group() # Strip the surface names from the desvars list and save this # modified list as self.desvars desvar_names = [] for desvar in self.desvars.keys(): # Check to make sure that the surface's name is in the design # variable and only add the desvar to the list if it corresponds # to this surface. if name[:-1] in desvar: desvar_names.append(''.join(desvar.split('.')[1:])) # Add independent variables that do not belong to a specific component. # Note that these are the only ones necessary for structual-only # analysis and optimization. # Here we check and only add the variables that are desvars or a # special var, radius, which is necessary to compute weight. indep_vars = [('loads', surface['loads'])] for var in surface['geo_vars']: if var in desvar_names or 'thickness' in var or var in surface['initial_geo']: indep_vars.append((var, surface[var])) # Add structural components to the surface-specific group tmp_group.add('indep_vars', IndepVarComp(indep_vars), promotes=['*']) tmp_group.add('mesh', GeometryMesh(surface, self.desvars), promotes=['*']) tmp_group.add('tube', MaterialsTube(surface), promotes=['*']) tmp_group.add('struct_setup', SpatialBeamSetup(surface), promotes=['*']) tmp_group.add('struct_states', SpatialBeamStates(surface), promotes=['*']) tmp_group.add('struct_funcs', SpatialBeamFunctionals(surface), promotes=['*']) # Add bspline components for active bspline geometric variables. # We only add the component if the corresponding variable is a desvar # or special (radius). for var in surface['bsp_vars']: if var in desvar_names or var in surface['initial_geo'] or 'thickness' in var: n_pts = surface['num_y'] if var in ['thickness_cp', 'radius_cp']: n_pts -= 1 trunc_var = var.split('_')[0] tmp_group.add(trunc_var + '_bsp', Bspline(var, trunc_var, surface['num_'+var], n_pts), promotes=['*']) # Add tmp_group to the problem with the name of the surface. # The default is 'wing'. root.add(name[:-1], tmp_group, promotes=[]) root.add_metadata(surface['name'] + 'yield_stress', surface['yield']) root.add_metadata(surface['name'] + 'fem_origin', surface['fem_origin']) # Actually set up the problem self.setup_prob()