From 8286530ed3a1cd58ee3b378b8023e6ca82091690 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 13:40:47 -0500 Subject: [PATCH 01/82] Changes towards refactored model building --- .gitignore | 4 +- .vscode/settings.json | 7 + docs/source/conf.py | 3 +- docs/source/developers/builders.rst | 27 +-- examples/tacs_only/mphys_crm_example.py | 107 +++------- examples/tacs_only/tacs_setup.py | 45 +++++ mphys/__init__.py | 2 +- mphys/builder.py | 80 ++++++++ mphys/coupling_aerostructural.py | 43 ++++ mphys/coupling_group.py | 6 + mphys/geo_disp.py | 12 +- mphys/mphys_group.py | 40 ++++ mphys/mphys_meld.py | 4 - mphys/mphys_tacs.py | 258 ++++++++---------------- mphys/mphys_vlm.py | 57 +----- mphys/multipoint.py | 232 +++------------------ mphys/multipoint_old.py | 219 ++++++++++++++++++++ mphys/scenario.py | 90 ++------- mphys/scenario_aerostructural.py | 27 +++ mphys/scenario_old.py | 78 +++++++ mphys/scenario_structural.py | 14 ++ 21 files changed, 725 insertions(+), 630 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 examples/tacs_only/tacs_setup.py create mode 100644 mphys/builder.py create mode 100644 mphys/coupling_aerostructural.py create mode 100644 mphys/coupling_group.py create mode 100644 mphys/mphys_group.py create mode 100644 mphys/multipoint_old.py create mode 100644 mphys/scenario_aerostructural.py create mode 100644 mphys/scenario_old.py create mode 100644 mphys/scenario_structural.py diff --git a/.gitignore b/.gitignore index 7895cbeb..68f8d3f3 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,6 @@ build/ dist/ # input files -tests/input_files/*.xyz \ No newline at end of file +tests/input_files/*.xyz + +.ropeproject diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..5002b028 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "restructuredtext.confPath": "${workspaceFolder}/docs/source", + "python.formatting.provider": "autopep8", + "python.formatting.autopep8Args": [ + "--max-line-length", "100", "--experimental" + ] +} diff --git a/docs/source/conf.py b/docs/source/conf.py index 832b7d6e..3cf5d0d5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = 'Mphys' -copyright = '2020, NASA' +copyright = '2021, NASA' author = 'NASA' @@ -38,6 +38,7 @@ exclude_patterns = [] autoclass_content = 'both' +autodoc_member_order = 'bysource' # -- Options for HTML output ------------------------------------------------- diff --git a/docs/source/developers/builders.rst b/docs/source/developers/builders.rst index 8fbd8e9c..6f09ef8e 100644 --- a/docs/source/developers/builders.rst +++ b/docs/source/developers/builders.rst @@ -15,30 +15,7 @@ Solver Builders The code/discipline assembler are designed for either solvers or transfer scheme modules. The three tasks common to all the code assemblers are: -#. Define functions to add its components to the appropriate group levels in the model hierarchy. -#. Identify the outputs of its components by adding them the :code:`connection_srcs` dictionary; -#. Connect its components' inputs from the :code:`connection_srcs` dictionary. +.. automodule:: mphys.builder -The solver assemblers must provide three additional getter functions: :code:`get_comm`, :code:`get_nnodes`, and :code:`get_ndof` which get the mpi communicator, number of nodes owned by the local processor, and the number of degrees of freedom per node, respectively. -Thse getter functions allow transfer scheme components - - -.. automodule:: omfsi.assembler - -.. autoclass:: OmfsiAssembler - :members: - -.. autoclass:: OmfsiSolverAssembler - :members: - :show-inheritance: - -************************** -Coupling Problem Assembler -************************** -The coupling problem assemblers wrap the code assemblers into smaller functions that the user can use to build their model. - -.. automodule:: omfsi.fsi_assembler - - -.. autoclass:: FsiAssembler +.. autoclass:: Builder :members: diff --git a/examples/tacs_only/mphys_crm_example.py b/examples/tacs_only/mphys_crm_example.py index 28a9a8c5..003f2827 100644 --- a/examples/tacs_only/mphys_crm_example.py +++ b/examples/tacs_only/mphys_crm_example.py @@ -5,108 +5,55 @@ from __future__ import division, print_function import numpy as np -from openmdao.api import Problem, ScipyOptimizeDriver -from openmdao.api import IndepVarComp, Group -from openmdao.api import NonlinearBlockGS, LinearBlockGS +import openmdao.api as om -from tacs import elements, constitutive, TACS, functions -from mphys.mphys_tacs import TacsBuilder -from mphys.multipoint import Multipoint +from mphys.mphys_tacs import TacsBuilder, TacsMesh +from mphys import Multipoint +from mphys.scenario_structural import ScenarioStructural +import tacs_setup -class Top(Group): +use_tacs = True +class Top(Multipoint): def setup(self): + if use_tacs: + tacs_options = {'add_elements' : tacs_setup.add_elements, + 'mesh_file' : 'CRM_box_2nd.bdf', + 'get_funcs' : tacs_setup.get_funcs, + 'load_function': tacs_setup.forcer_function, + 'f5_writer' : tacs_setup.f5_writer} - ################################################################################ - # Tacs solver pieces - ################################################################################ - def add_elements(mesh): - rho = 2500.0 # density, kg/m^3 - E = 70.0e9 # elastic modulus, Pa - nu = 0.3 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 350e6 # yield stress, Pa - thickness = 0.020 - min_thickness = 0.00 - max_thickness = 1.00 + struct_builder = TacsBuilder(tacs_options) + struct_builder.initialize(self.comm) + self.ndv_struct = struct_builder.get_ndv() - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) + self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + self.dvs.add_output('dv_struct',np.array(self.ndv_struct*[0.0031])) - ndof = 6 - ndv = num_components + self.add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) - return ndof, ndv + self.mphys_add_scenario('analysis', ScenarioStructural(struct_builder=struct_builder)) - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - - def forcer_function(x_s,ndof): - # apply uniform z load - f_s = np.zeros(int(x_s.size/3)*ndof) - f_s[2::ndof] = 100.0 - return f_s - - def f5_writer(tacs): - flag = (TACS.ToFH5.NODES | - TACS.ToFH5.DISPLACEMENTS | - TACS.ToFH5.STRAINS | - TACS.ToFH5.EXTRAS) - f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) - f5.writeToFile('ucrm.f5') - - tacs_setup = {'add_elements': add_elements, - 'mesh_file' : 'CRM_box_2nd.bdf', - 'get_funcs' : get_funcs, - 'load_function': forcer_function, - 'f5_writer' : f5_writer} - - # assembler = TacsOmfsiAssembler(tacs_setup,add_forcer=True) - tacs_builder = TacsBuilder(tacs_setup) - - # ivc to keep the top level DVs - dvs = self.add_subsystem('dvs', IndepVarComp(), promotes=['*']) - - # create the multiphysics multipoint group. - mp = self.add_subsystem( - 'mp_group', - Multipoint(struct_builder = tacs_builder) - ) - - # this is the method that needs to be called for every point in this mp_group - mp.mphys_add_scenario('s0') - - def configure(self): - # add the structural thickness DVs - ndv_struct = self.mp_group.struct_builder.get_ndv() - self.dvs.add_output('dv_struct',np.array(240*[0.0031])) - self.connect('dv_struct', ['mp_group.s0.solver_group.struct.dv_struct', 'mp_group.s0.struct_funcs.dv_struct']) + self.mphys_connect_scenario_coordinate_source('mesh','analysis','struct') + self.connect('dv_struct', 'analysis.dv_struct') ################################################################################ # OpenMDAO setup ################################################################################ -prob = Problem() +prob = om.Problem() prob.model = Top() model = prob.model model.add_design_var('dv_struct',lower=0.001,upper=0.075,scaler=1.0/1.0) -model.add_objective('mp_group.s0.struct_funcs.mass.mass',scaler=1.0/100000.0) -model.add_constraint('mp_group.s0.struct_funcs.funcs.func_struct',lower = 0.0, upper = 2.0/3.0,scaler=1000.0/1.0) +model.add_objective('analysis.mass',scaler=1.0/100000.0) +model.add_constraint('analysis.func_struct',lower = 0.0, upper = 2.0/3.0,scaler=1000.0/1.0) -prob.driver = ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=1500) +prob.driver = om.ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=1500) prob.driver.options['optimizer'] = 'SLSQP' prob.setup() - +om.n2(prob, show_browser=False, outfile='mphys_struct.html') prob.run_model() # TODO verify that the optimization works # prob.run_driver() diff --git a/examples/tacs_only/tacs_setup.py b/examples/tacs_only/tacs_setup.py new file mode 100644 index 00000000..92c9f987 --- /dev/null +++ b/examples/tacs_only/tacs_setup.py @@ -0,0 +1,45 @@ +import numpy as np +from tacs import TACS, elements, constitutive, functions + +def add_elements(mesh): + rho = 2500.0 # density, kg/m^3 + E = 70.0e9 # elastic modulus, Pa + nu = 0.3 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 350e6 # yield stress, Pa + thickness = 0.020 + min_thickness = 0.00 + max_thickness = 1.00 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ["CQUAD", "CQUADR", "CQUAD4"]: + element = elements.MITCShell(2,stiff,component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + +def get_funcs(tacs): + ks_weight = 50.0 + return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] + +def forcer_function(x_s,ndof): + # apply uniform z load + f_s = np.zeros(int(x_s.size/3)*ndof) + f_s[2::ndof] = 100.0 + return f_s + +def f5_writer(tacs): + flag = (TACS.ToFH5.NODES | + TACS.ToFH5.DISPLACEMENTS | + TACS.ToFH5.STRAINS | + TACS.ToFH5.EXTRAS) + f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) + f5.writeToFile('ucrm.f5') diff --git a/mphys/__init__.py b/mphys/__init__.py index e9cc89c6..d931c596 100644 --- a/mphys/__init__.py +++ b/mphys/__init__.py @@ -1,3 +1,3 @@ #!/usr/bin/env python -from .base_classes import SolverBuilder, DummyBuilder, XferBuilder +from .builder import Builder from .multipoint import Multipoint diff --git a/mphys/builder.py b/mphys/builder.py new file mode 100644 index 00000000..9f54ed58 --- /dev/null +++ b/mphys/builder.py @@ -0,0 +1,80 @@ +class Builder: + """ + MPHYS builder base class. Template for developers to create their builders. + """ + + def __init__(self): + """ + Because the MPI communicator that will be used inside the OpenMDAO + problem is not known when the builder is instantiated. The actual + solver, transfer scheme, etc. should not be instantiated in the + constructor. + """ + self.solver = None + + def initialize(self, comm): + """ + Initialize the solver, transfer scheme, etc. + This method will be called when the MPI comm is available + + Parameters + ---------- + comm : ~mpi4py.MPI.Comm + The communicator object created for this xfer object instance. + + """ + pass + + def get_mesh_coordinate_subsytem(self): + return None + + def get_coupling_group_subsystem(self): + """ + The element that this builder will add to the CouplingGroup + + Returns + ------- + solver_element : ~openmdao.api.Component or ~openmdao.api.Group + The openmdao element that handles all the computations for + this solver. Transfer schemes can return multiple elements + """ + return None + + def get_scenario_subsystems(self): + """ + Method that returns the openmdao element to be added to each scenario + + Returns + ------- + pre_coupling_element : openmdao.api.Component or ~openmdao.api.Group + The openmdao element that handles all the added computations for + that should be run in each scenario. These may represent functional that + are run after a coupled analysis is converged. + """ + pre_coupling_element = None + post_coupling_element = None + return pre_coupling_element, post_coupling_element + + def get_number_of_nodes(self): + """ + Method that returns the number of nodes defining the interface + (input) mesh + + + Returns + ------- + number_of_nodes : int + number of nodes in the computational domain + """ + return None + + def get_ndof(self): + """ + The number of degrees of freedom used at each output location. + + Returns + ------- + ndof : int + number of degrees of freedom of each node in the domain + """ + return None diff --git a/mphys/coupling_aerostructural.py b/mphys/coupling_aerostructural.py new file mode 100644 index 00000000..102b6b94 --- /dev/null +++ b/mphys/coupling_aerostructural.py @@ -0,0 +1,43 @@ +from .coupling_group import CouplingGroup +from .geo_disp import GeoDisp + + +class CouplingAeroStructural(CouplingGroup): + """ + The standard aerostructural coupling problem. + """ + + def initialize(self): + self.options.declare('aero_builder', recordable=False) + self.options.declare('struct_builder', recordable=False) + self.options.declare('xfer_builder', recordable=False) + + def setup(self): + aero_builder = self.options['aero_builder'] + struct_builder = self.options['struct_builder'] + xfer_builder = self.options['xfer_builder'] + + disp_xfer, load_xfer = xfer_builder.get_coupling_group_subsystem() + aero = aero_builder.get_coupling_group_subsystem() + struct = struct_builder.get_coupling_group_subsystem() + + geo_disp = GeoDisp(number_of_nodes=aero_builder.get_number_of_nodes()) + + self.mphys_add_subsystem('disp_xfer', disp_xfer) + self.mphys_add_subsystem('geo_disp', geo_disp) + self.mphys_add_subsystem('aero', aero) + self.mphys_add_subsystem('load_xfer', load_xfer) + self.mphys_add_subsystem('struct', struct) + + def configure(self): + self.connect('disp_xfer.u_aero', 'geo_disp.u_aero') + self.connect('geo_disp.x_aero', 'aero.x_aero') + self.connect('aero.f_aero', 'load_xfer.f_aero') + self.connect('load_xfer.f_struct', 'struct.f_struct') + self.connect('struct.u_struct', 'disp_xfer.u_struct') + + # only nonlinear xfers have load_xfer.u_struct + if self._mphys_variable_is_in_subsystem_inputs(self.load_xfer, 'u_struct'): + self.connect('struct.u_struct', ['load_xfer.u_struct']) + + super().configure() diff --git a/mphys/coupling_group.py b/mphys/coupling_group.py new file mode 100644 index 00000000..e8915491 --- /dev/null +++ b/mphys/coupling_group.py @@ -0,0 +1,6 @@ +from .mphys_group import MphysGroup + +class CouplingGroup(MphysGroup): + def _mphys_variable_is_in_subsystem_inputs(self, element, variable_name): + meta_data = element.get_io_metadata(iotypes='input', includes=variable_name) + return bool(meta_data) diff --git a/mphys/geo_disp.py b/mphys/geo_disp.py index c21ed8fa..790c6bc8 100644 --- a/mphys/geo_disp.py +++ b/mphys/geo_disp.py @@ -1,4 +1,3 @@ -import numpy as np import openmdao.api as om class GeoDisp(om.ExplicitComponent): @@ -8,18 +7,13 @@ class GeoDisp(om.ExplicitComponent): """ def initialize(self): self.options['distributed'] = True - self.options.declare('number_of_surface_nodes') + self.options.declare('number_of_nodes') def setup(self): - nnodes = self.options['number_of_surface_nodes'] + nnodes = self.options['number_of_nodes'] local_size = nnodes * 3 - n_list = self.comm.allgather(local_size) - irank = self.comm.rank - n1 = np.sum(n_list[:irank]) - n2 = np.sum(n_list[:irank+1]) - - self.add_input('x_aero0', shape_by_conn=True, + self.add_input('x_aero0', shape_by_conn=True, tags = ['mphys_coordinates'], desc='aerodynamic surface with geom changes') self.add_input('u_aero', shape_by_conn=True, desc='aerodynamic surface displacements') diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py new file mode 100644 index 00000000..cbf44903 --- /dev/null +++ b/mphys/mphys_group.py @@ -0,0 +1,40 @@ +from openmdao.api import Group + +class MphysGroup(Group): + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self.mphys_subsystems = [] + + def mphys_add_subsystem(self,name,subsystem): + subsystem = self.add_subsystem(name,subsystem) + self.mphys_subsystems.append(subsystem) + return subsystem + + def _mphys_promote_by_tag(self,iotype,tag): + for subsystem in self.mphys_subsystems: + promoted = [] + tagged_variables = subsystem.get_io_metadata(iotypes=iotype, metadata_keys=['tags'], tags=tag) + for val in tagged_variables.values(): + variable = val['prom_name'] + if variable not in promoted: + self.promotes(subsystem.name,any=[variable]) + promoted.append(variable) + + def _mphys_promote_coupling_variables(self): + self._mphys_promote_by_tag(['input','output'],'mphys_coupling') + + def _mphys_promote_design_variables(self): + self._mphys_promote_by_tag('input','mphys_dv') + + def _mphys_promote_mesh_coordinates(self): + self._mphys_promote_by_tag('input','mphys_coordinates') + + def _mphys_promote_results(self): + self._mphys_promote_by_tag('output','mphys_result') + + def configure(self): + self._mphys_promote_coupling_variables() + self._mphys_promote_design_variables() + self._mphys_promote_mesh_coordinates() + self._mphys_promote_results() diff --git a/mphys/mphys_meld.py b/mphys/mphys_meld.py index ee5bda00..5d62471f 100644 --- a/mphys/mphys_meld.py +++ b/mphys/mphys_meld.py @@ -315,10 +315,6 @@ def init_xfer_object(self, comm): self.struct_nnodes = self.struct_builder.get_nnodes() self.aero_nnodes = self.aero_builder.get_nnodes() - # api level method for all builders - def get_xfer_object(self): - return self.xfer_object - # api level method for all builders def get_element(self): diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index aebf00f4..dca31f69 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -1,59 +1,23 @@ -from __future__ import division, print_function -# from builder_class import Builder -import openmdao.api as om -from tacs import TACS,functions import numpy as np +from tacs import TACS,functions -# from sum_loads import SumLoads +import openmdao.api as om +from mphys.builder import Builder -class TacsMesh(om.ExplicitComponent): +class TacsMesh(om.IndepVarComp): """ Component to read the initial mesh coordinates with TACS - """ def initialize(self): - # self.options.declare('get_tacs', default = None, desc='function to get tacs') - self.options.declare('struct_solver', default = None, desc='the tacs object itself', recordable=False) - self.options.declare('surface_nodes', default = None, desc='surface nodes') + self.options.declare('tacs_assembler', default = None, desc='the tacs object itself', recordable=False) self.options['distributed'] = True def setup(self): - - tacs = self.options['struct_solver'] - # create some TACS bvecs that will be needed later - self.xpts = tacs.createNodeVec() - tacs.getNodes(self.xpts) - - # OpenMDAO setup - node_size = self.xpts.getArray().size - print('mesher x_struct0', node_size) - - self.surface_nodes = self.options['surface_nodes'] - self.add_output('x_struct0', shape=node_size, desc='structural node coordinates') - - # self.add_output('x_struct0_surface', shape= self.surface_nodes.size, desc='structural node coordinates') - - def mphys_add_coordinate_input(self): - self.add_input('x_struct0_points', shape_by_conn=True, desc='structural node coordinates') - - # return the promoted name and coordinates - return 'x_struct0_points', self.xpts.getArray() - - def compute(self,inputs,outputs): - if 'x_struct0_points' in inputs: - outputs['x_struct0'] = inputs['x_struct0_points'] - else: - - outputs['x_struct0'] = self.xpts.getArray() - # outputs['x_s0_surface'] = self.surface_nodes - - def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): - if mode == 'fwd': - if 'x_struct0_points' in d_inputs: - d_outputs['x_struct0'] += d_inputs['x_struct0_points'] - elif mode == 'rev': - if 'x_struct0_points' in d_inputs: - d_inputs['x_struct0_points'] += d_outputs['x_struct0'] + tacs_assembler = self.options['tacs_assembler'] + xpts = tacs_assembler.createNodeVec() + x = xpts.getArray() + tacs_assembler.getNodes(xpts) + self.add_output('x_struct0', val=x, shape=x.size, desc='structural node coordinates') class TacsSolver(om.ImplicitComponent): """ @@ -65,7 +29,7 @@ class TacsSolver(om.ImplicitComponent): """ def initialize(self): - self.options.declare('struct_solver', recordable=False) + self.options.declare('tacs_assembler', recordable=False) self.options.declare('struct_objects', recordable=False) self.options.declare('check_partials') @@ -90,7 +54,7 @@ def setup(self): self.check_partials = self.options['check_partials'] #self.set_check_partial_options(wrt='*',method='cs',directional=True) - tacs_assembler = self.options['struct_solver'] + tacs_assembler = self.options['tacs_assembler'] struct_objects = self.options['struct_objects'] # these objects come from self.struct_objects but ideally, they should be attributes of the struct solver object mat = struct_objects[0] @@ -120,13 +84,13 @@ def setup(self): self.ndof = int(state_size/(node_size/3)) # inputs - self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables') - self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates') - self.add_input('f_struct', shape_by_conn=True, desc='structural load vector') + self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates',tags=['mphys_coordinates']) + self.add_input('f_struct', shape_by_conn=True, desc='structural load vector', tags=['mphys_coupling']) # outputs # its important that we set this to zero since this displacement value is used for the first iteration of the aero - self.add_output('u_struct', shape=state_size, val = np.zeros(state_size),desc='structural state vector') + self.add_output('u_struct', shape=state_size, val = np.zeros(state_size),desc='structural state vector', tags=['mphys_coupling']) # partials #self.declare_partials('u_struct',['dv_struct','x_struct0','f_struct']) @@ -277,7 +241,6 @@ def solve_linear(self,d_outputs,d_residuals,mode): d_residuals['u_struct'] -= np.array(after - before,dtype=np.float64) def apply_linear(self,inputs,outputs,d_inputs,d_outputs,d_residuals,mode): - # import ipdb; ipdb.set_trace() self._update_internal(inputs,outputs) if mode == 'fwd': if self.check_partials: @@ -357,7 +320,7 @@ def _design_vector_changed(self,x): return False -class TacsSolver_Conduction(om.ImplicitComponent): +class TacsSolverConduction(om.ImplicitComponent): """ Component to perform TACS steady conduction analysis @@ -370,13 +333,13 @@ class TacsSolver_Conduction(om.ImplicitComponent): """ def initialize(self): - self.options.declare('struct_solver') + self.options.declare('tacs_assembler') self.options.declare('struct_objects') self.options.declare('check_partials') self.options['distributed'] = True - self.tacs = None + self.tacs_assembler = None self.pc = None self.res = None @@ -391,30 +354,23 @@ def initialize(self): def setup(self): self.check_partials = self.options['check_partials'] - tacs = self.options['struct_solver'] + self.tacs_assembler = self.options['tacs_assembler'] struct_objects = self.options['struct_objects'] # these objects come from self.struct_objects but ideally, they should be attributes of the struct solver object - mat = struct_objects[0] - pc = struct_objects[1] - gmres = struct_objects[2] - ndv = struct_objects[3]['ndv'] + self.mat = struct_objects[0] + self.pc = struct_objects[1] + self.gmres = struct_objects[2] + self.ndv = struct_objects[3]['ndv'] self.solver_dict = struct_objects[3] - # TACS assembler setup - self.tacs = tacs - self.mat = mat - self.pc = pc - self.gmres = gmres - self.ndv = ndv - # create some TACS bvecs that will be needed later - self.res = tacs.createVec() - self.force = tacs.createVec() - self.ans = tacs.createVec() - self.heat = tacs.createVec() - self.struct_rhs = tacs.createVec() - self.psi_s = tacs.createVec() - self.xpt_sens = tacs.createNodeVec() + self.res = self.tacs_assembler.createVec() + self.force = self.tacs_assembler.createVec() + self.ans = self.tacs_assembler.createVec() + self.heat = self.tacs_assembler.createVec() + self.struct_rhs = self.tacs_assembler.createVec() + self.psi_s = self.tacs_assembler.createVec() + self.xpt_sens = self.tacs_assembler.createNodeVec() # OpenMDAO setup surface_nodes = self.solver_dict['surface_nodes'] @@ -426,11 +382,11 @@ def setup(self): # inputs # self.add_input('dv_struct', shape=ndv , desc='tacs design variables') - self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates') - self.add_input('q_conduct', shape_by_conn=True, desc='structural load vector') + self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates', tags=['mphys_coordinates']) + self.add_input('q_conduct', shape_by_conn=True, desc='structural load vector', tags=['mphys_coupling']) # outputs - self.add_output('T_conduct', shape=surface_nodes.size//3, val = np.ones(surface_nodes.size//3)*300,desc='temperature vector') + self.add_output('T_conduct', shape=surface_nodes.size//3, val = np.ones(surface_nodes.size//3)*300,desc='temperature vector', tags=['mphys_coupling']) # partials #self.declare_partials('u_struct',['dv_struct','x_struct0','f_struct']) @@ -455,22 +411,21 @@ def _update_internal(self,inputs,outputs=None): beta = 0.0 gamma = 0.0 - xpts = self.tacs.createNodeVec() - self.tacs.getNodes(xpts) + xpts = self.tacs_assembler.createNodeVec() + self.tacs_assembler.getNodes(xpts) xpts_array = xpts.getArray() xpts_array[:] = inputs['x_struct0'] - self.tacs.setNodes(xpts) + self.tacs_assembler.setNodes(xpts) - res = self.tacs.createVec() + res = self.tacs_assembler.createVec() res_array = res.getArray() res_array[:] = 0.0 - self.tacs.assembleJacobian(alpha,beta,gamma,res,self.mat) + self.tacs_assembler.assembleJacobian(alpha,beta,gamma,res,self.mat) pc.factor() def solve_nonlinear(self, inputs, outputs): - tacs = self.tacs force = self.force ans = self.ans pc = self.pc @@ -485,12 +440,12 @@ def solve_nonlinear(self, inputs, outputs): heat_array[self.mapping[i]] = inputs['q_conduct'][i] - self.tacs.setBCs(heat) + self.tacs_assembler.setBCs(heat) gmres.solve(heat, ans) ans_array = ans.getArray() - tacs.setVariables(ans) + self.tacs_assembler.setVariables(ans) ans_array = ans.getArray() @@ -509,7 +464,7 @@ class TacsFunctions(om.ExplicitComponent): Component to compute TACS functions """ def initialize(self): - self.options.declare('struct_solver', recordable=False) + self.options.declare('tacs_assembler', recordable=False) self.options.declare('struct_objects', recordable=False) self.options.declare('check_partials') @@ -520,7 +475,7 @@ def initialize(self): def setup(self): - self.tacs_assembler = self.options['struct_solver'] + self.tacs_assembler = self.options['tacs_assembler'] self.struct_objects = self.options['struct_objects'] self.check_partials = self.options['check_partials'] @@ -546,9 +501,9 @@ def setup(self): # OpenMDAO part of setup # TODO move the dv_struct to an external call where we add the DVs - self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables') - self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates') - self.add_input('u_struct', shape_by_conn=True, desc='structural state vector') + self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates',tags=['mphys_coordinates']) + self.add_input('u_struct', shape_by_conn=True, desc='structural state vector', tags=['mphys_coupling']) # Remove the mass function from the func list if it is there # since it is not dependent on the structural state @@ -559,7 +514,7 @@ def setup(self): self.func_list = func_no_mass if len(self.func_list) > 0: - self.add_output('func_struct', shape=len(self.func_list), desc='structural function values') + self.add_output('func_struct', shape=len(self.func_list), desc='structural function values', tags=['mphys_result']) # declare the partials #self.declare_partials('f_struct',['dv_struct','x_struct0','u_struct']) @@ -642,7 +597,7 @@ class TacsMass(om.ExplicitComponent): Component to compute TACS mass """ def initialize(self): - self.options.declare('struct_solver', recordable=False) + self.options.declare('tacs_assembler', recordable=False) self.options.declare('struct_objects', recordable=False) self.options.declare('check_partials') @@ -655,7 +610,7 @@ def initialize(self): def setup(self): - self.tacs_assembler = self.options['struct_solver'] + self.tacs_assembler = self.options['tacs_assembler'] self.struct_objects = self.options['struct_objects'] self.check_partials = self.options['check_partials'] @@ -670,10 +625,10 @@ def setup(self): self.xpt_sens = tacs_assembler.createNodeVec() # OpenMDAO part of setup - self.add_input('dv_struct', shape=ndv, desc='tacs design variables') - self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates') + self.add_input('dv_struct', shape=ndv, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates', tags=['mphys_coordinates']) - self.add_output('mass', 0.0, desc = 'structural mass') + self.add_output('mass', 0.0, desc = 'structural mass', tags=['mphys_result']) #self.declare_partials('mass',['dv_struct','x_struct0']) def _update_internal(self,inputs): @@ -747,8 +702,8 @@ def setup(self): self.ndof = int(state_size / ( node_size / 3 )) # OpenMDAO setup - self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates') - self.add_output('f_struct', shape=state_size, desc='structural load') + self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates', tags=['mphys_coordinates']) + self.add_output('f_struct', shape=state_size, desc='structural load', tags=['mphys_coupling']) #self.declare_partials('f_struct','x_struct0') @@ -758,7 +713,7 @@ def compute(self,inputs,outputs): class TacsGroup(om.Group): def initialize(self): - self.options.declare('solver', recordable=False) + self.options.declare('tacs_assembler', recordable=False) self.options.declare('solver_objects', recordable=False) self.options.declare('check_partials') self.options.declare('conduction', default=False) @@ -766,7 +721,7 @@ def initialize(self): def setup(self): - self.struct_solver = self.options['solver'] + self.tacs_assembler = self.options['tacs_assembler'] self.struct_objects = self.options['solver_objects'] self.check_partials = self.options['check_partials'] @@ -777,12 +732,12 @@ def setup(self): self.prescribed_load = True self.add_subsystem('loads', PrescribedLoad( load_function=solver_dict['load_function'], - tacs_assembler=self.struct_solver + tacs_assembler=self.tacs_assembler ), promotes_inputs=['x_struct0'], promotes_outputs=['f_struct']) if self.options['conduction']: - self.add_subsystem('solver', TacsSolver_Conduction( - struct_solver=self.struct_solver, + self.add_subsystem('solver', TacsSolverConduction( + tacs_assembler=self.tacs_assembler, struct_objects=self.struct_objects, check_partials=self.check_partials), promotes_inputs=['q_conduct', 'x_struct0'], @@ -790,16 +745,16 @@ def setup(self): ) else: self.add_subsystem('solver', TacsSolver( - struct_solver=self.struct_solver, + tacs_assembler=self.tacs_assembler, struct_objects=self.struct_objects, check_partials=self.check_partials), promotes_inputs=['f_struct', 'x_struct0', 'dv_struct'], promotes_outputs=['u_struct'] ) # sum aero, inertial, and fual loads: result is F_summed, which tacs accepts as an input - nnodes = int(self.struct_solver.createNodeVec().getArray().size/3) + nnodes = int(self.tacs_assembler.createNodeVec().getArray().size/3) - vec_size_g = np.sum(self.comm.gather(self.struct_solver.getNumOwnedNodes()*6)) + vec_size_g = np.sum(self.comm.gather(self.tacs_assembler.getNumOwnedNodes()*6)) vec_size_g = int(self.comm.bcast(vec_size_g)) # self.add_subsystem('sum_loads',SumLoads( @@ -824,24 +779,25 @@ def setup(self): class TACSFuncsGroup(om.Group): def initialize(self): - self.options.declare('solver', recordable=False) + self.options.declare('tacs_assembler', recordable=False) self.options.declare('solver_objects', recordable=False) self.options.declare('check_partials') def setup(self): - self.struct_solver = self.options['solver'] + self.tacs_assembler = self.options['tacs_assembler'] self.struct_objects = self.options['solver_objects'] self.check_partials = self.options['check_partials'] self.add_subsystem('funcs', TacsFunctions( - struct_solver=self.struct_solver, + tacs_assembler=self.tacs_assembler, struct_objects=self.struct_objects, check_partials=self.check_partials), - promotes_inputs=['x_struct0', 'dv_struct'] + promotes_inputs=['x_struct0', 'u_struct','dv_struct'], + promotes_outputs=['func_struct'] ) self.add_subsystem('mass', TacsMass( - struct_solver=self.struct_solver, + tacs_assembler=self.tacs_assembler, struct_objects=self.struct_objects, check_partials=self.check_partials), promotes_inputs=['x_struct0', 'dv_struct'], @@ -851,7 +807,7 @@ def setup(self): def configure(self): pass -class TacsBuilder(object): +class TacsBuilder(Builder): def __init__(self, options,check_partials=False, conduction=False): # super(TACS_builder, self).__init__(options) @@ -859,9 +815,8 @@ def __init__(self, options,check_partials=False, conduction=False): self.check_partials = check_partials self.conduction = conduction - # api level method for all builders - def init_solver(self, comm): + def initialize(self, comm): # if self.solver is None: solver_dict={} @@ -870,11 +825,10 @@ def init_solver(self, comm): mesh.scanBDFFile(self.options['mesh_file']) ndof, ndv = self.options['add_elements'](mesh) - self.n_dv_struct = ndv tacs_assembler = mesh.createTACS(ndof) - nnodes = int(tacs_assembler.createNodeVec().getArray().size / 3) + nnodes = tacs_assembler.createNodeVec().getArray().size // 3 if self.conduction: mat = tacs_assembler.createSchurMat() @@ -894,62 +848,36 @@ def init_solver(self, comm): #use the supplied function to get the surface points and mapping if self.conduction: - solver_dict['surface_nodes'], solver_dict['mapping'] = self.options['get_surface'](tacs) + solver_dict['surface_nodes'], solver_dict['mapping'] = self.options['get_surface'](tacs_assembler) if 'f5_writer' in self.options.keys(): solver_dict['f5_writer'] = self.options['f5_writer'] # check if the user provided a load function - if 'load_function' in self.options: + if 'load_function' in self.options.keys(): solver_dict['load_function'] = self.options['load_function'] self.solver_dict=solver_dict - - # put the rest of the stuff in a tuple - solver_objects = [mat, pc, gmres, solver_dict] - - self.solver = tacs_assembler - self.solver_objects = solver_objects - - # api level method for all builders - def get_solver(self): - return self.solver + self.tacs_assembler = tacs_assembler + self.solver_objects = [mat, pc, gmres, solver_dict] # api level method for all builders - def get_element(self, **kwargs): - return TacsGroup(solver=self.solver, solver_objects=self.solver_objects, check_partials=self.check_partials, **kwargs) - - def get_mesh_element(self): - return TacsMesh(struct_solver=self.solver) - - def get_mesh_connections(self): - return { - 'solver':{ - 'x_struct0' : 'x_struct0', - }, - 'funcs':{ - 'x_struct0' : 'x_struct0', - }, - } - - def get_scenario_element(self): - return TACSFuncsGroup( - solver=self.solver, + def get_coupling_group_subsystem(self, **kwargs): + return TacsGroup(tacs_assembler=self.tacs_assembler, + solver_objects=self.solver_objects, + check_partials=self.check_partials, + **kwargs) + + def get_mesh_coordinate_subsystem(self): + return TacsMesh(tacs_assembler=self.tacs_assembler) + + def get_scenario_subsystems(self): + return None, TACSFuncsGroup( + tacs_assembler=self.tacs_assembler, solver_objects=self.solver_objects, check_partials=self.check_partials ) - def get_scenario_connections(self): - # this is the stuff we want to be connected - # between the solver and the functionals. - # these variables FROM the solver are connected - # TO the funcs element. So the solver has output - # and funcs has input. key is the output, - # variable is the input in the returned dict. - return { - 'u_struct' : 'funcs.u_struct', - } - def get_ndof(self): return self.solver_dict['ndof'] @@ -958,15 +886,3 @@ def get_nnodes(self): def get_ndv(self): return self.solver_dict['ndv'] - - - def get_object(self): - return self.solver - - def build_object(self, comm): - self.init_solver(comm) - - def get_component(self, **kwargs): - yield '_mesh', TacsMesh(struct_solver=self.solver, surface_nodes=self.solver_dict['surface_nodes']) - yield '', TacsGroup(solver=self.solver, solver_objects=self.solver_objects, check_partials=self.check_partials, conduction=self.conduction, **kwargs) - # yield 'hi', 0 diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index e9f39d54..287b677b 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -2,6 +2,7 @@ import numpy as np import openmdao.api as om +from mphys import Builder from vlm_solver import VLM_solver, VLM_forces @@ -17,12 +18,6 @@ def setup(self): self.x_a0 = self.options['x_aero0'] self.add_output('x_aero0',np.zeros(N_nodes*3)) - def mphys_add_coordinate_input(self): - - N_nodes = self.options['N_nodes'] - self.add_input('x_aero0_points',np.zeros(N_nodes*3)) - return 'x_aero0_points', self.x_a0 - def compute(self,inputs,outputs): if 'x_aero0_points' in inputs: @@ -38,41 +33,6 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'x_aero0_points' in d_inputs: d_inputs['x_aero0_points'] += d_outputs['x_aero0'] -class GeoDisp(om.ExplicitComponent): - """ - This component adds the aerodynamic - displacements to the geometry-changed aerodynamic surface - """ - def initialize(self): - self.options['distributed'] = True - self.options.declare('nnodes') - - def setup(self): - aero_nnodes = self.options['nnodes'] - local_size = aero_nnodes * 3 - - self.add_input('x_aero0', shape_by_conn=True ,desc='aerodynamic surface with geom changes') - self.add_input('u_aero', shape_by_conn=True, desc='aerodynamic surface displacements') - - self.add_output('x_aero',shape=local_size,desc='deformed aerodynamic surface') - - def compute(self,inputs,outputs): - outputs['x_aero'] = inputs['x_aero0'] + inputs['u_aero'] - - def compute_jacvec_product(self,inputs,d_inputs,d_outputs,mode): - if mode == 'fwd': - if 'x_aero' in d_outputs: - if 'x_aero0' in d_inputs: - d_outputs['x_aero'] += d_inputs['x_aero0'] - if 'u_aero' in d_inputs: - d_outputs['x_aero'] += d_inputs['u_aero'] - if mode == 'rev': - if 'x_aero' in d_outputs: - if 'x_aero0' in d_inputs: - d_inputs['x_aero0'] += d_outputs['x_aero'] - if 'u_aero' in d_inputs: - d_inputs['u_aero'] += d_outputs['x_aero'] - class VlmGroup(om.Group): def initialize(self): @@ -97,24 +57,21 @@ def setup(self): if 'compute_traction' in options_dict: compute_traction = options_dict['compute_traction'] - self.add_subsystem('geo_disp', GeoDisp(nnodes=N_nodes), promotes_inputs=['u_aero', 'x_aero0']) - self.add_subsystem('solver', VLM_solver( N_nodes=N_nodes, N_elements=N_elements, quad=quad), - promotes_inputs=['aoa','mach']) + promotes_inputs=['aoa','mach',('xa','x_aero'),]) self.add_subsystem('forces', VLM_forces( N_nodes=N_nodes, N_elements=N_elements, quad=quad, compute_traction=compute_traction), - promotes_inputs=['mach','q_inf','vel','mu'], + promotes_inputs=['mach','q_inf','vel','mu', ('xa','x_aero')], promotes_outputs=[('fa','f_aero'),'CL','CD']) def configure(self): - self.connect('geo_disp.x_aero', ['solver.xa', 'forces.xa']) self.connect('solver.Cp', 'forces.Cp') class DummyVlmSolver(object): @@ -126,20 +83,18 @@ class DummyVlmSolver(object): def __init__(self, options, comm): self.options = options self.comm = comm - self.allWallsGroup = 'allWalls' # the methods below here are required for RLT def getSurfaceCoordinates(self, group): - # just return the full coordinates return self.options['x_aero0'] def getSurfaceConnectivity(self, group): - # -1 for the conversion between fortran and C - conn = self.options['quad'].copy() -1 + fortran_offset = -1 + conn = self.options['quad'].copy() + fortran_offset faceSizes = 4*np.ones(len(conn), 'intc') return conn.astype('intc'), faceSizes -class VlmBuilder(object): +class VlmBuilder(Builder): def __init__(self, options): self.options = options diff --git a/mphys/multipoint.py b/mphys/multipoint.py index cc0b67d3..2b5ad263 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -1,219 +1,33 @@ -from collections import OrderedDict import openmdao.api as om -from mphys.scenario import Scenario -from mphys.error import MPHYS_Error class Multipoint(om.Group): + def mphys_add_scenario(self, name, scenario, coupling_nonlinear_solver=None, + coupling_linear_solver=None): + scenario = self.add_subsystem(name,scenario) - def initialize(self): + if coupling_nonlinear_solver is not None: + scenario.coupling.nonlinear_solver = coupling_nonlinear_solver - # define the inputs we need - self.options.declare('aero_builder', default=None, recordable=False) - self.options.declare('struct_builder', default=None, recordable=False) - self.options.declare('xfer_builder', default=None, recordable=False) - self.options.declare('prop_builder', default=None, recordable=False) + if coupling_linear_solver is not None: + scenario.coupling.nonlinear_solver = coupling_linear_solver - # ordered dict to save all the scenarios user adds - self.scenarios = OrderedDict() + def mphys_connect_scenario_coordinate_source(self, source, scenarios, disciplines): + """ - def setup(self): + Parameters + ---------- + disciplines : str or list[str] + The extension of the after the underscore in x_ for + source: openmdao.api.Group or openmdao.api.Component - # set the builders - self.aero_builder = self.options['aero_builder'] - self.struct_builder = self.options['struct_builder'] - self.xfer_builder = self.options['xfer_builder'] - self.prop_builder = self.options['prop_builder'] + scenario_list : str or list[str] - # we need to initialize the aero and struct objects before the xfer one - # potentially need to do fancy stuff with the comms here if user wants to run in parallel - # in that case, the scenarios themselves would likely need to initialize solvers themselves + """ + scenarios_list = scenarios if type(scenarios) == list else [scenarios] + disciplines_list = disciplines if type(disciplines) == list else [disciplines] - # only initialize solvers if we have the builders - if self.aero_builder is not None: - self.aero_builder.init_solver(self.comm) - self.aero_discipline = True - else: - # no aero builder, so we won't have this discipline - self.aero_discipline = False - - if self.struct_builder is not None: - self.struct_builder.init_solver(self.comm) - self.struct_discipline = True - else: - # no struct builder, so we won't have this discipline - self.struct_discipline = False - - if self.prop_builder is not None: - self.prop_builder.init_solver(self.comm) - self.prop_discipline = True - else: - self.prop_discipline = False - - # check if we have aero and structure - if self.aero_discipline and self.struct_discipline: - - # aerostructural coupling is active - self.as_coupling = True - - # we need a xfer builder for aero and structures - if self.xfer_builder is None: - raise MPHYS_Error('Multipoint group requires a transfer builder to couple aerodynamic and structural analyses.') - - # now initialize the xfer object - self.xfer_builder.init_xfer_object(self.comm) - else: - # we dont have aerostructural coupling - self.as_coupling = False - - # get the mesh elements from disciplines - if self.aero_discipline: - aero_mesh = self.aero_builder.get_mesh_element() - self.add_subsystem('aero_mesh', aero_mesh) - - if self.struct_discipline: - struct_mesh = self.struct_builder.get_mesh_element() - self.add_subsystem('struct_mesh', struct_mesh) - - # add openmdao groups for each scenario - for name, kwargs in self.scenarios.items(): - self._mphys_add_scenario(name, **kwargs) - - # set solvers - self.nonlinear_solver = om.NonlinearRunOnce() - self.linear_solver = om.LinearRunOnce() - - def configure(self): - # connect the initial mesh coordinates. - # at this stage, everything is allocated and every group/component - # below this level is set up. - - # loop over scenarios and connect them all - for name in self.scenarios: - if self.struct_discipline: - # make the default mesh connections for as_coupling - if self.as_coupling: - target_x_s0 = ['%s.solver_group.disp_xfer.x_struct0'%name] - target_x_s0.append('%s.solver_group.load_xfer.x_struct0'%name) - self.connect('struct_mesh.x_struct0', target_x_s0) - - # check if we have custom mesh connections - if hasattr(self.struct_builder, 'get_mesh_connections'): - mesh_conn = self.struct_builder.get_mesh_connections() - - # if mesh_conn has entries called 'solver' or 'funcs', - # then we know that these are dictionaries of connections - # to be made to solver or funcs. If there are no solver - # or funcs entries in here, we just assume every key - # will be connected to the solver. - if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): - # if solver is in the dict, we connect the keys of that dict - if 'solver' in mesh_conn: - mesh_to_solver = mesh_conn['solver'] - # if solver is not in this, it means that funcs is not in - # mesh_conn, which then means that mesh_conn only has - # connections that go to the solver by default - else: - mesh_to_solver = mesh_conn - - for k,v in mesh_to_solver.items(): - self.connect('struct_mesh.%s'%k, '%s.solver_group.struct.%s'%(name, v)) - - # if funcs is in the dict, we just connect the entries from this to the funcs - if 'funcs' in mesh_conn: - mesh_to_funcs = mesh_conn['funcs'] - for k,v in mesh_to_funcs.items(): - self.connect('struct_mesh.%s'%k, '%s.struct_funcs.%s'%(name, v)) - - # if the solver did not define any custom mesh connections, - # we will just connect the nodes from the mesh to solver - else: - self.connect('struct_mesh.x_struct0', '%s.solver_group.struct.x_struct0'%name) - - if self.aero_discipline: - # make the default mesh connections for as_coupling - if self.as_coupling: - target_x_a0 = ['%s.solver_group.load_xfer.x_aero0'%name] - target_x_a0.append('%s.solver_group.disp_xfer.x_aero0'%name) - self.connect('aero_mesh.x_aero0', target_x_a0) - - # check if we have custom mesh connections - if hasattr(self.aero_builder, 'get_mesh_connections'): - mesh_conn = self.aero_builder.get_mesh_connections() - - # if mesh_conn has entries called 'solver' or 'funcs', - # then we know that these are dictionaries of connections - # to be made to solver or funcs. If there are no solver - # or funcs entries in here, we just assume every key - # will be connected to the solver. - if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): - # if solver is in the dict, we connect the keys of that dict - if 'solver' in mesh_conn: - mesh_to_solver = mesh_conn['solver'] - # if solver is not in this, it means that funcs is not in - # mesh_conn, which then means that mesh_conn only has - # connections that go to the solver by default - else: - mesh_to_solver = mesh_conn - - for k,v in mesh_to_solver.items(): - self.connect('aero_mesh.%s'%k, '%s.solver_group.aero.%s'%(name, v)) - - # if funcs is in the dict, we just connect the entries from this to the funcs - if 'funcs' in mesh_conn: - mesh_to_funcs = mesh_conn['funcs'] - for k,v in mesh_to_funcs.items(): - self.connect('aero_mesh.%s'%k, '%s.aero_funcs.%s'%(name, v)) - - # if the solver did not define any custom mesh connections, - # we will just connect the nodes from the mesh to solver - else: - self.connect('aero_mesh.x_aero0', '%s.solver_group.aero.x_aero0'%name) - - - def mphys_add_scenario(self, name, **kwargs): - # save all the data until we are ready to initialize the objects themselves - self.scenarios[name] = kwargs - - def _mphys_add_scenario(self, name, min_procs=None, max_procs=None, aero_kwargs={}, struct_kwargs={}, xfer_kwargs={}): - # this is the actual routine that does the addition of the OpenMDAO groups - # this is called during the setup of this class - self.add_subsystem( - name, - Scenario( - builders = { - 'aero': self.aero_builder, - 'struct': self.struct_builder, - 'prop': self.prop_builder, - 'xfer': self.xfer_builder, - }, - aero_discipline = self.aero_discipline, - struct_discipline = self.struct_discipline, - prop_discipline = self.prop_discipline, - as_coupling = self.as_coupling - ) - ) - - def mphys_add_coordinate_input(self): - # add the coordinates as inputs for every discipline we have - points = {} - - if self.aero_discipline: - name, x_a0 = self.aero_mesh.mphys_add_coordinate_input() - points['aero_points'] = x_a0 - self.promotes('aero_mesh', inputs=[(name, 'aero_points')]) - - if self.struct_discipline: - name, x_s0 = self.struct_mesh.mphys_add_coordinate_input() - points['struct_points'] = x_s0 - self.promotes('struct_mesh', inputs=[(name, 'struct_points')]) - - return points - - - def mphys_get_triangulated_surface(self): - # get triangulated surface for computing constraints - if self.aero_discipline: - x_a0_tri = self.aero_mesh.mphys_get_triangulated_surface() - return x_a0_tri - else: - raise NotImplementedError('Only ADFlow format supported so far') + for scenario in scenarios_list: + for discipline in disciplines_list: + src = "%s.x_%s0" % (source, discipline) + target = "%s.x_%s0" % (scenario, discipline) + self.connect(src,target) diff --git a/mphys/multipoint_old.py b/mphys/multipoint_old.py new file mode 100644 index 00000000..cc0b67d3 --- /dev/null +++ b/mphys/multipoint_old.py @@ -0,0 +1,219 @@ +from collections import OrderedDict +import openmdao.api as om +from mphys.scenario import Scenario +from mphys.error import MPHYS_Error + +class Multipoint(om.Group): + + def initialize(self): + + # define the inputs we need + self.options.declare('aero_builder', default=None, recordable=False) + self.options.declare('struct_builder', default=None, recordable=False) + self.options.declare('xfer_builder', default=None, recordable=False) + self.options.declare('prop_builder', default=None, recordable=False) + + # ordered dict to save all the scenarios user adds + self.scenarios = OrderedDict() + + def setup(self): + + # set the builders + self.aero_builder = self.options['aero_builder'] + self.struct_builder = self.options['struct_builder'] + self.xfer_builder = self.options['xfer_builder'] + self.prop_builder = self.options['prop_builder'] + + # we need to initialize the aero and struct objects before the xfer one + # potentially need to do fancy stuff with the comms here if user wants to run in parallel + # in that case, the scenarios themselves would likely need to initialize solvers themselves + + # only initialize solvers if we have the builders + if self.aero_builder is not None: + self.aero_builder.init_solver(self.comm) + self.aero_discipline = True + else: + # no aero builder, so we won't have this discipline + self.aero_discipline = False + + if self.struct_builder is not None: + self.struct_builder.init_solver(self.comm) + self.struct_discipline = True + else: + # no struct builder, so we won't have this discipline + self.struct_discipline = False + + if self.prop_builder is not None: + self.prop_builder.init_solver(self.comm) + self.prop_discipline = True + else: + self.prop_discipline = False + + # check if we have aero and structure + if self.aero_discipline and self.struct_discipline: + + # aerostructural coupling is active + self.as_coupling = True + + # we need a xfer builder for aero and structures + if self.xfer_builder is None: + raise MPHYS_Error('Multipoint group requires a transfer builder to couple aerodynamic and structural analyses.') + + # now initialize the xfer object + self.xfer_builder.init_xfer_object(self.comm) + else: + # we dont have aerostructural coupling + self.as_coupling = False + + # get the mesh elements from disciplines + if self.aero_discipline: + aero_mesh = self.aero_builder.get_mesh_element() + self.add_subsystem('aero_mesh', aero_mesh) + + if self.struct_discipline: + struct_mesh = self.struct_builder.get_mesh_element() + self.add_subsystem('struct_mesh', struct_mesh) + + # add openmdao groups for each scenario + for name, kwargs in self.scenarios.items(): + self._mphys_add_scenario(name, **kwargs) + + # set solvers + self.nonlinear_solver = om.NonlinearRunOnce() + self.linear_solver = om.LinearRunOnce() + + def configure(self): + # connect the initial mesh coordinates. + # at this stage, everything is allocated and every group/component + # below this level is set up. + + # loop over scenarios and connect them all + for name in self.scenarios: + if self.struct_discipline: + # make the default mesh connections for as_coupling + if self.as_coupling: + target_x_s0 = ['%s.solver_group.disp_xfer.x_struct0'%name] + target_x_s0.append('%s.solver_group.load_xfer.x_struct0'%name) + self.connect('struct_mesh.x_struct0', target_x_s0) + + # check if we have custom mesh connections + if hasattr(self.struct_builder, 'get_mesh_connections'): + mesh_conn = self.struct_builder.get_mesh_connections() + + # if mesh_conn has entries called 'solver' or 'funcs', + # then we know that these are dictionaries of connections + # to be made to solver or funcs. If there are no solver + # or funcs entries in here, we just assume every key + # will be connected to the solver. + if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): + # if solver is in the dict, we connect the keys of that dict + if 'solver' in mesh_conn: + mesh_to_solver = mesh_conn['solver'] + # if solver is not in this, it means that funcs is not in + # mesh_conn, which then means that mesh_conn only has + # connections that go to the solver by default + else: + mesh_to_solver = mesh_conn + + for k,v in mesh_to_solver.items(): + self.connect('struct_mesh.%s'%k, '%s.solver_group.struct.%s'%(name, v)) + + # if funcs is in the dict, we just connect the entries from this to the funcs + if 'funcs' in mesh_conn: + mesh_to_funcs = mesh_conn['funcs'] + for k,v in mesh_to_funcs.items(): + self.connect('struct_mesh.%s'%k, '%s.struct_funcs.%s'%(name, v)) + + # if the solver did not define any custom mesh connections, + # we will just connect the nodes from the mesh to solver + else: + self.connect('struct_mesh.x_struct0', '%s.solver_group.struct.x_struct0'%name) + + if self.aero_discipline: + # make the default mesh connections for as_coupling + if self.as_coupling: + target_x_a0 = ['%s.solver_group.load_xfer.x_aero0'%name] + target_x_a0.append('%s.solver_group.disp_xfer.x_aero0'%name) + self.connect('aero_mesh.x_aero0', target_x_a0) + + # check if we have custom mesh connections + if hasattr(self.aero_builder, 'get_mesh_connections'): + mesh_conn = self.aero_builder.get_mesh_connections() + + # if mesh_conn has entries called 'solver' or 'funcs', + # then we know that these are dictionaries of connections + # to be made to solver or funcs. If there are no solver + # or funcs entries in here, we just assume every key + # will be connected to the solver. + if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): + # if solver is in the dict, we connect the keys of that dict + if 'solver' in mesh_conn: + mesh_to_solver = mesh_conn['solver'] + # if solver is not in this, it means that funcs is not in + # mesh_conn, which then means that mesh_conn only has + # connections that go to the solver by default + else: + mesh_to_solver = mesh_conn + + for k,v in mesh_to_solver.items(): + self.connect('aero_mesh.%s'%k, '%s.solver_group.aero.%s'%(name, v)) + + # if funcs is in the dict, we just connect the entries from this to the funcs + if 'funcs' in mesh_conn: + mesh_to_funcs = mesh_conn['funcs'] + for k,v in mesh_to_funcs.items(): + self.connect('aero_mesh.%s'%k, '%s.aero_funcs.%s'%(name, v)) + + # if the solver did not define any custom mesh connections, + # we will just connect the nodes from the mesh to solver + else: + self.connect('aero_mesh.x_aero0', '%s.solver_group.aero.x_aero0'%name) + + + def mphys_add_scenario(self, name, **kwargs): + # save all the data until we are ready to initialize the objects themselves + self.scenarios[name] = kwargs + + def _mphys_add_scenario(self, name, min_procs=None, max_procs=None, aero_kwargs={}, struct_kwargs={}, xfer_kwargs={}): + # this is the actual routine that does the addition of the OpenMDAO groups + # this is called during the setup of this class + self.add_subsystem( + name, + Scenario( + builders = { + 'aero': self.aero_builder, + 'struct': self.struct_builder, + 'prop': self.prop_builder, + 'xfer': self.xfer_builder, + }, + aero_discipline = self.aero_discipline, + struct_discipline = self.struct_discipline, + prop_discipline = self.prop_discipline, + as_coupling = self.as_coupling + ) + ) + + def mphys_add_coordinate_input(self): + # add the coordinates as inputs for every discipline we have + points = {} + + if self.aero_discipline: + name, x_a0 = self.aero_mesh.mphys_add_coordinate_input() + points['aero_points'] = x_a0 + self.promotes('aero_mesh', inputs=[(name, 'aero_points')]) + + if self.struct_discipline: + name, x_s0 = self.struct_mesh.mphys_add_coordinate_input() + points['struct_points'] = x_s0 + self.promotes('struct_mesh', inputs=[(name, 'struct_points')]) + + return points + + + def mphys_get_triangulated_surface(self): + # get triangulated surface for computing constraints + if self.aero_discipline: + x_a0_tri = self.aero_mesh.mphys_get_triangulated_surface() + return x_a0_tri + else: + raise NotImplementedError('Only ADFlow format supported so far') diff --git a/mphys/scenario.py b/mphys/scenario.py index 1450e4a7..4527353c 100644 --- a/mphys/scenario.py +++ b/mphys/scenario.py @@ -1,78 +1,12 @@ -import openmdao.api as om -from mphys.solver_group import SolverGroup - -class Scenario(om.Group): - - def initialize(self): - - # define the inputs we need - self.options.declare('builders', allow_none=False, recordable=False) - self.options.declare('aero_discipline', allow_none=False) - self.options.declare('struct_discipline', allow_none=False) - self.options.declare('prop_discipline', allow_none=False) - self.options.declare('as_coupling', allow_none=False) - - def setup(self): - - # set flags - self.aero_discipline = self.options['aero_discipline'] - self.struct_discipline = self.options['struct_discipline'] - self.prop_discipline = self.options['prop_discipline'] - self.as_coupling = self.options['as_coupling'] - - # set the builders - self.aero_builder = self.options['builders']['aero'] - self.struct_builder = self.options['builders']['struct'] - self.prop_builder = self.options['builders']['prop'] - self.xfer_builder = self.options['builders']['xfer'] - - # add the solver group itself and pass in the builders - # this group will converge the nonlinear analysis - self.add_subsystem( - 'solver_group', - SolverGroup( - builders=self.options['builders'], - aero_discipline = self.aero_discipline, - struct_discipline = self.struct_discipline, - prop_discipline = self.prop_discipline, - as_coupling = self.as_coupling - ) - ) - - # check if builders provide a scenario-level element. - # e.g. a functionals component that is run once after - # the nonlinear solver is converged. - # we only check for disciplines, and we assume transfer - # components do not have scenario level elements. - if hasattr(self.aero_builder, 'get_scenario_element'): - aero_scenario_element = self.aero_builder.get_scenario_element() - self.add_subsystem('aero_funcs', aero_scenario_element) - - # if we have a scenario level element, we also need to - # figure out what needs to be connected from the solver - # level to the scenario level. - scenario_conn = self.aero_builder.get_scenario_connections() - # we can make these connections here - for k, v in scenario_conn.items(): - self.connect('solver_group.aero.%s'%k, 'aero_funcs.%s'%v) - - # do the same for struct - if hasattr(self.struct_builder, 'get_scenario_element'): - struct_scenario_element = self.struct_builder.get_scenario_element() - self.add_subsystem('struct_funcs', struct_scenario_element) - - scenario_conn = self.struct_builder.get_scenario_connections() - for k, v in scenario_conn.items(): - self.connect('solver_group.struct.%s'%k, 'struct_funcs.%s'%v) - - def configure(self): - pass - - def mphys_make_aeroprop_conn(self, aero2prop_conn, prop2aero_conn): - # do the connections. we may want to do the solver level connections on the solver level but do them here for now - for k,v in aero2prop_conn.items(): - self.connect('solver_group.aero.%s'%k, 'solver_group.prop.%s'%v) - - # also do the connections from prop to aero... - for k,v in prop2aero_conn.items(): - self.connect('solver_group.prop.%s'%k, 'solver_group.aero.%s'%v) +from .mphys_group import MphysGroup + +class Scenario(MphysGroup): + def mphys_add_pre_coupling_subsystem(self, name, builder): + subsystem, _ = builder.get_scenario_subsystems() + if subsystem is not None: + self.mphys_add_subsystem(name+'_pre', subsystem) + + def mphys_add_post_coupling_subsystem(self, name, builder): + _, subsystem = builder.get_scenario_subsystems() + if subsystem is not None: + self.mphys_add_subsystem(name+'_post', subsystem) diff --git a/mphys/scenario_aerostructural.py b/mphys/scenario_aerostructural.py new file mode 100644 index 00000000..2d6fd2ee --- /dev/null +++ b/mphys/scenario_aerostructural.py @@ -0,0 +1,27 @@ +from .scenario import Scenario +from .coupling_aerostructural import CouplingAeroStructural + +class ScenarioAeroStructural(Scenario): + + def initialize(self): + self.options.declare('aero_builder', recordable=False) + self.options.declare('struct_builder', recordable=False) + self.options.declare('xfer_builder', recordable=False) + + def setup(self): + aero_builder = self.options['aero_builder'] + struct_builder = self.options['struct_builder'] + xfer_builder = self.options['xfer_builder'] + + self.mphys_add_pre_coupling_subsystem('aero', aero_builder) + self.mphys_add_pre_coupling_subsystem('struct', struct_builder) + self.mphys_add_pre_coupling_subsystem('xfer', xfer_builder) + + coupling_group = CouplingAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + xfer_builder=xfer_builder) + self.mphys_add_subsystem('coupling',coupling_group) + + self.mphys_add_post_coupling_subsystem('aero', aero_builder) + self.mphys_add_post_coupling_subsystem('struct', struct_builder) + self.mphys_add_post_coupling_subsystem('xfer', xfer_builder) diff --git a/mphys/scenario_old.py b/mphys/scenario_old.py new file mode 100644 index 00000000..1450e4a7 --- /dev/null +++ b/mphys/scenario_old.py @@ -0,0 +1,78 @@ +import openmdao.api as om +from mphys.solver_group import SolverGroup + +class Scenario(om.Group): + + def initialize(self): + + # define the inputs we need + self.options.declare('builders', allow_none=False, recordable=False) + self.options.declare('aero_discipline', allow_none=False) + self.options.declare('struct_discipline', allow_none=False) + self.options.declare('prop_discipline', allow_none=False) + self.options.declare('as_coupling', allow_none=False) + + def setup(self): + + # set flags + self.aero_discipline = self.options['aero_discipline'] + self.struct_discipline = self.options['struct_discipline'] + self.prop_discipline = self.options['prop_discipline'] + self.as_coupling = self.options['as_coupling'] + + # set the builders + self.aero_builder = self.options['builders']['aero'] + self.struct_builder = self.options['builders']['struct'] + self.prop_builder = self.options['builders']['prop'] + self.xfer_builder = self.options['builders']['xfer'] + + # add the solver group itself and pass in the builders + # this group will converge the nonlinear analysis + self.add_subsystem( + 'solver_group', + SolverGroup( + builders=self.options['builders'], + aero_discipline = self.aero_discipline, + struct_discipline = self.struct_discipline, + prop_discipline = self.prop_discipline, + as_coupling = self.as_coupling + ) + ) + + # check if builders provide a scenario-level element. + # e.g. a functionals component that is run once after + # the nonlinear solver is converged. + # we only check for disciplines, and we assume transfer + # components do not have scenario level elements. + if hasattr(self.aero_builder, 'get_scenario_element'): + aero_scenario_element = self.aero_builder.get_scenario_element() + self.add_subsystem('aero_funcs', aero_scenario_element) + + # if we have a scenario level element, we also need to + # figure out what needs to be connected from the solver + # level to the scenario level. + scenario_conn = self.aero_builder.get_scenario_connections() + # we can make these connections here + for k, v in scenario_conn.items(): + self.connect('solver_group.aero.%s'%k, 'aero_funcs.%s'%v) + + # do the same for struct + if hasattr(self.struct_builder, 'get_scenario_element'): + struct_scenario_element = self.struct_builder.get_scenario_element() + self.add_subsystem('struct_funcs', struct_scenario_element) + + scenario_conn = self.struct_builder.get_scenario_connections() + for k, v in scenario_conn.items(): + self.connect('solver_group.struct.%s'%k, 'struct_funcs.%s'%v) + + def configure(self): + pass + + def mphys_make_aeroprop_conn(self, aero2prop_conn, prop2aero_conn): + # do the connections. we may want to do the solver level connections on the solver level but do them here for now + for k,v in aero2prop_conn.items(): + self.connect('solver_group.aero.%s'%k, 'solver_group.prop.%s'%v) + + # also do the connections from prop to aero... + for k,v in prop2aero_conn.items(): + self.connect('solver_group.prop.%s'%k, 'solver_group.aero.%s'%v) diff --git a/mphys/scenario_structural.py b/mphys/scenario_structural.py new file mode 100644 index 00000000..8d1415c4 --- /dev/null +++ b/mphys/scenario_structural.py @@ -0,0 +1,14 @@ +from .scenario import Scenario +from .coupling_aerostructural import CouplingAeroStructural + +class ScenarioStructural(Scenario): + + def initialize(self): + self.options.declare('struct_builder', recordable=False) + + def setup(self): + struct_builder = self.options['struct_builder'] + + self.mphys_add_pre_coupling_subsystem('struct', struct_builder) + self.mphys_add_subsystem('coupling',struct_builder.get_coupling_group_subsystem()) + self.mphys_add_post_coupling_subsystem('struct', struct_builder) From fbd4480b9ef92679bf249b61287e80185b297cdf Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 13:45:21 -0500 Subject: [PATCH 02/82] Add readme for tacs_only scripts --- examples/tacs_only/README.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/tacs_only/README.md diff --git a/examples/tacs_only/README.md b/examples/tacs_only/README.md new file mode 100644 index 00000000..0cb80a46 --- /dev/null +++ b/examples/tacs_only/README.md @@ -0,0 +1,5 @@ +Scripts to compare and test how TACS is run with mphys vs directly with the python/pyopt. + +mphys_crm_example.py - run running mphys +tacs_analysis.py - standalone TACS analysis +pyopt_mass_min.py - TACS optimization directly using pyopt + TACS From 3c7ca2a66afeae6fea7ab85c20710b19489cfdae Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:44:01 -0500 Subject: [PATCH 03/82] fix spelling of subsystem --- mphys/builder.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mphys/builder.py b/mphys/builder.py index 9f54ed58..eb226e38 100644 --- a/mphys/builder.py +++ b/mphys/builder.py @@ -25,7 +25,7 @@ def initialize(self, comm): """ pass - def get_mesh_coordinate_subsytem(self): + def get_mesh_coordinate_subsystem(self): return None def get_coupling_group_subsystem(self): From 087d15807de8df9eeab7c73b7a18f8ee46fad300 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:46:31 -0500 Subject: [PATCH 04/82] Remove unnecessary use_tacs conditional --- examples/tacs_only/mphys_crm_example.py | 26 +++++++++++-------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/examples/tacs_only/mphys_crm_example.py b/examples/tacs_only/mphys_crm_example.py index 003f2827..49f078d9 100644 --- a/examples/tacs_only/mphys_crm_example.py +++ b/examples/tacs_only/mphys_crm_example.py @@ -12,29 +12,25 @@ from mphys.scenario_structural import ScenarioStructural import tacs_setup -use_tacs = True - class Top(Multipoint): def setup(self): - if use_tacs: - tacs_options = {'add_elements' : tacs_setup.add_elements, - 'mesh_file' : 'CRM_box_2nd.bdf', - 'get_funcs' : tacs_setup.get_funcs, - 'load_function': tacs_setup.forcer_function, - 'f5_writer' : tacs_setup.f5_writer} + tacs_options = {'add_elements' : tacs_setup.add_elements, + 'mesh_file' : 'CRM_box_2nd.bdf', + 'get_funcs' : tacs_setup.get_funcs, + 'load_function': tacs_setup.forcer_function, + 'f5_writer' : tacs_setup.f5_writer} - struct_builder = TacsBuilder(tacs_options) - struct_builder.initialize(self.comm) - self.ndv_struct = struct_builder.get_ndv() + struct_builder = TacsBuilder(tacs_options) + struct_builder.initialize(self.comm) + self.ndv_struct = struct_builder.get_ndv() - self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - self.dvs.add_output('dv_struct',np.array(self.ndv_struct*[0.0031])) + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('dv_struct',np.array(self.ndv_struct*[0.0031])) self.add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) - self.mphys_add_scenario('analysis', ScenarioStructural(struct_builder=struct_builder)) - self.mphys_connect_scenario_coordinate_source('mesh','analysis','struct') + self.connect('dv_struct', 'analysis.dv_struct') ################################################################################ From 2e1809cd78ce50fc7b92417d6b1e5fb28a49e6e9 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:48:56 -0500 Subject: [PATCH 05/82] Update number of nodes signature --- mphys/mphys_tacs.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index dca31f69..5ad167df 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -828,7 +828,7 @@ def initialize(self, comm): tacs_assembler = mesh.createTACS(ndof) - nnodes = tacs_assembler.createNodeVec().getArray().size // 3 + number_of_nodes = tacs_assembler.createNodeVec().getArray().size // 3 if self.conduction: mat = tacs_assembler.createSchurMat() @@ -843,7 +843,7 @@ def initialize(self, comm): solver_dict['ndv'] = ndv solver_dict['ndof'] = ndof - solver_dict['nnodes'] = nnodes + solver_dict['number_of_nodes'] = number_of_nodes solver_dict['get_funcs'] = self.options['get_funcs'] #use the supplied function to get the surface points and mapping @@ -861,7 +861,6 @@ def initialize(self, comm): self.tacs_assembler = tacs_assembler self.solver_objects = [mat, pc, gmres, solver_dict] - # api level method for all builders def get_coupling_group_subsystem(self, **kwargs): return TacsGroup(tacs_assembler=self.tacs_assembler, solver_objects=self.solver_objects, @@ -881,8 +880,8 @@ def get_scenario_subsystems(self): def get_ndof(self): return self.solver_dict['ndof'] - def get_nnodes(self): - return self.solver_dict['nnodes'] + def get_number_of_nodes(self): + return self.solver_dict['number_of_nodes'] def get_ndv(self): return self.solver_dict['ndv'] From e8177893a6e1f5f9e9943440183e2c149b08a283 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:49:33 -0500 Subject: [PATCH 06/82] Add tags to integrated forces --- mphys/integrated_forces.py | 149 ++++++++++++++++++------------------- 1 file changed, 72 insertions(+), 77 deletions(-) diff --git a/mphys/integrated_forces.py b/mphys/integrated_forces.py index a1760a32..27b690be 100644 --- a/mphys/integrated_forces.py +++ b/mphys/integrated_forces.py @@ -2,43 +2,38 @@ import openmdao.api as om class IntegratedSurfaceForces(om.ExplicitComponent): - def initialize(self): - self.options.declare('number_of_surface_nodes') - def setup(self): - nnodes = self.options['number_of_surface_nodes'] - - self.add_input('aoa',desc = 'angle of attack', units='deg') - self.add_input('yaw',desc = 'yaw angle',units='deg') - self.add_input('ref_area', val = 1.0) - self.add_input('moment_center',shape=3) - self.add_input('ref_length', val = 1.0) - self.add_input('q_inf', val = 1.0) - - self.add_input('x_aero', shape_by_conn=True, desc = 'surface coordinates') - self.add_input('f_aero', shape_by_conn=True, desc = 'dimensional forces at nodes') - - self.add_output('C_L', desc = 'Lift coefficient') - self.add_output('C_D', desc = 'Drag coefficient') - self.add_output('C_X', desc = 'X Force coefficient') - self.add_output('C_Y', desc = 'Y Force coefficient') - self.add_output('C_Z', desc = 'Z Force coefficient') - self.add_output('CM_X', desc = 'X Moment coefficient') - self.add_output('CM_Y', desc = 'Y Moment coefficient') - self.add_output('CM_Z', desc = 'Z Moment coefficient') - - self.add_output('Lift', desc = 'Total Lift') - self.add_output('Drag', desc = 'Total Drag') - self.add_output('F_X', desc = 'Total X Force') - self.add_output('F_Y', desc = 'Total Y Force') - self.add_output('F_Z', desc = 'Total Z Force') - self.add_output('M_X', desc = 'Total X Moment') - self.add_output('M_Y', desc = 'Total Y Moment') - self.add_output('M_Z', desc = 'Total Z Moment') + self.add_input('aoa',desc = 'angle of attack', units='rad',tags=['mphys_dv']) + self.add_input('yaw',desc = 'yaw angle',units='rad',tags=['mphys_dv']) + self.add_input('ref_area', val = 1.0,tags=['mphys_dv']) + self.add_input('moment_center',shape=3,tags=['mphys_dv']) + self.add_input('ref_length', val = 1.0,tags=['mphys_dv']) + self.add_input('q_inf', val = 1.0,tags=['mphys_dv']) + + self.add_input('x_aero', shape_by_conn=True, desc = 'surface coordinates', tags=['mphys_coupling']) + self.add_input('f_aero', shape_by_conn=True, desc = 'dimensional forces at nodes', tags=['mphys_coupling']) + + self.add_output('C_L', desc = 'Lift coefficient', tags=['mphys_result']) + self.add_output('C_D', desc = 'Drag coefficient', tags=['mphys_result']) + self.add_output('C_X', desc = 'X Force coefficient', tags=['mphys_result']) + self.add_output('C_Y', desc = 'Y Force coefficient', tags=['mphys_result']) + self.add_output('C_Z', desc = 'Z Force coefficient', tags=['mphys_result']) + self.add_output('CM_X', desc = 'X Moment coefficient', tags=['mphys_result']) + self.add_output('CM_Y', desc = 'Y Moment coefficient', tags=['mphys_result']) + self.add_output('CM_Z', desc = 'Z Moment coefficient', tags=['mphys_result']) + + self.add_output('Lift', desc = 'Total Lift', tags=['mphys_result']) + self.add_output('Drag', desc = 'Total Drag', tags=['mphys_result']) + self.add_output('F_X', desc = 'Total X Force', tags=['mphys_result']) + self.add_output('F_Y', desc = 'Total Y Force', tags=['mphys_result']) + self.add_output('F_Z', desc = 'Total Z Force', tags=['mphys_result']) + self.add_output('M_X', desc = 'Total X Moment', tags=['mphys_result']) + self.add_output('M_Y', desc = 'Total Y Moment', tags=['mphys_result']) + self.add_output('M_Z', desc = 'Total Z Moment', tags=['mphys_result']) def compute(self,inputs,outputs): - aoa_rad = np.pi / 180.0 * inputs['aoa'] - yaw_rad = np.pi / 180.0 * inputs['yaw'] + aoa = inputs['aoa'] + yaw = inputs['yaw'] area = inputs['ref_area'] q_inf = inputs['q_inf'] xc = inputs['moment_center'][0] @@ -65,10 +60,10 @@ def compute(self,inputs,outputs): outputs['C_Y'] = fy_total / (q_inf * area) outputs['C_Z'] = fz_total / (q_inf * area) - outputs['Lift'] = -fx_total * np.sin(aoa_rad) + fz_total * np.cos(aoa_rad) - outputs['Drag'] = ( fx_total * np.cos(aoa_rad) * np.cos(yaw_rad) - - fy_total * np.sin(yaw_rad) - + fz_total * np.sin(aoa_rad) * np.cos(yaw_rad) + outputs['Lift'] = -fx_total * np.sin(aoa) + fz_total * np.cos(aoa) + outputs['Drag'] = ( fx_total * np.cos(aoa) * np.cos(yaw) + - fy_total * np.sin(yaw) + + fz_total * np.sin(aoa) * np.cos(yaw) ) outputs['C_L'] = outputs['Lift'] / (q_inf * area) @@ -83,8 +78,8 @@ def compute(self,inputs,outputs): outputs['CM_Z'] = outputs['M_Z'] / (q_inf * area * c) def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): - aoa_rad = np.pi / 180.0 * inputs['aoa'] - yaw_rad = np.pi / 180.0 * inputs['yaw'] + aoa = inputs['aoa'] + yaw = inputs['yaw'] area = inputs['ref_area'] q_inf = inputs['q_inf'] xc = inputs['moment_center'][0] @@ -103,10 +98,10 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): fx_total = np.sum(fx) fy_total = np.sum(fy) fz_total = np.sum(fz) - lift = -fx_total * np.sin(aoa_rad) + fz_total * np.cos(aoa_rad) - drag = ( fx_total * np.cos(aoa_rad) * np.cos(yaw_rad) - - fy_total * np.sin(yaw_rad) - + fz_total * np.sin(aoa_rad) * np.cos(yaw_rad) + lift = -fx_total * np.sin(aoa) + fz_total * np.cos(aoa) + drag = ( fx_total * np.cos(aoa) * np.cos(yaw) + - fy_total * np.sin(yaw) + + fz_total * np.sin(aoa) * np.cos(yaw) ) m_x = np.dot(fz,(y-yc)) - np.dot(fy,(z-zc)) @@ -117,16 +112,16 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'aoa' in d_inputs: daoa_rad = np.pi / 180.0 * d_inputs['aoa'] if 'Lift' in d_outputs or 'C_L' in d_outputs: - d_lift_d_aoa = ( - fx_total * np.cos(aoa_rad) * daoa_rad - - fz_total * np.sin(aoa_rad) * daoa_rad ) + d_lift_d_aoa = ( - fx_total * np.cos(aoa) * daoa_rad + - fz_total * np.sin(aoa) * daoa_rad ) if 'Lift' in d_outputs: d_outputs['Lift'] += d_lift_d_aoa if 'C_L' in d_outputs: d_outputs['C_L'] += d_lift_d_aoa / (q_inf * area) if 'Drag' in d_outputs or 'C_D' in d_outputs: - d_drag_d_aoa = ( fx_total * (-np.sin(aoa_rad) * daoa_rad) * np.cos(yaw_rad) - + fz_total * ( np.cos(aoa_rad) * daoa_rad) * np.cos(yaw_rad)) + d_drag_d_aoa = ( fx_total * (-np.sin(aoa) * daoa_rad) * np.cos(yaw) + + fz_total * ( np.cos(aoa) * daoa_rad) * np.cos(yaw)) if 'Drag' in d_outputs: d_outputs['Drag'] += d_drag_d_aoa if 'C_D' in d_outputs: @@ -135,9 +130,9 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'yaw' in d_inputs: dyaw_rad = np.pi / 180.0 * d_inputs['yaw'] if 'Drag' in d_outputs or 'C_D' in d_outputs: - d_drag_d_yaw = ( fx_total * np.cos(aoa_rad) * (-np.sin(yaw_rad) * dyaw_rad) - - fy_total * np.cos(yaw_rad) * dyaw_rad - + fz_total * np.sin(aoa_rad) * (-np.sin(yaw_rad) * dyaw_rad) + d_drag_d_yaw = ( fx_total * np.cos(aoa) * (-np.sin(yaw) * dyaw_rad) + - fy_total * np.cos(yaw) * dyaw_rad + + fz_total * np.sin(aoa) * (-np.sin(yaw) * dyaw_rad) ) if 'Drag' in d_outputs: d_outputs['Drag'] += d_drag_d_yaw @@ -244,18 +239,18 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'C_Z' in d_outputs: d_outputs['C_Z'] += dfz_total / (q_inf * area) if 'Lift' in d_outputs: - d_outputs['Lift'] += -dfx_total * np.sin(aoa_rad) + dfz_total * np.cos(aoa_rad) + d_outputs['Lift'] += -dfx_total * np.sin(aoa) + dfz_total * np.cos(aoa) if 'Drag' in d_outputs: - d_outputs['Drag'] += ( dfx_total * np.cos(aoa_rad) * np.cos(yaw_rad) - - dfy_total * np.sin(yaw_rad) - + dfz_total * np.sin(aoa_rad) * np.cos(yaw_rad) + d_outputs['Drag'] += ( dfx_total * np.cos(aoa) * np.cos(yaw) + - dfy_total * np.sin(yaw) + + dfz_total * np.sin(aoa) * np.cos(yaw) ) if 'C_L' in d_outputs: - d_outputs['C_L'] += (-dfx_total * np.sin(aoa_rad) + dfz_total * np.cos(aoa_rad)) / (q_inf * area) + d_outputs['C_L'] += (-dfx_total * np.sin(aoa) + dfz_total * np.cos(aoa)) / (q_inf * area) if 'C_D' in d_outputs: - d_outputs['C_D'] += ( dfx_total * np.cos(aoa_rad) * np.cos(yaw_rad) - - dfy_total * np.sin(yaw_rad) - + dfz_total * np.sin(aoa_rad) * np.cos(yaw_rad) + d_outputs['C_D'] += ( dfx_total * np.cos(aoa) * np.cos(yaw) + - dfy_total * np.sin(yaw) + + dfz_total * np.sin(aoa) * np.cos(yaw) ) / (q_inf * area) if 'M_X' in d_outputs: @@ -276,25 +271,25 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'Lift' in d_outputs or 'C_L' in d_outputs: d_lift = d_outputs['Lift'] if 'Lift' in d_outputs else 0.0 d_cl = d_outputs['C_L'] if 'C_L' in d_outputs else 0.0 - d_lift_d_aoa_rad = ( - fx_total * np.cos(aoa_rad) - - fz_total * np.sin(aoa_rad) + d_lift_d_aoa_rad = ( - fx_total * np.cos(aoa) + - fz_total * np.sin(aoa) ) * (d_lift + d_cl / (q_inf * area)) d_inputs['aoa'] += d_lift_d_aoa_rad * np.pi / 180.0 if 'Drag' in d_outputs or 'C_D' in d_outputs: d_drag = d_outputs['Drag'] if 'Drag' in d_outputs else 0.0 d_cd = d_outputs['C_D'] if 'C_D' in d_outputs else 0.0 - d_drag_d_aoa_rad = ( fx_total * (-np.sin(aoa_rad)) * np.cos(yaw_rad) - + fz_total * ( np.cos(aoa_rad)) * np.cos(yaw_rad) + d_drag_d_aoa_rad = ( fx_total * (-np.sin(aoa)) * np.cos(yaw) + + fz_total * ( np.cos(aoa)) * np.cos(yaw) ) * (d_drag + d_cd / (q_inf * area)) d_inputs['aoa'] += d_drag_d_aoa_rad * np.pi / 180.0 if 'yaw' in d_inputs: if 'Drag' in d_outputs or 'C_D' in d_outputs: d_drag = d_outputs['Drag'] if 'Drag' in d_outputs else 0.0 d_cd = d_outputs['C_D'] if 'C_D' in d_outputs else 0.0 - d_drag_d_yaw_rad = ( fx_total * np.cos(aoa_rad) * (-np.sin(yaw_rad)) - - fy_total * np.cos(yaw_rad) - + fz_total * np.sin(aoa_rad) * (-np.sin(yaw_rad)) + d_drag_d_yaw_rad = ( fx_total * np.cos(aoa) * (-np.sin(yaw)) + - fy_total * np.cos(yaw) + + fz_total * np.sin(aoa) * (-np.sin(yaw)) ) * (d_drag + d_cd / (q_inf * area)) d_inputs['yaw'] += d_drag_d_yaw_rad * np.pi / 180.0 @@ -390,19 +385,19 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): if 'C_Z' in d_outputs: d_inputs['f_aero'][2::3] += d_outputs['C_Z'] / (q_inf * area) if 'Lift' in d_outputs: - d_inputs['f_aero'][0::3] += -np.sin(aoa_rad) * d_outputs['Lift'] - d_inputs['f_aero'][2::3] += np.cos(aoa_rad) * d_outputs['Lift'] + d_inputs['f_aero'][0::3] += -np.sin(aoa) * d_outputs['Lift'] + d_inputs['f_aero'][2::3] += np.cos(aoa) * d_outputs['Lift'] if 'Drag' in d_outputs: - d_inputs['f_aero'][0::3] += np.cos(aoa_rad) * np.cos(yaw_rad) * d_outputs['Drag'] - d_inputs['f_aero'][1::3] += -np.sin(yaw_rad) * d_outputs['Drag'] - d_inputs['f_aero'][2::3] += np.sin(aoa_rad) * np.cos(yaw_rad) * d_outputs['Drag'] + d_inputs['f_aero'][0::3] += np.cos(aoa) * np.cos(yaw) * d_outputs['Drag'] + d_inputs['f_aero'][1::3] += -np.sin(yaw) * d_outputs['Drag'] + d_inputs['f_aero'][2::3] += np.sin(aoa) * np.cos(yaw) * d_outputs['Drag'] if 'C_L' in d_outputs: - d_inputs['f_aero'][0::3] += -np.sin(aoa_rad) * d_outputs['C_L'] / (q_inf * area) - d_inputs['f_aero'][2::3] += np.cos(aoa_rad) * d_outputs['C_L'] / (q_inf * area) + d_inputs['f_aero'][0::3] += -np.sin(aoa) * d_outputs['C_L'] / (q_inf * area) + d_inputs['f_aero'][2::3] += np.cos(aoa) * d_outputs['C_L'] / (q_inf * area) if 'C_D' in d_outputs: - d_inputs['f_aero'][0::3] += np.cos(aoa_rad) * np.cos(yaw_rad) * d_outputs['C_D'] / (q_inf * area) - d_inputs['f_aero'][1::3] += -np.sin(yaw_rad) * d_outputs['C_D'] / (q_inf * area) - d_inputs['f_aero'][2::3] += np.sin(aoa_rad) * np.cos(yaw_rad) * d_outputs['C_D'] / (q_inf * area) + d_inputs['f_aero'][0::3] += np.cos(aoa) * np.cos(yaw) * d_outputs['C_D'] / (q_inf * area) + d_inputs['f_aero'][1::3] += -np.sin(yaw) * d_outputs['C_D'] / (q_inf * area) + d_inputs['f_aero'][2::3] += np.sin(aoa) * np.cos(yaw) * d_outputs['C_D'] / (q_inf * area) if 'M_X' in d_outputs: d_inputs['f_aero'][1::3] += -(z-zc) * d_outputs['M_X'] @@ -437,7 +432,7 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): ivc.add_output('x_aero',shape=3*nnodes,val=np.random.rand(3*nnodes)) ivc.add_output('f_aero',shape=3*nnodes,val=np.random.rand(3*nnodes)) prob.model.add_subsystem('ivc',ivc,promotes_outputs=['*']) - prob.model.add_subsystem('forces',IntegratedSurfaceForces(number_of_surface_nodes = nnodes), + prob.model.add_subsystem('forces',IntegratedSurfaceForces(), promotes_inputs=['*']) prob.setup(force_alloc_complex=True) From f6601e583666986b4050efd645343ee5a2b9ba8e Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:49:48 -0500 Subject: [PATCH 07/82] Remove unnecessary import --- mphys/scenario_structural.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mphys/scenario_structural.py b/mphys/scenario_structural.py index 8d1415c4..c533d370 100644 --- a/mphys/scenario_structural.py +++ b/mphys/scenario_structural.py @@ -1,5 +1,4 @@ from .scenario import Scenario -from .coupling_aerostructural import CouplingAeroStructural class ScenarioStructural(Scenario): From 825c8cdd6fb45239c7faba88def1d7f14895ec9d Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 16:53:44 -0500 Subject: [PATCH 08/82] Tag VLM and set up an aero only example --- examples/vlm_only/run_vlm.py | 52 +++++++++++++ mphys/mphys_vlm.py | 141 +++++++++++++++-------------------- mphys/scenario_aero.py | 12 +++ 3 files changed, 123 insertions(+), 82 deletions(-) create mode 100644 examples/vlm_only/run_vlm.py create mode 100644 mphys/scenario_aero.py diff --git a/examples/vlm_only/run_vlm.py b/examples/vlm_only/run_vlm.py new file mode 100644 index 00000000..d8495d90 --- /dev/null +++ b/examples/vlm_only/run_vlm.py @@ -0,0 +1,52 @@ +#rst Imports +from __future__ import print_function, division +import numpy as np +from mpi4py import MPI + +import openmdao.api as om + +from mphys import Multipoint +from mphys.mphys_vlm import VlmBuilder +from mphys.scenario_aero import ScenarioAero + + +class Top(Multipoint): + def setup(self): + self.modal_struct = False + + # VLM options + mesh_file = 'wing_VLM.dat' + + mach = 0.85, + aoa = 2.0 + q_inf = 3000. + vel = 178. + mu = 3.5E-5 + ref_area = 45.59409734822606 + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + self.add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder)) + self.connect('mesh.x_aero0','cruise.x_aero') + + for dv in ['aoa', 'mach', 'q_inf', 'vel', 'mu']: + self.connect(dv,'cruise.%s' % dv) + +prob = om.Problem() +prob.model = Top() +prob.setup() + +om.n2(prob, show_browser=False, outfile='mphys_vlm.html') + +prob.run_model() +if MPI.COMM_WORLD.rank == 0: + print('C_L, C_D =',prob['cruise.C_L'], prob['cruise.C_D']) diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index 287b677b..e0aedd4a 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -6,112 +6,89 @@ from vlm_solver import VLM_solver, VLM_forces -class VlmMesh(om.ExplicitComponent): - +class VlmMesh(om.IndepVarComp): def initialize(self): - self.options.declare('N_nodes') self.options.declare('x_aero0') - def setup(self): - - N_nodes = self.options['N_nodes'] - self.x_a0 = self.options['x_aero0'] - self.add_output('x_aero0',np.zeros(N_nodes*3)) - - def compute(self,inputs,outputs): - - if 'x_aero0_points' in inputs: - outputs['x_aero0'] = inputs['x_aero0_points'] - else: - outputs['x_aero0'] = self.x_a0 - - def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): - if mode == 'fwd': - if 'x_aero0_points' in d_inputs: - d_outputs['x_aero0'] += d_inputs['x_aero0_points'] - elif mode == 'rev': - if 'x_aero0_points' in d_inputs: - d_inputs['x_aero0_points'] += d_outputs['x_aero0'] + self.add_output('x_aero0', val=self.options['x_aero0']) class VlmGroup(om.Group): def initialize(self): - self.options.declare('options_dict') - # Flag to enable AS coupling items - # TODO we do not use this now and assume we have as_coupling = True always. - # this needs to be updated to run aero-only vlm - self.options.declare('as_coupling') + self.options.declare('connectivity') + self.options.declare('number_of_nodes') + self.options.declare('compute_tractions', default=False) def setup(self): + self.add_subsystem('solver', VLM_solver(connectivity=self.options['connectivity']), + promotes_inputs=['aoa','mach','x_aero']) + + self.add_subsystem('forces', VLM_forces(connectivity=self.options['connectivity'], + number_of_nodes = self.options['number_of_nodes'], + compute_tractions=self.options['compute_tractions']), + promotes_inputs=['mach','q_inf','vel','mu', 'x_aero'], + promotes_outputs=['f_aero','C_L','C_D']) - options_dict = self.options['options_dict'] - # this can be done much more cleanly with **options_dict - N_nodes = options_dict['N_nodes'] - N_elements = options_dict['N_elements'] - x_a0 = options_dict['x_aero0'] - quad = options_dict['quad'] - - # by default, we use nodal forces. however, if the user wants to use - # tractions, they can specify it in the options_dict - compute_traction = False - if 'compute_traction' in options_dict: - compute_traction = options_dict['compute_traction'] - - self.add_subsystem('solver', VLM_solver( - N_nodes=N_nodes, - N_elements=N_elements, - quad=quad), - promotes_inputs=['aoa','mach',('xa','x_aero'),]) - - self.add_subsystem('forces', VLM_forces( - N_nodes=N_nodes, - N_elements=N_elements, - quad=quad, - compute_traction=compute_traction), - promotes_inputs=['mach','q_inf','vel','mu', ('xa','x_aero')], - promotes_outputs=[('fa','f_aero'),'CL','CD']) - - def configure(self): self.connect('solver.Cp', 'forces.Cp') class DummyVlmSolver(object): - ''' - a dummy object that can be used to hold the - memory associated with a single VLM solver so that - multiple OpenMDAO components share the same memory. - ''' - def __init__(self, options, comm): - self.options = options - self.comm = comm - - # the methods below here are required for RLT + """ + RLT specific data storage and methods + """ + def __init__(self, x_aero0, conn): + self.x_aero0 = x_aero0 + self.conn = conn + def getSurfaceCoordinates(self, group): - return self.options['x_aero0'] + return self.x_aero0 def getSurfaceConnectivity(self, group): fortran_offset = -1 - conn = self.options['quad'].copy() + fortran_offset + conn = self.conn.copy() + fortran_offset faceSizes = 4*np.ones(len(conn), 'intc') return conn.astype('intc'), faceSizes class VlmBuilder(Builder): + def __init__(self, meshfile, compute_tractions=False): + self.meshfile = meshfile + self.compute_tractions = compute_tractions + + def read_mesh(self, meshfile): + with open(meshfile,'r') as f: + contents = f.read().split() + + a = [i for i in contents if 'NODES' in i][0] + num_nodes = int(a[a.find("=")+1:a.find(",")]) + a = [i for i in contents if 'ELEMENTS' in i][0] + num_elements = int(a[a.find("=")+1:a.find(",")]) + + a = np.array(contents[16:16+num_nodes*3],'float') + x = a[0:num_nodes*3:3] + y = a[1:num_nodes*3:3] + z = a[2:num_nodes*3:3] + a = np.array(contents[16+num_nodes*3:None],'int') + + self.connectivity = np.reshape(a,[num_elements,4]) + self.x_aero0 = np.c_[x,y,z].flatten(order='C') - def __init__(self, options): - self.options = options + def initialize(self, comm): + self.read_mesh(self.meshfile) + self.solver = DummyVlmSolver(self.x_aero0, self.connectivity) - def init_solver(self, comm): - self.solver = DummyVlmSolver(options=self.options, comm=comm) + def get_mesh_coordinate_subsystem(self): + return VlmMesh(x_aero0=self.x_aero0) - def get_solver(self): - return self.solver + def get_coupling_group_subsystem(self): + number_of_nodes = self.x_aero0.size // 3 + return VlmGroup(connectivity=self.connectivity, + number_of_nodes=number_of_nodes, + compute_tractions=self.compute_tractions) - def get_element(self, **kwargs): - return VlmGroup(options_dict=self.options, **kwargs) + def get_scenario_subsystems(self): + return None, None - def get_mesh_element(self): - N_nodes = self.options['N_nodes'] - x_aero0 = self.options['x_aero0'] - return VlmMesh(N_nodes=N_nodes, x_aero0=x_aero0) + def get_number_of_nodes(self): + return self.x_aero0.size // 3 - def get_nnodes(self): - return self.options['N_nodes'] + def get_ndof(self): + return self.x_aero0.size // 3 diff --git a/mphys/scenario_aero.py b/mphys/scenario_aero.py new file mode 100644 index 00000000..3e449775 --- /dev/null +++ b/mphys/scenario_aero.py @@ -0,0 +1,12 @@ +from .scenario import Scenario + +class ScenarioAero(Scenario): + def initialize(self): + self.options.declare('aero_builder', recordable=False) + + def setup(self): + aero_builder = self.options['aero_builder'] + + self.mphys_add_pre_coupling_subsystem('aero', aero_builder) + self.mphys_add_subsystem('coupling',aero_builder.get_coupling_group_subsystem()) + self.mphys_add_post_coupling_subsystem('aero', aero_builder) From 7a95526a33d1c4196c268b13ba6132e9936b870e Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 20:12:44 -0500 Subject: [PATCH 09/82] Parallel Group VLM and aerostructural example --- .../as_opt.py | 0 .../component_list.txt | 0 examples/aerostruct_with_vlm/mphys_as_vlm.py | 97 ++++++++ .../struct_dv_components.py | 0 examples/aerostruct_with_vlm/tacs_setup.py | 39 ++++ .../wing_VLM.dat | 0 .../wingbox_Y_Z_flip.bdf | 0 .../aerostruct_with_VLM/mphys_as_vlm.py | 184 --------------- examples/tacs_only/mphys_crm_example.py | 4 +- examples/vlm_only/run_vlm.py | 7 - examples/vlm_only/run_vlm_2scenarios.py | 54 +++++ .../vlm_only/run_vlm_2scenarios_parallel.py | 63 +++++ mphys/__init__.py | 2 +- mphys/coupling_aerostructural.py | 13 -- mphys/geo_disp.py | 12 +- mphys/mphys_group.py | 3 +- mphys/mphys_meld.py | 70 +++--- mphys/mphys_tacs.py | 52 +---- mphys/mphys_vlm.py | 12 +- mphys/multipoint.py | 32 ++- mphys/multipoint_old.py | 219 ------------------ mphys/scenario_aero.py | 5 + mphys/scenario_old.py | 78 ------- 23 files changed, 346 insertions(+), 600 deletions(-) rename examples/{mach_tutorials/aerostruct_with_VLM => aerostruct_with_vlm}/as_opt.py (100%) rename examples/{mach_tutorials/aerostruct_with_VLM => aerostruct_with_vlm}/component_list.txt (100%) create mode 100644 examples/aerostruct_with_vlm/mphys_as_vlm.py rename examples/{mach_tutorials/aerostruct_with_VLM => aerostruct_with_vlm}/struct_dv_components.py (100%) create mode 100644 examples/aerostruct_with_vlm/tacs_setup.py rename examples/{mach_tutorials/aerostruct_with_VLM => aerostruct_with_vlm}/wing_VLM.dat (100%) rename examples/{mach_tutorials/aerostruct_with_VLM => aerostruct_with_vlm}/wingbox_Y_Z_flip.bdf (100%) delete mode 100644 examples/mach_tutorials/aerostruct_with_VLM/mphys_as_vlm.py create mode 100644 examples/vlm_only/run_vlm_2scenarios.py create mode 100644 examples/vlm_only/run_vlm_2scenarios_parallel.py delete mode 100644 mphys/multipoint_old.py delete mode 100644 mphys/scenario_old.py diff --git a/examples/mach_tutorials/aerostruct_with_VLM/as_opt.py b/examples/aerostruct_with_vlm/as_opt.py similarity index 100% rename from examples/mach_tutorials/aerostruct_with_VLM/as_opt.py rename to examples/aerostruct_with_vlm/as_opt.py diff --git a/examples/mach_tutorials/aerostruct_with_VLM/component_list.txt b/examples/aerostruct_with_vlm/component_list.txt similarity index 100% rename from examples/mach_tutorials/aerostruct_with_VLM/component_list.txt rename to examples/aerostruct_with_vlm/component_list.txt diff --git a/examples/aerostruct_with_vlm/mphys_as_vlm.py b/examples/aerostruct_with_vlm/mphys_as_vlm.py new file mode 100644 index 00000000..44c56dce --- /dev/null +++ b/examples/aerostruct_with_vlm/mphys_as_vlm.py @@ -0,0 +1,97 @@ +#rst Imports +from __future__ import print_function, division +import numpy as np +from mpi4py import MPI + +import openmdao.api as om + +from mphys import Multipoint +from mphys.scenario_aerostructural import ScenarioAeroStructural +from mphys.mphys_vlm import VlmBuilder +from mphys.mphys_tacs import TacsBuilder +from mphys.mphys_meld import MeldBuilder + +import tacs_setup + +class Top(Multipoint): + + def setup(self): + self.modal_struct = False + + # VLM + mesh_file = 'wing_VLM.dat' + mach = 0.85, + aoa = 2.0 + q_inf = 3000. + vel = 178. + mu = 3.5E-5 + + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + self.add_subsystem('mesh_aero',aero_builder.get_mesh_coordinate_subsystem()) + + # TACS + tacs_options = {'add_elements': tacs_setup.add_elements, + 'get_funcs' : tacs_setup.get_funcs, + 'mesh_file' : 'wingbox_Y_Z_flip.bdf', + 'f5_writer' : tacs_setup.f5_writer } + + struct_builder = TacsBuilder(tacs_options) + struct_builder.initialize(self.comm) + ndv_struct = struct_builder.get_ndv() + + self.add_subsystem('mesh_struct',struct_builder.get_mesh_coordinate_subsystem()) + + dvs.add_output('dv_struct', np.array(ndv_struct*[0.002])) + + # MELD setup + isym = 1 + xfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) + xfer_builder.initialize(self.comm) + + nonlinear_solver = om.NonlinearBlockGS(maxiter=20, iprint=2, use_aitken=True, rtol = 1E-14, atol=1E-14) + linear_solver = om.LinearBlockGS(maxiter=20, iprint=2, use_aitken=True, rtol = 1e-14, atol=1e-14) + + self.mphys_add_scenario('cruise',ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + xfer_builder=xfer_builder), + coupling_nonlinear_solver=nonlinear_solver, + coupling_linear_solver=linear_solver) + + + for discipline in ['aero','struct']: + self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline ,'cruise', discipline) + + for dv in ['aoa','q_inf','vel','mu','mach', 'dv_struct']: + self.connect(dv, 'cruise.%s' % dv) + +################################################################################ +# OpenMDAO setup +################################################################################ +prob = om.Problem() +prob.model = Top() +model = prob.model + +# optional but we can set it here. +model.nonlinear_solver = om.NonlinearRunOnce() +model.linear_solver = om.LinearRunOnce() + + +prob.setup() + +om.n2(prob, show_browser=False, outfile='mphys_as_vlm.html') + +prob.run_model() + +if MPI.COMM_WORLD.rank == 0: + print('func_struct =',prob['cruise.func_struct']) + print('mass =',prob['cruise.mass']) + print('C_L =',prob['cruise.C_L']) diff --git a/examples/mach_tutorials/aerostruct_with_VLM/struct_dv_components.py b/examples/aerostruct_with_vlm/struct_dv_components.py similarity index 100% rename from examples/mach_tutorials/aerostruct_with_VLM/struct_dv_components.py rename to examples/aerostruct_with_vlm/struct_dv_components.py diff --git a/examples/aerostruct_with_vlm/tacs_setup.py b/examples/aerostruct_with_vlm/tacs_setup.py new file mode 100644 index 00000000..91f9da14 --- /dev/null +++ b/examples/aerostruct_with_vlm/tacs_setup.py @@ -0,0 +1,39 @@ +import numpy as np +from tacs import TACS, elements, constitutive, functions + +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness= 0.003 + min_thickness = 0.002 + max_thickness = 0.05 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ["CQUAD", "CQUADR", "CQUAD4"]: + element = elements.MITCShell(2,stiff,component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + +def get_funcs(tacs): + ks_weight = 50.0 + return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] + +def f5_writer(tacs): + flag = (TACS.ToFH5.NODES | + TACS.ToFH5.DISPLACEMENTS | + TACS.ToFH5.STRAINS | + TACS.ToFH5.EXTRAS) + f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) + f5.writeToFile('wingbox.f5') diff --git a/examples/mach_tutorials/aerostruct_with_VLM/wing_VLM.dat b/examples/aerostruct_with_vlm/wing_VLM.dat similarity index 100% rename from examples/mach_tutorials/aerostruct_with_VLM/wing_VLM.dat rename to examples/aerostruct_with_vlm/wing_VLM.dat diff --git a/examples/mach_tutorials/aerostruct_with_VLM/wingbox_Y_Z_flip.bdf b/examples/aerostruct_with_vlm/wingbox_Y_Z_flip.bdf similarity index 100% rename from examples/mach_tutorials/aerostruct_with_VLM/wingbox_Y_Z_flip.bdf rename to examples/aerostruct_with_vlm/wingbox_Y_Z_flip.bdf diff --git a/examples/mach_tutorials/aerostruct_with_VLM/mphys_as_vlm.py b/examples/mach_tutorials/aerostruct_with_VLM/mphys_as_vlm.py deleted file mode 100644 index 4877a348..00000000 --- a/examples/mach_tutorials/aerostruct_with_VLM/mphys_as_vlm.py +++ /dev/null @@ -1,184 +0,0 @@ -#rst Imports -from __future__ import print_function, division -import numpy as np -from mpi4py import MPI - -import openmdao.api as om - -from tacs import elements, constitutive, functions, TACS - -from mphys.multipoint import Multipoint -from mphys.mphys_vlm import VlmBuilder -from mphys.mphys_tacs import TacsBuilder -from mphys.mphys_modal_solver import ModalBuilder -from mphys.mphys_meld import MeldBuilder - - -class Top(om.Group): - - def setup(self): - self.modal_struct = False - - # VLM options - self.aero_options = { - 'mesh_file':'wing_VLM.dat', - 'mach':0.85, - 'aoa':2*np.pi/180., - 'q_inf':3000., - 'vel':178., - 'mu':3.5E-5, - } - - # VLM mesh read - - def read_VLM_mesh(mesh): - f=open(mesh, "r") - contents = f.read().split() - - a = [i for i in contents if 'NODES' in i][0] - N_nodes = int(a[a.find("=")+1:a.find(",")]) - a = [i for i in contents if 'ELEMENTS' in i][0] - N_elements = int(a[a.find("=")+1:a.find(",")]) - - a = np.array(contents[16:16+N_nodes*3],'float') - X = a[0:N_nodes*3:3] - Y = a[1:N_nodes*3:3] - Z = a[2:N_nodes*3:3] - a = np.array(contents[16+N_nodes*3:None],'int') - quad = np.reshape(a,[N_elements,4]) - - xa = np.c_[X,Y,Z].flatten(order='C') - - f.close() - - return N_nodes, N_elements, xa, quad - - self.aero_options['N_nodes'], self.aero_options['N_elements'], self.aero_options['x_aero0'], self.aero_options['quad'] = read_VLM_mesh(self.aero_options['mesh_file']) - - # VLM builder - vlm_builder = VlmBuilder(self.aero_options) - - # TACS setup - - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness= 0.003 - min_thickness = 0.002 - max_thickness = 0.05 - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - - def f5_writer(tacs): - flag = (TACS.ToFH5.NODES | - TACS.ToFH5.DISPLACEMENTS | - TACS.ToFH5.STRAINS | - TACS.ToFH5.EXTRAS) - f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) - f5.writeToFile('wingbox.f5') - - - # common setup options - tacs_setup = {'add_elements': add_elements, - 'get_funcs' : get_funcs, - 'mesh_file' : 'wingbox_Y_Z_flip.bdf', - 'f5_writer' : f5_writer } - - if self.modal_struct: - nmodes = 15 - struct_builder = ModalBuilder(tacs_setup,nmodes) - else: - struct_builder = TacsBuilder(tacs_setup) - - # MELD setup - meld_options = {'isym': 1, - 'n': 200, - 'beta': 0.5} - - # MELD builder - meld_builder = MeldBuilder(meld_options, vlm_builder, struct_builder) - - ################################################################################ - # MPHYS setup - ################################################################################ - # ivc to keep the top level DVs - dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - - # each MPHYS_Multipoint instance can keep multiple points with the same formulation - mp = self.add_subsystem( - 'mp_group', - Multipoint( - aero_builder = vlm_builder, - struct_builder = struct_builder, - xfer_builder = meld_builder - ) - ) - - # this is the method that needs to be called for every point in this mp_group - mp.mphys_add_scenario('s0') - - def configure(self): - - # add aero DVs - for dv_name in ['aoa','q_inf','vel','mu','mach']: - if dv_name == 'aoa': - self.dvs.add_output(dv_name, val=self.aero_options[dv_name], units='rad') - else: - self.dvs.add_output(dv_name, val=self.aero_options[dv_name]) - self.connect(dv_name, 'mp_group.s0.solver_group.aero.%s' % dv_name) - - # add the structural thickness DVs - ndv_struct = self.mp_group.struct_builder.get_ndv() - self.dvs.add_output('dv_struct', np.array(ndv_struct*[0.002])) - - # connect solver data - if self.modal_struct: - self.connect('dv_struct', ['mp_group.struct_mesh.dv_struct']) - else: - self.connect('dv_struct', ['mp_group.s0.solver_group.struct.dv_struct', 'mp_group.s0.struct_funcs.dv_struct']) - -################################################################################ -# OpenMDAO setup -################################################################################ -prob = om.Problem() -prob.model = Top() -model = prob.model - -# optional but we can set it here. -model.nonlinear_solver = om.NonlinearRunOnce() -model.linear_solver = om.LinearRunOnce() - - -prob.setup() - -# model.mp_group.s0.nonlinear_solver = om.NonlinearBlockGS(maxiter=20, iprint=2, use_aitken=False, rtol = 1E-14, atol=1E-14) -# model.mp_group.s0.linear_solver = om.LinearBlockGS(maxiter=20, iprint=2, rtol = 1e-14, atol=1e-14) - -om.n2(prob, show_browser=False, outfile='mphys_as_vlm.html') - -prob.run_model() - -if MPI.COMM_WORLD.rank == 0: - print('func_struct =',prob['mp_group.s0.struct_funcs.funcs.func_struct']) - print('mass =',prob['mp_group.s0.struct_funcs.mass.mass']) - print('cl =',prob['mp_group.s0.solver_group.aero.forces.CL']) diff --git a/examples/tacs_only/mphys_crm_example.py b/examples/tacs_only/mphys_crm_example.py index 49f078d9..7378d9db 100644 --- a/examples/tacs_only/mphys_crm_example.py +++ b/examples/tacs_only/mphys_crm_example.py @@ -22,10 +22,10 @@ def setup(self): struct_builder = TacsBuilder(tacs_options) struct_builder.initialize(self.comm) - self.ndv_struct = struct_builder.get_ndv() + ndv_struct = struct_builder.get_ndv() dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - dvs.add_output('dv_struct',np.array(self.ndv_struct*[0.0031])) + dvs.add_output('dv_struct',np.array(ndv_struct*[0.0031])) self.add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) self.mphys_add_scenario('analysis', ScenarioStructural(struct_builder=struct_builder)) diff --git a/examples/vlm_only/run_vlm.py b/examples/vlm_only/run_vlm.py index d8495d90..0a63a09a 100644 --- a/examples/vlm_only/run_vlm.py +++ b/examples/vlm_only/run_vlm.py @@ -1,8 +1,4 @@ -#rst Imports -from __future__ import print_function, division -import numpy as np from mpi4py import MPI - import openmdao.api as om from mphys import Multipoint @@ -12,8 +8,6 @@ class Top(Multipoint): def setup(self): - self.modal_struct = False - # VLM options mesh_file = 'wing_VLM.dat' @@ -22,7 +16,6 @@ def setup(self): q_inf = 3000. vel = 178. mu = 3.5E-5 - ref_area = 45.59409734822606 dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) dvs.add_output('aoa', val=aoa, units='deg') diff --git a/examples/vlm_only/run_vlm_2scenarios.py b/examples/vlm_only/run_vlm_2scenarios.py new file mode 100644 index 00000000..17beb279 --- /dev/null +++ b/examples/vlm_only/run_vlm_2scenarios.py @@ -0,0 +1,54 @@ +from mpi4py import MPI +import openmdao.api as om + +from mphys import Multipoint +from mphys.mphys_vlm import VlmBuilder +from mphys.scenario_aero import ScenarioAero + +class Top(Multipoint): + def setup(self): + # VLM options + mesh_file = 'wing_VLM.dat' + + mach = 0.85, + aoa0 = 0.0 + aoa1 = 2.0 + q_inf = 3000. + vel = 178. + mu = 3.5E-5 + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa0', val=aoa0, units='deg') + dvs.add_output('aoa1', val=aoa1, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + self.add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder)) + + for dv in ['mach', 'q_inf', 'vel', 'mu']: + self.connect(dv,'cruise.%s' % dv) + self.connect('aoa0','cruise.aoa') + + self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder)) + for dv in ['mach', 'q_inf', 'vel', 'mu']: + self.connect(dv,'cruise_higher_aoa.%s' % dv) + self.connect('aoa1','cruise_higher_aoa.aoa') + + self.connect('mesh.x_aero0',['cruise.x_aero','cruise_higher_aoa.x_aero']) + +prob = om.Problem() +prob.model = Top() +prob.setup() + +om.n2(prob, show_browser=False, outfile='mphys_vlm_2scenarios.html') + +prob.run_model() +if MPI.COMM_WORLD.rank == 0: + for scenario in ['cruise','cruise_higher_aoa']: + print('%s: C_L = %f, C_D=%f' % (scenario, prob['%s.C_L'%scenario], prob['%s.C_D'%scenario])) diff --git a/examples/vlm_only/run_vlm_2scenarios_parallel.py b/examples/vlm_only/run_vlm_2scenarios_parallel.py new file mode 100644 index 00000000..3d633fc2 --- /dev/null +++ b/examples/vlm_only/run_vlm_2scenarios_parallel.py @@ -0,0 +1,63 @@ +from mpi4py import MPI +import openmdao.api as om + +from mphys import MultipointParallelGroup +from mphys.mphys_vlm import VlmBuilderAeroOnly +from mphys.scenario_aero import ScenarioAero + + +class ParallelCruises(MultipointParallelGroup): + def setup(self): + # VLM options + mesh_file = 'wing_VLM.dat' + + aero_builder = VlmBuilderAeroOnly(mesh_file) + self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder, + in_MultipointParallelGroup=True)) + + self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder, + in_MultipointParallelGroup=True)) + +class Top(om.Group): + def setup(self): + mach = 0.85, + aoa0 = 0.0 + aoa1 = 2.0 + q_inf = 3000. + vel = 178. + mu = 3.5E-5 + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa0', val=aoa0, units='deg') + dvs.add_output('aoa1', val=aoa1, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + self.add_subsystem('mp',ParallelCruises()) + for dv in ['mach', 'q_inf', 'vel', 'mu']: + self.connect(dv,'mp.cruise.%s' % dv) + self.connect('aoa0','mp.cruise.aoa') + for dv in ['mach', 'q_inf', 'vel', 'mu']: + self.connect(dv,'mp.cruise_higher_aoa.%s' % dv) + self.connect('aoa1','mp.cruise_higher_aoa.aoa') +prob = om.Problem() +prob.model = Top() +prob.setup() + +om.n2(prob, show_browser=False, outfile='mphys_vlm_2scenarios.html') + +prob.run_model() +#if MPI.COMM_WORLD.rank == 0: +# for scenario in ['cruise','cruise_higher_aoa']: +# print('%s: C_L = %f, C_D=%f' % (scenario, prob.get_val(['mp.%s.C_L'%scenario],get_remote=True), +# prob.get_val(['mp.%s.C_D'%scenario],get_remote=True))) +if MPI.COMM_WORLD.rank == 0: + scenario = 'cruise' + print('%s: C_L = %f, C_D = %f' % (scenario, prob['mp.%s.C_L'%scenario], + prob['mp.%s.C_D'%scenario])) +else: + scenario = 'cruise_higher_aoa' + print('%s: C_L = %f, C_D = %f' % (scenario, prob['mp.%s.C_L'%scenario], + prob['mp.%s.C_D'%scenario])) diff --git a/mphys/__init__.py b/mphys/__init__.py index d931c596..314204fd 100644 --- a/mphys/__init__.py +++ b/mphys/__init__.py @@ -1,3 +1,3 @@ #!/usr/bin/env python from .builder import Builder -from .multipoint import Multipoint +from .multipoint import Multipoint, MultipointParallelGroup diff --git a/mphys/coupling_aerostructural.py b/mphys/coupling_aerostructural.py index 102b6b94..0035ac85 100644 --- a/mphys/coupling_aerostructural.py +++ b/mphys/coupling_aerostructural.py @@ -28,16 +28,3 @@ def setup(self): self.mphys_add_subsystem('aero', aero) self.mphys_add_subsystem('load_xfer', load_xfer) self.mphys_add_subsystem('struct', struct) - - def configure(self): - self.connect('disp_xfer.u_aero', 'geo_disp.u_aero') - self.connect('geo_disp.x_aero', 'aero.x_aero') - self.connect('aero.f_aero', 'load_xfer.f_aero') - self.connect('load_xfer.f_struct', 'struct.f_struct') - self.connect('struct.u_struct', 'disp_xfer.u_struct') - - # only nonlinear xfers have load_xfer.u_struct - if self._mphys_variable_is_in_subsystem_inputs(self.load_xfer, 'u_struct'): - self.connect('struct.u_struct', ['load_xfer.u_struct']) - - super().configure() diff --git a/mphys/geo_disp.py b/mphys/geo_disp.py index 790c6bc8..8feb25f6 100644 --- a/mphys/geo_disp.py +++ b/mphys/geo_disp.py @@ -13,12 +13,16 @@ def setup(self): nnodes = self.options['number_of_nodes'] local_size = nnodes * 3 - self.add_input('x_aero0', shape_by_conn=True, tags = ['mphys_coordinates'], - desc='aerodynamic surface with geom changes') + self.add_input('x_aero0', shape_by_conn=True, + desc='aerodynamic surface with geom changes', + tags=['mphys_coordinates']) self.add_input('u_aero', shape_by_conn=True, - desc='aerodynamic surface displacements') + desc='aerodynamic surface displacements', + tags=['mphys_coupling']) - self.add_output('x_aero', shape=local_size, desc='deformed aerodynamic surface') + self.add_output('x_aero', shape=local_size, + desc='deformed aerodynamic surface', + tags=['mphys_coupling']) def compute(self,inputs,outputs): outputs['x_aero'] = inputs['x_aero0'] + inputs['u_aero'] diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index cbf44903..a5b044ec 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -1,3 +1,4 @@ +from re import sub from openmdao.api import Group class MphysGroup(Group): @@ -28,7 +29,7 @@ def _mphys_promote_design_variables(self): self._mphys_promote_by_tag('input','mphys_dv') def _mphys_promote_mesh_coordinates(self): - self._mphys_promote_by_tag('input','mphys_coordinates') + self._mphys_promote_by_tag(['input','output'],'mphys_coordinates') def _mphys_promote_results(self): self._mphys_promote_by_tag('output','mphys_result') diff --git a/mphys/mphys_meld.py b/mphys/mphys_meld.py index 5d62471f..4553fd9f 100644 --- a/mphys/mphys_meld.py +++ b/mphys/mphys_meld.py @@ -1,5 +1,7 @@ import numpy as np import openmdao.api as om +from mphys import Builder + from funtofem import TransferScheme class MeldDispXfer(om.ExplicitComponent): @@ -35,16 +37,20 @@ def setup(self): # inputs self.add_input('x_struct0', shape_by_conn=True, - desc='initial structural node coordinates') + desc='initial structural node coordinates', + tags=['mphys_coordinates']) self.add_input('x_aero0', shape_by_conn=True, - desc='initial aero surface node coordinates') + desc='initial aero surface node coordinates', + tags=['mphys_coordinates']) self.add_input('u_struct', shape_by_conn=True, - desc='structural node displacements') + desc='structural node displacements', + tags=['mphys_coupling']) # outputs self.add_output('u_aero', shape = self.aero_nnodes*3, val=np.zeros(self.aero_nnodes*3), - desc='aerodynamic surface displacements') + desc='aerodynamic surface displacements', + tags=['mphys_coupling']) # partials #self.declare_partials('u_aero',['x_struct0','x_aero0','u_struct']) @@ -168,17 +174,22 @@ def setup(self): # inputs self.add_input('x_struct0', shape_by_conn=True, - desc='initial structural node coordinates') + desc='initial structural node coordinates', + tags=['mphys_coordinates']) self.add_input('x_aero0', shape_by_conn=True, - desc='initial aero surface node coordinates') + desc='initial aero surface node coordinates', + tags=['mphys_coordinates']) self.add_input('u_struct', shape_by_conn=True, - desc='structural node displacements') + desc='structural node displacements', + tags=['mphys_coupling']) self.add_input('f_aero', shape_by_conn=True, - desc='aerodynamic force vector') + desc='aerodynamic force vector', + tags=['mphys_coupling']) # outputs self.add_output('f_struct', shape = struct_nnodes*struct_ndof, - desc='structural force vector') + desc='structural force vector', + tags=['mphys_coupling']) # partials #self.declare_partials('f_struct',['x_struct0','x_aero0','u_struct','f_aero']) @@ -290,36 +301,29 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): self.meld.applydLdxS0(d_out,prod) d_inputs['x_struct0'] -= np.array(prod,dtype=float) -class MeldBuilder(object): - - def __init__(self, options, aero_builder, struct_builder,check_partials=False): - self.options=options - self.check_partials = check_partials - # TODO we can move the aero and struct builder to init_xfer_object call so that user does not need to worry about this +class MeldBuilder(Builder): + def __init__(self, aero_builder: Builder, struct_builder, + isym=-1, n=200, beta = 0.5, check_partials=False): self.aero_builder = aero_builder self.struct_builder = struct_builder + self.isym = isym + self.n = n + self.beta = beta + self.check_partials = check_partials - # api level method for all builders - def init_xfer_object(self, comm): - # create the transfer - self.xfer_object = TransferScheme.pyMELD(comm, - comm, 0, - comm, 0, - self.options['isym'], - self.options['n'], - self.options['beta']) - - # TODO also do the necessary calls to the struct and aero builders to fully initialize MELD - # for now, just save the counts + def initialize(self, comm): + self.aero_nnodes = self.aero_builder.get_number_of_nodes() + self.struct_nnodes = self.struct_builder.get_number_of_nodes() self.struct_ndof = self.struct_builder.get_ndof() - self.struct_nnodes = self.struct_builder.get_nnodes() - self.aero_nnodes = self.aero_builder.get_nnodes() - # api level method for all builders - def get_element(self): + self.meld = TransferScheme.pyMELD(comm, + comm, 0, + comm, 0, + self.isym, self.n, self.beta) + def get_coupling_group_subsystem(self): disp_xfer = MeldDispXfer( - xfer_object=self.xfer_object, + xfer_object=self.meld, struct_ndof=self.struct_ndof, struct_nnodes=self.struct_nnodes, aero_nnodes=self.aero_nnodes, @@ -327,7 +331,7 @@ def get_element(self): ) load_xfer = MeldLoadXfer( - xfer_object=self.xfer_object, + xfer_object=self.meld, struct_ndof=self.struct_ndof, struct_nnodes=self.struct_nnodes, aero_nnodes=self.aero_nnodes, diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 5ad167df..8b327776 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -95,18 +95,6 @@ def setup(self): # partials #self.declare_partials('u_struct',['dv_struct','x_struct0','f_struct']) - def get_ndof(self): - return self.solver_dict['ndof'] - - def get_nnodes(self): - return self.solver_dict['nnodes'] - - def get_ndv(self): - return self.solver_dict['ndv'] - - def get_funcs(self): - return self.solver_dict['get_funcs'] - def _need_update(self,inputs): update = False @@ -424,11 +412,8 @@ def _update_internal(self,inputs,outputs=None): self.tacs_assembler.assembleJacobian(alpha,beta,gamma,res,self.mat) pc.factor() - def solve_nonlinear(self, inputs, outputs): - force = self.force ans = self.ans - pc = self.pc gmres = self.gmres self._update_internal(inputs) @@ -439,15 +424,12 @@ def solve_nonlinear(self, inputs, outputs): for i in range(len(self.mapping)): heat_array[self.mapping[i]] = inputs['q_conduct'][i] - self.tacs_assembler.setBCs(heat) - gmres.solve(heat, ans) ans_array = ans.getArray() self.tacs_assembler.setVariables(ans) - ans_array = ans.getArray() # get specifically the temps from the nodes in the mapping @@ -456,9 +438,6 @@ def solve_nonlinear(self, inputs, outputs): outputs['T_conduct'][i] = ans_array[self.mapping[i]] - - - class TacsFunctions(om.ExplicitComponent): """ Component to compute TACS functions @@ -717,7 +696,6 @@ def initialize(self): self.options.declare('solver_objects', recordable=False) self.options.declare('check_partials') self.options.declare('conduction', default=False) - self.options.declare('as_coupling') def setup(self): @@ -751,30 +729,6 @@ def setup(self): promotes_inputs=['f_struct', 'x_struct0', 'dv_struct'], promotes_outputs=['u_struct'] ) - # sum aero, inertial, and fual loads: result is F_summed, which tacs accepts as an input - nnodes = int(self.tacs_assembler.createNodeVec().getArray().size/3) - - vec_size_g = np.sum(self.comm.gather(self.tacs_assembler.getNumOwnedNodes()*6)) - vec_size_g = int(self.comm.bcast(vec_size_g)) - - # self.add_subsystem('sum_loads',SumLoads( - # load_size=vec_size_g, - # load_list=['F_inertial','F_fuel','f_struct']), - # promotes_inputs=['f_struct'], - # promotes_outputs=['F_summed'] - # ) - - # self.add_subsystem('solver', TacsSolver( - # struct_solver=self.struct_solver, - # struct_objects=self.struct_objects, - # check_partials=self.check_partials), - # promotes_inputs=['x_struct0', 'dv_struct', 'F_summed'], - # promotes_outputs=['u_struct'] - # ) - - # def configure(self): - # if not self.options['conduction']: - # self.connect('u_struct', 'funcs.u_struct') class TACSFuncsGroup(om.Group): @@ -809,16 +763,12 @@ def configure(self): class TacsBuilder(Builder): - def __init__(self, options,check_partials=False, conduction=False): - # super(TACS_builder, self).__init__(options) + def __init__(self, options, check_partials=False, conduction=False): self.options = options self.check_partials = check_partials self.conduction = conduction - # api level method for all builders def initialize(self, comm): - # if self.solver is None: - solver_dict={} mesh = TACS.MeshLoader(comm) diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index e0aedd4a..fb9bfc45 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -10,7 +10,13 @@ class VlmMesh(om.IndepVarComp): def initialize(self): self.options.declare('x_aero0') def setup(self): - self.add_output('x_aero0', val=self.options['x_aero0']) + self.add_output('x_aero0', val=self.options['x_aero0'], tags=['mphys_coordinates']) + +class VlmMeshAeroOnly(om.IndepVarComp): + def initialize(self): + self.options.declare('x_aero0') + def setup(self): + self.add_output('x_aero', val=self.options['x_aero0'], tags=['mphys_coordinates']) class VlmGroup(om.Group): @@ -92,3 +98,7 @@ def get_number_of_nodes(self): def get_ndof(self): return self.x_aero0.size // 3 + +class VlmBuilderAeroOnly(VlmBuilder): + def get_mesh_coordinate_subsystem(self): + return VlmMeshAeroOnly(x_aero0=self.x_aero0) diff --git a/mphys/multipoint.py b/mphys/multipoint.py index 2b5ad263..e393b4b6 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -1,15 +1,30 @@ import openmdao.api as om -class Multipoint(om.Group): +class MultipointBase: + def __init__(self): + self.mphys_coupling_solvers = [] + def mphys_add_scenario(self, name, scenario, coupling_nonlinear_solver=None, coupling_linear_solver=None): - scenario = self.add_subsystem(name,scenario) + solver_tuple = (coupling_nonlinear_solver, coupling_linear_solver) + self.mphys_coupling_solvers.append((scenario,solver_tuple)) + return self.add_subsystem(name,scenario) + + def configure(self): + self._mphys_set_coupling_algorithms_in_scenarios() - if coupling_nonlinear_solver is not None: - scenario.coupling.nonlinear_solver = coupling_nonlinear_solver + def _mphys_set_coupling_algorithms_in_scenarios(self): + for scenario, solvers in self.mphys_coupling_solvers: + if solvers[0] is not None: + scenario.nonlinear_solver = solvers[0] - if coupling_linear_solver is not None: - scenario.coupling.nonlinear_solver = coupling_linear_solver + if solvers[1] is not None: + scenario.linear_solver = solvers[1] + +class Multipoint(om.Group,MultipointBase): + def __init__(self, **kwargs): + MultipointBase.__init__(self) + om.Group.__init__(self, **kwargs) def mphys_connect_scenario_coordinate_source(self, source, scenarios, disciplines): """ @@ -31,3 +46,8 @@ def mphys_connect_scenario_coordinate_source(self, source, scenarios, discipline src = "%s.x_%s0" % (source, discipline) target = "%s.x_%s0" % (scenario, discipline) self.connect(src,target) + +class MultipointParallelGroup(om.ParallelGroup, MultipointBase): + def __init__(self, **kwargs): + MultipointBase.__init__(self) + om.ParallelGroup.__init__(self, **kwargs) diff --git a/mphys/multipoint_old.py b/mphys/multipoint_old.py deleted file mode 100644 index cc0b67d3..00000000 --- a/mphys/multipoint_old.py +++ /dev/null @@ -1,219 +0,0 @@ -from collections import OrderedDict -import openmdao.api as om -from mphys.scenario import Scenario -from mphys.error import MPHYS_Error - -class Multipoint(om.Group): - - def initialize(self): - - # define the inputs we need - self.options.declare('aero_builder', default=None, recordable=False) - self.options.declare('struct_builder', default=None, recordable=False) - self.options.declare('xfer_builder', default=None, recordable=False) - self.options.declare('prop_builder', default=None, recordable=False) - - # ordered dict to save all the scenarios user adds - self.scenarios = OrderedDict() - - def setup(self): - - # set the builders - self.aero_builder = self.options['aero_builder'] - self.struct_builder = self.options['struct_builder'] - self.xfer_builder = self.options['xfer_builder'] - self.prop_builder = self.options['prop_builder'] - - # we need to initialize the aero and struct objects before the xfer one - # potentially need to do fancy stuff with the comms here if user wants to run in parallel - # in that case, the scenarios themselves would likely need to initialize solvers themselves - - # only initialize solvers if we have the builders - if self.aero_builder is not None: - self.aero_builder.init_solver(self.comm) - self.aero_discipline = True - else: - # no aero builder, so we won't have this discipline - self.aero_discipline = False - - if self.struct_builder is not None: - self.struct_builder.init_solver(self.comm) - self.struct_discipline = True - else: - # no struct builder, so we won't have this discipline - self.struct_discipline = False - - if self.prop_builder is not None: - self.prop_builder.init_solver(self.comm) - self.prop_discipline = True - else: - self.prop_discipline = False - - # check if we have aero and structure - if self.aero_discipline and self.struct_discipline: - - # aerostructural coupling is active - self.as_coupling = True - - # we need a xfer builder for aero and structures - if self.xfer_builder is None: - raise MPHYS_Error('Multipoint group requires a transfer builder to couple aerodynamic and structural analyses.') - - # now initialize the xfer object - self.xfer_builder.init_xfer_object(self.comm) - else: - # we dont have aerostructural coupling - self.as_coupling = False - - # get the mesh elements from disciplines - if self.aero_discipline: - aero_mesh = self.aero_builder.get_mesh_element() - self.add_subsystem('aero_mesh', aero_mesh) - - if self.struct_discipline: - struct_mesh = self.struct_builder.get_mesh_element() - self.add_subsystem('struct_mesh', struct_mesh) - - # add openmdao groups for each scenario - for name, kwargs in self.scenarios.items(): - self._mphys_add_scenario(name, **kwargs) - - # set solvers - self.nonlinear_solver = om.NonlinearRunOnce() - self.linear_solver = om.LinearRunOnce() - - def configure(self): - # connect the initial mesh coordinates. - # at this stage, everything is allocated and every group/component - # below this level is set up. - - # loop over scenarios and connect them all - for name in self.scenarios: - if self.struct_discipline: - # make the default mesh connections for as_coupling - if self.as_coupling: - target_x_s0 = ['%s.solver_group.disp_xfer.x_struct0'%name] - target_x_s0.append('%s.solver_group.load_xfer.x_struct0'%name) - self.connect('struct_mesh.x_struct0', target_x_s0) - - # check if we have custom mesh connections - if hasattr(self.struct_builder, 'get_mesh_connections'): - mesh_conn = self.struct_builder.get_mesh_connections() - - # if mesh_conn has entries called 'solver' or 'funcs', - # then we know that these are dictionaries of connections - # to be made to solver or funcs. If there are no solver - # or funcs entries in here, we just assume every key - # will be connected to the solver. - if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): - # if solver is in the dict, we connect the keys of that dict - if 'solver' in mesh_conn: - mesh_to_solver = mesh_conn['solver'] - # if solver is not in this, it means that funcs is not in - # mesh_conn, which then means that mesh_conn only has - # connections that go to the solver by default - else: - mesh_to_solver = mesh_conn - - for k,v in mesh_to_solver.items(): - self.connect('struct_mesh.%s'%k, '%s.solver_group.struct.%s'%(name, v)) - - # if funcs is in the dict, we just connect the entries from this to the funcs - if 'funcs' in mesh_conn: - mesh_to_funcs = mesh_conn['funcs'] - for k,v in mesh_to_funcs.items(): - self.connect('struct_mesh.%s'%k, '%s.struct_funcs.%s'%(name, v)) - - # if the solver did not define any custom mesh connections, - # we will just connect the nodes from the mesh to solver - else: - self.connect('struct_mesh.x_struct0', '%s.solver_group.struct.x_struct0'%name) - - if self.aero_discipline: - # make the default mesh connections for as_coupling - if self.as_coupling: - target_x_a0 = ['%s.solver_group.load_xfer.x_aero0'%name] - target_x_a0.append('%s.solver_group.disp_xfer.x_aero0'%name) - self.connect('aero_mesh.x_aero0', target_x_a0) - - # check if we have custom mesh connections - if hasattr(self.aero_builder, 'get_mesh_connections'): - mesh_conn = self.aero_builder.get_mesh_connections() - - # if mesh_conn has entries called 'solver' or 'funcs', - # then we know that these are dictionaries of connections - # to be made to solver or funcs. If there are no solver - # or funcs entries in here, we just assume every key - # will be connected to the solver. - if ('solver' in mesh_conn) or ('funcs' not in mesh_conn): - # if solver is in the dict, we connect the keys of that dict - if 'solver' in mesh_conn: - mesh_to_solver = mesh_conn['solver'] - # if solver is not in this, it means that funcs is not in - # mesh_conn, which then means that mesh_conn only has - # connections that go to the solver by default - else: - mesh_to_solver = mesh_conn - - for k,v in mesh_to_solver.items(): - self.connect('aero_mesh.%s'%k, '%s.solver_group.aero.%s'%(name, v)) - - # if funcs is in the dict, we just connect the entries from this to the funcs - if 'funcs' in mesh_conn: - mesh_to_funcs = mesh_conn['funcs'] - for k,v in mesh_to_funcs.items(): - self.connect('aero_mesh.%s'%k, '%s.aero_funcs.%s'%(name, v)) - - # if the solver did not define any custom mesh connections, - # we will just connect the nodes from the mesh to solver - else: - self.connect('aero_mesh.x_aero0', '%s.solver_group.aero.x_aero0'%name) - - - def mphys_add_scenario(self, name, **kwargs): - # save all the data until we are ready to initialize the objects themselves - self.scenarios[name] = kwargs - - def _mphys_add_scenario(self, name, min_procs=None, max_procs=None, aero_kwargs={}, struct_kwargs={}, xfer_kwargs={}): - # this is the actual routine that does the addition of the OpenMDAO groups - # this is called during the setup of this class - self.add_subsystem( - name, - Scenario( - builders = { - 'aero': self.aero_builder, - 'struct': self.struct_builder, - 'prop': self.prop_builder, - 'xfer': self.xfer_builder, - }, - aero_discipline = self.aero_discipline, - struct_discipline = self.struct_discipline, - prop_discipline = self.prop_discipline, - as_coupling = self.as_coupling - ) - ) - - def mphys_add_coordinate_input(self): - # add the coordinates as inputs for every discipline we have - points = {} - - if self.aero_discipline: - name, x_a0 = self.aero_mesh.mphys_add_coordinate_input() - points['aero_points'] = x_a0 - self.promotes('aero_mesh', inputs=[(name, 'aero_points')]) - - if self.struct_discipline: - name, x_s0 = self.struct_mesh.mphys_add_coordinate_input() - points['struct_points'] = x_s0 - self.promotes('struct_mesh', inputs=[(name, 'struct_points')]) - - return points - - - def mphys_get_triangulated_surface(self): - # get triangulated surface for computing constraints - if self.aero_discipline: - x_a0_tri = self.aero_mesh.mphys_get_triangulated_surface() - return x_a0_tri - else: - raise NotImplementedError('Only ADFlow format supported so far') diff --git a/mphys/scenario_aero.py b/mphys/scenario_aero.py index 3e449775..522db902 100644 --- a/mphys/scenario_aero.py +++ b/mphys/scenario_aero.py @@ -3,10 +3,15 @@ class ScenarioAero(Scenario): def initialize(self): self.options.declare('aero_builder', recordable=False) + self.options.declare('in_MultipointParallelGroup', default=False) def setup(self): aero_builder = self.options['aero_builder'] + if self.options['in_MultipointParallelGroup']: + aero_builder.initialize(self.comm) + self.mphys_add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_pre_coupling_subsystem('aero', aero_builder) self.mphys_add_subsystem('coupling',aero_builder.get_coupling_group_subsystem()) self.mphys_add_post_coupling_subsystem('aero', aero_builder) diff --git a/mphys/scenario_old.py b/mphys/scenario_old.py deleted file mode 100644 index 1450e4a7..00000000 --- a/mphys/scenario_old.py +++ /dev/null @@ -1,78 +0,0 @@ -import openmdao.api as om -from mphys.solver_group import SolverGroup - -class Scenario(om.Group): - - def initialize(self): - - # define the inputs we need - self.options.declare('builders', allow_none=False, recordable=False) - self.options.declare('aero_discipline', allow_none=False) - self.options.declare('struct_discipline', allow_none=False) - self.options.declare('prop_discipline', allow_none=False) - self.options.declare('as_coupling', allow_none=False) - - def setup(self): - - # set flags - self.aero_discipline = self.options['aero_discipline'] - self.struct_discipline = self.options['struct_discipline'] - self.prop_discipline = self.options['prop_discipline'] - self.as_coupling = self.options['as_coupling'] - - # set the builders - self.aero_builder = self.options['builders']['aero'] - self.struct_builder = self.options['builders']['struct'] - self.prop_builder = self.options['builders']['prop'] - self.xfer_builder = self.options['builders']['xfer'] - - # add the solver group itself and pass in the builders - # this group will converge the nonlinear analysis - self.add_subsystem( - 'solver_group', - SolverGroup( - builders=self.options['builders'], - aero_discipline = self.aero_discipline, - struct_discipline = self.struct_discipline, - prop_discipline = self.prop_discipline, - as_coupling = self.as_coupling - ) - ) - - # check if builders provide a scenario-level element. - # e.g. a functionals component that is run once after - # the nonlinear solver is converged. - # we only check for disciplines, and we assume transfer - # components do not have scenario level elements. - if hasattr(self.aero_builder, 'get_scenario_element'): - aero_scenario_element = self.aero_builder.get_scenario_element() - self.add_subsystem('aero_funcs', aero_scenario_element) - - # if we have a scenario level element, we also need to - # figure out what needs to be connected from the solver - # level to the scenario level. - scenario_conn = self.aero_builder.get_scenario_connections() - # we can make these connections here - for k, v in scenario_conn.items(): - self.connect('solver_group.aero.%s'%k, 'aero_funcs.%s'%v) - - # do the same for struct - if hasattr(self.struct_builder, 'get_scenario_element'): - struct_scenario_element = self.struct_builder.get_scenario_element() - self.add_subsystem('struct_funcs', struct_scenario_element) - - scenario_conn = self.struct_builder.get_scenario_connections() - for k, v in scenario_conn.items(): - self.connect('solver_group.struct.%s'%k, 'struct_funcs.%s'%v) - - def configure(self): - pass - - def mphys_make_aeroprop_conn(self, aero2prop_conn, prop2aero_conn): - # do the connections. we may want to do the solver level connections on the solver level but do them here for now - for k,v in aero2prop_conn.items(): - self.connect('solver_group.aero.%s'%k, 'solver_group.prop.%s'%v) - - # also do the connections from prop to aero... - for k,v in prop2aero_conn.items(): - self.connect('solver_group.prop.%s'%k, 'solver_group.aero.%s'%v) From 72de6c3a352c680dbdec68c3295ff747301e5b2e Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 21:43:32 -0500 Subject: [PATCH 10/82] remove import from future --- mphys/mphys_vlm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index fb9bfc45..d838d153 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -1,4 +1,3 @@ -from __future__ import division, print_function import numpy as np import openmdao.api as om From f2be50b2576403072278c39f81c7df5fd28ce04c Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 21:44:54 -0500 Subject: [PATCH 11/82] Tag coodinate output for TACS --- mphys/mphys_group.py | 4 ++++ mphys/mphys_tacs.py | 3 +-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index a5b044ec..195141c0 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -8,6 +8,10 @@ def __init__(self, **kwargs): self.mphys_subsystems = [] def mphys_add_subsystem(self,name,subsystem): + """ + Adding an mphys subsystem will add the subsystem and then set the group + to automaticallly promote the mphys tagged variables + """ subsystem = self.add_subsystem(name,subsystem) self.mphys_subsystems.append(subsystem) return subsystem diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 8b327776..19cf46e6 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -17,7 +17,7 @@ def setup(self): xpts = tacs_assembler.createNodeVec() x = xpts.getArray() tacs_assembler.getNodes(xpts) - self.add_output('x_struct0', val=x, shape=x.size, desc='structural node coordinates') + self.add_output('x_struct0', val=x, shape=x.size, desc='structural node coordinates', tags=['mphys_coordinates']) class TacsSolver(om.ImplicitComponent): """ @@ -533,7 +533,6 @@ def compute(self,inputs,outputs): if 'func_struct' in outputs: outputs['func_struct'] = self.tacs_assembler.evalFunctions(self.func_list) - print('func_struct',outputs['func_struct']) if self.f5_writer is not None: self.f5_writer(self.tacs_assembler) From bd584bf5f08237f09075b099b073d291b753aa1a Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 21:45:46 -0500 Subject: [PATCH 12/82] Make vlm+tacs example multipoint --- examples/aerostruct_with_vlm/mphys_as_vlm.py | 44 ++++++++------------ 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/examples/aerostruct_with_vlm/mphys_as_vlm.py b/examples/aerostruct_with_vlm/mphys_as_vlm.py index 44c56dce..bd2003c4 100644 --- a/examples/aerostruct_with_vlm/mphys_as_vlm.py +++ b/examples/aerostruct_with_vlm/mphys_as_vlm.py @@ -14,14 +14,12 @@ import tacs_setup class Top(Multipoint): - def setup(self): - self.modal_struct = False - # VLM mesh_file = 'wing_VLM.dat' mach = 0.85, - aoa = 2.0 + aoa0 = 2.0 + aoa1 = 5.0 q_inf = 3000. vel = 178. mu = 3.5E-5 @@ -30,7 +28,7 @@ def setup(self): aero_builder.initialize(self.comm) dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('aoa', val=[aoa0,aoa1], units='deg') dvs.add_output('mach', mach) dvs.add_output('q_inf', q_inf) dvs.add_output('vel', vel) @@ -57,41 +55,33 @@ def setup(self): xfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) xfer_builder.initialize(self.comm) - nonlinear_solver = om.NonlinearBlockGS(maxiter=20, iprint=2, use_aitken=True, rtol = 1E-14, atol=1E-14) - linear_solver = om.LinearBlockGS(maxiter=20, iprint=2, use_aitken=True, rtol = 1e-14, atol=1e-14) - - self.mphys_add_scenario('cruise',ScenarioAeroStructural(aero_builder=aero_builder, - struct_builder=struct_builder, - xfer_builder=xfer_builder), - coupling_nonlinear_solver=nonlinear_solver, - coupling_linear_solver=linear_solver) - + for iscen, scenario in enumerate(['cruise','maneuver']): + nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1E-14, atol=1E-14) + linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1e-14, atol=1e-14) + self.mphys_add_scenario(scenario,ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + xfer_builder=xfer_builder), + coupling_nonlinear_solver=nonlinear_solver, + coupling_linear_solver=linear_solver) - for discipline in ['aero','struct']: - self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline ,'cruise', discipline) + for discipline in ['aero','struct']: + self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline, scenario, discipline) - for dv in ['aoa','q_inf','vel','mu','mach', 'dv_struct']: - self.connect(dv, 'cruise.%s' % dv) + for dv in ['q_inf','vel','mu','mach', 'dv_struct']: + self.connect(dv, '%s.%s' % (scenario, dv)) + self.connect('aoa', '%s.aoa' % scenario, src_indices=[iscen]) ################################################################################ # OpenMDAO setup ################################################################################ prob = om.Problem() prob.model = Top() -model = prob.model - -# optional but we can set it here. -model.nonlinear_solver = om.NonlinearRunOnce() -model.linear_solver = om.LinearRunOnce() - prob.setup() - om.n2(prob, show_browser=False, outfile='mphys_as_vlm.html') prob.run_model() if MPI.COMM_WORLD.rank == 0: - print('func_struct =',prob['cruise.func_struct']) - print('mass =',prob['cruise.mass']) print('C_L =',prob['cruise.C_L']) + print('func_struct =',prob['maneuver.func_struct']) From 8bdf5efd4a59c5e7409100cdaf19f3169a610ab5 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 21:46:10 -0500 Subject: [PATCH 13/82] call configure from multipoint base class --- mphys/multipoint.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mphys/multipoint.py b/mphys/multipoint.py index e393b4b6..1fcf4a8f 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -47,7 +47,13 @@ def mphys_connect_scenario_coordinate_source(self, source, scenarios, discipline target = "%s.x_%s0" % (scenario, discipline) self.connect(src,target) + def configure(self): + return MultipointBase.configure(self) + class MultipointParallelGroup(om.ParallelGroup, MultipointBase): def __init__(self, **kwargs): MultipointBase.__init__(self) om.ParallelGroup.__init__(self, **kwargs) + + def configure(self): + return MultipointBase.configure(self) From ad41506678cf241ab43bc5193caadda7f965b23d Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 21:47:45 -0500 Subject: [PATCH 14/82] Add parallel option to struct only scenarion and an untested geometry group --- mphys/scenario_structural.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/mphys/scenario_structural.py b/mphys/scenario_structural.py index c533d370..60f31f47 100644 --- a/mphys/scenario_structural.py +++ b/mphys/scenario_structural.py @@ -4,10 +4,40 @@ class ScenarioStructural(Scenario): def initialize(self): self.options.declare('struct_builder', recordable=False) + self.options.declare('in_MultipointParallelGroup', default=False) def setup(self): struct_builder = self.options['struct_builder'] + if self.options['in_MultipointParallelGroup']: + struct_builder.initialize(self.comm) + self.mphys_add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) + + self.mphys_add_pre_coupling_subsystem('struct', struct_builder) + self.mphys_add_subsystem('coupling',struct_builder.get_coupling_group_subsystem()) + self.mphys_add_post_coupling_subsystem('struct', struct_builder) + + +# UNTESTED: to show in_MultipointParallelGroup option isn't necessary and add geometry +class ScenarioStructuralParallelGeometry(Scenario): + + def initialize(self): + self.options.declare('struct_builder', recordable=False) + self.options.declare('geometry_builder',recordable=False) + + def setup(self): + struct_builder = self.options['struct_builder'] + geometry_builder = self.options['geometry_builder'] + + struct_builder.initialize(self.comm) + geometry_builder.initialize(self.comm) + + # don't use mphys_add_subsystem for mesh so that the geometry's coordinate output + # are promoted, not the mesh + self.add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_subsystem('geometry',geometry_builder.get_mesh_coordinate_subsystem()) + self.connect('mesh.x_struct0','geometry.x_struct_in') + self.mphys_add_pre_coupling_subsystem('struct', struct_builder) self.mphys_add_subsystem('coupling',struct_builder.get_coupling_group_subsystem()) self.mphys_add_post_coupling_subsystem('struct', struct_builder) From 00f502506bd592228c1ea88d3ed8aa16d16d1bae Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 22:34:51 -0500 Subject: [PATCH 15/82] fstrings to make some strings easier to read --- examples/vlm_only/run_vlm.py | 2 +- examples/vlm_only/run_vlm_2scenarios.py | 6 +- .../vlm_only/run_vlm_2scenarios_parallel.py | 4 +- mphys/base_classes.py | 230 ------------------ mphys/multipoint.py | 4 +- 5 files changed, 8 insertions(+), 238 deletions(-) delete mode 100644 mphys/base_classes.py diff --git a/examples/vlm_only/run_vlm.py b/examples/vlm_only/run_vlm.py index 0a63a09a..a6ebdacc 100644 --- a/examples/vlm_only/run_vlm.py +++ b/examples/vlm_only/run_vlm.py @@ -32,7 +32,7 @@ def setup(self): self.connect('mesh.x_aero0','cruise.x_aero') for dv in ['aoa', 'mach', 'q_inf', 'vel', 'mu']: - self.connect(dv,'cruise.%s' % dv) + self.connect(dv, f'cruise.{dv}') prob = om.Problem() prob.model = Top() diff --git a/examples/vlm_only/run_vlm_2scenarios.py b/examples/vlm_only/run_vlm_2scenarios.py index 17beb279..1e7c732e 100644 --- a/examples/vlm_only/run_vlm_2scenarios.py +++ b/examples/vlm_only/run_vlm_2scenarios.py @@ -32,12 +32,12 @@ def setup(self): self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder)) for dv in ['mach', 'q_inf', 'vel', 'mu']: - self.connect(dv,'cruise.%s' % dv) + self.connect(dv, f'cruise.{dv}') self.connect('aoa0','cruise.aoa') self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder)) for dv in ['mach', 'q_inf', 'vel', 'mu']: - self.connect(dv,'cruise_higher_aoa.%s' % dv) + self.connect(dv, f'cruise_higher_aoa.{dv}') self.connect('aoa1','cruise_higher_aoa.aoa') self.connect('mesh.x_aero0',['cruise.x_aero','cruise_higher_aoa.x_aero']) @@ -51,4 +51,4 @@ def setup(self): prob.run_model() if MPI.COMM_WORLD.rank == 0: for scenario in ['cruise','cruise_higher_aoa']: - print('%s: C_L = %f, C_D=%f' % (scenario, prob['%s.C_L'%scenario], prob['%s.C_D'%scenario])) + print('%s: C_L = %f, C_D = %f' % (scenario, prob['%s.C_L'%scenario], prob['%s.C_D'%scenario])) diff --git a/examples/vlm_only/run_vlm_2scenarios_parallel.py b/examples/vlm_only/run_vlm_2scenarios_parallel.py index 3d633fc2..ede8e0b9 100644 --- a/examples/vlm_only/run_vlm_2scenarios_parallel.py +++ b/examples/vlm_only/run_vlm_2scenarios_parallel.py @@ -37,10 +37,10 @@ def setup(self): self.add_subsystem('mp',ParallelCruises()) for dv in ['mach', 'q_inf', 'vel', 'mu']: - self.connect(dv,'mp.cruise.%s' % dv) + self.connect(dv, f'mp.cruise.{dv}') self.connect('aoa0','mp.cruise.aoa') for dv in ['mach', 'q_inf', 'vel', 'mu']: - self.connect(dv,'mp.cruise_higher_aoa.%s' % dv) + self.connect(dv, f'mp.cruise_higher_aoa.{dv}') self.connect('aoa1','mp.cruise_higher_aoa.aoa') prob = om.Problem() prob.model = Top() diff --git a/mphys/base_classes.py b/mphys/base_classes.py deleted file mode 100644 index 7ab2ff22..00000000 --- a/mphys/base_classes.py +++ /dev/null @@ -1,230 +0,0 @@ -from __future__ import division, print_function - -class SolverBuilder(object): - """ - MPHYS builder base class. Template for developers to create their builders. - """ - - def __init__(self, options): - """ - Initialization routine for the solver builder. This is called by the user in the - main runscript. The input parameters presented here are not strictly required by - the mphys api, but they are a good starting point. This is because the user - initializes this object, and therefore, they have complete freedom on the input - args. - - Parameters - ---------- - options : dictionary - A dictionary containing all the options the user wants to pass to the - solver object. - """ - pass - - def init_solver(self, comm): - """ - The method that initializes the python objects required for the solver. - - Parameters - ---------- - comm : mpi communicator - The communicator object created for this xfer object instance. - - """ - pass - - def get_solver(self): - """ - The method that returns the python objects that represents the solver. - - Returns - _______ - solver : object - The solver used inside of the openmdao component that does the calculations - """ - if hasattr(self, "solver"): - return self.solver - else: - raise NotImplementedError - - def get_element(self, **kwargs): - """ - Method that returns the openmdao element for this solver - - Parameters - __________ - **kwargs : optional parameter dictionary - The builder may or may not propagate the keyword arguments - provided in this dictionary to the element. These options - can contain args such as "as_coupling", which is a bool flag - to determine if the "hooks" required for aerostructural analysis - should be enabled in the solver. Subject to change - - Returns - _______ - solver_element : openmdao component or group - The openmdao element that handles all the computations for - this solver. This group needs to comply with the MPHYS API - with the i/o it provides to couple additional physics. - """ - if hasattr(self, "element"): - return self.element - else: - raise NotImplementedError - - def get_mesh_element(self): - """ - Method that returns the mesh element for this solver. This method - is subject to change; however, currently the mesh element it returns - is connected to the every flight condition that is created using this builder - - Returns - _______ - mesh_element : openmdao component (or group) - The openmdao element that stores the original coordinates of the mesh used - for this solver. For an aerodynamic or structural solver examples, these - coordinates are the undeflected shape, and they can be used in the context - of a geometry manipulation routine. - """ - if hasattr(self, "mesh_element"): - return self.mesh_element - else: - raise NotImplementedError - - def get_mesh_connections(self): - """ - Method that returns a dictionary of connections between the mesh element and the element - Returns - _______ - mesh_element_conns : dict - The keys of the dictionary represent the components of the element the mesh is connected to and the keys - represent the variables to connect from the mesh to the element component - """ - if hasattr(self, "mesh_connections"): - return self.mesh_connections - else: - raise NotImplementedError - - def get_scenario_element(self): - """ - Method that returns the openmdao element to be added to each scenario - - Returns - _______ - scenario_element : openmdao component or group - The openmdao element that handles all the added computations for - that should be run in each scenario. These may represent functional that - are run after a coupled analysis is converged. - """ - if hasattr(self, "scenario_element"): - return self.scenario_element - else: - raise NotImplementedError - - def get_scenario_connections(self): - """ - Method that returns a dictionary of connections between the scenario element and the element - - Returns - _______ - scenario_connections : dict - The keys of the dictionary represent the components of the element the scenario is connected to and the keys - represent the variables to connect from the scenario to the element component - """ - if hasattr(self, "scenario_connections"): - return self.scenario_connections - else: - raise NotImplementedError - - def get_nnodes(self): - """ - Method that returns the number of nodes used in the calculation - - Returns - _______ - nnodes : int - number of nodes in the computational domain - """ - if hasattr(self, "nnodes"): - return self.nnodes - else: - raise NotImplementedError - - def get_ndof(self): - """ - Method that returns the number of degrees of freedom used at each node. - - Returns - _______ - ndof : int - number of degrees of freedom of each node in the computational domain - """ - if hasattr(self, "ndof"): - return self.ndof - else: - raise NotImplementedError - - -class DummyBuilder(SolverBuilder): - def __init__(self, **kwargs): - """ - all the keywords that are passed as argument are set as attributes of the class - """ - self.__dict__.update(kwargs) - - -class XferBuilder(object): - def __init__(self, options, aero_builder, struct_builder): - """ - Initialization routine for the xfer builder. This is called by the user in the - main runscript. This method simply saves the options and the two other solver - builders that this xfer object will connect. The input parameters we present - here are not strictly required by the mphys api, but they are a good starting - point. This is because the user initializes this object, and therefore, they - have complete freedom on the input args. - - Parameters - ---------- - options : dictionary - A dictionary containing all the options the user wants to pass to the - xfer object. - aero_builder : solver_builder - This is the builder for the aero solver this scheme couples - struct_builder : solver_builder - This is the builder for the struct solver this scheme couples - """ - pass - - def init_xfer_object(self, comm): - """ - The method that initializes the python objects required for the transfer scheme. - In this method, the xfer builder can make calls to the aero and struct builders - that are not in the mphys API. E.g. the developers can implement custom methods - in the respective solver builders to set up the transfer scheme that couples the - solvers. - - Parameters - ---------- - comm : mpi communicator - The communicator object created for this xfer object instance. - - """ - pass - - def get_element(self): - """ - Method that returns the openmdao elements for the xfer scheme. - Unlike the get_element methods for a solver_builder, this method returns - two elements, e.g. one for transfering the displacements and one for - transfering the loads. - - Returns - ------- - disp_xfer : Openmdao component (or group) - The openmdao "element" that is responsible from propagating the displacements - from the struct solver to the aero solver - load_xfer : Openmdao component (or group) - The openmdao "element" that is responsible from propagating the loads - from the aero solver to the struct solver - """ - pass diff --git a/mphys/multipoint.py b/mphys/multipoint.py index 1fcf4a8f..9febd25e 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -43,8 +43,8 @@ def mphys_connect_scenario_coordinate_source(self, source, scenarios, discipline for scenario in scenarios_list: for discipline in disciplines_list: - src = "%s.x_%s0" % (source, discipline) - target = "%s.x_%s0" % (scenario, discipline) + src = f'{source}.x_{discipline}0' + target = f'{scenario}.x_{discipline}0' self.connect(src,target) def configure(self): From 13e048d983432c41274fd29326b31ef6f49a58ff Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 22:35:07 -0500 Subject: [PATCH 16/82] Remove unused function --- mphys/coupling_group.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mphys/coupling_group.py b/mphys/coupling_group.py index e8915491..fe560806 100644 --- a/mphys/coupling_group.py +++ b/mphys/coupling_group.py @@ -1,6 +1,4 @@ from .mphys_group import MphysGroup class CouplingGroup(MphysGroup): - def _mphys_variable_is_in_subsystem_inputs(self, element, variable_name): - meta_data = element.get_io_metadata(iotypes='input', includes=variable_name) - return bool(meta_data) + pass From de8ff132d375f2f2e99a56cb4be03941aafd7cf3 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 22:35:32 -0500 Subject: [PATCH 17/82] Better scaling for tacs optimization problem --- examples/tacs_only/mphys_crm_example.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/tacs_only/mphys_crm_example.py b/examples/tacs_only/mphys_crm_example.py index 7378d9db..a462308b 100644 --- a/examples/tacs_only/mphys_crm_example.py +++ b/examples/tacs_only/mphys_crm_example.py @@ -41,17 +41,17 @@ def setup(self): prob.model = Top() model = prob.model -model.add_design_var('dv_struct',lower=0.001,upper=0.075,scaler=1.0/1.0) -model.add_objective('analysis.mass',scaler=1.0/100000.0) -model.add_constraint('analysis.func_struct',lower = 0.0, upper = 2.0/3.0,scaler=1000.0/1.0) +model.add_design_var('dv_struct',lower=0.001,upper=0.075,scaler=1000.0) +model.add_objective('analysis.mass',scaler=1.0/1000.0) +model.add_constraint('analysis.func_struct',lower = 0.0, upper = 2.0/3.0,scaler=1.0) -prob.driver = om.ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=1500) +prob.driver = om.ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=150) prob.driver.options['optimizer'] = 'SLSQP' prob.setup() om.n2(prob, show_browser=False, outfile='mphys_struct.html') prob.run_model() -# TODO verify that the optimization works -# prob.run_driver() -#for i in range(240): -# print('final dvs',i,prob['dv_struct'][i]) + +prob.run_driver() +for i in range(240): + print('final dvs',i,prob['dv_struct'][i]) From 0f0ac438d8a4fb7c464df238978487fccde17a69 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Fri, 5 Mar 2021 22:35:47 -0500 Subject: [PATCH 18/82] Single quotes instead of double --- examples/tacs_only/tacs_setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tacs_only/tacs_setup.py b/examples/tacs_only/tacs_setup.py index 92c9f987..d8972bb5 100644 --- a/examples/tacs_only/tacs_setup.py +++ b/examples/tacs_only/tacs_setup.py @@ -17,7 +17,7 @@ def add_elements(mesh): stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, min_thickness, max_thickness) element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: + if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: element = elements.MITCShell(2,stiff,component_num=i) mesh.setElement(i, element) From 6139b98a2b9da7ee359ba2d2de2cc2186bf7efd3 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sat, 6 Mar 2021 20:30:50 -0500 Subject: [PATCH 19/82] Update some names --- mphys/builder.py | 20 ++++++++++---------- mphys/integrated_forces.py | 12 ++++++------ mphys/mphys_group.py | 2 +- mphys/mphys_tacs.py | 6 +++--- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mphys/builder.py b/mphys/builder.py index eb226e38..8f7364c6 100644 --- a/mphys/builder.py +++ b/mphys/builder.py @@ -30,30 +30,30 @@ def get_mesh_coordinate_subsystem(self): def get_coupling_group_subsystem(self): """ - The element that this builder will add to the CouplingGroup + The subsystem that this builder will add to the CouplingGroup Returns ------- - solver_element : ~openmdao.api.Component or ~openmdao.api.Group - The openmdao element that handles all the computations for - this solver. Transfer schemes can return multiple elements + solver_subsystem : ~openmdao.api.Component or ~openmdao.api.Group + The openmdao subsystem that handles all the computations for + this solver. Transfer schemes can return multiple subsystems """ return None def get_scenario_subsystems(self): """ - Method that returns the openmdao element to be added to each scenario + Method that returns the openmdao subsystem to be added to each scenario Returns ------- - pre_coupling_element : openmdao.api.Component or ~openmdao.api.Group - The openmdao element that handles all the added computations for + pre_coupling_subsystem : openmdao.api.Component or ~openmdao.api.Group + The openmdao subsystem that handles all the added computations for that should be run in each scenario. These may represent functional that are run after a coupled analysis is converged. """ - pre_coupling_element = None - post_coupling_element = None - return pre_coupling_element, post_coupling_element + pre_coupling_subsystem = None + post_coupling_subsystem = None + return pre_coupling_subsystem, post_coupling_subsystem def get_number_of_nodes(self): """ diff --git a/mphys/integrated_forces.py b/mphys/integrated_forces.py index 27b690be..ec7792bd 100644 --- a/mphys/integrated_forces.py +++ b/mphys/integrated_forces.py @@ -3,12 +3,12 @@ class IntegratedSurfaceForces(om.ExplicitComponent): def setup(self): - self.add_input('aoa',desc = 'angle of attack', units='rad',tags=['mphys_dv']) - self.add_input('yaw',desc = 'yaw angle',units='rad',tags=['mphys_dv']) - self.add_input('ref_area', val = 1.0,tags=['mphys_dv']) - self.add_input('moment_center',shape=3,tags=['mphys_dv']) - self.add_input('ref_length', val = 1.0,tags=['mphys_dv']) - self.add_input('q_inf', val = 1.0,tags=['mphys_dv']) + self.add_input('aoa',desc = 'angle of attack', units='rad',tags=['mphys_input']) + self.add_input('yaw',desc = 'yaw angle',units='rad',tags=['mphys_input']) + self.add_input('ref_area', val = 1.0,tags=['mphys_input']) + self.add_input('moment_center',shape=3,tags=['mphys_input']) + self.add_input('ref_length', val = 1.0,tags=['mphys_input']) + self.add_input('q_inf', val = 1.0,tags=['mphys_input']) self.add_input('x_aero', shape_by_conn=True, desc = 'surface coordinates', tags=['mphys_coupling']) self.add_input('f_aero', shape_by_conn=True, desc = 'dimensional forces at nodes', tags=['mphys_coupling']) diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index 195141c0..41be3553 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -30,7 +30,7 @@ def _mphys_promote_coupling_variables(self): self._mphys_promote_by_tag(['input','output'],'mphys_coupling') def _mphys_promote_design_variables(self): - self._mphys_promote_by_tag('input','mphys_dv') + self._mphys_promote_by_tag('input','mphys_input') def _mphys_promote_mesh_coordinates(self): self._mphys_promote_by_tag(['input','output'],'mphys_coordinates') diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 19cf46e6..2eb95ed3 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -84,7 +84,7 @@ def setup(self): self.ndof = int(state_size/(node_size/3)) # inputs - self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_input']) self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates',tags=['mphys_coordinates']) self.add_input('f_struct', shape_by_conn=True, desc='structural load vector', tags=['mphys_coupling']) @@ -480,7 +480,7 @@ def setup(self): # OpenMDAO part of setup # TODO move the dv_struct to an external call where we add the DVs - self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_input']) self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates',tags=['mphys_coordinates']) self.add_input('u_struct', shape_by_conn=True, desc='structural state vector', tags=['mphys_coupling']) @@ -603,7 +603,7 @@ def setup(self): self.xpt_sens = tacs_assembler.createNodeVec() # OpenMDAO part of setup - self.add_input('dv_struct', shape=ndv, desc='tacs design variables', tags=['mphys_dv']) + self.add_input('dv_struct', shape=ndv, desc='tacs design variables', tags=['mphys_input']) self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates', tags=['mphys_coordinates']) self.add_output('mass', 0.0, desc = 'structural mass', tags=['mphys_result']) From 92fae41fb501d8c06805c528fa1637ebfd5e77ad Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 13:45:55 -0500 Subject: [PATCH 20/82] Rename parallel multipoint, and xfer to ldxfer --- examples/aerostruct_with_vlm/mphys_as_vlm.py | 9 ++++----- examples/vlm_only/run_vlm_2scenarios_parallel.py | 8 ++++---- mphys/__init__.py | 2 +- mphys/coupling_aerostructural.py | 6 +++--- mphys/mphys_group.py | 4 ++-- mphys/multipoint.py | 2 +- mphys/scenario_aero.py | 4 ++-- mphys/scenario_aerostructural.py | 14 +++++++++----- 8 files changed, 26 insertions(+), 23 deletions(-) diff --git a/examples/aerostruct_with_vlm/mphys_as_vlm.py b/examples/aerostruct_with_vlm/mphys_as_vlm.py index bd2003c4..091aba17 100644 --- a/examples/aerostruct_with_vlm/mphys_as_vlm.py +++ b/examples/aerostruct_with_vlm/mphys_as_vlm.py @@ -52,17 +52,16 @@ def setup(self): # MELD setup isym = 1 - xfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) - xfer_builder.initialize(self.comm) + ldxfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) + ldxfer_builder.initialize(self.comm) for iscen, scenario in enumerate(['cruise','maneuver']): nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1E-14, atol=1E-14) linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1e-14, atol=1e-14) self.mphys_add_scenario(scenario,ScenarioAeroStructural(aero_builder=aero_builder, struct_builder=struct_builder, - xfer_builder=xfer_builder), - coupling_nonlinear_solver=nonlinear_solver, - coupling_linear_solver=linear_solver) + ldxfer_builder=ldxfer_builder), + nonlinear_solver, linear_solver) for discipline in ['aero','struct']: self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline, scenario, discipline) diff --git a/examples/vlm_only/run_vlm_2scenarios_parallel.py b/examples/vlm_only/run_vlm_2scenarios_parallel.py index ede8e0b9..9a5c8740 100644 --- a/examples/vlm_only/run_vlm_2scenarios_parallel.py +++ b/examples/vlm_only/run_vlm_2scenarios_parallel.py @@ -1,22 +1,22 @@ from mpi4py import MPI import openmdao.api as om -from mphys import MultipointParallelGroup +from mphys import MultipointParallel from mphys.mphys_vlm import VlmBuilderAeroOnly from mphys.scenario_aero import ScenarioAero -class ParallelCruises(MultipointParallelGroup): +class ParallelCruises(MultipointParallel): def setup(self): # VLM options mesh_file = 'wing_VLM.dat' aero_builder = VlmBuilderAeroOnly(mesh_file) self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder, - in_MultipointParallelGroup=True)) + in_MultipointParallel=True)) self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder, - in_MultipointParallelGroup=True)) + in_MultipointParallel=True)) class Top(om.Group): def setup(self): diff --git a/mphys/__init__.py b/mphys/__init__.py index 314204fd..64e35410 100644 --- a/mphys/__init__.py +++ b/mphys/__init__.py @@ -1,3 +1,3 @@ #!/usr/bin/env python from .builder import Builder -from .multipoint import Multipoint, MultipointParallelGroup +from .multipoint import Multipoint, MultipointParallel diff --git a/mphys/coupling_aerostructural.py b/mphys/coupling_aerostructural.py index 0035ac85..93c61ed3 100644 --- a/mphys/coupling_aerostructural.py +++ b/mphys/coupling_aerostructural.py @@ -10,14 +10,14 @@ class CouplingAeroStructural(CouplingGroup): def initialize(self): self.options.declare('aero_builder', recordable=False) self.options.declare('struct_builder', recordable=False) - self.options.declare('xfer_builder', recordable=False) + self.options.declare('ldxfer_builder', recordable=False) def setup(self): aero_builder = self.options['aero_builder'] struct_builder = self.options['struct_builder'] - xfer_builder = self.options['xfer_builder'] + ldxfer_builder = self.options['ldxfer_builder'] - disp_xfer, load_xfer = xfer_builder.get_coupling_group_subsystem() + disp_xfer, load_xfer = ldxfer_builder.get_coupling_group_subsystem() aero = aero_builder.get_coupling_group_subsystem() struct = struct_builder.get_coupling_group_subsystem() diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index 41be3553..ffc1f8d2 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -29,7 +29,7 @@ def _mphys_promote_by_tag(self,iotype,tag): def _mphys_promote_coupling_variables(self): self._mphys_promote_by_tag(['input','output'],'mphys_coupling') - def _mphys_promote_design_variables(self): + def _mphys_promote_inputs(self): self._mphys_promote_by_tag('input','mphys_input') def _mphys_promote_mesh_coordinates(self): @@ -40,6 +40,6 @@ def _mphys_promote_results(self): def configure(self): self._mphys_promote_coupling_variables() - self._mphys_promote_design_variables() + self._mphys_promote_inputs() self._mphys_promote_mesh_coordinates() self._mphys_promote_results() diff --git a/mphys/multipoint.py b/mphys/multipoint.py index 9febd25e..b0530490 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -50,7 +50,7 @@ def mphys_connect_scenario_coordinate_source(self, source, scenarios, discipline def configure(self): return MultipointBase.configure(self) -class MultipointParallelGroup(om.ParallelGroup, MultipointBase): +class MultipointParallel(om.ParallelGroup, MultipointBase): def __init__(self, **kwargs): MultipointBase.__init__(self) om.ParallelGroup.__init__(self, **kwargs) diff --git a/mphys/scenario_aero.py b/mphys/scenario_aero.py index 522db902..c9356520 100644 --- a/mphys/scenario_aero.py +++ b/mphys/scenario_aero.py @@ -3,12 +3,12 @@ class ScenarioAero(Scenario): def initialize(self): self.options.declare('aero_builder', recordable=False) - self.options.declare('in_MultipointParallelGroup', default=False) + self.options.declare('in_MultipointParallel', default=False) def setup(self): aero_builder = self.options['aero_builder'] - if self.options['in_MultipointParallelGroup']: + if self.options['in_MultipointParallel']: aero_builder.initialize(self.comm) self.mphys_add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) diff --git a/mphys/scenario_aerostructural.py b/mphys/scenario_aerostructural.py index 2d6fd2ee..d373b7e2 100644 --- a/mphys/scenario_aerostructural.py +++ b/mphys/scenario_aerostructural.py @@ -1,3 +1,4 @@ +import openmdao.api as om from .scenario import Scenario from .coupling_aerostructural import CouplingAeroStructural @@ -6,22 +7,25 @@ class ScenarioAeroStructural(Scenario): def initialize(self): self.options.declare('aero_builder', recordable=False) self.options.declare('struct_builder', recordable=False) - self.options.declare('xfer_builder', recordable=False) + self.options.declare('ldxfer_builder', recordable=False) def setup(self): aero_builder = self.options['aero_builder'] struct_builder = self.options['struct_builder'] - xfer_builder = self.options['xfer_builder'] + ldxfer_builder = self.options['ldxfer_builder'] self.mphys_add_pre_coupling_subsystem('aero', aero_builder) self.mphys_add_pre_coupling_subsystem('struct', struct_builder) - self.mphys_add_pre_coupling_subsystem('xfer', xfer_builder) + self.mphys_add_pre_coupling_subsystem('ldxfer', ldxfer_builder) coupling_group = CouplingAeroStructural(aero_builder=aero_builder, struct_builder=struct_builder, - xfer_builder=xfer_builder) + ldxfer_builder=ldxfer_builder) self.mphys_add_subsystem('coupling',coupling_group) self.mphys_add_post_coupling_subsystem('aero', aero_builder) self.mphys_add_post_coupling_subsystem('struct', struct_builder) - self.mphys_add_post_coupling_subsystem('xfer', xfer_builder) + self.mphys_add_post_coupling_subsystem('ldxfer', ldxfer_builder) + + self.nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, atol=1e-8, rtol=1e-8, use_aitken=True) + self.linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, atol=1e-8, rtol=1e-8, use_aitken=True) From 439cfba4e6b2f0c68e5c9af567fcd7804beb1b95 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 13:47:24 -0500 Subject: [PATCH 21/82] Add fun3d version of aero_only --- examples/aero_only/run_fun3d.py | 47 +++++++ examples/{vlm_only => aero_only}/run_vlm.py | 0 .../run_vlm_2scenarios.py | 0 .../run_vlm_2scenarios_parallel.py | 0 mphys/mphys_fun3d.py | 129 ++++++------------ 5 files changed, 91 insertions(+), 85 deletions(-) create mode 100644 examples/aero_only/run_fun3d.py rename examples/{vlm_only => aero_only}/run_vlm.py (100%) rename examples/{vlm_only => aero_only}/run_vlm_2scenarios.py (100%) rename examples/{vlm_only => aero_only}/run_vlm_2scenarios_parallel.py (100%) diff --git a/examples/aero_only/run_fun3d.py b/examples/aero_only/run_fun3d.py new file mode 100644 index 00000000..cec615dd --- /dev/null +++ b/examples/aero_only/run_fun3d.py @@ -0,0 +1,47 @@ + +from mpi4py import MPI +import openmdao.api as om + +from mphys import Multipoint +from mphys.mphys_fun3d import Fun3dSfeBuilder +from mphys.scenario_aero import ScenarioAero + + +class Top(Multipoint): + def setup(self): + # FUN3D options + project_rootname = 'agard_debug' + boundary_tags = [3] + + mach = 0.5, + aoa = 0.0 + yaw = 0.0 + q_inf = 3000. + reynolds = 1.0 + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('yaw', val=yaw, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('reynolds', reynolds) + + aero_builder = Fun3dSfeBuilder(project_rootname, boundary_tags) + aero_builder.initialize(self.comm) + + self.add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder)) + self.connect('mesh.x_aero0','cruise.x_aero') + + for dv in ['aoa', 'yaw', 'mach', 'q_inf','reynolds']: + self.connect(dv, f'cruise.{dv}') + +prob = om.Problem() +prob.model = Top() +prob.setup() + +om.n2(prob, show_browser=False, outfile='mphys_fun3d.html') + +prob.run_model() +if MPI.COMM_WORLD.rank == 0: + print('C_L, C_D =',prob['cruise.C_L'], prob['cruise.C_D']) diff --git a/examples/vlm_only/run_vlm.py b/examples/aero_only/run_vlm.py similarity index 100% rename from examples/vlm_only/run_vlm.py rename to examples/aero_only/run_vlm.py diff --git a/examples/vlm_only/run_vlm_2scenarios.py b/examples/aero_only/run_vlm_2scenarios.py similarity index 100% rename from examples/vlm_only/run_vlm_2scenarios.py rename to examples/aero_only/run_vlm_2scenarios.py diff --git a/examples/vlm_only/run_vlm_2scenarios_parallel.py b/examples/aero_only/run_vlm_2scenarios_parallel.py similarity index 100% rename from examples/vlm_only/run_vlm_2scenarios_parallel.py rename to examples/aero_only/run_vlm_2scenarios_parallel.py diff --git a/mphys/mphys_fun3d.py b/mphys/mphys_fun3d.py index 1591ad5d..ca79e29d 100644 --- a/mphys/mphys_fun3d.py +++ b/mphys/mphys_fun3d.py @@ -1,18 +1,17 @@ -import numpy as np import openmdao.api as om -from mphys.base_classes import SolverBuilder +from mphys.builder import Builder +from libmeshdef.openmdao import MeshDeformationOpenMdao +from libmeshdef.mesh_deformer import MeshDeformer -from iris_wrapper import Iris -from parfait.distance_calculator import DistanceCalculator -from libmeshdef.meshdef_parfait import MeshDeformation -from libmeshdef.meshdef_openmdao import MeshDeformationOpenMdao -from sfe.sfe_parfait import SFE -from sfe.sfe_openmdao import SfeSolverOpenMdao, SfeForcesOpenMdao +from simple_problem.wrapper import SimpleProblem +from parfait.preprocessor import create_preprocessor +from sfe.om_component import SfeSolverOpenMdao, SfeForcesOpenMdao +from sfe.solver import SfeSolver from mphys.integrated_forces import IntegratedSurfaceForces -from mphys.geo_disp import GeoDisp +from pancake4py.iris import Iris -class Fun3dMesh(om.ExplicitComponent): +class Fun3dMesh(om.IndepVarComp): def initialize(self): self.options.declare('meshdef_solver') self.options.declare('boundary_tag_list') @@ -21,33 +20,9 @@ def initialize(self): def setup(self): boundary_tag_list = self.options['boundary_tag_list'] meshdef = self.options['meshdef_solver'] - x,y,z = meshdef.get_boundary_node_coordinates(boundary_tag_list, owned_only = True) - self.x_a0 = self._flatten_vectors(x,y,z) - coord_size = self.x_a0.size - self.add_output('x_aero0', shape=coord_size, desc='initial aerodynamic surface node coordinates') - - def _flatten_vectors(self, x, y, z): - matrix = np.concatenate((x.reshape((-1,1)),y.reshape((-1,1)),z.reshape((-1,1))),axis=1) - return matrix.flatten(order="C") - - def mphys_add_coordinate_input(self): - self.add_input('x_aero0_points',shape_by_conn=True, desc='aerodynamic surface with geom changes') - - return 'x_aero0_points', self.x_a0 - - def compute(self,inputs,outputs): - if 'x_aero0_points' in inputs: - outputs['x_aero0'] = inputs['x_aero0_points'] - else: - outputs['x_aero0'] = self.x_a0 - - def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): - if mode == 'fwd': - if 'x_aero0_points' in d_inputs: - d_outputs['x_aero0'] += d_inputs['x_aero0_points'] - elif mode == 'rev': - if 'x_aero0_points' in d_inputs: - d_inputs['x_aero0_points'] += d_outputs['x_aero0'] + x_aero0 = meshdef.get_boundary_node_coordinates(boundary_tag_list, owned_only = True, concatenate=True) + self.add_output('x_aero0', val=x_aero0.flatten(), desc='initial aerodynamic surface node coordinates', + tags=['mphys_coordinates']) class Fun3dFsiSolverGroup(om.Group): def initialize(self): @@ -57,50 +32,43 @@ def initialize(self): self.options.declare('number_of_surface_nodes') def setup(self): - geodisp_comp = GeoDisp(number_of_surface_nodes=self.options['number_of_surface_nodes']) meshdef_comp = self.options['meshdef_comp'] flow_comp = self.options['flow_comp'] forces_comp = self.options['forces_comp'] - self.add_subsystem('geo_disp', geodisp_comp, - promotes_inputs=['u_aero', 'x_aero0'], - promotes_outputs=['x_aero']) - self.add_subsystem('meshdef', meshdef_comp) - self.add_subsystem('flow', flow_comp) + self.add_subsystem('meshdef', meshdef_comp, promotes_inputs=['x_aero']) + self.add_subsystem('flow', flow_comp, promotes_inputs=['mach','reynolds','aoa','yaw']) self.add_subsystem('forces', forces_comp, + promotes_inputs=['mach','reynolds','q_inf'], promotes_outputs=['f_aero']) def configure(self): - self.connect('x_aero','meshdef.x_aero') self.connect('meshdef.u_g',['flow.u_g','forces.u_g']) self.connect('flow.q','forces.q') -class Fun3dSfeBuilder(SolverBuilder): - def __init__(self, meshfile, boundary_tag_list): - self.meshfile = meshfile +class Fun3dSfeBuilder(Builder): + def __init__(self, project_rootname, boundary_tag_list): + self.project_rootname = project_rootname self.boundary_tag_list = boundary_tag_list - self.is_initialized = False - def init_solver(self, comm): - if not self.is_initialized: - self.dist_calc = DistanceCalculator.from_meshfile(self.meshfile,comm) - distance = self.dist_calc.compute(self.boundary_tag_list) + def initialize(self, comm): + iris = Iris(comm) + prob = SimpleProblem(iris) + prob.project_rootname = self.project_rootname + print('problem:', prob.project_rootname) + self.mesh = create_preprocessor(prob.problem, iris) + self.sfe = SfeSolver(prob.problem, self.mesh, iris) + self.meshdef = MeshDeformer(prob.problem, self.mesh, iris) + self.number_of_nodes = self.meshdef.get_boundary_node_global_ids(self.boundary_tag_list, owned_only=True).size - self.iris = Iris(comm) - self.sfe = SFE(self.dist_calc.mesh,self.iris) - self.meshdef = MeshDeformation(self.dist_calc.mesh,self.iris) - self.nnodes = self.meshdef.get_boundary_node_global_ids(self.boundary_tag_list, owned_only=True).size + #self.sfe.set_node_wall_distance(distance, owned_only = False) + #self.meshdef.set_node_wall_distance(distance, owned_only = False) - self.sfe.set_node_wall_distance(distance, owned_only = False) - self.meshdef.set_node_wall_distance(distance, owned_only = False) - - self.is_initialized = True - - def get_mesh_element(self): + def get_mesh_coordinate_subsystem(self): return Fun3dMesh(meshdef_solver = self.meshdef, boundary_tag_list = self.boundary_tag_list) - def get_element(self, **kwargs): + def get_coupling_group_subsystem(self): meshdef_om = MeshDeformationOpenMdao(meshdef_solver = self.meshdef, boundary_tag_list = self.boundary_tag_list) sfe_om = SfeSolverOpenMdao(sfe_solver = self.sfe) @@ -109,25 +77,16 @@ def get_element(self, **kwargs): return Fun3dFsiSolverGroup(meshdef_comp = meshdef_om, flow_comp = sfe_om, forces_comp = forces_om, - number_of_surface_nodes = self.nnodes) - def get_scenario_element(self): - return IntegratedSurfaceForces(number_of_surface_nodes=self.nnodes) - - def get_nnodes(self): - return self.nnodes - - def get_scenario_connections(self): - mydict = { - 'f_aero': 'f_aero', - 'x_aero': 'x_aero' - } - return mydict - - def get_mesh_connections(self): - mydict = { - 'solver':{ - 'x_aero0' : 'x_aero0', - }, - 'funcs':{}, - } - return mydict + number_of_surface_nodes = self.number_of_nodes) + def get_scenario_subsystems(self): + return None, IntegratedSurfaceForces() + + def get_number_of_nodes(self): + return self.number_of_nodes + + def get_ndof(self): + return 3 + +prob = {} + +print('KS =',prob['mp_group.s0.struct_funcs.funcs.func_struct']) From af822674f2782f9baa795489277eab1e3f67ab01 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 13:48:19 -0500 Subject: [PATCH 22/82] Move vlm's read mesh function --- mphys/mphys_vlm.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index d838d153..8994512f 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -58,26 +58,8 @@ def __init__(self, meshfile, compute_tractions=False): self.meshfile = meshfile self.compute_tractions = compute_tractions - def read_mesh(self, meshfile): - with open(meshfile,'r') as f: - contents = f.read().split() - - a = [i for i in contents if 'NODES' in i][0] - num_nodes = int(a[a.find("=")+1:a.find(",")]) - a = [i for i in contents if 'ELEMENTS' in i][0] - num_elements = int(a[a.find("=")+1:a.find(",")]) - - a = np.array(contents[16:16+num_nodes*3],'float') - x = a[0:num_nodes*3:3] - y = a[1:num_nodes*3:3] - z = a[2:num_nodes*3:3] - a = np.array(contents[16+num_nodes*3:None],'int') - - self.connectivity = np.reshape(a,[num_elements,4]) - self.x_aero0 = np.c_[x,y,z].flatten(order='C') - def initialize(self, comm): - self.read_mesh(self.meshfile) + self._read_mesh(self.meshfile) self.solver = DummyVlmSolver(self.x_aero0, self.connectivity) def get_mesh_coordinate_subsystem(self): @@ -98,6 +80,24 @@ def get_number_of_nodes(self): def get_ndof(self): return self.x_aero0.size // 3 + def _read_mesh(self, meshfile): + with open(meshfile,'r') as f: + contents = f.read().split() + + a = [i for i in contents if 'NODES' in i][0] + num_nodes = int(a[a.find("=")+1:a.find(",")]) + a = [i for i in contents if 'ELEMENTS' in i][0] + num_elements = int(a[a.find("=")+1:a.find(",")]) + + a = np.array(contents[16:16+num_nodes*3],'float') + x = a[0:num_nodes*3:3] + y = a[1:num_nodes*3:3] + z = a[2:num_nodes*3:3] + a = np.array(contents[16+num_nodes*3:None],'int') + + self.connectivity = np.reshape(a,[num_elements,4]) + self.x_aero0 = np.c_[x,y,z].flatten(order='C') + class VlmBuilderAeroOnly(VlmBuilder): def get_mesh_coordinate_subsystem(self): return VlmMeshAeroOnly(x_aero0=self.x_aero0) From 5e7d675778fcd4d535890fccd731e7e213fb44ee Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 13:52:54 -0500 Subject: [PATCH 23/82] Add other versions of struct_only. Organize some examples --- .../crm9/fun3d_meld_tacs}/aero_check.py | 0 .../crm9/fun3d_meld_tacs}/mphys_analysis.py | 0 .../crm9/fun3d_meld_tacs}/sfe.cfg | 0 .../structural_patches_component.py | 0 .../vlm_meld_tacs}/as_opt.py | 0 .../vlm_meld_tacs}/component_list.txt | 0 .../vlm_meld_tacs}/mphys_as_vlm.py | 0 .../vlm_meld_tacs}/struct_dv_components.py | 0 .../vlm_meld_tacs}/tacs_setup.py | 0 .../vlm_meld_tacs}/wing_VLM.dat | 0 .../vlm_meld_tacs}/wingbox_Y_Z_flip.bdf | 0 examples/{tacs_only => struct_only}/README.md | 0 examples/{tacs_only => struct_only}/debug.bdf | 0 .../mphys_crm_example.py | 0 .../pyopt_mass_min.py | 0 .../tacs_analysis.py | 0 .../{tacs_only => struct_only}/tacs_setup.py | 0 .../README.md | 0 .../modal_comps.py | 0 .../sphere_body1_mode1.dat | 0 .../sphere_body1_mode2.dat | 0 .../timestep_groups/modal_comps.py | 0 .../timestep_groups/modal_integrate.py | 0 .../timestep_groups/sphere_body1_mode1.dat | 0 .../timestep_groups/sphere_body1_mode2.dat | 0 .../timestep_loop/modal_comps.py | 0 .../timestep_loop/modal_integrate.py | 0 .../timestep_loop/modal_integrator.py | 0 mphys/scenario_structural.py | 27 ++++++++++++++++--- 29 files changed, 24 insertions(+), 3 deletions(-) rename examples/{crm9/fun3d_tacs_meld => aerostruct/crm9/fun3d_meld_tacs}/aero_check.py (100%) rename examples/{crm9/fun3d_tacs_meld => aerostruct/crm9/fun3d_meld_tacs}/mphys_analysis.py (100%) rename examples/{crm9/fun3d_tacs_meld => aerostruct/crm9/fun3d_meld_tacs}/sfe.cfg (100%) rename examples/{crm9/fun3d_tacs_meld => aerostruct/crm9/fun3d_meld_tacs}/structural_patches_component.py (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/as_opt.py (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/component_list.txt (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/mphys_as_vlm.py (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/struct_dv_components.py (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/tacs_setup.py (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/wing_VLM.dat (100%) rename examples/{aerostruct_with_vlm => aerostruct/mach_tutorial_wing/vlm_meld_tacs}/wingbox_Y_Z_flip.bdf (100%) rename examples/{tacs_only => struct_only}/README.md (100%) rename examples/{tacs_only => struct_only}/debug.bdf (100%) rename examples/{tacs_only => struct_only}/mphys_crm_example.py (100%) rename examples/{tacs_only => struct_only}/pyopt_mass_min.py (100%) rename examples/{tacs_only => struct_only}/tacs_analysis.py (100%) rename examples/{tacs_only => struct_only}/tacs_setup.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/README.md (100%) rename examples/{modal_testing => time_domain_modal_testing}/modal_comps.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/sphere_body1_mode1.dat (100%) rename examples/{modal_testing => time_domain_modal_testing}/sphere_body1_mode2.dat (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_groups/modal_comps.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_groups/modal_integrate.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_groups/sphere_body1_mode1.dat (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_groups/sphere_body1_mode2.dat (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_loop/modal_comps.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_loop/modal_integrate.py (100%) rename examples/{modal_testing => time_domain_modal_testing}/timestep_loop/modal_integrator.py (100%) diff --git a/examples/crm9/fun3d_tacs_meld/aero_check.py b/examples/aerostruct/crm9/fun3d_meld_tacs/aero_check.py similarity index 100% rename from examples/crm9/fun3d_tacs_meld/aero_check.py rename to examples/aerostruct/crm9/fun3d_meld_tacs/aero_check.py diff --git a/examples/crm9/fun3d_tacs_meld/mphys_analysis.py b/examples/aerostruct/crm9/fun3d_meld_tacs/mphys_analysis.py similarity index 100% rename from examples/crm9/fun3d_tacs_meld/mphys_analysis.py rename to examples/aerostruct/crm9/fun3d_meld_tacs/mphys_analysis.py diff --git a/examples/crm9/fun3d_tacs_meld/sfe.cfg b/examples/aerostruct/crm9/fun3d_meld_tacs/sfe.cfg similarity index 100% rename from examples/crm9/fun3d_tacs_meld/sfe.cfg rename to examples/aerostruct/crm9/fun3d_meld_tacs/sfe.cfg diff --git a/examples/crm9/fun3d_tacs_meld/structural_patches_component.py b/examples/aerostruct/crm9/fun3d_meld_tacs/structural_patches_component.py similarity index 100% rename from examples/crm9/fun3d_tacs_meld/structural_patches_component.py rename to examples/aerostruct/crm9/fun3d_meld_tacs/structural_patches_component.py diff --git a/examples/aerostruct_with_vlm/as_opt.py b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/as_opt.py similarity index 100% rename from examples/aerostruct_with_vlm/as_opt.py rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/as_opt.py diff --git a/examples/aerostruct_with_vlm/component_list.txt b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/component_list.txt similarity index 100% rename from examples/aerostruct_with_vlm/component_list.txt rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/component_list.txt diff --git a/examples/aerostruct_with_vlm/mphys_as_vlm.py b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py similarity index 100% rename from examples/aerostruct_with_vlm/mphys_as_vlm.py rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py diff --git a/examples/aerostruct_with_vlm/struct_dv_components.py b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py similarity index 100% rename from examples/aerostruct_with_vlm/struct_dv_components.py rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py diff --git a/examples/aerostruct_with_vlm/tacs_setup.py b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py similarity index 100% rename from examples/aerostruct_with_vlm/tacs_setup.py rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py diff --git a/examples/aerostruct_with_vlm/wing_VLM.dat b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat similarity index 100% rename from examples/aerostruct_with_vlm/wing_VLM.dat rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat diff --git a/examples/aerostruct_with_vlm/wingbox_Y_Z_flip.bdf b/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf similarity index 100% rename from examples/aerostruct_with_vlm/wingbox_Y_Z_flip.bdf rename to examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf diff --git a/examples/tacs_only/README.md b/examples/struct_only/README.md similarity index 100% rename from examples/tacs_only/README.md rename to examples/struct_only/README.md diff --git a/examples/tacs_only/debug.bdf b/examples/struct_only/debug.bdf similarity index 100% rename from examples/tacs_only/debug.bdf rename to examples/struct_only/debug.bdf diff --git a/examples/tacs_only/mphys_crm_example.py b/examples/struct_only/mphys_crm_example.py similarity index 100% rename from examples/tacs_only/mphys_crm_example.py rename to examples/struct_only/mphys_crm_example.py diff --git a/examples/tacs_only/pyopt_mass_min.py b/examples/struct_only/pyopt_mass_min.py similarity index 100% rename from examples/tacs_only/pyopt_mass_min.py rename to examples/struct_only/pyopt_mass_min.py diff --git a/examples/tacs_only/tacs_analysis.py b/examples/struct_only/tacs_analysis.py similarity index 100% rename from examples/tacs_only/tacs_analysis.py rename to examples/struct_only/tacs_analysis.py diff --git a/examples/tacs_only/tacs_setup.py b/examples/struct_only/tacs_setup.py similarity index 100% rename from examples/tacs_only/tacs_setup.py rename to examples/struct_only/tacs_setup.py diff --git a/examples/modal_testing/README.md b/examples/time_domain_modal_testing/README.md similarity index 100% rename from examples/modal_testing/README.md rename to examples/time_domain_modal_testing/README.md diff --git a/examples/modal_testing/modal_comps.py b/examples/time_domain_modal_testing/modal_comps.py similarity index 100% rename from examples/modal_testing/modal_comps.py rename to examples/time_domain_modal_testing/modal_comps.py diff --git a/examples/modal_testing/sphere_body1_mode1.dat b/examples/time_domain_modal_testing/sphere_body1_mode1.dat similarity index 100% rename from examples/modal_testing/sphere_body1_mode1.dat rename to examples/time_domain_modal_testing/sphere_body1_mode1.dat diff --git a/examples/modal_testing/sphere_body1_mode2.dat b/examples/time_domain_modal_testing/sphere_body1_mode2.dat similarity index 100% rename from examples/modal_testing/sphere_body1_mode2.dat rename to examples/time_domain_modal_testing/sphere_body1_mode2.dat diff --git a/examples/modal_testing/timestep_groups/modal_comps.py b/examples/time_domain_modal_testing/timestep_groups/modal_comps.py similarity index 100% rename from examples/modal_testing/timestep_groups/modal_comps.py rename to examples/time_domain_modal_testing/timestep_groups/modal_comps.py diff --git a/examples/modal_testing/timestep_groups/modal_integrate.py b/examples/time_domain_modal_testing/timestep_groups/modal_integrate.py similarity index 100% rename from examples/modal_testing/timestep_groups/modal_integrate.py rename to examples/time_domain_modal_testing/timestep_groups/modal_integrate.py diff --git a/examples/modal_testing/timestep_groups/sphere_body1_mode1.dat b/examples/time_domain_modal_testing/timestep_groups/sphere_body1_mode1.dat similarity index 100% rename from examples/modal_testing/timestep_groups/sphere_body1_mode1.dat rename to examples/time_domain_modal_testing/timestep_groups/sphere_body1_mode1.dat diff --git a/examples/modal_testing/timestep_groups/sphere_body1_mode2.dat b/examples/time_domain_modal_testing/timestep_groups/sphere_body1_mode2.dat similarity index 100% rename from examples/modal_testing/timestep_groups/sphere_body1_mode2.dat rename to examples/time_domain_modal_testing/timestep_groups/sphere_body1_mode2.dat diff --git a/examples/modal_testing/timestep_loop/modal_comps.py b/examples/time_domain_modal_testing/timestep_loop/modal_comps.py similarity index 100% rename from examples/modal_testing/timestep_loop/modal_comps.py rename to examples/time_domain_modal_testing/timestep_loop/modal_comps.py diff --git a/examples/modal_testing/timestep_loop/modal_integrate.py b/examples/time_domain_modal_testing/timestep_loop/modal_integrate.py similarity index 100% rename from examples/modal_testing/timestep_loop/modal_integrate.py rename to examples/time_domain_modal_testing/timestep_loop/modal_integrate.py diff --git a/examples/modal_testing/timestep_loop/modal_integrator.py b/examples/time_domain_modal_testing/timestep_loop/modal_integrator.py similarity index 100% rename from examples/modal_testing/timestep_loop/modal_integrator.py rename to examples/time_domain_modal_testing/timestep_loop/modal_integrator.py diff --git a/mphys/scenario_structural.py b/mphys/scenario_structural.py index 60f31f47..20235c32 100644 --- a/mphys/scenario_structural.py +++ b/mphys/scenario_structural.py @@ -1,15 +1,32 @@ from .scenario import Scenario +class ScenarioStructuralV1(Scenario): + + def initialize(self): + self.options.declare('struct_builder', recordable=False) + + def setup(self): + struct_builder = self.options['struct_builder'] + + self.mphys_add_pre_coupling_subsystem('struct', struct_builder) + self.mphys_add_subsystem('coupling',struct_builder.get_coupling_group_subsystem()) + self.mphys_add_post_coupling_subsystem('struct', struct_builder) + + + + + +# a class that works for both Multipoint and MultipointParallel class ScenarioStructural(Scenario): def initialize(self): self.options.declare('struct_builder', recordable=False) - self.options.declare('in_MultipointParallelGroup', default=False) + self.options.declare('in_MultipointParallel', default=False) def setup(self): struct_builder = self.options['struct_builder'] - if self.options['in_MultipointParallelGroup']: + if self.options['in_MultipointParallel']: struct_builder.initialize(self.comm) self.mphys_add_subsystem('mesh',struct_builder.get_mesh_coordinate_subsystem()) @@ -18,7 +35,11 @@ def setup(self): self.mphys_add_post_coupling_subsystem('struct', struct_builder) -# UNTESTED: to show in_MultipointParallelGroup option isn't necessary and add geometry + + + + +# UNTESTED: to show in_MultipointParallel option isn't necessary and add geometry class ScenarioStructuralParallelGeometry(Scenario): def initialize(self): From 37915a39494f12d7406f832c0a7c694e2a9103a6 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 14:42:53 -0500 Subject: [PATCH 24/82] Clean up vlm aero only example --- examples/aero_only/{ => agard}/run_fun3d.py | 0 .../{ => mach_tutorial_wing}/run_vlm.py | 4 ++-- .../run_vlm_2scenarios.py | 21 ++++++++++--------- .../run_vlm_2scenarios_parallel.py | 20 ++++++++++-------- 4 files changed, 24 insertions(+), 21 deletions(-) rename examples/aero_only/{ => agard}/run_fun3d.py (100%) rename examples/aero_only/{ => mach_tutorial_wing}/run_vlm.py (94%) rename examples/aero_only/{ => mach_tutorial_wing}/run_vlm_2scenarios.py (78%) rename examples/aero_only/{ => mach_tutorial_wing}/run_vlm_2scenarios_parallel.py (83%) diff --git a/examples/aero_only/run_fun3d.py b/examples/aero_only/agard/run_fun3d.py similarity index 100% rename from examples/aero_only/run_fun3d.py rename to examples/aero_only/agard/run_fun3d.py diff --git a/examples/aero_only/run_vlm.py b/examples/aero_only/mach_tutorial_wing/run_vlm.py similarity index 94% rename from examples/aero_only/run_vlm.py rename to examples/aero_only/mach_tutorial_wing/run_vlm.py index a6ebdacc..acb7ac04 100644 --- a/examples/aero_only/run_vlm.py +++ b/examples/aero_only/mach_tutorial_wing/run_vlm.py @@ -12,7 +12,7 @@ def setup(self): mesh_file = 'wing_VLM.dat' mach = 0.85, - aoa = 2.0 + aoa = 0.0 q_inf = 3000. vel = 178. mu = 3.5E-5 @@ -38,7 +38,7 @@ def setup(self): prob.model = Top() prob.setup() -om.n2(prob, show_browser=False, outfile='mphys_vlm.html') +om.n2(prob, show_browser=False, outfile='vlm_aero.html') prob.run_model() if MPI.COMM_WORLD.rank == 0: diff --git a/examples/aero_only/run_vlm_2scenarios.py b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios.py similarity index 78% rename from examples/aero_only/run_vlm_2scenarios.py rename to examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios.py index 1e7c732e..29af0ac1 100644 --- a/examples/aero_only/run_vlm_2scenarios.py +++ b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios.py @@ -10,9 +10,10 @@ def setup(self): # VLM options mesh_file = 'wing_VLM.dat' - mach = 0.85, + mach0 = 0.85, + mach1 = 0.80, aoa0 = 0.0 - aoa1 = 2.0 + aoa1 = 1.0 q_inf = 3000. vel = 178. mu = 3.5E-5 @@ -20,7 +21,8 @@ def setup(self): dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) dvs.add_output('aoa0', val=aoa0, units='deg') dvs.add_output('aoa1', val=aoa1, units='deg') - dvs.add_output('mach', mach) + dvs.add_output('mach0', mach0) + dvs.add_output('mach1', mach1) dvs.add_output('q_inf', q_inf) dvs.add_output('vel', vel) dvs.add_output('mu', mu) @@ -30,15 +32,14 @@ def setup(self): self.add_subsystem('mesh',aero_builder.get_mesh_coordinate_subsystem()) self.mphys_add_scenario('cruise',ScenarioAero(aero_builder=aero_builder)) + self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder)) - for dv in ['mach', 'q_inf', 'vel', 'mu']: + for dv in ['q_inf', 'vel', 'mu']: self.connect(dv, f'cruise.{dv}') - self.connect('aoa0','cruise.aoa') - - self.mphys_add_scenario('cruise_higher_aoa',ScenarioAero(aero_builder=aero_builder)) - for dv in ['mach', 'q_inf', 'vel', 'mu']: self.connect(dv, f'cruise_higher_aoa.{dv}') - self.connect('aoa1','cruise_higher_aoa.aoa') + for dv in ['aoa', 'mach']: + self.connect(f'{dv}0', f'cruise.{dv}') + self.connect(f'{dv}1', f'cruise_higher_aoa.{dv}') self.connect('mesh.x_aero0',['cruise.x_aero','cruise_higher_aoa.x_aero']) @@ -46,7 +47,7 @@ def setup(self): prob.model = Top() prob.setup() -om.n2(prob, show_browser=False, outfile='mphys_vlm_2scenarios.html') +om.n2(prob, show_browser=False, outfile='vlm_aero_2cruises.html') prob.run_model() if MPI.COMM_WORLD.rank == 0: diff --git a/examples/aero_only/run_vlm_2scenarios_parallel.py b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py similarity index 83% rename from examples/aero_only/run_vlm_2scenarios_parallel.py rename to examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py index 9a5c8740..925f1ceb 100644 --- a/examples/aero_only/run_vlm_2scenarios_parallel.py +++ b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py @@ -5,7 +5,6 @@ from mphys.mphys_vlm import VlmBuilderAeroOnly from mphys.scenario_aero import ScenarioAero - class ParallelCruises(MultipointParallel): def setup(self): # VLM options @@ -20,9 +19,10 @@ def setup(self): class Top(om.Group): def setup(self): - mach = 0.85, + mach0 = 0.85, + mach1 = 0.80, aoa0 = 0.0 - aoa1 = 2.0 + aoa1 = 1.0 q_inf = 3000. vel = 178. mu = 3.5E-5 @@ -30,23 +30,25 @@ def setup(self): dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) dvs.add_output('aoa0', val=aoa0, units='deg') dvs.add_output('aoa1', val=aoa1, units='deg') - dvs.add_output('mach', mach) + dvs.add_output('mach0', mach0) + dvs.add_output('mach1', mach1) dvs.add_output('q_inf', q_inf) dvs.add_output('vel', vel) dvs.add_output('mu', mu) self.add_subsystem('mp',ParallelCruises()) - for dv in ['mach', 'q_inf', 'vel', 'mu']: + for dv in ['q_inf', 'vel', 'mu']: self.connect(dv, f'mp.cruise.{dv}') - self.connect('aoa0','mp.cruise.aoa') - for dv in ['mach', 'q_inf', 'vel', 'mu']: self.connect(dv, f'mp.cruise_higher_aoa.{dv}') - self.connect('aoa1','mp.cruise_higher_aoa.aoa') + for dv in ['mach', 'aoa']: + self.connect(f'{dv}0',f'mp.cruise.{dv}') + self.connect(f'{dv}1',f'mp.cruise_higher_aoa.{dv}') + prob = om.Problem() prob.model = Top() prob.setup() -om.n2(prob, show_browser=False, outfile='mphys_vlm_2scenarios.html') +om.n2(prob, show_browser=False, outfile='vlm_aero_2cruises_parallel.html') prob.run_model() #if MPI.COMM_WORLD.rank == 0: From db5e95581b4a06977b62fc17aefa777c67a3bfc9 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 17:32:43 -0500 Subject: [PATCH 25/82] Steps towards distributed TACS. Something still wrong internal to TACS? --- examples/struct_only/{ => crm9}/README.md | 0 examples/struct_only/{ => crm9}/pyopt_mass_min.py | 0 .../{mphys_crm_example.py => crm9/run_tacs_mphys.py} | 8 ++++---- examples/struct_only/{ => crm9}/tacs_analysis.py | 0 examples/struct_only/{ => crm9}/tacs_setup.py | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename examples/struct_only/{ => crm9}/README.md (100%) rename examples/struct_only/{ => crm9}/pyopt_mass_min.py (100%) rename examples/struct_only/{mphys_crm_example.py => crm9/run_tacs_mphys.py} (88%) rename examples/struct_only/{ => crm9}/tacs_analysis.py (100%) rename examples/struct_only/{ => crm9}/tacs_setup.py (100%) diff --git a/examples/struct_only/README.md b/examples/struct_only/crm9/README.md similarity index 100% rename from examples/struct_only/README.md rename to examples/struct_only/crm9/README.md diff --git a/examples/struct_only/pyopt_mass_min.py b/examples/struct_only/crm9/pyopt_mass_min.py similarity index 100% rename from examples/struct_only/pyopt_mass_min.py rename to examples/struct_only/crm9/pyopt_mass_min.py diff --git a/examples/struct_only/mphys_crm_example.py b/examples/struct_only/crm9/run_tacs_mphys.py similarity index 88% rename from examples/struct_only/mphys_crm_example.py rename to examples/struct_only/crm9/run_tacs_mphys.py index a462308b..62df7cde 100644 --- a/examples/struct_only/mphys_crm_example.py +++ b/examples/struct_only/crm9/run_tacs_mphys.py @@ -42,14 +42,14 @@ def setup(self): model = prob.model model.add_design_var('dv_struct',lower=0.001,upper=0.075,scaler=1000.0) -model.add_objective('analysis.mass',scaler=1.0/1000.0) -model.add_constraint('analysis.func_struct',lower = 0.0, upper = 2.0/3.0,scaler=1.0) +model.add_objective('analysis.mass', index=0, scaler=1.0/1000.0) +model.add_constraint('analysis.func_struct', indices=[0], lower = 0.0, upper = 2.0/3.0,scaler=1.0) -prob.driver = om.ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=150) +prob.driver = om.ScipyOptimizeDriver(debug_print=['objs','nl_cons'],maxiter=1) prob.driver.options['optimizer'] = 'SLSQP' prob.setup() -om.n2(prob, show_browser=False, outfile='mphys_struct.html') +om.n2(prob, show_browser=False, outfile='tacs_struct.html') prob.run_model() prob.run_driver() diff --git a/examples/struct_only/tacs_analysis.py b/examples/struct_only/crm9/tacs_analysis.py similarity index 100% rename from examples/struct_only/tacs_analysis.py rename to examples/struct_only/crm9/tacs_analysis.py diff --git a/examples/struct_only/tacs_setup.py b/examples/struct_only/crm9/tacs_setup.py similarity index 100% rename from examples/struct_only/tacs_setup.py rename to examples/struct_only/crm9/tacs_setup.py From 8c29da4dc67236c404fe252725fa9a7d8e38a5ba Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 17:33:50 -0500 Subject: [PATCH 26/82] Renmae aerostructural example dir --- .../crm9/fun3d_meld_tacs/aero_check.py | 0 .../crm9/fun3d_meld_tacs/mphys_analysis.py | 0 .../{aerostruct => aerostructural}/crm9/fun3d_meld_tacs/sfe.cfg | 0 .../crm9/fun3d_meld_tacs/structural_patches_component.py | 0 .../mach_tutorial_wing/vlm_meld_tacs/as_opt.py | 0 .../mach_tutorial_wing/vlm_meld_tacs/component_list.txt | 0 .../mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py | 0 .../mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py | 0 .../mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py | 0 .../mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat | 0 .../mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf | 0 11 files changed, 0 insertions(+), 0 deletions(-) rename examples/{aerostruct => aerostructural}/crm9/fun3d_meld_tacs/aero_check.py (100%) rename examples/{aerostruct => aerostructural}/crm9/fun3d_meld_tacs/mphys_analysis.py (100%) rename examples/{aerostruct => aerostructural}/crm9/fun3d_meld_tacs/sfe.cfg (100%) rename examples/{aerostruct => aerostructural}/crm9/fun3d_meld_tacs/structural_patches_component.py (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/as_opt.py (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/component_list.txt (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat (100%) rename examples/{aerostruct => aerostructural}/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf (100%) diff --git a/examples/aerostruct/crm9/fun3d_meld_tacs/aero_check.py b/examples/aerostructural/crm9/fun3d_meld_tacs/aero_check.py similarity index 100% rename from examples/aerostruct/crm9/fun3d_meld_tacs/aero_check.py rename to examples/aerostructural/crm9/fun3d_meld_tacs/aero_check.py diff --git a/examples/aerostruct/crm9/fun3d_meld_tacs/mphys_analysis.py b/examples/aerostructural/crm9/fun3d_meld_tacs/mphys_analysis.py similarity index 100% rename from examples/aerostruct/crm9/fun3d_meld_tacs/mphys_analysis.py rename to examples/aerostructural/crm9/fun3d_meld_tacs/mphys_analysis.py diff --git a/examples/aerostruct/crm9/fun3d_meld_tacs/sfe.cfg b/examples/aerostructural/crm9/fun3d_meld_tacs/sfe.cfg similarity index 100% rename from examples/aerostruct/crm9/fun3d_meld_tacs/sfe.cfg rename to examples/aerostructural/crm9/fun3d_meld_tacs/sfe.cfg diff --git a/examples/aerostruct/crm9/fun3d_meld_tacs/structural_patches_component.py b/examples/aerostructural/crm9/fun3d_meld_tacs/structural_patches_component.py similarity index 100% rename from examples/aerostruct/crm9/fun3d_meld_tacs/structural_patches_component.py rename to examples/aerostructural/crm9/fun3d_meld_tacs/structural_patches_component.py diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/as_opt.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/as_opt.py similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/as_opt.py rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/as_opt.py diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/component_list.txt b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/component_list.txt similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/component_list.txt rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/component_list.txt diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/struct_dv_components.py diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/tacs_setup.py diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/wing_VLM.dat diff --git a/examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf similarity index 100% rename from examples/aerostruct/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/wingbox_Y_Z_flip.bdf From 330ccfc85a3b05157208ba252b72abce4a7663e6 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Sun, 7 Mar 2021 17:34:20 -0500 Subject: [PATCH 27/82] Distributed TACS functions for shape by connection to work properly --- mphys/mphys_tacs.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 2eb95ed3..8ec1c330 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -447,6 +447,8 @@ def initialize(self): self.options.declare('struct_objects', recordable=False) self.options.declare('check_partials') + self.options['distributed'] = True + self.ans = None self.tacs_assembler = None @@ -579,6 +581,8 @@ def initialize(self): self.options.declare('struct_objects', recordable=False) self.options.declare('check_partials') + self.options['distributed'] = True + self.ans = None self.tacs_assembler = None @@ -638,7 +642,6 @@ def compute_jacvec_product(self,inputs, d_inputs, d_outputs, mode): if 'mass' in d_outputs: func = functions.StructuralMass(self.tacs_assembler) if 'dv_struct' in d_inputs: - size = d_inputs['dv_struct'].size dvsens = np.zeros(d_inputs['dv_struct'].size,dtype=TACS.dtype) self.tacs_assembler.evalDVSens(func, dvsens) @@ -648,7 +651,6 @@ def compute_jacvec_product(self,inputs, d_inputs, d_outputs, mode): xpt_sens = self.xpt_sens xpt_sens_array = xpt_sens.getArray() self.tacs_assembler.evalXptSens(func, xpt_sens) - d_inputs['x_struct0'] += np.array(xpt_sens_array,dtype=float) * d_outputs['mass'] From 7775a26e5af065abda9bf9fc722669972d36f62c Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:03:28 -0500 Subject: [PATCH 28/82] Rename crm directory --- examples/aerostructural/crm9/{fun3d_meld_tacs => }/aero_check.py | 0 .../aerostructural/crm9/{fun3d_meld_tacs => }/mphys_analysis.py | 0 examples/aerostructural/crm9/{fun3d_meld_tacs => }/sfe.cfg | 0 .../crm9/{fun3d_meld_tacs => }/structural_patches_component.py | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename examples/aerostructural/crm9/{fun3d_meld_tacs => }/aero_check.py (100%) rename examples/aerostructural/crm9/{fun3d_meld_tacs => }/mphys_analysis.py (100%) rename examples/aerostructural/crm9/{fun3d_meld_tacs => }/sfe.cfg (100%) rename examples/aerostructural/crm9/{fun3d_meld_tacs => }/structural_patches_component.py (100%) diff --git a/examples/aerostructural/crm9/fun3d_meld_tacs/aero_check.py b/examples/aerostructural/crm9/aero_check.py similarity index 100% rename from examples/aerostructural/crm9/fun3d_meld_tacs/aero_check.py rename to examples/aerostructural/crm9/aero_check.py diff --git a/examples/aerostructural/crm9/fun3d_meld_tacs/mphys_analysis.py b/examples/aerostructural/crm9/mphys_analysis.py similarity index 100% rename from examples/aerostructural/crm9/fun3d_meld_tacs/mphys_analysis.py rename to examples/aerostructural/crm9/mphys_analysis.py diff --git a/examples/aerostructural/crm9/fun3d_meld_tacs/sfe.cfg b/examples/aerostructural/crm9/sfe.cfg similarity index 100% rename from examples/aerostructural/crm9/fun3d_meld_tacs/sfe.cfg rename to examples/aerostructural/crm9/sfe.cfg diff --git a/examples/aerostructural/crm9/fun3d_meld_tacs/structural_patches_component.py b/examples/aerostructural/crm9/structural_patches_component.py similarity index 100% rename from examples/aerostructural/crm9/fun3d_meld_tacs/structural_patches_component.py rename to examples/aerostructural/crm9/structural_patches_component.py From ca3003e960f5feaaa436361b6c08f9d033b113f4 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:03:57 -0500 Subject: [PATCH 29/82] More general vlm mesh reader --- mphys/mphys_vlm.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index 8994512f..da24c9d5 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -89,11 +89,12 @@ def _read_mesh(self, meshfile): a = [i for i in contents if 'ELEMENTS' in i][0] num_elements = int(a[a.find("=")+1:a.find(",")]) - a = np.array(contents[16:16+num_nodes*3],'float') + start = len(contents)-num_nodes*3-num_elements*4 + a = np.array(contents[start:start+num_nodes*3],'float') x = a[0:num_nodes*3:3] y = a[1:num_nodes*3:3] z = a[2:num_nodes*3:3] - a = np.array(contents[16+num_nodes*3:None],'int') + a = np.array(contents[start+num_nodes*3:None],'int') self.connectivity = np.reshape(a,[num_elements,4]) self.x_aero0 = np.c_[x,y,z].flatten(order='C') From 661ad2d979adcbfdf4416a0e0bcb2cb1123bd930 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:04:16 -0500 Subject: [PATCH 30/82] Remove type hint --- mphys/mphys_meld.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mphys/mphys_meld.py b/mphys/mphys_meld.py index 4553fd9f..ea2306da 100644 --- a/mphys/mphys_meld.py +++ b/mphys/mphys_meld.py @@ -302,7 +302,7 @@ def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode): d_inputs['x_struct0'] -= np.array(prod,dtype=float) class MeldBuilder(Builder): - def __init__(self, aero_builder: Builder, struct_builder, + def __init__(self, aero_builder, struct_builder, isym=-1, n=200, beta = 0.5, check_partials=False): self.aero_builder = aero_builder self.struct_builder = struct_builder From f0b550c60bb5c50cf5c8dc782ec58dcbedabd96e Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:04:45 -0500 Subject: [PATCH 31/82] Remove print statement in fun3d builder --- mphys/mphys_fun3d.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/mphys/mphys_fun3d.py b/mphys/mphys_fun3d.py index ca79e29d..b2806738 100644 --- a/mphys/mphys_fun3d.py +++ b/mphys/mphys_fun3d.py @@ -55,7 +55,6 @@ def initialize(self, comm): iris = Iris(comm) prob = SimpleProblem(iris) prob.project_rootname = self.project_rootname - print('problem:', prob.project_rootname) self.mesh = create_preprocessor(prob.problem, iris) self.sfe = SfeSolver(prob.problem, self.mesh, iris) self.meshdef = MeshDeformer(prob.problem, self.mesh, iris) @@ -86,7 +85,3 @@ def get_number_of_nodes(self): def get_ndof(self): return 3 - -prob = {} - -print('KS =',prob['mp_group.s0.struct_funcs.funcs.func_struct']) From e954ec9dcde5a6ab58dfda445ff8e62212e1e188 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:05:15 -0500 Subject: [PATCH 32/82] Use fstring --- .../mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py index 091aba17..3d0c15f6 100644 --- a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py +++ b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py @@ -67,8 +67,8 @@ def setup(self): self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline, scenario, discipline) for dv in ['q_inf','vel','mu','mach', 'dv_struct']: - self.connect(dv, '%s.%s' % (scenario, dv)) - self.connect('aoa', '%s.aoa' % scenario, src_indices=[iscen]) + self.connect(dv, f'{scenario}.{dv}') + self.connect('aoa', f'{scenario}.aoa', src_indices=[iscen]) ################################################################################ # OpenMDAO setup From 5cf942c1339c50c64eb95666ddd1270a37765262 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 08:06:13 -0500 Subject: [PATCH 33/82] Update CRM aerostructural example --- .../aerostructural/crm9/mphys_analysis.py | 180 +++++++----------- .../crm9/structural_patches_component.py | 106 +++++------ examples/aerostructural/crm9/tacs_setup.py | 39 ++++ 3 files changed, 162 insertions(+), 163 deletions(-) create mode 100644 examples/aerostructural/crm9/tacs_setup.py diff --git a/examples/aerostructural/crm9/mphys_analysis.py b/examples/aerostructural/crm9/mphys_analysis.py index 600ba538..5af6127b 100644 --- a/examples/aerostructural/crm9/mphys_analysis.py +++ b/examples/aerostructural/crm9/mphys_analysis.py @@ -5,133 +5,93 @@ import openmdao.api as om -from mphys.multipoint import Multipoint - -# these imports will be from the respective codes' repos rather than mphys +from mphys import Multipoint +from mphys.scenario_aerostructural import ScenarioAeroStructural from mphys.mphys_fun3d import Fun3dSfeBuilder from mphys.mphys_tacs import TacsBuilder from mphys.mphys_meld import MeldBuilder +from mphys.mphys_vlm import VlmBuilder -from tacs import elements, constitutive, functions +import tacs_setup from structural_patches_component import LumpPatches # set these for convenience comm = MPI.COMM_WORLD rank = comm.rank -class Top(om.Group): +use_fun3d = True + +class Top(Multipoint): def setup(self): + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - ################################################################################ - # FUN3D options - ################################################################################ - meshfile = 'crm_invis_tet.b8.ugrid' - boundary_tag_list = [3] - aero_builder = Fun3dSfeBuilder(meshfile,boundary_tag_list) + aoa = 0.0 + mach = 0.2 + q_inf = 10.0 + vel = 217.6 + mu = 1.4E-5 + + if use_fun3d: + # FUN3D options + project_rootname = 'crm_invis_tet' + boundary_tag_list = [3] + aero_builder = Fun3dSfeBuilder(project_rootname, boundary_tag_list) + aero_builder.initialize(self.comm) + + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', val=mach) + dvs.add_output('reynolds', val=0.0) + dvs.add_output('q_inf', val=q_inf) + dvs.add_output('yaw', val=0.0, units='deg') + aero_dvs = ['aoa','mach','reynolds','q_inf','yaw'] + else: + mesh_file = 'CRM_VLM_mesh_extended.dat' + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + aero_dvs = ['aoa','mach','q_inf','vel','mu'] - ################################################################################ # TACS options - ################################################################################ - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness= 0.020 - min_thickness = 0.002 - max_thickness = 0.05 - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - tacs_options = { - 'add_elements': add_elements, + 'add_elements': tacs_setup.add_elements, 'mesh_file' : 'CRM_box_2nd.bdf', - 'get_funcs' : get_funcs + 'get_funcs' : tacs_setup.get_funcs, + 'f5_writer' : tacs_setup.f5_writer } + struct_builder = TacsBuilder(tacs_options) + struct_builder.initialize(self.comm) + ndv_struct = struct_builder.get_ndv() - tacs_builder = TacsBuilder(tacs_options) + dvs.add_output('thickness_lumped', val=0.01) + self.add_subsystem('lumper',LumpPatches(N=ndv_struct)) + self.connect('thickness_lumped','lumper.thickness_lumped') - ################################################################################ # Transfer scheme options - ################################################################################ + isym = 1 + ldxfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) + ldxfer_builder.initialize(self.comm) - xfer_options = { - 'isym': 1, - 'n': 200, - 'beta': 0.5, - } + self.add_subsystem('mesh_aero',aero_builder.get_mesh_coordinate_subsystem()) + self.add_subsystem('mesh_struct',struct_builder.get_mesh_coordinate_subsystem()) - xfer_builder = MeldBuilder(xfer_options, aero_builder, tacs_builder) + nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1e-8, atol=1e-8, aitken_max_factor=1.5, aitken_initial_factor=.1) + linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1e-8, atol=1e-8) + self.mphys_add_scenario('cruise',ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + ldxfer_builder=ldxfer_builder), + nonlinear_solver, linear_solver) - ################################################################################ - # MPHYS setup - ################################################################################ + for discipline in ['aero','struct']: + self.mphys_connect_scenario_coordinate_source('mesh_%s' % discipline, 'cruise', discipline) - # ivc to keep the top level DVs - dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) - lumper = self.add_subsystem('lumper',LumpPatches(N=240)) - - # create the multiphysics multipoint group. - mp = self.add_subsystem( - 'mp_group', - Multipoint( - aero_builder = aero_builder, - struct_builder = tacs_builder, - xfer_builder = xfer_builder - ) - ) - - # this is the method that needs to be called for every point in this mp_group - mp.mphys_add_scenario('s0') - - def configure(self): - self.dvs.add_output('aoa', val=0.0, units='deg') - self.dvs.add_output('mach', val=0.2) - self.dvs.add_output('reynolds', val=0.0) - #self.dvs.add_output('q_inf', val=20000.0) - self.dvs.add_output('q_inf', val=1000.0) - self.dvs.add_output('ref_area', val=1.0) - self.dvs.add_output('ref_length', val=1.0) - self.dvs.add_output('moment_center',val=np.zeros(3)) - self.dvs.add_output('yaw', val=0.0, units='deg') - - # connect to the aero for each scenario - self.connect('mach', ['mp_group.s0.solver_group.aero.flow.mach', - 'mp_group.s0.solver_group.aero.forces.mach']) - self.connect('aoa',['mp_group.s0.solver_group.aero.flow.aoa', - 'mp_group.s0.aero_funcs.aoa']) - self.connect('reynolds',['mp_group.s0.solver_group.aero.flow.reynolds_number', - 'mp_group.s0.solver_group.aero.forces.reynolds_number']) - self.connect('q_inf',['mp_group.s0.solver_group.aero.forces.q_inf', - 'mp_group.s0.aero_funcs.q_inf']) - self.connect('ref_area',['mp_group.s0.aero_funcs.ref_area']) - self.connect('ref_length',['mp_group.s0.aero_funcs.ref_length']) - self.connect('moment_center',['mp_group.s0.aero_funcs.moment_center']) - self.connect('yaw',['mp_group.s0.aero_funcs.yaw']) - - # add the structural thickness DVs - ndv_struct = self.mp_group.struct_builder.get_ndv() - self.dvs.add_output('thickness_lumped', val=0.01) - self.connect('thickness_lumped', 'lumper.thickness_lumped') - self.connect('lumper.thickness', ['mp_group.s0.solver_group.struct.dv_struct', 'mp_group.s0.struct_funcs.dv_struct',]) + self.connect('lumper.thickness', ['cruise.dv_struct']) + for dv in aero_dvs: + self.connect(dv, f'cruise.{dv}') ################################################################################ @@ -142,13 +102,13 @@ def configure(self): model = prob.model prob.setup(mode='rev') prob.final_setup() -#om.n2(prob, show_browser=False, outfile='mphys_as.html') +om.n2(prob, show_browser=False, outfile='crm_aerostruct.html') prob.run_model() if MPI.COMM_WORLD.rank == 0: print("Scenario 0") - print('Lift =',prob['mp_group.s0.aero_funcs.Lift']) - print('CD =',prob['mp_group.s0.aero_funcs.C_D']) - print('KS =',prob['mp_group.s0.struct_funcs.funcs.func_struct']) -output = prob.check_totals(of=['mp_group.s0.aero_funcs.Lift'], wrt=['thickness_lumped'],) -if MPI.COMM_WORLD.rank == 0: - print('check_totals output',output) + print('C_L =',prob['cruise.C_L']) + print('C_D =',prob['cruise.C_D']) + print('KS =',prob['cruise.func_struct']) +#output = prob.check_totals(of=['mp_group.s0.aero_funcs.Lift'], wrt=['thickness_lumped'],) +#if MPI.COMM_WORLD.rank == 0: +# print('check_totals output',output) diff --git a/examples/aerostructural/crm9/structural_patches_component.py b/examples/aerostructural/crm9/structural_patches_component.py index ebcb4ae6..d42b7d15 100644 --- a/examples/aerostructural/crm9/structural_patches_component.py +++ b/examples/aerostructural/crm9/structural_patches_component.py @@ -2,64 +2,64 @@ import openmdao.api as om class PatchList: - + def __init__(self,bdf): - + self.bdf = bdf - + f = open(self.bdf, "r") self.contents = f.read().split() f.close() - + def read_families(self): - + a = [i for i, s in enumerate(self.contents) if 'family' in s] families = [0 for x in range(len(a))] family_ID = np.zeros(len(a),'int') for i in range(0,len(a)): families[i] = self.contents[a[i]+1] family_ID[i] = int(self.contents[a[i]+4]) - + self.families = families self.family_ID = family_ID - + def create_DVs(self): - + upper_skin = np.zeros([len(self.families),10],'int') lower_skin = np.zeros([len(self.families),10],'int') le_spar = np.zeros([len(self.families),10],'int') te_spar = np.zeros([len(self.families),10],'int') rib = np.zeros([len(self.families),10],'int') - + for i in range(0,len(self.families)): _,comp,seg = self.families[i].split('/') - + if 'IMPD' in seg: seg = seg[0:seg.find(':')] - + if 'U_SKIN' in comp: _,c_id = comp.split('.') _,s_id = seg.split('.') upper_skin[int(c_id),int(s_id)] = self.family_ID[i] - + if 'L_SKIN' in comp: _,c_id = comp.split('.') _,s_id = seg.split('.') lower_skin[int(c_id),int(s_id)] = self.family_ID[i] - + if 'LE_SPAR' in comp: _,s_id = seg.split('.') - le_spar[int(s_id),0] = self.family_ID[i] - + le_spar[int(s_id),0] = self.family_ID[i] + if 'TE_SPAR' in comp: _,s_id = seg.split('.') - te_spar[int(s_id),0] = self.family_ID[i] + te_spar[int(s_id),0] = self.family_ID[i] if 'RIB' in comp: _,c_id = comp.split('.') _,s_id = seg.split('.') rib[int(c_id),int(s_id)] = self.family_ID[i] - + upper_skin = upper_skin[np.sum(upper_skin,axis=1)>0,:] self.upper_skin = upper_skin[:,np.sum(upper_skin,axis=0)>0] self.upper_skin = np.flip(self.upper_skin,axis=0) @@ -69,19 +69,19 @@ def create_DVs(self): self.lower_skin = lower_skin[:,np.sum(lower_skin,axis=0)>0] self.lower_skin = np.flip(self.lower_skin,axis=0) self.n_ls = np.size(self.lower_skin,axis=0) - + le_spar = le_spar[np.sum(le_spar,axis=1)>0,:] self.le_spar = le_spar[:,np.sum(le_spar,axis=0)>0] self.n_le = np.size(self.le_spar,axis=0) - + te_spar = te_spar[np.sum(te_spar,axis=1)>0,:] self.te_spar = te_spar[:,np.sum(te_spar,axis=0)>0] self.n_te = np.size(self.te_spar,axis=0) - + rib = rib[np.sum(rib,axis=1)>0,:] self.rib = rib[:,np.sum(rib,axis=0)>0] self.n_rib = np.size(self.rib,axis=0) - + self.n_dvs = (self.upper_skin>0).sum() + (self.lower_skin>0).sum() + \ (self.le_spar>0).sum() + (self.te_spar>0).sum() + \ (self.rib>0).sum() @@ -89,97 +89,97 @@ def create_DVs(self): class DesignPatches(om.ExplicitComponent): - + def initialize(self): - + self.options.declare('patch_list') - + def setup(self): - + patches = self.options['patch_list'] - + self.add_input('upper_skin_thickness',shape=patches.n_us) self.add_input('lower_skin_thickness',shape=patches.n_ls) self.add_input('le_spar_thickness',shape=patches.n_le) self.add_input('te_spar_thickness',shape=patches.n_te) self.add_input('rib_thickness',shape=patches.n_rib) - + self.add_output('dv_struct',shape=patches.n_dvs) - + self.declare_partials('dv_struct','upper_skin_thickness') self.declare_partials('dv_struct','lower_skin_thickness') self.declare_partials('dv_struct','le_spar_thickness') self.declare_partials('dv_struct','te_spar_thickness') self.declare_partials('dv_struct','rib_thickness') - + def compute(self,inputs,outputs): - + patches = self.options['patch_list'] - + self.T_us = np.zeros([patches.n_dvs,patches.n_us]) self.T_us[patches.upper_skin[np.nonzero(patches.upper_skin)[0],np.nonzero(patches.upper_skin)[1]]-1,np.nonzero(patches.upper_skin)[0]] = 1 - + self.T_ls = np.zeros([patches.n_dvs,patches.n_ls]) self.T_ls[patches.lower_skin[np.nonzero(patches.lower_skin)[0],np.nonzero(patches.lower_skin)[1]]-1,np.nonzero(patches.lower_skin)[0]] = 1 - + self.T_le = np.zeros([patches.n_dvs,patches.n_le]) self.T_le[patches.le_spar[np.nonzero(patches.le_spar)[0],np.nonzero(patches.le_spar)[1]]-1,np.nonzero(patches.le_spar)[0]] = 1 - + self.T_te = np.zeros([patches.n_dvs,patches.n_te]) self.T_te[patches.te_spar[np.nonzero(patches.te_spar)[0],np.nonzero(patches.te_spar)[1]]-1,np.nonzero(patches.te_spar)[0]] = 1 - + self.T_rib = np.zeros([patches.n_dvs,patches.n_rib]) self.T_rib[patches.rib[np.nonzero(patches.rib)[0],np.nonzero(patches.rib)[1]]-1,np.nonzero(patches.rib)[0]] = 1 - + outputs['dv_struct'] = self.T_us@inputs['upper_skin_thickness'] + \ self.T_ls@inputs['lower_skin_thickness'] + \ self.T_le@inputs['le_spar_thickness'] + \ self.T_te@inputs['te_spar_thickness'] + \ self.T_rib@inputs['rib_thickness'] - + def compute_partials(self,inputs,partials): - + partials['dv_struct','upper_skin_thickness'] = self.T_us partials['dv_struct','lower_skin_thickness'] = self.T_ls partials['dv_struct','le_spar_thickness'] = self.T_le partials['dv_struct','te_spar_thickness'] = self.T_te partials['dv_struct','rib_thickness'] = self.T_rib - + class PatchSmoothness(om.ExplicitComponent): - + def initialize(self): - + self.options.declare('N', types=int) self.options.declare('delta',default=0.001) - - def setup(self): - + + def setup(self): + self.add_input('thickness',shape=self.options['N']) self.add_output('diff',shape=2*(self.options['N']-1)) self.declare_partials('diff','thickness') - + def compute(self,inputs,outputs): - + j = 0 for i in range(0,self.options['N']-1): outputs['diff'][j] = inputs['thickness'][i] - inputs['thickness'][i+1] - self.options['delta'] outputs['diff'][j+1] = inputs['thickness'][i+1] - inputs['thickness'][i] - self.options['delta'] j += 2 - + def compute_partials(self,inputs,partials): - + partials['diff','thickness'] = np.zeros([2*(self.options['N']-1),len(inputs['thickness'])]) - + j = 0 for i in range(0,self.options['N']-1): partials['diff','thickness'][j,i] = 1 partials['diff','thickness'][j,i+1] = -1 partials['diff','thickness'][j+1,i+1] = 1 - partials['diff','thickness'][j+1,i] = -1 + partials['diff','thickness'][j+1,i] = -1 j += 2 - + class LumpPatches(om.ExplicitComponent): def initialize(self): @@ -190,13 +190,13 @@ def setup(self): self.add_input('thickness_lumped',shape=1) self.add_output('thickness',shape=self.options['N']) - self.declare_partials('thickness','thickness_lumped') + self.declare_partials('thickness','thickness_lumped') def compute(self,inputs,outputs): self.T = np.ones(self.options['N']) outputs['thickness'] = self.T*inputs['thickness_lumped'] - + def compute_partials(self,inputs,partials): partials['thickness','thickness_lumped'] = self.T diff --git a/examples/aerostructural/crm9/tacs_setup.py b/examples/aerostructural/crm9/tacs_setup.py new file mode 100644 index 00000000..54faae76 --- /dev/null +++ b/examples/aerostructural/crm9/tacs_setup.py @@ -0,0 +1,39 @@ + +from tacs import elements, constitutive, functions, TACS + +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness= 0.020 + min_thickness = 0.002 + max_thickness = 0.05 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: + element = elements.MITCShell(2,stiff,component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + +def get_funcs(tacs): + ks_weight = 50.0 + return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] + +def f5_writer(tacs): + flag = (TACS.ToFH5.NODES | + TACS.ToFH5.DISPLACEMENTS | + TACS.ToFH5.STRAINS | + TACS.ToFH5.EXTRAS) + f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) + f5.writeToFile('wingbox.f5') From b11eb2fd8fbc3d98bbd263467022bcb5890c8663 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 09:23:24 -0500 Subject: [PATCH 34/82] Add parallel FSI example script --- .vscode/settings.json | 4 +- .../{mphys_as_vlm.py => run_analysis.py} | 0 .../vlm_meld_tacs/run_analysis_parallel.py | 86 +++++++++++++++++++ mphys/scenario_aerostructural.py | 42 ++++++++- 4 files changed, 128 insertions(+), 4 deletions(-) rename examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/{mphys_as_vlm.py => run_analysis.py} (100%) create mode 100644 examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis_parallel.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 5002b028..b48ea8db 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,8 @@ "restructuredtext.confPath": "${workspaceFolder}/docs/source", "python.formatting.provider": "autopep8", "python.formatting.autopep8Args": [ - "--max-line-length", "100", "--experimental" + "--max-line-length", + "100", + "--experimental" ] } diff --git a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py similarity index 100% rename from examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/mphys_as_vlm.py rename to examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py diff --git a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis_parallel.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis_parallel.py new file mode 100644 index 00000000..9ada2d02 --- /dev/null +++ b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis_parallel.py @@ -0,0 +1,86 @@ +#rst Imports +from __future__ import print_function, division +import numpy as np +from mpi4py import MPI + +import openmdao.api as om + +from mphys import MultipointParallel +from mphys.scenario_aerostructural import ScenarioAeroStructural +from mphys.mphys_vlm import VlmBuilder +from mphys.mphys_tacs import TacsBuilder +from mphys.mphys_meld import MeldBuilder + +import tacs_setup + +class AerostructParallel(MultipointParallel): + def setup(self): + # VLM + mesh_file = 'wing_VLM.dat' + aero_builder = VlmBuilder(mesh_file) + + # TACS + tacs_options = {'add_elements': tacs_setup.add_elements, + 'get_funcs' : tacs_setup.get_funcs, + 'mesh_file' : 'wingbox_Y_Z_flip.bdf', + 'f5_writer' : tacs_setup.f5_writer } + + struct_builder = TacsBuilder(tacs_options) + + # MELD + isym = 1 + ldxfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym) + + for scenario in ['cruise','maneuver']: + nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1E-14, atol=1E-14) + linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, use_aitken=True, rtol = 1e-14, atol=1e-14) + self.mphys_add_scenario(scenario,ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + ldxfer_builder=ldxfer_builder, + in_MultipointParallel=True), + nonlinear_solver, linear_solver) + +class Top(om.Group): + def setup(self): + # VLM + mach = 0.85, + aoa0 = 2.0 + aoa1 = 5.0 + q_inf = 3000. + vel = 178. + mu = 3.5E-5 + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=[aoa0,aoa1], units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + # TACS + ndv_struct = 810 + dvs.add_output('dv_struct', np.array(ndv_struct*[0.002])) + + self.add_subsystem('multipoint',AerostructParallel()) + + for iscen, scenario in enumerate(['cruise','maneuver']): + for dv in ['q_inf','vel','mu','mach', 'dv_struct']: + self.connect(dv, f'multipoint.{scenario}.{dv}') + self.connect('aoa', f'multipoint.{scenario}.aoa', src_indices=[iscen]) + +################################################################################ +# OpenMDAO setup +################################################################################ +prob = om.Problem() +prob.model = Top() + +prob.setup() +om.n2(prob, show_browser=False, outfile='mphys_as_parallel.html') + +prob.run_model() + +ks = prob.get_val('multipoint.maneuver.func_struct',get_remote=True) +cl = prob.get_val('multipoint.cruise.C_L', get_remote=True) +if MPI.COMM_WORLD.rank == 0: + print('C_L =', cl) + print('func_struct =', ks) diff --git a/mphys/scenario_aerostructural.py b/mphys/scenario_aerostructural.py index d373b7e2..be57f610 100644 --- a/mphys/scenario_aerostructural.py +++ b/mphys/scenario_aerostructural.py @@ -2,17 +2,27 @@ from .scenario import Scenario from .coupling_aerostructural import CouplingAeroStructural + class ScenarioAeroStructural(Scenario): def initialize(self): self.options.declare('aero_builder', recordable=False) self.options.declare('struct_builder', recordable=False) self.options.declare('ldxfer_builder', recordable=False) + self.options.declare('geometry_builder', default=None, recordable=False) + self.options.declare('in_MultipointParallel', default=False) def setup(self): aero_builder = self.options['aero_builder'] struct_builder = self.options['struct_builder'] ldxfer_builder = self.options['ldxfer_builder'] + geometry_builder = self.options['geometry_builder'] + + if self.options['in_MultipointParallel']: + self._mphys_initialize_builders(aero_builder, struct_builder, + ldxfer_builder, geometry_builder) + self._mphys_add_mesh_and_geometry_subsystems(aero_builder, struct_builder, + geometry_builder) self.mphys_add_pre_coupling_subsystem('aero', aero_builder) self.mphys_add_pre_coupling_subsystem('struct', struct_builder) @@ -21,11 +31,37 @@ def setup(self): coupling_group = CouplingAeroStructural(aero_builder=aero_builder, struct_builder=struct_builder, ldxfer_builder=ldxfer_builder) - self.mphys_add_subsystem('coupling',coupling_group) + self.mphys_add_subsystem('coupling', coupling_group) self.mphys_add_post_coupling_subsystem('aero', aero_builder) self.mphys_add_post_coupling_subsystem('struct', struct_builder) self.mphys_add_post_coupling_subsystem('ldxfer', ldxfer_builder) - self.nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, atol=1e-8, rtol=1e-8, use_aitken=True) - self.linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, atol=1e-8, rtol=1e-8, use_aitken=True) + self.nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, + atol=1e-8, rtol=1e-8, + use_aitken=True) + self.linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, + atol=1e-8, rtol=1e-8, + use_aitken=True) + + def _mphys_initialize_builders(self, aero_builder, struct_builder, + ldxfer_builder, geometry_builder): + aero_builder.initialize(self.comm) + struct_builder.initialize(self.comm) + ldxfer_builder.initialize(self.comm) + if geometry_builder is not None: + geometry_builder.initialize(self.comm) + + def _mphys_add_mesh_and_geometry_subsystems(self, aero_builder, struct_builder, + geometry_builder): + if geometry_builder is None: + self.mphys_add_subsystem('mesh_aero', aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_subsystem('mesh_struct', struct_builder.get_mesh_coordinate_subsystem()) + + else: + self.add_subsystem('mesh_aero', aero_builder.get_mesh_coordinate_subsystem()) + self.add_subsystem('mesh_struct', struct_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_subsystem('geometry', geometry_builder.get_mesh_coordinate_subsystem()) + + self.connect('mesh_aero.x_aero0', 'geometry.x_aero_in') + self.connect('mesh_struct.x_struct0', 'geometry.x_struct_in') From f5becd5b180f3955c9ae8f8a45880de6d78dded2 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 09:24:17 -0500 Subject: [PATCH 35/82] Move configure ahead of private methods in mphysgroup --- mphys/mphys_group.py | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index ffc1f8d2..2f7138a4 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -1,45 +1,48 @@ from re import sub from openmdao.api import Group + class MphysGroup(Group): def __init__(self, **kwargs): super().__init__(**kwargs) self.mphys_subsystems = [] - def mphys_add_subsystem(self,name,subsystem): + def mphys_add_subsystem(self, name, subsystem): """ Adding an mphys subsystem will add the subsystem and then set the group to automaticallly promote the mphys tagged variables """ - subsystem = self.add_subsystem(name,subsystem) + subsystem = self.add_subsystem(name, subsystem) self.mphys_subsystems.append(subsystem) return subsystem - def _mphys_promote_by_tag(self,iotype,tag): + def configure(self): + self._mphys_promote_coupling_variables() + self._mphys_promote_inputs() + self._mphys_promote_mesh_coordinates() + self._mphys_promote_results() + + def _mphys_promote_by_tag(self, iotype, tag): for subsystem in self.mphys_subsystems: promoted = [] - tagged_variables = subsystem.get_io_metadata(iotypes=iotype, metadata_keys=['tags'], tags=tag) + tagged_variables = subsystem.get_io_metadata(iotypes=iotype, + metadata_keys=['tags'], + tags=tag) for val in tagged_variables.values(): variable = val['prom_name'] if variable not in promoted: - self.promotes(subsystem.name,any=[variable]) + self.promotes(subsystem.name, any=[variable]) promoted.append(variable) def _mphys_promote_coupling_variables(self): - self._mphys_promote_by_tag(['input','output'],'mphys_coupling') + self._mphys_promote_by_tag(['input', 'output'], 'mphys_coupling') def _mphys_promote_inputs(self): - self._mphys_promote_by_tag('input','mphys_input') + self._mphys_promote_by_tag('input', 'mphys_input') def _mphys_promote_mesh_coordinates(self): - self._mphys_promote_by_tag(['input','output'],'mphys_coordinates') + self._mphys_promote_by_tag(['input', 'output'], 'mphys_coordinates') def _mphys_promote_results(self): - self._mphys_promote_by_tag('output','mphys_result') - - def configure(self): - self._mphys_promote_coupling_variables() - self._mphys_promote_inputs() - self._mphys_promote_mesh_coordinates() - self._mphys_promote_results() + self._mphys_promote_by_tag('output', 'mphys_result') From 44030aff6cdf2e9dfad3c2ec9f890e2546c295a9 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 09:40:03 -0500 Subject: [PATCH 36/82] Fix print for parallel VLM run --- .../run_vlm_2scenarios_parallel.py | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py index 925f1ceb..db63e917 100644 --- a/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py +++ b/examples/aero_only/mach_tutorial_wing/run_vlm_2scenarios_parallel.py @@ -51,15 +51,9 @@ def setup(self): om.n2(prob, show_browser=False, outfile='vlm_aero_2cruises_parallel.html') prob.run_model() -#if MPI.COMM_WORLD.rank == 0: -# for scenario in ['cruise','cruise_higher_aoa']: -# print('%s: C_L = %f, C_D=%f' % (scenario, prob.get_val(['mp.%s.C_L'%scenario],get_remote=True), -# prob.get_val(['mp.%s.C_D'%scenario],get_remote=True))) -if MPI.COMM_WORLD.rank == 0: - scenario = 'cruise' - print('%s: C_L = %f, C_D = %f' % (scenario, prob['mp.%s.C_L'%scenario], - prob['mp.%s.C_D'%scenario])) -else: - scenario = 'cruise_higher_aoa' - print('%s: C_L = %f, C_D = %f' % (scenario, prob['mp.%s.C_L'%scenario], - prob['mp.%s.C_D'%scenario])) + +for scenario in ['cruise','cruise_higher_aoa']: + cl = prob.get_val(f'mp.{scenario}.C_L',get_remote=True) + cd = prob.get_val(f'mp.{scenario}.C_D',get_remote=True) + if MPI.COMM_WORLD.rank == 0: + print(f'{scenario}: C_L = {cl}, C_D={cd}') From d06e6634ad26327ab5ea141feafca27fec3b0d25 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 10:42:33 -0500 Subject: [PATCH 37/82] Add comment in struct_only about the "coupling group". Remove unused import --- mphys/mphys_group.py | 1 - mphys/scenario_structural.py | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/mphys/mphys_group.py b/mphys/mphys_group.py index 2f7138a4..39428891 100644 --- a/mphys/mphys_group.py +++ b/mphys/mphys_group.py @@ -1,4 +1,3 @@ -from re import sub from openmdao.api import Group diff --git a/mphys/scenario_structural.py b/mphys/scenario_structural.py index 20235c32..26780cc3 100644 --- a/mphys/scenario_structural.py +++ b/mphys/scenario_structural.py @@ -9,6 +9,8 @@ def setup(self): struct_builder = self.options['struct_builder'] self.mphys_add_pre_coupling_subsystem('struct', struct_builder) + + # the "coupling" group for struct_only would just have the struct subsystem so add it directly here. self.mphys_add_subsystem('coupling',struct_builder.get_coupling_group_subsystem()) self.mphys_add_post_coupling_subsystem('struct', struct_builder) @@ -44,7 +46,7 @@ class ScenarioStructuralParallelGeometry(Scenario): def initialize(self): self.options.declare('struct_builder', recordable=False) - self.options.declare('geometry_builder',recordable=False) + self.options.declare('geometry_builder', recordable=False) def setup(self): struct_builder = self.options['struct_builder'] From 6f9460c14918a3e2363ab82f4fc7d8d520e23e14 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 20:41:16 -0500 Subject: [PATCH 38/82] Split scenario components into two methods --- mphys/builder.py | 21 +++++++++++++-------- mphys/mphys_fun3d.py | 4 ++-- mphys/mphys_tacs.py | 4 ++-- mphys/mphys_vlm.py | 3 --- mphys/scenario.py | 4 ++-- 5 files changed, 19 insertions(+), 17 deletions(-) diff --git a/mphys/builder.py b/mphys/builder.py index 8f7364c6..401daf8b 100644 --- a/mphys/builder.py +++ b/mphys/builder.py @@ -40,20 +40,25 @@ def get_coupling_group_subsystem(self): """ return None - def get_scenario_subsystems(self): + def get_pre_coupling_subsystem(self): """ - Method that returns the openmdao subsystem to be added to each scenario + Method that returns the openmdao subsystem to be added to each scenario before the coupling group Returns ------- pre_coupling_subsystem : openmdao.api.Component or ~openmdao.api.Group - The openmdao subsystem that handles all the added computations for - that should be run in each scenario. These may represent functional that - are run after a coupled analysis is converged. """ - pre_coupling_subsystem = None - post_coupling_subsystem = None - return pre_coupling_subsystem, post_coupling_subsystem + return None + + def get_post_coupling_subsystem(self): + """ + Method that returns the openmdao subsystem to be added to each scenario after the coupling group + + Returns + ------- + post_coupling_subsystem : openmdao.api.Component or ~openmdao.api.Group + """ + return None def get_number_of_nodes(self): """ diff --git a/mphys/mphys_fun3d.py b/mphys/mphys_fun3d.py index b2806738..59fd29e3 100644 --- a/mphys/mphys_fun3d.py +++ b/mphys/mphys_fun3d.py @@ -77,8 +77,8 @@ def get_coupling_group_subsystem(self): flow_comp = sfe_om, forces_comp = forces_om, number_of_surface_nodes = self.number_of_nodes) - def get_scenario_subsystems(self): - return None, IntegratedSurfaceForces() + def get_post_coupling_subsystem(self): + return IntegratedSurfaceForces() def get_number_of_nodes(self): return self.number_of_nodes diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 8ec1c330..3eb3b81c 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -821,8 +821,8 @@ def get_coupling_group_subsystem(self, **kwargs): def get_mesh_coordinate_subsystem(self): return TacsMesh(tacs_assembler=self.tacs_assembler) - def get_scenario_subsystems(self): - return None, TACSFuncsGroup( + def get_post_coupling_subsystem(self): + return TACSFuncsGroup( tacs_assembler=self.tacs_assembler, solver_objects=self.solver_objects, check_partials=self.check_partials diff --git a/mphys/mphys_vlm.py b/mphys/mphys_vlm.py index da24c9d5..dfc4be92 100644 --- a/mphys/mphys_vlm.py +++ b/mphys/mphys_vlm.py @@ -71,9 +71,6 @@ def get_coupling_group_subsystem(self): number_of_nodes=number_of_nodes, compute_tractions=self.compute_tractions) - def get_scenario_subsystems(self): - return None, None - def get_number_of_nodes(self): return self.x_aero0.size // 3 diff --git a/mphys/scenario.py b/mphys/scenario.py index 4527353c..83588b2d 100644 --- a/mphys/scenario.py +++ b/mphys/scenario.py @@ -2,11 +2,11 @@ class Scenario(MphysGroup): def mphys_add_pre_coupling_subsystem(self, name, builder): - subsystem, _ = builder.get_scenario_subsystems() + subsystem = builder.get_pre_coupling_subsystem() if subsystem is not None: self.mphys_add_subsystem(name+'_pre', subsystem) def mphys_add_post_coupling_subsystem(self, name, builder): - _, subsystem = builder.get_scenario_subsystems() + subsystem = builder.get_post_coupling_subsystem() if subsystem is not None: self.mphys_add_subsystem(name+'_post', subsystem) From b0c1be3c540a78de95f5fa5360f1b4c640579da5 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Mon, 8 Mar 2021 21:38:16 -0500 Subject: [PATCH 39/82] Don't shape TACS dv vector by conn or else it gets split in parallel runs. We want the whole vector on each rank. --- mphys/mphys_tacs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mphys/mphys_tacs.py b/mphys/mphys_tacs.py index 8ec1c330..bb120c1e 100644 --- a/mphys/mphys_tacs.py +++ b/mphys/mphys_tacs.py @@ -84,7 +84,7 @@ def setup(self): self.ndof = int(state_size/(node_size/3)) # inputs - self.add_input('dv_struct', shape_by_conn=True, desc='tacs design variables', tags=['mphys_input']) + self.add_input('dv_struct', shape=ndv, desc='tacs design variables', tags=['mphys_input']) self.add_input('x_struct0', shape_by_conn=True, desc='structural node coordinates',tags=['mphys_coordinates']) self.add_input('f_struct', shape_by_conn=True, desc='structural load vector', tags=['mphys_coupling']) From b5355fd024c90a9f1d9b3f7b99ab3eabe6c4f557 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 10 Mar 2021 20:31:18 -0500 Subject: [PATCH 40/82] Set block GS solvers in coupling group not scenario --- mphys/coupling_aerostructural.py | 8 ++++++++ mphys/multipoint.py | 4 ++-- mphys/scenario_aerostructural.py | 7 ------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mphys/coupling_aerostructural.py b/mphys/coupling_aerostructural.py index 93c61ed3..5e0240d5 100644 --- a/mphys/coupling_aerostructural.py +++ b/mphys/coupling_aerostructural.py @@ -1,3 +1,4 @@ +import openmdao.api as om from .coupling_group import CouplingGroup from .geo_disp import GeoDisp @@ -28,3 +29,10 @@ def setup(self): self.mphys_add_subsystem('aero', aero) self.mphys_add_subsystem('load_xfer', load_xfer) self.mphys_add_subsystem('struct', struct) + + self.nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, + atol=1e-8, rtol=1e-8, + use_aitken=True) + self.linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, + atol=1e-8, rtol=1e-8, + use_aitken=True) diff --git a/mphys/multipoint.py b/mphys/multipoint.py index b0530490..fbf41a65 100644 --- a/mphys/multipoint.py +++ b/mphys/multipoint.py @@ -16,10 +16,10 @@ def configure(self): def _mphys_set_coupling_algorithms_in_scenarios(self): for scenario, solvers in self.mphys_coupling_solvers: if solvers[0] is not None: - scenario.nonlinear_solver = solvers[0] + scenario.coupling.nonlinear_solver = solvers[0] if solvers[1] is not None: - scenario.linear_solver = solvers[1] + scenario.coupling.linear_solver = solvers[1] class Multipoint(om.Group,MultipointBase): def __init__(self, **kwargs): diff --git a/mphys/scenario_aerostructural.py b/mphys/scenario_aerostructural.py index be57f610..ef97735a 100644 --- a/mphys/scenario_aerostructural.py +++ b/mphys/scenario_aerostructural.py @@ -37,13 +37,6 @@ def setup(self): self.mphys_add_post_coupling_subsystem('struct', struct_builder) self.mphys_add_post_coupling_subsystem('ldxfer', ldxfer_builder) - self.nonlinear_solver = om.NonlinearBlockGS(maxiter=25, iprint=2, - atol=1e-8, rtol=1e-8, - use_aitken=True) - self.linear_solver = om.LinearBlockGS(maxiter=25, iprint=2, - atol=1e-8, rtol=1e-8, - use_aitken=True) - def _mphys_initialize_builders(self, aero_builder, struct_builder, ldxfer_builder, geometry_builder): aero_builder.initialize(self.comm) From f9ad33a13ab3e05b1702dfd2fc5dafacd324d6d2 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Tue, 16 Mar 2021 10:30:42 -0400 Subject: [PATCH 41/82] Update tacs check partials --- .../integration_tests/tacs_check_partials.py | 102 +++++++++--------- 1 file changed, 48 insertions(+), 54 deletions(-) diff --git a/tests/integration_tests/tacs_check_partials.py b/tests/integration_tests/tacs_check_partials.py index 814b685b..b383d9ab 100644 --- a/tests/integration_tests/tacs_check_partials.py +++ b/tests/integration_tests/tacs_check_partials.py @@ -5,76 +5,70 @@ import openmdao.api as om from mphys.mphys_tacs import TacsBuilder from mphys.multipoint import Multipoint +from mphys.scenario_structural import ScenarioStructural from tacs import elements, constitutive, functions -class Top(om.Group): +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness = 0.003 + min_thickness = 0.002 + max_thickness = 0.05 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ["CQUAD", "CQUADR", "CQUAD4"]: + element = elements.MITCShell(2, stiff, component_num=i) + mesh.setElement(i, element) + ndof = 6 + ndv = num_components + + return ndof, ndv + + +def get_funcs(tacs): + ks_weight = 50.0 + return [functions.KSFailure(tacs, ks_weight), functions.StructuralMass(tacs)] + + +def forcer(x_s0, ndof): + return np.random.rand(int(x_s0.size/3*ndof)) + + +class Top(Multipoint): def setup(self): - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness= 0.003 - min_thickness = 0.002 - max_thickness = 0.05 - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - def forcer(x_s0,ndof): - return np.random.rand(int(x_s0.size/3*ndof)) tacs_options = {'add_elements': add_elements, - 'get_funcs' : get_funcs, - 'mesh_file' : '../input_files/debug.bdf', - 'load_function' : forcer} + 'get_funcs': get_funcs, + 'mesh_file': '../input_files/debug.bdf', + 'load_function': forcer} tacs_builder = TacsBuilder(tacs_options, check_partials=True) + tacs_builder.initialize(self.comm) + ndv_struct = tacs_builder.get_ndv() - ################################################################################ - # MPHY setup - ################################################################################ - - # ivc to keep the top level DVs dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('dv_struct', np.array(ndv_struct*[0.01])) - # create the multiphysics multipoint group. - mp = self.add_subsystem( - 'mp_group', - Multipoint(struct_builder = tacs_builder) - ) - - # this is the method that needs to be called for every point in this mp_group - mp.mphys_add_scenario('s0') + self.add_subsystem('mesh', tacs_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('analysis', ScenarioStructural(struct_builder=tacs_builder)) + self.connect('mesh.x_struct0', 'analysis.x_struct0') + self.connect('dv_struct', 'analysis.dv_struct') - def configure(self): - # add the structural thickness DVs - ndv_struct = self.mp_group.struct_builder.get_ndv() - self.dvs.add_output('dv_struct', np.array(ndv_struct*[0.01])) - self.connect('dv_struct', ['mp_group.s0.solver_group.struct.dv_struct', 'mp_group.s0.struct_funcs.dv_struct']) prob = om.Problem() prob.model = Top() -prob.setup(mode='rev',force_alloc_complex=True) +prob.setup(mode='rev', force_alloc_complex=True) prob.run_model() -prob.check_partials(method='cs',compact_print=True) +prob.check_partials(method='cs', compact_print=True) From fc469f50926dcb4b31e0c3fb2b490638868d4f6a Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Tue, 16 Mar 2021 10:30:56 -0400 Subject: [PATCH 42/82] Remove unnecessary comma --- .../mach_tutorial_wing/vlm_meld_tacs/run_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py index 3d0c15f6..6fd88457 100644 --- a/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py +++ b/examples/aerostructural/mach_tutorial_wing/vlm_meld_tacs/run_analysis.py @@ -17,7 +17,7 @@ class Top(Multipoint): def setup(self): # VLM mesh_file = 'wing_VLM.dat' - mach = 0.85, + mach = 0.85 aoa0 = 2.0 aoa1 = 5.0 q_inf = 3000. From ae0aafc6ee7612c8259465c6557096c6eb403cae Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Tue, 16 Mar 2021 11:52:31 -0400 Subject: [PATCH 43/82] Update TACS integration test --- tests/integration_tests/test_tacs_derivs.py | 166 +++++++++----------- 1 file changed, 70 insertions(+), 96 deletions(-) diff --git a/tests/integration_tests/test_tacs_derivs.py b/tests/integration_tests/test_tacs_derivs.py index cd552635..3608e1b4 100644 --- a/tests/integration_tests/test_tacs_derivs.py +++ b/tests/integration_tests/test_tacs_derivs.py @@ -19,12 +19,12 @@ from openmdao.utils.assert_utils import assert_near_equal from mphys.multipoint import Multipoint +from mphys.scenario_structural import ScenarioStructural from tacs import elements, constitutive, functions, TACS from mphys.mphys_tacs import TacsBuilder - baseDir = os.path.dirname(os.path.abspath(__file__)) @@ -38,59 +38,50 @@ alpha0 = 3.725 -class Top(om.Group): - def setup(self): +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness = initial_thickness + min_t = min_thickness + max_t = max_thickness - ################################################################################ - # STRUCT - ################################################################################ + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, min_t, max_t) + element = None + if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: + element = elements.MITCShell(2, stiff, component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + + +def get_funcs(tacs): + ks = functions.KSFailure(tacs, ks_weight) + ks.setLoadFactor(load_factor) + ms = functions.StructuralMass(tacs) + return [ks, ms] + + +class Top(Multipoint): + def setup(self): - # creating the options dictionary is same for both solvers - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness = initial_thickness - min_t = min_thickness - max_t = max_thickness - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, min_t, max_t) - element = None - if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: - element = elements.MITCShell(2, stiff, component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks = functions.KSFailure(tacs, ks_weight) - ks.setLoadFactor(load_factor) - ms = functions.StructuralMass(tacs) - return [ks, ms] - - def f5_writer(tacs): - flag = TACS.ToFH5.NODES | TACS.ToFH5.DISPLACEMENTS | TACS.ToFH5.STRAINS | TACS.ToFH5.EXTRAS - f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) - f5.writeToFile(os.path.join(baseDir, '../output_files/wingbox.f5')) - - # common setup options struct_options = { 'add_elements': add_elements, 'get_funcs': get_funcs, 'mesh_file': os.path.join(baseDir, '../input_files/debug.bdf'), - # 'f5_writer' : f5_writer, } - - struct_builder = TacsBuilder(struct_options) - + struct_builder = TacsBuilder(struct_options, check_partials=False) + struct_builder.initialize(self.comm) + ndv_struct = struct_builder.get_ndv() + f_size = struct_builder.get_ndof() * struct_builder.get_number_of_nodes() ################################################################################ # MPHYS setup @@ -98,31 +89,14 @@ def f5_writer(tacs): # ivc for DVs dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('dv_struct', np.array(ndv_struct * [0.01])) + dvs.add_output('f_struct', np.ones(f_size)) - # create the multiphysics multipoint group. - mp = self.add_subsystem('mp', Multipoint(aero_builder=None, struct_builder=struct_builder, xfer_builder=None)) - - mp.mphys_add_scenario('s0') - - def configure(self): - - # structural DVs are shared across TACS and modal solver - ndv_struct = self.mp.struct_builder.get_ndv() - - # a flat DV array w/o any mapping - self.dvs.add_output('dv_struct', np.array(ndv_struct * [0.01])) - - self.connect('dv_struct', ['mp.s0.solver_group.struct.dv_struct', 'mp.s0.struct_funcs.dv_struct']) - - f_size = self.mp.s0.solver_group.struct.solver.ans.getArray().size - self.dvs.add_output('f_struct', np.ones(f_size)) - - self.connect('f_struct', ['mp.s0.solver_group.struct.f_struct']) - - self.mp.struct_mesh.mphys_add_coordinate_input() - self.dvs.add_output('xpts', self.mp.struct_mesh.xpts.getArray()) - - self.connect('xpts', ['mp.struct_mesh.x_struct0_points']) + self.add_subsystem('mesh', struct_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('analysis', ScenarioStructural(struct_builder=struct_builder)) + self.connect('mesh.x_struct0', 'analysis.x_struct0') + self.connect('dv_struct', 'analysis.dv_struct') + self.connect('f_struct', 'analysis.f_struct') class TestTACs(unittest.TestCase): @@ -135,22 +109,15 @@ def setUp(self): prob.model = Top() # DVs + #prob.model.add_design_var('dv_struct', indices=[0], lower=-5, upper=10, ref=10.0) + #prob.model.add_design_var('f_struct', indices=[0, 12, 34, 40], lower=-5, upper=10, ref=10.0) + #prob.model.add_design_var('mesh.x_struct0', indices=[0, 2, 5, 10], lower=-5, upper=10, ref=10.0) # objectives and nonlinear constraints - prob.model.add_objective('mp.s0.struct_funcs.mass', ref=100.0) - prob.model.add_constraint('mp.s0.struct_funcs.funcs.func_struct', ref=1.0, upper=1.0) - - prob.model.add_design_var('dv_struct', indices=[0], lower=-5, upper=10, ref=10.0) - prob.model.add_design_var('f_struct', indices=[0, 12, 34, 40], lower=-5, upper=10, ref=10.0) - prob.model.add_design_var('xpts', indices=[0, 2, 5, 10], lower=-5, upper=10, ref=10.0) - - prob.setup(mode='rev',force_alloc_complex=True) - # om.n2( - # prob, - # show_browser=False, - # outfile='test_struct_derivs.html' , - # ) + #prob.model.add_objective('analysis.mass', ref=100.0) + #prob.model.add_constraint('analysis.func_struct', ref=1.0, upper=1.0) + prob.setup(mode='rev', force_alloc_complex=True) self.prob = prob def test_run_model(self): @@ -158,22 +125,29 @@ def test_run_model(self): def test_derivatives(self): self.prob.run_model() - print('----------------strating check totals--------------') - data = self.prob.check_totals(wrt='xpts', method='cs', step=1e-30, step_calc='rel') # out_stream=None - + print('----------------starting check totals--------------') + data = self.prob.check_totals(of=['analysis.func_struct','analysis.mass'], + wrt='mesh.x_struct0', method='cs', + step=1e-30, step_calc='rel') + #data = self.prob.check_totals(wrt='mesh.x_struct0', method='cs', + # step=1e-30, step_calc='rel') for var, err in data.items(): - - rel_err = err['rel error'] # , 'rel error'] + rel_err = err['rel error'] assert_near_equal(rel_err.forward, 0.0, 1e-8) - data = self.prob.check_totals(of=['mp.s0.struct_funcs.funcs.func_struct'], wrt='f_struct', method='cs', step=1e-30, step_calc='rel') # out_stream=None - for var, err in data.items(): - rel_err = err['rel error'] # , 'rel error'] - assert_near_equal(rel_err.forward, 0.0, 1e-8) - data = self.prob.check_totals(wrt='dv_struct', method='cs', step=1e-30, step_calc='rel') # out_stream=None + data = self.prob.check_totals(of=['analysis.func_struct'], wrt='f_struct', + method='cs', step=1e-30, step_calc='rel') for var, err in data.items(): - - rel_err = err['rel error'] # , 'rel error'] + rel_err = err['rel error'] + assert_near_equal(rel_err.forward, 0.0, 2e-8) + + data = self.prob.check_totals(of=['analysis.func_struct','analysis.mass'], + wrt='dv_struct', method='cs', + step=1e-30, step_calc='rel') + #data = self.prob.check_totals(wrt='dv_struct', method='cs', + # step=1e-30, step_calc='rel') + for var, err in data.items(): + rel_err = err['rel error'] assert_near_equal(rel_err.forward, 0.0, 5e-8) From f093134da39d4d3bd19ff6a8820754a98546f8d8 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 17 Mar 2021 12:06:08 -0400 Subject: [PATCH 44/82] Updapte meld test. Run two melds to make sure it can work for multipoint --- tests/integration_tests/test_meld_derivs.py | 93 +++++++++------------ 1 file changed, 38 insertions(+), 55 deletions(-) diff --git a/tests/integration_tests/test_meld_derivs.py b/tests/integration_tests/test_meld_derivs.py index 9181b5ee..a0f98f1e 100644 --- a/tests/integration_tests/test_meld_derivs.py +++ b/tests/integration_tests/test_meld_derivs.py @@ -13,27 +13,26 @@ # === External Python modules === import numpy as np from mpi4py import MPI -from parameterized import parameterized, parameterized_class # === Extension modules === import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal +from mphys import Builder from mphys.mphys_meld import MeldBuilder -import mphys +isym = 1 -meld_options = {'isym': 1, 'n': 200, 'beta': 0.5} +class DummyBuilder(Builder): + def __init__(self, num_nodes, ndof): + self.num_nodes = num_nodes + self.ndof = ndof + def get_number_of_nodes(self): + return self.num_nodes + def get_ndof(self): + return self.ndof - -@parameterized_class( - ('name', 'xfer_builder_class', 'xfer_options'), - [ - ('meld', MeldBuilder, meld_options), - # ('rlt', RltBuilder, rlt_options) # RLT can't take a dummy solver as input. It will be tested in regression tests - ], -) class TestXferClasses(unittest.TestCase): def setUp(self): class FakeStructMesh(om.ExplicitComponent): @@ -78,75 +77,59 @@ def compute(self, inputs, outputs): outputs['x_aero'] = self.nodes np.random.seed(0) + aero_builder = DummyBuilder(4,3) + struct_builder = DummyBuilder(4,3) + meld_builder = MeldBuilder(aero_builder, struct_builder, isym=1, check_partials=True) + meld_builder.initialize(MPI.COMM_WORLD) + prob = om.Problem() prob.model.add_subsystem('aero_mesh', FakeAeroMesh()) prob.model.add_subsystem('struct_mesh', FakeStructMesh()) prob.model.add_subsystem('struct_disps', FakeStructDisps()) prob.model.add_subsystem('aero_loads', FakeAeroLoads()) - # if self.name == 'rlt': - # class DummySolver(object): - # pass - - # aero_solver = DummySolver() - # def getSurfaceCoordinates(self, groupName=None, includeZipper=True): - # return aero_mesh.nodes - - # def getSurfaceConnectivity(self, groupName=None, includeZipper=True, includeCGNS=False): - # ncell = 8 - # conn = np.random.randint(0, 14, size=(ncell, 4)) - # faceSizes = 4*np.ones(len(conn), 'intc') - - # return conn, faceSizes - - # aero_solver.getSurfaceCoordinates = getSurfaceCoordinates - # aero_solver.getSurfaceConnectivity = getSurfaceConnectivity - # aero_solver.allWallsGroup = None - # aero_solver.comm = MPI.COMM_WORLD - - # aero_builder = mphys.DummyBuilder(nnodes=4, solver=aero_solver) - - # struct_builder = mphys.DummyBuilder(nnodes=4, ndof=3, solver=None) - # else: - aero_builder = mphys.DummyBuilder(nnodes=4) - - struct_builder = mphys.DummyBuilder(nnodes=4, ndof=3) - - self.builder = self.xfer_builder_class(self.xfer_options, aero_builder, struct_builder, check_partials=True) - - self.builder.init_xfer_object(MPI.COMM_WORLD) - disp_xfer, load_xfer = self.builder.get_element() - - prob.model.add_subsystem('disp_xfer', disp_xfer) - prob.model.add_subsystem('load_xfer', load_xfer) + disp, load = meld_builder.get_coupling_group_subsystem() + prob.model.add_subsystem('disp_xfer',disp) + prob.model.add_subsystem('load_xfer',load) prob.model.connect('aero_mesh.x_aero', ['disp_xfer.x_aero0', 'load_xfer.x_aero0']) prob.model.connect('struct_mesh.x_struct0', ['disp_xfer.x_struct0', 'load_xfer.x_struct0']) prob.model.connect('struct_disps.u_struct', ['disp_xfer.u_struct', 'load_xfer.u_struct']) prob.model.connect('aero_loads.f_aero', ['load_xfer.f_aero']) + prob.model.add_subsystem('aero_mesh2',FakeAeroMesh()) + prob.model.add_subsystem('struct_mesh2',FakeStructMesh()) + prob.model.add_subsystem('struct_disps2',FakeStructDisps()) + prob.model.add_subsystem('aero_loads2',FakeAeroLoads()) + + disp, load = meld_builder.get_coupling_group_subsystem() + prob.model.add_subsystem('disp_xfer2',disp) + prob.model.add_subsystem('load_xfer2',load) + + prob.model.connect('aero_mesh2.x_aero',['disp_xfer2.x_aero0','load_xfer2.x_aero0']) + prob.model.connect('struct_mesh2.x_struct0',['disp_xfer2.x_struct0','load_xfer2.x_struct0']) + prob.model.connect('struct_disps2.u_struct',['disp_xfer2.u_struct','load_xfer2.u_struct']) + prob.model.connect('aero_loads2.f_aero',['load_xfer2.f_aero']) + prob.setup(force_alloc_complex=True) self.prob = prob - # om.n2(prob, show_browser=False, outfile='test.html') + om.n2(prob, show_browser=False, outfile='test.html') def test_run_model(self): self.prob.run_model() def test_derivatives(self): self.prob.run_model() - data = self.prob.check_partials(out_stream=None, compact_print=False) + data = self.prob.check_partials(compact_print=True, method='cs') - # there is an openmdao util to check partiacles, but we can't use it + # there is an openmdao util to check partiales, but we can't use it # because only SOME of the fwd derivatives are implemented for key, comp in data.items(): for var, err in comp.items(): - - rel_err = err['rel error'] # , 'rel error'] - - assert_near_equal(rel_err.reverse, 0.0, 5e-6) - + rel_err = err['rel error'] + assert_near_equal(rel_err.reverse, 0.0, 1e-12) if var[1] == 'f_aero' or var[1] == 'u_struct': - assert_near_equal(rel_err.forward, 0.0, 5e-6) + assert_near_equal(rel_err.forward, 0.0, 1e-12) if __name__ == '__main__': From 5660c6663e555286dd17e2b8f16cd083a096c96b Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 17 Mar 2021 12:08:20 -0400 Subject: [PATCH 45/82] Clean up commented out code in tacs test --- tests/integration_tests/test_tacs_derivs.py | 30 +++------------------ 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/tests/integration_tests/test_tacs_derivs.py b/tests/integration_tests/test_tacs_derivs.py index 3608e1b4..9be7187c 100644 --- a/tests/integration_tests/test_tacs_derivs.py +++ b/tests/integration_tests/test_tacs_derivs.py @@ -12,8 +12,6 @@ # === External Python modules === import numpy as np -# from mpi4py import MPI - # === Extension modules === import openmdao.api as om from openmdao.utils.assert_utils import assert_near_equal @@ -21,7 +19,7 @@ from mphys.multipoint import Multipoint from mphys.scenario_structural import ScenarioStructural -from tacs import elements, constitutive, functions, TACS +from tacs import elements, constitutive, functions from mphys.mphys_tacs import TacsBuilder @@ -83,11 +81,6 @@ def setup(self): ndv_struct = struct_builder.get_ndv() f_size = struct_builder.get_ndof() * struct_builder.get_number_of_nodes() - ################################################################################ - # MPHYS setup - ################################################################################ - - # ivc for DVs dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) dvs.add_output('dv_struct', np.array(ndv_struct * [0.01])) dvs.add_output('f_struct', np.ones(f_size)) @@ -101,22 +94,9 @@ def setup(self): class TestTACs(unittest.TestCase): def setUp(self): - - ################################################################################ - # OpenMDAO setup - ################################################################################ prob = om.Problem() prob.model = Top() - # DVs - #prob.model.add_design_var('dv_struct', indices=[0], lower=-5, upper=10, ref=10.0) - #prob.model.add_design_var('f_struct', indices=[0, 12, 34, 40], lower=-5, upper=10, ref=10.0) - #prob.model.add_design_var('mesh.x_struct0', indices=[0, 2, 5, 10], lower=-5, upper=10, ref=10.0) - - # objectives and nonlinear constraints - #prob.model.add_objective('analysis.mass', ref=100.0) - #prob.model.add_constraint('analysis.func_struct', ref=1.0, upper=1.0) - prob.setup(mode='rev', force_alloc_complex=True) self.prob = prob @@ -126,11 +106,9 @@ def test_run_model(self): def test_derivatives(self): self.prob.run_model() print('----------------starting check totals--------------') - data = self.prob.check_totals(of=['analysis.func_struct','analysis.mass'], + data = self.prob.check_totals(of=['analysis.func_struct', 'analysis.mass'], wrt='mesh.x_struct0', method='cs', step=1e-30, step_calc='rel') - #data = self.prob.check_totals(wrt='mesh.x_struct0', method='cs', - # step=1e-30, step_calc='rel') for var, err in data.items(): rel_err = err['rel error'] assert_near_equal(rel_err.forward, 0.0, 1e-8) @@ -141,11 +119,9 @@ def test_derivatives(self): rel_err = err['rel error'] assert_near_equal(rel_err.forward, 0.0, 2e-8) - data = self.prob.check_totals(of=['analysis.func_struct','analysis.mass'], + data = self.prob.check_totals(of=['analysis.func_struct', 'analysis.mass'], wrt='dv_struct', method='cs', step=1e-30, step_calc='rel') - #data = self.prob.check_totals(wrt='dv_struct', method='cs', - # step=1e-30, step_calc='rel') for var, err in data.items(): rel_err = err['rel error'] assert_near_equal(rel_err.forward, 0.0, 5e-8) From 4a212a1f81d5cd85c193c98730cfe87a494b3f25 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 17 Mar 2021 12:09:08 -0400 Subject: [PATCH 46/82] Turn off n2 in meld test --- tests/integration_tests/test_meld_derivs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration_tests/test_meld_derivs.py b/tests/integration_tests/test_meld_derivs.py index a0f98f1e..fb27e36f 100644 --- a/tests/integration_tests/test_meld_derivs.py +++ b/tests/integration_tests/test_meld_derivs.py @@ -113,7 +113,7 @@ def compute(self, inputs, outputs): prob.setup(force_alloc_complex=True) self.prob = prob - om.n2(prob, show_browser=False, outfile='test.html') + #om.n2(prob, show_browser=False, outfile='test.html') def test_run_model(self): self.prob.run_model() From d0bfae66a50b1b8398d94b7f33830859f552a10b Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 17 Mar 2021 12:10:08 -0400 Subject: [PATCH 47/82] Remove meld_check_partials since the test_meld_derivs now does the same thing --- .../integration_tests/meld_check_partials.py | 90 ------------------- 1 file changed, 90 deletions(-) delete mode 100644 tests/integration_tests/meld_check_partials.py diff --git a/tests/integration_tests/meld_check_partials.py b/tests/integration_tests/meld_check_partials.py deleted file mode 100644 index c51d677f..00000000 --- a/tests/integration_tests/meld_check_partials.py +++ /dev/null @@ -1,90 +0,0 @@ -# complex step partial derivative check of MELD transfer components -# must compile funtofem in complex mode -import numpy as np -from mpi4py import MPI - -import openmdao.api as om -from mphys.mphys_meld import MeldDispXfer, MeldLoadXfer - -from funtofem import TransferScheme - -class FakeStructMesh(om.ExplicitComponent): - def initialize(self): - self.nodes = np.random.rand(12) - - def setup(self): - self.add_output('x_struct',shape=self.nodes.size) - - def compute(self,inputs,outputs): - outputs['x_struct'] = self.nodes - -class FakeStructDisps(om.ExplicitComponent): - def initialize(self): - self.nodes = np.random.rand(12) - self.nodes = np.arange(12) - - def setup(self): - self.add_output('u_struct',shape=self.nodes.size) - - def compute(self,inputs,outputs): - outputs['u_struct'] = self.nodes - -class FakeAeroLoads(om.ExplicitComponent): - def initialize(self): - self.nodes = np.random.rand(12) - - def setup(self): - self.add_output('f_aero',shape=self.nodes.size) - - def compute(self,inputs,outputs): - outputs['f_aero'] = self.nodes - -class FakeAeroMesh(om.ExplicitComponent): - def initialize(self): - self.nodes = np.random.rand(12) - - def setup(self): - self.add_output('x_aero',shape=self.nodes.size) - - def compute(self,inputs,outputs): - outputs['x_aero'] = self.nodes - -comm = MPI.COMM_WORLD -isym = 1 -n = 20 -beta = 0.5 -meld = TransferScheme.pyMELD(comm, comm,0, comm,0, isym,n,beta) - -struct_ndof = 3 -struct_nnodes = 4 -aero_nnodes = 4 - -prob = om.Problem() -prob.model.add_subsystem('aero_mesh',FakeAeroMesh()) -prob.model.add_subsystem('struct_mesh',FakeStructMesh()) -prob.model.add_subsystem('struct_disps',FakeStructDisps()) -prob.model.add_subsystem('aero_loads',FakeAeroLoads()) -disp = prob.model.add_subsystem('disp_xfer',MeldDispXfer(xfer_object=meld,struct_ndof=struct_ndof,struct_nnodes=struct_nnodes,aero_nnodes=aero_nnodes,check_partials=True)) -load = prob.model.add_subsystem('load_xfer',MeldLoadXfer(xfer_object=meld,struct_ndof=struct_ndof,struct_nnodes=struct_nnodes,aero_nnodes=aero_nnodes,check_partials=True)) - -prob.model.add_subsystem('aero_mesh2',FakeAeroMesh()) -prob.model.add_subsystem('struct_mesh2',FakeStructMesh()) -prob.model.add_subsystem('struct_disps2',FakeStructDisps()) -prob.model.add_subsystem('aero_loads2',FakeAeroLoads()) -disp = prob.model.add_subsystem('disp_xfer2',MeldDispXfer(xfer_object=meld,struct_ndof=struct_ndof,struct_nnodes=struct_nnodes,aero_nnodes=aero_nnodes,check_partials=True)) -load = prob.model.add_subsystem('load_xfer2',MeldLoadXfer(xfer_object=meld,struct_ndof=struct_ndof,struct_nnodes=struct_nnodes,aero_nnodes=aero_nnodes,check_partials=True)) - -prob.model.connect('aero_mesh.x_aero',['disp_xfer.x_aero0','load_xfer.x_aero0']) -prob.model.connect('struct_mesh.x_struct',['disp_xfer.x_struct0','load_xfer.x_struct0']) -prob.model.connect('struct_disps.u_struct',['disp_xfer.u_struct','load_xfer.u_struct']) -prob.model.connect('aero_loads.f_aero',['load_xfer.f_aero']) - -prob.model.connect('aero_mesh2.x_aero',['disp_xfer2.x_aero0','load_xfer2.x_aero0']) -prob.model.connect('struct_mesh2.x_struct',['disp_xfer2.x_struct0','load_xfer2.x_struct0']) -prob.model.connect('struct_disps2.u_struct',['disp_xfer2.u_struct','load_xfer2.u_struct']) -prob.model.connect('aero_loads2.f_aero',['load_xfer2.f_aero']) - - -prob.setup(force_alloc_complex=True) -prob.run_model() -prob.check_partials(method='cs',compact_print=True) From f29c39451b617eaf8e1a50cb6db4298c91cd2153 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Wed, 17 Mar 2021 17:42:15 -0400 Subject: [PATCH 48/82] Update remaining "check" scripts --- tests/integration_tests/vlm_check_partials.py | 79 ++----- .../vlm_tacs_meld_check_partials.py | 218 ++++++++---------- .../vlm_tacs_meld_check_totals.py | 214 ++++++++--------- 3 files changed, 216 insertions(+), 295 deletions(-) diff --git a/tests/integration_tests/vlm_check_partials.py b/tests/integration_tests/vlm_check_partials.py index b32b6da1..3c5f7b88 100644 --- a/tests/integration_tests/vlm_check_partials.py +++ b/tests/integration_tests/vlm_check_partials.py @@ -3,72 +3,41 @@ import openmdao.api as om from mphys.multipoint import Multipoint -from mphys.mphys_vlm import * +from mphys.scenario_aero import ScenarioAero +from mphys.mphys_vlm import VlmBuilder comm = MPI.COMM_WORLD -class Top(om.Group): - def setup(self): - # VLM options - - aero_options = { - 'mesh_file':'../input_files/debug_VLM.dat', - 'mach':0.85, - 'aoa':1*np.pi/180., - 'q_inf':25000., - 'vel':178., - 'mu':3.5E-5, - } - - # VLM mesh read - def read_VLM_mesh(mesh): - f=open(mesh, "r") - contents = f.read().split() - - a = [i for i in contents if 'NODES' in i][0] - N_nodes = int(a[a.find("=")+1:a.find(",")]) - a = [i for i in contents if 'ELEMENTS' in i][0] - N_elements = int(a[a.find("=")+1:a.find(",")]) - - a = np.array(contents[16:16+N_nodes*3],'float') - X = a[0:N_nodes*3:3] - Y = a[1:N_nodes*3:3] - Z = a[2:N_nodes*3:3] - a = np.array(contents[16+N_nodes*3:None],'int') - quad = np.reshape(a,[N_elements,4]) - - xa = np.c_[X,Y,Z].flatten(order='C') - f.close() - - return N_nodes, N_elements, xa, quad - - aero_options['N_nodes'], aero_options['N_elements'], aero_options['x_aero0'], aero_options['quad'] = read_VLM_mesh(aero_options['mesh_file']) - self.aero_options = aero_options +class Top(Multipoint): + def setup(self): + mesh_file = '../input_files/debug_VLM.dat' + mach = 0.85, + aoa = 1.0 + q_inf = 25000. + vel = 178. + mu = 3.5E-5 - dvs = self.add_subsystem('dvs',om.IndepVarComp(), promotes=['*']) + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) - vlm_builder = VlmBuilder(aero_options) - mp = self.add_subsystem( - 'mp_group', - Multipoint(aero_builder = vlm_builder) - ) + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) - mp.mphys_add_scenario('s0') + self.add_subsystem('mesh', aero_builder.get_mesh_coordinate_subsystem()) + self.mphys_add_scenario('cruise', ScenarioAero(aero_builder=aero_builder)) + self.connect('mesh.x_aero0', 'cruise.x_aero') - def configure(self): - self.dvs.add_output('aoa', self.aero_options['aoa'], units='rad') - self.dvs.add_output('u_aero', np.zeros_like(self.aero_options['x_aero0'])) - self.connect('aoa',['mp_group.s0.solver_group.aero.aoa']) - self.connect('u_aero','mp_group.s0.solver_group.aero.u_aero') + for dv in ['aoa', 'mach', 'q_inf', 'vel', 'mu']: + self.connect(dv, f'cruise.{dv}') -## openmdao setup -connection_srcs = {} prob = om.Problem() - prob.model = Top() - prob.setup(force_alloc_complex=True) prob.run_model() -prob.check_partials(method='cs',compact_print=True) +prob.check_partials(method='cs', compact_print=True) diff --git a/tests/integration_tests/vlm_tacs_meld_check_partials.py b/tests/integration_tests/vlm_tacs_meld_check_partials.py index 0030c879..3aa1baf0 100644 --- a/tests/integration_tests/vlm_tacs_meld_check_partials.py +++ b/tests/integration_tests/vlm_tacs_meld_check_partials.py @@ -1,11 +1,10 @@ # must compile funtofem and tacs in complex mode -from __future__ import print_function, division -from mpi4py import MPI import numpy as np import openmdao.api as om -from mphys.multipoint import Multipoint +from mphys import Multipoint +from mphys.scenario_aerostructural import ScenarioAeroStructural from mphys.mphys_vlm import VlmBuilder from mphys.mphys_tacs import TacsBuilder from mphys.mphys_meld import MeldBuilder @@ -14,140 +13,115 @@ use_modal = False -class Top(om.Group): + +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness = 0.003 + min_thickness = 0.002 + max_thickness = 0.05 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: + element = elements.MITCShell(2, stiff, component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + + +def get_funcs(tacs): + ks_weight = 50.0 + return [functions.KSFailure(tacs, ks_weight), functions.StructuralMass(tacs)] + + +def f5_writer(tacs): + flag = (TACS.ToFH5.NODES | + TACS.ToFH5.DISPLACEMENTS | + TACS.ToFH5.STRAINS | + TACS.ToFH5.EXTRAS) + f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) + f5.writeToFile('wingbox.f5') + + +class Top(Multipoint): def setup(self): - # VLM options - aero_options = { - 'mesh_file':'../input_files/debug_VLM.dat', - 'mach':0.85, - 'aoa':1*np.pi/180., - 'q_inf':25000., - 'vel':178., - 'mu':3.5E-5, - } - - # VLM mesh read - def read_VLM_mesh(mesh): - f=open(mesh, "r") - contents = f.read().split() - - a = [i for i in contents if 'NODES' in i][0] - N_nodes = int(a[a.find("=")+1:a.find(",")]) - a = [i for i in contents if 'ELEMENTS' in i][0] - N_elements = int(a[a.find("=")+1:a.find(",")]) - - a = np.array(contents[16:16+N_nodes*3],'float') - X = a[0:N_nodes*3:3] - Y = a[1:N_nodes*3:3] - Z = a[2:N_nodes*3:3] - a = np.array(contents[16+N_nodes*3:None],'int') - quad = np.reshape(a,[N_elements,4]) - - xa = np.c_[X,Y,Z].flatten(order='C') - - f.close() - - return N_nodes, N_elements, xa, quad - - aero_options['N_nodes'], aero_options['N_elements'], aero_options['x_aero0'], aero_options['quad'] = read_VLM_mesh(aero_options['mesh_file']) - self.aero_options = aero_options - - # VLM builder - aero_builder = VlmBuilder(aero_options) - - # TACS setup - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness= 0.003 - min_thickness = 0.002 - max_thickness = 0.05 - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - - def f5_writer(tacs): - flag = (TACS.ToFH5.NODES | - TACS.ToFH5.DISPLACEMENTS | - TACS.ToFH5.STRAINS | - TACS.ToFH5.EXTRAS) - f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) - f5.writeToFile('wingbox.f5') - - - # common setup options - tacs_setup = {'add_elements': add_elements, - 'get_funcs' : get_funcs, - 'mesh_file' : '../input_files/debug.bdf', - 'f5_writer' : f5_writer } - - # TACS assembler + # VLM + mesh_file = '../input_files/debug_VLM.dat' + mach = 0.85, + aoa = 1.0 + q_inf = 25000. + vel = 178. + mu = 3.5E-5 + + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + self.add_subsystem('mesh_aero', aero_builder.get_mesh_coordinate_subsystem()) + + # TACS + tacs_options = {'add_elements': add_elements, + 'get_funcs': get_funcs, + 'mesh_file': '../input_files/debug.bdf', + 'f5_writer': f5_writer} if use_modal: - tacs_setup['nmodes'] = 15 - #struct_assembler = ModalStructAssembler(tacs_setup) + tacs_options['nmodes'] = 15 + #struct_assembler = ModalStructAssembler(tacs_options) else: - struct_builder = TacsBuilder(tacs_setup, check_partials=True) + struct_builder = TacsBuilder(tacs_options, check_partials=True) - # MELD setup + struct_builder.initialize(self.comm) + ndv_struct = struct_builder.get_ndv() - meld_options = {'isym': 1, - 'n': 200, - 'beta': 0.5} + self.add_subsystem('mesh_struct', struct_builder.get_mesh_coordinate_subsystem()) + dvs.add_output('dv_struct', np.array(ndv_struct*[0.02])) - xfer_builder = MeldBuilder(meld_options,aero_builder,struct_builder,check_partials=True) - - # Multipoint group - dvs = self.add_subsystem('dvs',om.IndepVarComp(), promotes=['*']) + # MELD setup + isym = 1 + ldxfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym, check_partials=True) + ldxfer_builder.initialize(self.comm) - mp = self.add_subsystem( - 'mp_group', - Multipoint(aero_builder = aero_builder, - struct_builder = struct_builder, - xfer_builder = xfer_builder) - ) - s0 = mp.mphys_add_scenario('s0') + # Scenario + nonlinear_solver = om.NonlinearBlockGS( + maxiter=25, iprint=2, use_aitken=True, rtol=1e-14, atol=1e-14) + linear_solver = om.LinearBlockGS( + maxiter=25, iprint=2, use_aitken=True, rtol=1e-14, atol=1e-14) + self.mphys_add_scenario('cruise', ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + ldxfer_builder=ldxfer_builder), + nonlinear_solver, linear_solver) - def configure(self): - for dv_name in ['aoa','q_inf','vel','mu','mach']: - if dv_name == 'aoa': - self.dvs.add_output(dv_name, val=self.aero_options[dv_name], units='rad') - else: - self.dvs.add_output(dv_name, val=self.aero_options[dv_name]) - self.connect(dv_name, 'mp_group.s0.solver_group.aero.%s' % dv_name) + for discipline in ['aero', 'struct']: + self.mphys_connect_scenario_coordinate_source( + 'mesh_%s' % discipline, 'cruise', discipline) - self.dvs.add_output('dv_struct',np.array([0.03])) - self.connect('dv_struct',['mp_group.s0.solver_group.struct.dv_struct', - 'mp_group.s0.struct_funcs.dv_struct' ]) + for dv in ['aoa', 'q_inf', 'vel', 'mu', 'mach', 'dv_struct']: + self.connect(dv, f'cruise.{dv}') -# OpenMDAO setup prob = om.Problem() prob.model = Top() prob.setup(force_alloc_complex=True) - -prob.model.mp_group.s0.nonlinear_solver = om.NonlinearBlockGS(maxiter=100, iprint=2, use_aitken=True, atol=1E-9) -prob.model.mp_group.s0.linear_solver = om.LinearBlockGS(maxiter=10, iprint=2) +om.n2(prob) prob.run_model() -prob.check_partials(method='cs',compact_print=True) +prob.check_partials(method='cs', compact_print=True) diff --git a/tests/integration_tests/vlm_tacs_meld_check_totals.py b/tests/integration_tests/vlm_tacs_meld_check_totals.py index d69f34db..75e95949 100644 --- a/tests/integration_tests/vlm_tacs_meld_check_totals.py +++ b/tests/integration_tests/vlm_tacs_meld_check_totals.py @@ -4,7 +4,8 @@ import openmdao.api as om -from mphys.multipoint import Multipoint +from mphys import Multipoint +from mphys.scenario_aerostructural import ScenarioAeroStructural from mphys.mphys_vlm import VlmBuilder from mphys.mphys_tacs import TacsBuilder from mphys.mphys_meld import MeldBuilder @@ -13,130 +14,109 @@ use_modal = False -class Top(om.Group): +def add_elements(mesh): + rho = 2780.0 # density, kg/m^3 + E = 73.1e9 # elastic modulus, Pa + nu = 0.33 # poisson's ratio + kcorr = 5.0 / 6.0 # shear correction factor + ys = 324.0e6 # yield stress, Pa + thickness = 0.003 + min_thickness = 0.002 + max_thickness = 0.05 + + num_components = mesh.getNumComponents() + for i in range(num_components): + descript = mesh.getElementDescript(i) + stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, + min_thickness, max_thickness) + element = None + if descript in ['CQUAD', 'CQUADR', 'CQUAD4']: + element = elements.MITCShell(2, stiff, component_num=i) + mesh.setElement(i, element) + + ndof = 6 + ndv = num_components + + return ndof, ndv + + +def get_funcs(tacs): + ks_weight = 50.0 + return [functions.KSFailure(tacs, ks_weight), functions.StructuralMass(tacs)] + + +def f5_writer(tacs): + flag = (TACS.ToFH5.NODES | + TACS.ToFH5.DISPLACEMENTS | + TACS.ToFH5.STRAINS | + TACS.ToFH5.EXTRAS) + f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) + f5.writeToFile('wingbox.f5') + + +class Top(Multipoint): def setup(self): - # VLM options - aero_options = { - 'mesh_file':'../input_files/debug_VLM.dat', - 'mach':0.85, - 'aoa':1*np.pi/180., - 'q_inf':25000., - 'vel':178., - 'mu':3.5E-5, - } - - # VLM mesh read - def read_VLM_mesh(mesh): - f=open(mesh, "r") - contents = f.read().split() - - a = [i for i in contents if 'NODES' in i][0] - N_nodes = int(a[a.find("=")+1:a.find(",")]) - a = [i for i in contents if 'ELEMENTS' in i][0] - N_elements = int(a[a.find("=")+1:a.find(",")]) - - a = np.array(contents[16:16+N_nodes*3],'float') - X = a[0:N_nodes*3:3] - Y = a[1:N_nodes*3:3] - Z = a[2:N_nodes*3:3] - a = np.array(contents[16+N_nodes*3:None],'int') - quad = np.reshape(a,[N_elements,4]) - - xa = np.c_[X,Y,Z].flatten(order='C') - - f.close() - - return N_nodes, N_elements, xa, quad - - aero_options['N_nodes'], aero_options['N_elements'], aero_options['x_aero0'], aero_options['quad'] = read_VLM_mesh(aero_options['mesh_file']) - self.aero_options = aero_options - - # VLM builder - aero_builder = VlmBuilder(aero_options) - - # TACS setup - def add_elements(mesh): - rho = 2780.0 # density, kg/m^3 - E = 73.1e9 # elastic modulus, Pa - nu = 0.33 # poisson's ratio - kcorr = 5.0 / 6.0 # shear correction factor - ys = 324.0e6 # yield stress, Pa - thickness= 0.003 - min_thickness = 0.002 - max_thickness = 0.05 - - num_components = mesh.getNumComponents() - for i in range(num_components): - descript = mesh.getElementDescript(i) - stiff = constitutive.isoFSDT(rho, E, nu, kcorr, ys, thickness, i, - min_thickness, max_thickness) - element = None - if descript in ["CQUAD", "CQUADR", "CQUAD4"]: - element = elements.MITCShell(2,stiff,component_num=i) - mesh.setElement(i, element) - - ndof = 6 - ndv = num_components - - return ndof, ndv - - def get_funcs(tacs): - ks_weight = 50.0 - return [ functions.KSFailure(tacs,ks_weight), functions.StructuralMass(tacs)] - - def f5_writer(tacs): - flag = (TACS.ToFH5.NODES | - TACS.ToFH5.DISPLACEMENTS | - TACS.ToFH5.STRAINS | - TACS.ToFH5.EXTRAS) - f5 = TACS.ToFH5(tacs, TACS.PY_SHELL, flag) - f5.writeToFile('wingbox.f5') - - - # common setup options - tacs_setup = {'add_elements': add_elements, - 'get_funcs' : get_funcs, - 'mesh_file' : '../input_files/debug.bdf', - 'f5_writer' : f5_writer } - - # TACS assembler + # VLM + mesh_file = '../input_files/debug_VLM.dat' + mach = 0.85, + aoa = 1.0 + q_inf = 25000. + vel = 178. + mu = 3.5E-5 + + aero_builder = VlmBuilder(mesh_file) + aero_builder.initialize(self.comm) + + dvs = self.add_subsystem('dvs', om.IndepVarComp(), promotes=['*']) + dvs.add_output('aoa', val=aoa, units='deg') + dvs.add_output('mach', mach) + dvs.add_output('q_inf', q_inf) + dvs.add_output('vel', vel) + dvs.add_output('mu', mu) + + self.add_subsystem('mesh_aero', aero_builder.get_mesh_coordinate_subsystem()) + + # TACS + tacs_options = {'add_elements': add_elements, + 'get_funcs': get_funcs, + 'mesh_file': '../input_files/debug.bdf', + 'f5_writer': f5_writer} if use_modal: - tacs_setup['nmodes'] = 15 - #struct_assembler = ModalStructAssembler(tacs_setup) + tacs_options['nmodes'] = 15 + #struct_assembler = ModalStructAssembler(tacs_options) else: - struct_builder = TacsBuilder(tacs_setup, check_partials=True) + struct_builder = TacsBuilder(tacs_options, check_partials=True) - # MELD setup + struct_builder.initialize(self.comm) + ndv_struct = struct_builder.get_ndv() - meld_options = {'isym': 1, - 'n': 200, - 'beta': 0.5} + self.add_subsystem('mesh_struct', struct_builder.get_mesh_coordinate_subsystem()) + dvs.add_output('dv_struct', np.array(ndv_struct*[0.02])) - xfer_builder = MeldBuilder(meld_options,aero_builder,struct_builder,check_partials=True) + # MELD setup + isym = 1 + ldxfer_builder = MeldBuilder(aero_builder, struct_builder, isym=isym, check_partials=True) + ldxfer_builder.initialize(self.comm) - # Multipoint group - dvs = self.add_subsystem('dvs',om.IndepVarComp(), promotes=['*']) + # Scenario - mp = self.add_subsystem( - 'mp_group', - Multipoint(aero_builder = aero_builder, - struct_builder = struct_builder, - xfer_builder = xfer_builder) - ) - s0 = mp.mphys_add_scenario('s0') + #NOTE: use_aitken creates issues with complex step in check_totals + nonlinear_solver = om.NonlinearBlockGS( + maxiter=200, iprint=2, use_aitken=False, rtol=1e-14, atol=1e-14) + linear_solver = om.LinearBlockGS( + maxiter=200, iprint=2, use_aitken=False, rtol=1e-14, atol=1e-14) + self.mphys_add_scenario('cruise', ScenarioAeroStructural(aero_builder=aero_builder, + struct_builder=struct_builder, + ldxfer_builder=ldxfer_builder), + nonlinear_solver, linear_solver) - def configure(self): - for dv_name in ['aoa','q_inf','vel','mu','mach']: - if dv_name == 'aoa': - self.dvs.add_output(dv_name, val=self.aero_options[dv_name], units='rad') - else: - self.dvs.add_output(dv_name, val=self.aero_options[dv_name]) - self.connect(dv_name, 'mp_group.s0.solver_group.aero.%s' % dv_name) + for discipline in ['aero', 'struct']: + self.mphys_connect_scenario_coordinate_source( + 'mesh_%s' % discipline, 'cruise', discipline) - self.dvs.add_output('dv_struct',np.array([0.03])) - self.connect('dv_struct',['mp_group.s0.solver_group.struct.dv_struct', - 'mp_group.s0.struct_funcs.dv_struct' ]) + for dv in ['aoa', 'q_inf', 'vel', 'mu', 'mach', 'dv_struct']: + self.connect(dv, f'cruise.{dv}') # OpenMDAO setup @@ -145,10 +125,8 @@ def configure(self): prob.setup(force_alloc_complex=True, mode='rev') -prob.model.mp_group.s0.nonlinear_solver = om.NonlinearBlockGS(maxiter=100, iprint=2, use_aitken=True, atol=1E-9) -prob.model.mp_group.s0.linear_solver = om.LinearBlockGS(maxiter=10, iprint=2) - om.n2(prob, show_browser=False, outfile='check_totals.html') prob.run_model() -prob.check_totals(of=['mp_group.s0.struct_funcs.funcs.func_struct'], wrt=['aoa'], method='cs') +prob.check_totals(of=['cruise.func_struct'], wrt=['aoa'], method='cs') +#prob.check_totals(of=['cruise.C_L'], wrt=['aoa'], method='cs') From ff65ba598bd795a13dcc17bb54dd16b9ea033c61 Mon Sep 17 00:00:00 2001 From: Kevin Jacobson Date: Thu, 18 Mar 2021 20:58:38 -0400 Subject: [PATCH 49/82] Add some docs --- docs/source/_exts/__init__.py | 0 docs/source/_exts/embed_bibtex.py | 76 +++ docs/source/_exts/embed_code.py | 270 ++++++++ docs/source/_exts/embed_compare.py | 140 ++++ docs/source/_exts/embed_n2.py | 108 +++ docs/source/_exts/embed_options.py | 70 ++ docs/source/_exts/embed_shell_cmd.py | 162 +++++ .../source/_exts/link_class_from_docstring.py | 89 +++ docs/source/_exts/tags.py | 72 ++ docs/source/_theme/static/searchtools.js | 629 ++++++++++++++++++ docs/source/_theme/static/style.css | 222 +++++++ docs/source/_theme/theme.conf | 7 + .../{developers => basics}/builders.rst | 0 .../model_hierarchy.rst | 0 .../naming_conventions.rst | 2 +- docs/source/conf.py | 112 +++- docs/source/index.rst | 45 +- docs/source/scenarios/aerodynamic.rst | 15 + docs/source/scenarios/aerostructural.rst | 31 + docs/source/scenarios/structural.rst | 17 + mphys/scenario_structural.py | 37 +- 21 files changed, 2040 insertions(+), 64 deletions(-) create mode 100644 docs/source/_exts/__init__.py create mode 100644 docs/source/_exts/embed_bibtex.py create mode 100644 docs/source/_exts/embed_code.py create mode 100644 docs/source/_exts/embed_compare.py create mode 100644 docs/source/_exts/embed_n2.py create mode 100644 docs/source/_exts/embed_options.py create mode 100644 docs/source/_exts/embed_shell_cmd.py create mode 100644 docs/source/_exts/link_class_from_docstring.py create mode 100644 docs/source/_exts/tags.py create mode 100644 docs/source/_theme/static/searchtools.js create mode 100644 docs/source/_theme/static/style.css create mode 100644 docs/source/_theme/theme.conf rename docs/source/{developers => basics}/builders.rst (100%) rename docs/source/{developers => basics}/model_hierarchy.rst (100%) rename docs/source/{developers => basics}/naming_conventions.rst (97%) create mode 100644 docs/source/scenarios/aerodynamic.rst create mode 100644 docs/source/scenarios/aerostructural.rst create mode 100644 docs/source/scenarios/structural.rst diff --git a/docs/source/_exts/__init__.py b/docs/source/_exts/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/docs/source/_exts/embed_bibtex.py b/docs/source/_exts/embed_bibtex.py new file mode 100644 index 00000000..e14c254e --- /dev/null +++ b/docs/source/_exts/embed_bibtex.py @@ -0,0 +1,76 @@ + +import sys +import importlib + +from docutils import nodes + +import sphinx +from docutils.parsers.rst import Directive + +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.html5 import HTML5Translator +from sphinx.errors import SphinxError + + +class bibtex_node(nodes.Element): + pass + + +def visit_bibtex_node(self, node): + pass + + +def depart_bibtex_node(self, node): + """ + This function creates the formatting that sets up the look of the blocks. + The look of the formatting is controlled by _theme/static/style.css + """ + if not isinstance(self, (HTMLTranslator, HTML5Translator)): + self.body.append("output only available for HTML\n") + return + + html = """ +
+
{}
+
""".format(node["text"]) + + self.body.append(html) + + +class EmbedBibtexDirective(Directive): + """ + EmbedBibtexDirective is a custom directive to allow a Bibtex citation to be embedded. + + .. embed-bibtex:: + openmdao.solvers.linear.petsc_ksp + PETScKrylov + + + The 2 arguments are the module path and the class name. + + What the above will do is replace the directive and its args with the Bibtex citation + for the class. + + """ + + required_arguments = 2 + optional_arguments = 0 + has_content = True + + def run(self): + module_path, class_name = self.arguments + mod = importlib.import_module(module_path) + obj = getattr(mod, class_name)() + + if not hasattr(obj, 'cite') or not obj.cite: + raise SphinxError("Couldn't find 'cite' in class '%s'" % class_name) + + return [bibtex_node(text=obj.cite)] + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('embed-bibtex', EmbedBibtexDirective) + app.add_node(bibtex_node, html=(visit_bibtex_node, depart_bibtex_node)) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/embed_code.py b/docs/source/_exts/embed_code.py new file mode 100644 index 00000000..018eef2d --- /dev/null +++ b/docs/source/_exts/embed_code.py @@ -0,0 +1,270 @@ +import unittest +from docutils import nodes +from docutils.parsers.rst import Directive +import re +from sphinx.errors import SphinxError +import sphinx +import traceback +import inspect +import os + +from docutils.parsers.rst.directives import unchanged, images + +from openmdao.docs._utils.docutil import get_source_code, remove_docstrings, \ + remove_initial_empty_lines, replace_asserts_with_prints, \ + strip_header, dedent, insert_output_start_stop_indicators, run_code, \ + get_skip_output_node, get_interleaved_io_nodes, get_output_block_node, \ + split_source_into_input_blocks, extract_output_blocks, consolidate_input_blocks, node_setup, \ + strip_decorators + + +_plot_count = 0 + +plotting_functions = ['\.show\(', 'partial_deriv_plot\('] + + +class EmbedCodeDirective(Directive): + """ + EmbedCodeDirective is a custom directive to allow blocks of + python code to be shown in feature docs. An example usage would look like this: + + .. embed-code:: + openmdao.test.whatever.method + + What the above will do is replace the directive and its args with the block of code + for the class or method desired. + + By default, docstrings will be kept in the embedded code. There is an option + to the directive to strip the docstrings: + + .. embed-code:: + openmdao.test.whatever.method + :strip-docstrings: + """ + + # must have at least one directive for this to work + required_arguments = 1 + has_content = True + + option_spec = { + 'strip-docstrings': unchanged, + 'layout': unchanged, + 'scale': unchanged, + 'align': unchanged, + 'imports-not-required': unchanged, + } + + def run(self): + global _plot_count + + # + # error checking + # + allowed_layouts = set(['code', 'output', 'interleave', 'plot']) + + if 'layout' in self.options: + layout = [s.strip() for s in self.options['layout'].split(',')] + else: + layout = ['code'] + + if len(layout) > len(set(layout)): + raise SphinxError("No duplicate layout entries allowed.") + + bad = [n for n in layout if n not in allowed_layouts] + if bad: + raise SphinxError("The following layout options are invalid: %s" % bad) + + if 'interleave' in layout and ('code' in layout or 'output' in layout): + raise SphinxError("The interleave option is mutually exclusive to the code " + "and output options.") + + # + # Get the source code + # + path = self.arguments[0] + try: + source, indent, module, class_, method = get_source_code(path) + except Exception as err: + # Generally means the source couldn't be inspected or imported. + # Raise as a Directive warning (level 2 in docutils). + # This way, the sphinx build does not terminate if, for example, you are building on + # an environment where mpi or pyoptsparse are missing. + raise self.directive_error(2, str(err)) + + # + # script, test and/or plot? + # + is_script = path.endswith('.py') + + is_test = class_ is not None and inspect.isclass(class_) and issubclass(class_, unittest.TestCase) + + shows_plot = re.compile('|'.join(plotting_functions)).search(source) + + if 'plot' in layout: + plot_dir = os.getcwd() + plot_fname = 'doc_plot_%d.png' % _plot_count + _plot_count += 1 + + plot_file_abs = os.path.join(os.path.abspath(plot_dir), plot_fname) + if os.path.isfile(plot_file_abs): + # remove any existing plot file + os.remove(plot_file_abs) + + # + # Modify the source prior to running + # + if 'strip-docstrings' in self.options: + source = remove_docstrings(source) + + if is_test: + try: + source = dedent(source) + source = strip_decorators(source) + source = strip_header(source) + source = dedent(source) + source = replace_asserts_with_prints(source) + source = remove_initial_empty_lines(source) + + class_name = class_.__name__ + method_name = path.rsplit('.', 1)[1] + + # make 'self' available to test code (as an instance of the test case) + self_code = "from %s import %s\nself = %s('%s')\n" % \ + (module.__name__, class_name, class_name, method_name) + + # get setUp and tearDown but don't duplicate if it is the method being tested + setup_code = '' if method_name == 'setUp' else dedent(strip_header(remove_docstrings( + inspect.getsource(getattr(class_, 'setUp'))))) + + teardown_code = '' if method_name == 'tearDown' else dedent(strip_header( + remove_docstrings(inspect.getsource(getattr(class_, 'tearDown'))))) + + # for interleaving, we need to mark input/output blocks + if 'interleave' in layout: + interleaved = insert_output_start_stop_indicators(source) + code_to_run = '\n'.join([self_code, setup_code, interleaved, teardown_code]).strip() + else: + code_to_run = '\n'.join([self_code, setup_code, source, teardown_code]).strip() + except Exception: + err = traceback.format_exc() + raise SphinxError("Problem with embed of " + path + ": \n" + str(err)) + else: + if indent > 0: + source = dedent(source) + if 'interleave' in layout: + source = insert_output_start_stop_indicators(source) + code_to_run = source[:] + + # + # Run the code (if necessary) + # + skipped = failed = False + + if 'output' in layout or 'interleave' in layout or 'plot' in layout: + + imports_not_required = 'imports-not-required' in self.options + + if shows_plot: + # import matplotlib AFTER __future__ (if it's there) + mpl_import = "\nimport matplotlib\nmatplotlib.use('Agg')\n" + idx = code_to_run.find("from __future__") + idx = code_to_run.find('\n', idx) if idx >= 0 else 0 + code_to_run = code_to_run[:idx] + mpl_import + code_to_run[idx:] + + if 'plot' in layout: + code_to_run = code_to_run + ('\nmatplotlib.pyplot.savefig("%s")' % plot_file_abs) + + if is_test and getattr(method, '__unittest_skip__', False): + skipped = True + failed = False + run_outputs = method.__unittest_skip_why__ + else: + skipped, failed, run_outputs = run_code(code_to_run, path, module=module, cls=class_, + imports_not_required=imports_not_required, + shows_plot=shows_plot) + + # + # Handle output + # + if failed: + # Failed cases raised as a Directive warning (level 2 in docutils). + # This way, the sphinx build does not terminate if, for example, you are building on + # an environment where mpi or pyoptsparse are missing. + raise self.directive_error(2, run_outputs) + elif skipped: + # issue a warning unless it's about missing SNOPT when building a Travis pull request + PR = os.environ.get("TRAVIS_PULL_REQUEST") + if not (PR and PR != "false" and "pyoptsparse is not providing SNOPT" in run_outputs): + self.state_machine.reporter.warning(run_outputs) + + io_nodes = [get_skip_output_node(run_outputs)] + + else: + if 'output' in layout: + output_blocks = run_outputs if isinstance(run_outputs, list) else [run_outputs] + + elif 'interleave' in layout: + if is_test: + start = len(self_code) + len(setup_code) + end = len(code_to_run) - len(teardown_code) + input_blocks = split_source_into_input_blocks(code_to_run[start:end]) + else: + input_blocks = split_source_into_input_blocks(code_to_run) + + output_blocks = extract_output_blocks(run_outputs) + + # Merge any input blocks for which there is no corresponding output + # with subsequent input blocks that do have output + input_blocks = consolidate_input_blocks(input_blocks, output_blocks) + + if 'plot' in layout: + if not os.path.isfile(plot_file_abs): + raise SphinxError("Can't find plot file '%s'" % plot_file_abs) + + directive_dir = os.path.relpath(os.getcwd(), + os.path.dirname(self.state.document.settings._source)) + # this filename must NOT contain an absolute path, else the Figure will not + # be able to find the image file in the generated html dir. + plot_file = os.path.join(directive_dir, plot_fname) + + # create plot node + fig = images.Figure(self.name, [plot_file], self.options, self.content, self.lineno, + self.content_offset, self.block_text, self.state, + self.state_machine) + plot_nodes = fig.run() + + # + # create a list of document nodes to return based on layout + # + doc_nodes = [] + skip_fail_shown = False + for opt in layout: + if opt == 'code': + # we want the body of code to be formatted and code highlighted + body = nodes.literal_block(source, source) + body['language'] = 'python' + doc_nodes.append(body) + elif skipped: + if not skip_fail_shown: + body = nodes.literal_block(source, source) + body['language'] = 'python' + doc_nodes.append(body) + doc_nodes.extend(io_nodes) + skip_fail_shown = True + else: + if opt == 'interleave': + doc_nodes.extend(get_interleaved_io_nodes(input_blocks, output_blocks)) + elif opt == 'output': + doc_nodes.append(get_output_block_node(output_blocks)) + else: # plot + doc_nodes.extend(plot_nodes) + + return doc_nodes + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('embed-code', EmbedCodeDirective) + node_setup(app) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/embed_compare.py b/docs/source/_exts/embed_compare.py new file mode 100644 index 00000000..49363c44 --- /dev/null +++ b/docs/source/_exts/embed_compare.py @@ -0,0 +1,140 @@ +""" Sphinx directive for a side by side code comparison.""" + +from docutils import nodes + +import sphinx +from docutils.parsers.rst import Directive +from openmdao.docs._utils.docutil import get_source_code + + +class ContentContainerDirective(Directive): + """ + Just for having an outer div. + + Relevant CSS: rosetta_outer + """ + + has_content = True + optional_arguments = 1 + + def run(self): + self.assert_has_content() + text = '\n'.join(self.content) + node = nodes.container(text) + node['classes'].append('rosetta_outer') + + if self.arguments and self.arguments[0]: + node['classes'].append(self.arguments[0]) + + self.add_name(node) + self.state.nested_parse(self.content, self.content_offset, node) + return [node] + + +class EmbedCompareDirective(Directive): + """ + EmbedCompareDirective is a custom directive to allow blocks of + python code to be shown side by side to compare the new API with the old API. An + exmple looks like this: + + .. embed-compare:: + openmdao.test.whatever.method + optional text for searching for the first line + optional text for searching for the end line + optional style + + Old OpenMDAO lines of code go here. + + What the above will do is replace the directive and its args with the block of code + containing the class for method1 on the left and the class for method2 on the right. + + For optional styles, use 'style2' to use the alternate CSS style that has a light background on + both sides instead of red and green. Use 'no_compare' for straight code embedding without the + side-by-side comparison. (This is for pasting fragments of pre-tested code from a test.) + + Relevant CSS: rosetta_left and rosetta_right + """ + + # must have at least one directive for this to work + required_arguments = 1 + optional_arguments = 3 + has_content = True + + def run(self): + arg = self.arguments + compare = True + + # create a list of document nodes to return + doc_nodes = [] + + # Choose style + left_style = 'rosetta_left' + right_style = 'rosetta_right' + if len(arg) == 4: + if arg[3] == 'style2': + left_style = 'rosetta_left2' + right_style = 'rosetta_right2' + elif arg[3] == 'no_compare': + compare = False + + # LEFT side = Old OpenMDAO + if compare: + text = '\n'.join(self.content) + left_body = nodes.literal_block(text, text) + left_body['language'] = 'python' + left_body['classes'].append(left_style) + + # for RIGHT side, get the code block, and reduce it if requested + right_method = arg[0] + text, _, _, _, _ = get_source_code(right_method) + if len(arg) >= 3: + start_txt = arg[1] + end_txt = arg[2] + lines = text.split('\n') + + istart = 0 + for j, line in enumerate(lines): + if start_txt in line: + istart = j + break + + lines = lines[istart:] + iend = len(lines) + for j, line in enumerate(lines): + if end_txt in line: + iend = j+1 + break + + lines = lines[:iend] + + # Remove the check suppression. + for j, line in enumerate(lines): + if "prob.setup(check=False" in line: + lines[j] = lines[j].replace('check=False, ', '') + lines[j] = lines[j].replace('check=False', '') + + # prune whitespace down to match first line + while lines[0].startswith(' '): + lines = [line[4:] for line in lines] + + text = '\n'.join(lines) + + # RIGHT side = Current OpenMDAO + right_body = nodes.literal_block(text, text) + right_body['language'] = 'python' + if compare: + right_body['classes'].append(right_style) + + if compare: + doc_nodes.append(left_body) + doc_nodes.append(right_body) + + return doc_nodes + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('content-container', ContentContainerDirective) + app.add_directive('embed-compare', EmbedCompareDirective) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/embed_n2.py b/docs/source/_exts/embed_n2.py new file mode 100644 index 00000000..45fbab97 --- /dev/null +++ b/docs/source/_exts/embed_n2.py @@ -0,0 +1,108 @@ +from docutils import nodes +from docutils.statemachine import ViewList +from docutils.parsers.rst import Directive +import subprocess +import sphinx +from sphinx.util.nodes import nested_parse_with_titles +import os.path + + +class EmbedN2Directive(Directive): + """ + EmbedN2Directive is a custom directive to build and embed an N2 diagram into docs + An example usage would look like this: + + .. embed-n2:: + ../../examples/model.py + + What the above will do is replace the directive and its arg with an N2 diagram. + + The one required argument is the model file to be diagrammed. + Optional arguments are numerical width and height of the embedded object, and + "toolbar" if the toolbar should be visible by default. + + Example with width of 1500, height of 800, and toolbar displayed: + + .. embed-n2: + ../../examples/model.py + 1500 + 800 + toolbar + + """ + + required_arguments = 1 + optional_arguments = 3 + has_content = True + + def run(self): + path_to_model = self.arguments[0] + n2_dims = [ 1200, 700 ] + show_toolbar = False + + if len(self.arguments) > 1 and self.arguments[1]: + n2_dim_idx = 0 + for idx in range(1, len(self.arguments)): + if self.arguments[idx] == "toolbar": + show_toolbar = True + else: + n2_dims[n2_dim_idx] = self.arguments[idx] + n2_dim_idx = 1 + + np = os.path.normpath(os.path.join(os.getcwd(), path_to_model)) + + # check that the file exists + if not os.path.isfile(np): + raise IOError('File does not exist({0})'.format(np)) + + # Generate N2 files into the target_dir. Those files are later copied + # into the top of the HTML hierarchy, so the HTML doc file needs a + # relative path to them. + target_dir = os.path.join(os.getcwd(), "_n2html") + + rel_dir = os.path.relpath(os.getcwd(), + os.path.dirname(self.state.document.settings._source)) + html_base_name = os.path.basename(path_to_model).split('.')[0] + "_n2.html" + html_name = os.path.join(target_dir, html_base_name) + html_rel_name = os.path.join(rel_dir, html_base_name) + if show_toolbar: + html_rel_name += '#toolbar' + + cmd = subprocess.Popen( + ['openmdao', 'n2', np, '--no_browser', '--embed', '-o' + html_name]) + cmd_out, cmd_err = cmd.communicate() + + rst = ViewList() + + # Add the content one line at a time. + # Second argument is the filename to report in any warnings + # or errors, third argument is the line number. + env = self.state.document.settings.env + docname = env.doc2path(env.docname) + + object_tag = ( + "" + ) + + rst.append(".. raw:: html", docname, self.lineno) + rst.append("", docname, self.lineno) # leave an empty line + rst.append(" %s" % object_tag, docname, self.lineno) + + # Create a node. + node = nodes.section() + + # Parse the rst. + nested_parse_with_titles(self.state, rst, node) + + # And return the result. + return node.children + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('embed-n2', EmbedN2Directive) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/embed_options.py b/docs/source/_exts/embed_options.py new file mode 100644 index 00000000..be71a97a --- /dev/null +++ b/docs/source/_exts/embed_options.py @@ -0,0 +1,70 @@ + +import importlib + +from docutils import nodes +from docutils.statemachine import ViewList + +import sphinx +from docutils.parsers.rst import Directive +from sphinx.util.nodes import nested_parse_with_titles +from openmdao.utils.options_dictionary import OptionsDictionary + + +class EmbedOptionsDirective(Directive): + """ + EmbedOptionsDirective is a custom directive to allow an OptionsDictionary + to be shown in a nice table form. An example usage would look like this: + + .. embed-options:: + openmdao.solvers.linear.petsc_ksp + PETScKrylov + options + + The 3 arguments are the module path, the class name, and name of the options dictionary. + + What the above will do is replace the directive and its args with a list of options + for the desired class. + + """ + + required_arguments = 3 + optional_arguments = 0 + has_content = True + + def run(self): + module_path, class_name, attribute_name = self.arguments + + mod = importlib.import_module(module_path) + klass = getattr(mod, class_name) + options = getattr(klass(), attribute_name) + + if not isinstance(options, OptionsDictionary): + raise TypeError("Object '%s' is not an OptionsDictionary." % attribute_name) + + lines = ViewList() + + n = 0 + for line in options.__rst__(): + lines.append(line, "options table", n) + n += 1 + + # Note applicable to System, Solver and Driver 'options', but not to 'recording_options' + if attribute_name != 'recording_options': + lines.append("", "options table", n+1) # Blank line required after table. + + # Create a node. + node = nodes.section() + node.document = self.state.document + + # Parse the rst. + nested_parse_with_titles(self.state, lines, node) + + # And return the result. + return node.children + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('embed-options', EmbedOptionsDirective) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/embed_shell_cmd.py b/docs/source/_exts/embed_shell_cmd.py new file mode 100644 index 00000000..5f281b35 --- /dev/null +++ b/docs/source/_exts/embed_shell_cmd.py @@ -0,0 +1,162 @@ +""" +Sphinx directive to embed shell command output in the docs. + +The shell command is executed and the output is captured. +""" + +import sys +import os +from docutils import nodes +from docutils.parsers.rst.directives import unchanged + +import subprocess + +import sphinx +from docutils.parsers.rst import Directive +from sphinx.writers.html import HTMLTranslator +from sphinx.writers.html5 import HTML5Translator +from sphinx.errors import SphinxError + +import html as cgiesc + + +class failed_node(nodes.Element): + pass + + +def visit_failed_node(self, node): + pass + + +def depart_failed_node(self, node): + if not isinstance(self, (HTMLTranslator, HTML5Translator)): + self.body.append("output only available for HTML\n") + return + + html = """ +
+
+
+
{}
+
+
+
""".format(node["text"]) + self.body.append(html) + + +class cmd_node(nodes.Element): + pass + + +def visit_cmd_node(self, node): + pass + + +def depart_cmd_node(self, node): + """ + This function creates the formatting that sets up the look of the blocks. + The look of the formatting is controlled by _theme/static/style.css + """ + if not isinstance(self, (HTMLTranslator, HTML5Translator)): + self.body.append("output only available for HTML\n") + return + + html = """ +
+
{}
+
""".format(node["text"]) + + self.body.append(html) + + +class EmbedShellCmdDirective(Directive): + """ + EmbedShellCmdDirective is a custom directive to allow a shell command and the result + of running it to be shown in feature docs. + An example usage would look like this: + + .. embed-shell-cmd:: + :cmd: ls -ltr + + What the above will do is replace the directive and its args with the shell command, + run the command, and show the resulting output. + + """ + + # must have at least one arg (embedded test) for this to work + required_arguments = 0 + optional_arguments = 0 + has_content = False + + option_spec = { + 'cmd': unchanged, # shell command to execute + 'dir': unchanged, # working dir + 'show_cmd': unchanged, # set this to make the shell command visible + 'stderr': unchanged # set this to include stderr contents with the output + } + + def run(self): + """ + Create a list of document nodes to return. + """ + if 'cmd' in self.options: + cmdstr = self.options['cmd'] + cmd = cmdstr.split() + else: + raise SphinxError("'cmd' is not defined for embed-shell-cmd.") + + startdir = os.getcwd() + + if 'dir' in self.options: + workdir = os.path.abspath(os.path.expandvars(os.path.expanduser(self.options['dir']))) + else: + workdir = os.getcwd() + + if 'stderr' in self.options: + stderr = subprocess.STDOUT + else: + stderr = None + + os.chdir(workdir) + + try: + output = subprocess.check_output(cmd, stderr=subprocess.STDOUT,env=os.environ).decode('utf-8', 'ignore') + except subprocess.CalledProcessError as err: + # Failed cases raised as a Directive warning (level 2 in docutils). + # This way, the sphinx build does not terminate if, for example, you are building on + # an environment where mpi or pyoptsparse are missing. + msg = "Running of embedded shell command '{}' in docs failed. Output was: \n{}" \ + .format(cmdstr, err.output.decode('utf-8')) + raise self.directive_error(2, msg) + except Exception as err: + msg = "Running of embedded shell command '{}' in docs failed. Output was: \n{}" \ + .format(cmdstr, err) + raise self.directive_error(2, msg) + finally: + os.chdir(startdir) + + output = cgiesc.escape(output) + + show = True + if 'show_cmd' in self.options: + show = self.options['show_cmd'].lower().strip() == 'true' + + if show: + input_node = nodes.literal_block(cmdstr, cmdstr) + input_node['language'] = 'none' + + output_node = cmd_node(text=output) + + if show: + return [input_node, output_node] + else: + return [output_node] + + +def setup(app): + """add custom directive into Sphinx so that it is found during document parsing""" + app.add_directive('embed-shell-cmd', EmbedShellCmdDirective) + app.add_node(failed_node, html=(visit_failed_node, depart_failed_node)) + app.add_node(cmd_node, html=(visit_cmd_node, depart_cmd_node)) + + return {'version': sphinx.__display_version__, 'parallel_read_safe': True} diff --git a/docs/source/_exts/link_class_from_docstring.py b/docs/source/_exts/link_class_from_docstring.py new file mode 100644 index 00000000..ca251904 --- /dev/null +++ b/docs/source/_exts/link_class_from_docstring.py @@ -0,0 +1,89 @@ +# a short sphinx extension to take care of hyperlinking in docstrings +# where a syntax of is employed. +import openmdao +import pkgutil +import inspect +import re +from openmdao.docs.config_params import IGNORE_LIST + +# first, we will need a dict that contains full pathnames to every class. +# we construct that here, once, then use it for lookups in om_process_docstring +package = openmdao + +om_classes = {} + +def build_dict(): + global om_classes + for importer, modname, ispkg in pkgutil.walk_packages(path=package.__path__, + prefix=package.__name__ + '.', + onerror=lambda x: None): + if not ispkg: + if 'docs' not in modname: + if any(ignore in modname for ignore in IGNORE_LIST): + continue + module = importer.find_module(modname).load_module(modname) + for classname, class_object in inspect.getmembers(module, inspect.isclass): + if class_object.__module__.startswith("openmdao"): + om_classes[classname] = class_object.__module__ + "." + classname + + +def om_process_docstring(app, what, name, obj, options, lines): + """ + our process_docstring + """ + global om_classes + if not om_classes: + build_dict() + + for i in range(len(lines)): + # create a regex pattern to match + pat = r'(<.*?>)' + # find all matches of the pattern in a line + match = re.findall(pat, lines[i]) + if match: + for ma in match: + # strip off the angle brackets `<>` + m = ma[1:-1] + # to get rid of bad matches in OrderedDict.set_item + if m == "==": + continue + # if there's a dot in the pattern, it's a method + # e.g. + if '.' in m: + # need to grab the class name and method name separately + split_match = m.split('.') + justclass = split_match[0] # class + justmeth = split_match[1] # method + if justclass in om_classes: + classfullpath = om_classes[justclass] + # construct a link :meth:`class.method ` + link = ":meth:`" + m + " <" + classfullpath + "." + justmeth + ">`" + # replace the text with the constructed line. + lines[i] = lines[i].replace(ma, link) + else: + # the class isn't in the class table! + print("WARNING: {} not found in dictionary of OpenMDAO methods".format + (justclass)) + # replace instances of with just class in docstring + # (strip angle brackets) + lines[i] = lines[i].replace(ma, m) + # otherwise, it's a class + else: + if m in om_classes: + classfullpath = om_classes[m] + lines[i] = lines[i].replace(ma, ":class:`~" + classfullpath + "`") + else: + # the class isn't in the class table! + print("WARNING: {} not found in dictionary of OpenMDAO classes" + .format(m)) + # replace instances of with class in docstring + # (strip angle brackets) + lines[i] = lines[i].replace(ma, m) + + +# This is the crux of the extension--connecting an internal +# Sphinx event, "autodoc-process-docstring" with our own custom function. +def setup(app): + """ + """ + app.connect('autodoc-process-docstring', om_process_docstring) diff --git a/docs/source/_exts/tags.py b/docs/source/_exts/tags.py new file mode 100644 index 00000000..704baf2a --- /dev/null +++ b/docs/source/_exts/tags.py @@ -0,0 +1,72 @@ +# tag.py, this custom Sphinx extension is activated in conf.py +# and allows the use of the custom directive for tags in our rst (e.g.): +# .. tags:: tag1, tag2, tag3 +from docutils.parsers.rst import Directive + +from docutils.parsers.rst.directives.admonitions import Admonition +from docutils import nodes +from sphinx.locale import _ + + +# The setup function for the Sphinx extension +def setup(app): + # This adds a new node class to build sys, with custom functs, (same name as file) + app.add_node(tag, html=(visit_tag_node, depart_tag_node)) + # This creates a new ".. tags:: " directive in Sphinx + app.add_directive('tags', TagDirective) + # These are event handlers, functions connected to events. + app.connect('doctree-resolved', process_tag_nodes) + app.connect('env-purge-doc', purge_tags) + # Identifies the version of our extension + return {'version': '0.1'} + + +def visit_tag_node(self, node): + self.visit_admonition(node) + + +def depart_tag_node(self, node): + self.depart_admonition(node) + + +def purge_tags(app, env, docname): + return + + +def process_tag_nodes(app, doctree, fromdocname): + env = app.builder.env + + +class tag (nodes.Admonition, nodes.Element): + pass + + +class TagDirective(Directive): + # This allows content in the directive, e.g. to list tags here + has_content = True + + def run(self): + env = self.state.document.settings.env + targetid = "tag-%d" % env.new_serialno('tag') + targetnode = nodes.target('', '', ids=[targetid]) + + # The tags fetched from the custom directive are one piece of text + # sitting in self.content[0] + taggs = self.content[0].split(", ") + links = [] + + for tagg in taggs: + # Create Sphinx doc refs of format :ref:`Tagname` + link = ":ref:`" + tagg + "<" + tagg + ">`" + links.append(link) + # Put links back in a single comma-separated string together + linkjoin = ", ".join(links) + + # Replace content[0] with hyperlinks to display in admonition + self.content[0] = linkjoin + + ad = Admonition(self.name, [_('Tags')], self.options, + self.content, self.lineno, self.content_offset, + self.block_text, self.state, self.state_machine) + + return [targetnode] + ad.run() diff --git a/docs/source/_theme/static/searchtools.js b/docs/source/_theme/static/searchtools.js new file mode 100644 index 00000000..81b18e5b --- /dev/null +++ b/docs/source/_theme/static/searchtools.js @@ -0,0 +1,629 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2019 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + * Modified for OpenMDAO: + * - option to include/exclude source docs (default is to exclude) + * - if source docs are included, sort them to the end of the results with a separator + * - if source docs are included, only include one result for each filename + * + */ +if (!Scorer) { + /** + * Simple result scoring code. + */ + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [filename, title, anchor, descr, score] + // and returns the new score. + + score: function(result) { + // relevance: non-source > source > test_suite + // modifiers: -30 -60 + filename = result[0]; + if (filename.indexOf("test_suite") >= 0) + return result[4] - 60; + else if (filename.startsWith("_srcdocs")) + return result[4] - 30; + else + return result[4]; + }, + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5 // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2 + }; +} + +function splitQuery(query) { + var result = []; + var start = -1; + var quote = false; + + for (var i = 0; i < query.length; i++) { + if (query.charAt(i) == '"') { + if (quote) { + // closing quote + result.push(query.slice(start+1, i)); + start = -1; + quote = false + } + else { + // opening quote + quote = true; + start = i; + } + } + else if (!quote && splitChars[query.charCodeAt(i)]) { + if (start !== -1) { + result.push(query.slice(start, i)); + start = -1; + } + } + else if (start === -1) { + start = i; + } + } + + if (start !== -1) { + result.push(query.slice(start)); + } + + return result; +} + +/** + * Search Module + */ +var Search = { + + _index: null, + _queued_query: null, + _pulse_status: -1, + _search_source: false, + + htmlToText: function(htmlString) { + var htmlElement = document.createElement('span'); + htmlElement.innerHTML = htmlString; + $(htmlElement).find('.headerlink').remove(); + docContent = $(htmlElement).find('[role=main]')[0]; + return docContent.textContent || docContent.innerText; + }, + + init: function() { + var params = $.getQueryParameters(); + + if (params.search_source) { + this._search_source = true + $('input[name="search_source"]')[0].checked = true; + } + + if (params.q) { + var query = params.q[0]; + $('input[name="q"]')[0].value = query; + this.performSearch(query); + } + }, + + loadIndex: function(url) { + $.ajax({ + type: "GET", + url: url, + data: null, + dataType: "script", + cache: true, + complete: function(jqxhr, textstatus) { + if (textstatus != "success") { + document.getElementById("searchindexloader").src = url; + } + } + }); + }, + + setIndex: function(index) { + var q; + this._index = index; + if ((q = this._queued_query) !== null) { + this._queued_query = null; + Search.query(q); + } + }, + + hasIndex: function() { + return this._index !== null; + }, + + deferQuery: function(query) { + this._queued_query = query; + }, + + stopPulse: function() { + this._pulse_status = 0; + }, + + startPulse: function() { + if (this._pulse_status >= 0) + return; + + function pulse() { + var i; + Search._pulse_status = (Search._pulse_status + 1) % 4; + var dotString = ''; + for (i = 0; i < Search._pulse_status; i++) + dotString += '.'; + Search.dots.text(dotString); + if (Search._pulse_status > -1) + window.setTimeout(pulse, 500); + } + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: function(query) { + // create the required interface elements + this.out = $('#search-results'); + this.title = $('

' + _('Searching') + '

').appendTo(this.out); + this.dots = $('').appendTo(this.title); + this.status = $('

 

').appendTo(this.out); + this.output = $('