Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tweaks for remote components #173

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/basics/remote_components.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Troubleshooting
---------------
The :code:`dump_json` option for :code:`RemoteZeroMQComp` will make the component write input and output JSON files, which contain all data sent to and received from the server.
An exception is the :code:`wall_time` entry (given in seconds) in the output JSON file, which is added on the client-side after the server has completed the design evaluation.
Similarly, the :code:`down_time` entry keeps track of the elapsed time between the end of the previous design evaluation and the beginning of the current one.
Another entry that is only provided for informational purposes is :code:`design_counter`, which keeps track of how many different designs have been evaluated on the current server.
If :code:`dump_separate_json` is set to True, then separate files will be written for each design evaluation.
On the server side, an n2 file titled :code:`n2_inner_analysis_<component name>.html` will be written after each evaluation.
Expand Down
37 changes: 37 additions & 0 deletions examples/aerostructural/supersonic_panel/README
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
Summary of top-level codes:

run.py: most basic code that demonstrates a single scenario, doing a derivative check at the starting design
run_parallel.py: code that demonstrates two scenarios (differing by Mach and dynamic pressure), evaluated in parallel with MultipointParallel, doing a derivative check at the starting design
as_opt_parallel.py: runs an optimization of the previous code's two scenarios (mass minimization subject to lift and stress constraints at the two flight conditions)
as_opt_remote_serial.py: runs the same optimization using one remote component that evaluates the MultipointParallel group in as_opt_parallel.py
as_opt_remote_parallel.py: runs the same optimization using two parallel remote components, which each evaluates the Multipoint analysis in run.py

The optimizations should complete with the following metrics (with C_L being lift coefficient and func_struct being an aggregated von Mises stress).
Note that objective and constraint names can vary slightly based on the optimization script.

Design Vars
{'aoa': array([10.52590682, 18.2314054 ]),
'dv_struct': array([0.0001 , 0.0001 , 0.0001 , 0.0001 , 0.0001 ,
0.0001 , 0.0001 , 0.0001 , 0.0001 , 0.00010421,
0.00010883, 0.00011221, 0.00011371, 0.00011452, 0.0001133 ,
0.00010892, 0.00010359, 0.0001 , 0.0001 , 0.0001 ]),
'geometry_morph_param': array([0.1])}

Nonlinear constraints
{'multipoint.aerostructural1.C_L': array([0.15]),
'multipoint.aerostructural1.func_struct': array([1.00000023]),
'multipoint.aerostructural2.C_L': array([0.45]),
'multipoint.aerostructural2.func_struct': array([1.00000051])}

Objectives
{'multipoint.aerostructural1.mass': array([8.73298752e-05])}

Optimization terminated successfully (Exit mode 0)
Current function value: 0.008732987524877025
Iterations: 22
Function evaluations: 24
Gradient evaluations: 22

Note that the remote scripts, which both use mphys_server.py to launch the HPC job used for the analyses, are set up to use the K4 queue of NASA Langley's K cluster.
To run this script on an HPC not supported by pbs4py, you will likely have to write your own pbs4py Launcher constructor.
Further details on remote components may be found on the document page on remote components: https://openmdao.github.io/mphys/basics/remote_components.html
39 changes: 19 additions & 20 deletions examples/aerostructural/supersonic_panel/aerodynamics_mphys.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,27 @@

from piston_theory import PistonTheory

X_AERO0_MESH = MPhysVariables.Aerodynamics.Surface.Mesh.COORDINATES
X_AERO = MPhysVariables.Aerodynamics.Surface.COORDINATES
F_AERO = MPhysVariables.Aerodynamics.Surface.LOADS

# IVC which returns a baseline mesh
class AeroMesh(om.IndepVarComp):
def initialize(self):
self.options.declare('x_aero0')
def setup(self):
self.x_aero0_name = MPhysVariables.Aerodynamics.Surface.COORDINATES_INITIAL
self.add_output(self.x_aero0_name, val=self.options['x_aero0'], distributed=True, tags=['mphys_coordinates'])
self.add_output(X_AERO0_MESH, val=self.options['x_aero0'], distributed=True, tags=['mphys_coordinates'])


# IC which computes aero pressures
class AeroSolver(om.ImplicitComponent):
def initialize(self):
self.options.declare('solver')

self.x_aero_name = MPhysVariables.Aerodynamics.Surface.COORDINATES

def setup(self):
self.solver = self.options['solver']

self.add_input(self.x_aero_name, shape_by_conn=True, distributed=True, tags=['mphys_coordinates'])
self.add_input(X_AERO, shape_by_conn=True, distributed=True, tags=['mphys_coordinates'])
self.add_input('aoa', 0., units = 'deg', tags=['mphys_input'])
self.add_input('qdyn', 0., tags=['mphys_input'])
self.add_input('mach', 0., tags=['mphys_input'])
Expand All @@ -38,15 +39,15 @@ def setup(self):

def solve_nonlinear(self,inputs,outputs):

self.solver.xyz = inputs[self.x_aero_name]
self.solver.xyz = inputs[X_AERO]
self.solver.aoa = inputs['aoa']
self.solver.qdyn = inputs['qdyn']
self.solver.mach = inputs['mach']

outputs['pressure'] = self.solver.compute_pressure()

def apply_nonlinear(self,inputs,outputs,residuals):
self.solver.xyz = inputs[self.x_aero_name]
self.solver.xyz = inputs[X_AERO]
self.solver.aoa = inputs['aoa']
self.solver.qdyn = inputs['qdyn']
self.solver.mach = inputs['mach']
Expand All @@ -69,8 +70,8 @@ def apply_linear(self,inputs,outputs,d_inputs,d_outputs,d_residuals,mode):
adjoint=d_residuals['pressure']
)

if self.x_aero_name in d_inputs:
d_inputs[self.x_aero_name] += d_xa
if X_AERO in d_inputs:
d_inputs[X_AERO] += d_xa
if 'aoa' in d_inputs:
d_inputs['aoa'] += d_aoa
if 'qdyn' in d_inputs:
Expand All @@ -85,30 +86,28 @@ def initialize(self):
self.options.declare('solver')

def setup(self):
self.x_aero_name = MPhysVariables.Aerodynamics.Surface.COORDINATES
self.f_aero_name = MPhysVariables.Aerodynamics.Surface.LOADS

self.solver = self.options['solver']

self.add_input(self.x_aero_name, shape_by_conn=True, distributed=True, tags=['mphys_coordinates'])
self.add_input(X_AERO, shape_by_conn=True, distributed=True, tags=['mphys_coordinates'])
self.add_input('pressure', shape_by_conn=True, distributed=True, tags=['mphys_coupling'])
self.add_output(self.f_aero_name, np.zeros(self.solver.n_nodes*self.solver.n_dof), distributed=True, tags=['mphys_coupling'])
self.add_output(F_AERO, np.zeros(self.solver.n_nodes*self.solver.n_dof), distributed=True, tags=['mphys_coupling'])

def compute(self,inputs,outputs):
self.solver.xyz = inputs[self.x_aero_name]
self.solver.xyz = inputs[X_AERO]
self.solver.pressure = inputs['pressure']

outputs[self.f_aero_name] = self.solver.compute_force()
outputs[F_AERO] = self.solver.compute_force()

def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
if mode == 'rev':
if self.f_aero_name in d_outputs:
if F_AERO in d_outputs:
d_xa, d_p = self.solver.compute_force_derivatives(
adjoint=d_outputs[self.f_aero_name]
adjoint=d_outputs[F_AERO]
)

if self.x_aero_name in d_inputs:
d_inputs[self.x_aero_name] += d_xa
if X_AERO in d_inputs:
d_inputs[X_AERO] += d_xa
if 'pressure' in d_inputs:
d_inputs['pressure'] += d_p

Expand Down Expand Up @@ -205,4 +204,4 @@ def get_number_of_nodes(self):
return self.solver.n_nodes

def get_ndof(self):
return self.soler.n_dof
return self.solver.n_dof
27 changes: 15 additions & 12 deletions examples/aerostructural/supersonic_panel/as_opt_parallel.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,25 @@ def initialize(self):
self.options.declare('scenario_names')

def setup(self):
for i in range(len(self.options['scenario_names'])):

for scenario_name in self.options['scenario_names']:

# create the run directory
if self.comm.rank==0:
if not os.path.isdir(self.options['scenario_names'][i]):
os.mkdir(self.options['scenario_names'][i])
if not os.path.isdir(scenario_name):
os.mkdir(scenario_name)
self.comm.Barrier()

nonlinear_solver = om.NonlinearBlockGS(maxiter=100, iprint=2, use_aitken=True, aitken_initial_factor=0.5)
linear_solver = om.LinearBlockGS(maxiter=40, iprint=2, use_aitken=True, aitken_initial_factor=0.5)
self.mphys_add_scenario(self.options['scenario_names'][i],
self.mphys_add_scenario(scenario_name,
ScenarioAeroStructural(
aero_builder=self.options['aero_builder'],
struct_builder=self.options['struct_builder'],
ldxfer_builder=self.options['xfer_builder'],
geometry_builder=self.options['geometry_builder'],
in_MultipointParallel=True,
run_directory=self.options['scenario_names'][i]),
run_directory=scenario_name),
coupling_nonlinear_solver=nonlinear_solver,
coupling_linear_solver=linear_solver)

Expand Down Expand Up @@ -98,12 +99,14 @@ def setup(self):
geometry_builder = GeometryBuilder(builders)

# add parallel multipoint group
self.add_subsystem('multipoint',AerostructParallel(
aero_builder=aero_builder,
struct_builder=struct_builder,
xfer_builder=xfer_builder,
geometry_builder=geometry_builder,
scenario_names=self.scenario_names))
self.add_subsystem('multipoint',
AerostructParallel(
aero_builder=aero_builder,
struct_builder=struct_builder,
xfer_builder=xfer_builder,
geometry_builder=geometry_builder,
scenario_names=self.scenario_names)
)

for i in range(len(self.scenario_names)):

Expand Down Expand Up @@ -173,7 +176,7 @@ def get_model(scenario_names):
prob.cleanup()

if prob.model.comm.rank==0: # write out data
cr = om.CaseReader("optimization_history.sql")
cr = om.CaseReader(f"{prob.get_outputs_dir()}/optimization_history.sql")
driver_cases = cr.list_cases('driver')

case = cr.get_case(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ def setup(self):

class TopLevelGroup(om.Group):
def setup(self):
if self.comm.size!=2:
raise SystemError('Please launch with 2 processors')

# IVCs that feed into both parallel groups
self.add_subsystem('ivc', om.IndepVarComp(), promotes=['*'])

Expand Down Expand Up @@ -114,7 +111,7 @@ def setup(self):

# write out data
if prob.model.comm.rank==0:
cr = om.CaseReader("optimization_history_parallel.sql")
cr = om.CaseReader(f"{prob.get_outputs_dir()}/optimization_history_parallel.sql")
driver_cases = cr.list_cases('driver')

case = cr.get_case(0)
Expand Down Expand Up @@ -142,5 +139,6 @@ def setup(self):
f.write(str(j) + ' ' + ' '.join(map(str,cr.get_case(case_id).get_design_vars(scaled=False)[k])) + '\n')
f.write(' ' + '\n')

# shutdown each rank's server
eval(f'prob.model.multipoint.remote_scenario{prob.model.comm.rank}.stop_server()')
# shutdown the servers
prob.model.multipoint.remote_scenario0.stop_server()
prob.model.multipoint.remote_scenario1.stop_server()
69 changes: 35 additions & 34 deletions examples/aerostructural/supersonic_panel/as_opt_remote_serial.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,40 +54,41 @@
prob.cleanup()

# write out data
cr = om.CaseReader("optimization_history.sql")
driver_cases = cr.list_cases('driver')

case = cr.get_case(0)
cons = case.get_constraints()
dvs = case.get_design_vars()
objs = case.get_objectives()

with open("optimization_history.dat","w+") as f:

for i, k in enumerate(objs.keys()):
f.write('objective: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + str(cr.get_case(case_id).get_objectives(scaled=False)[k][0]) + '\n')
if prob.model.comm.rank==0:
cr = om.CaseReader(f"{prob.get_outputs_dir()}/optimization_history.sql")
driver_cases = cr.list_cases('driver')

case = cr.get_case(0)
cons = case.get_constraints()
dvs = case.get_design_vars()
objs = case.get_objectives()

with open("optimization_history.dat","w+") as f:

for i, k in enumerate(objs.keys()):
f.write('objective: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + str(cr.get_case(case_id).get_objectives(scaled=False)[k][0]) + '\n')
f.write(' ' + '\n')

for i, k in enumerate(cons.keys()):
f.write('constraint: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + ' '.join(map(str,cr.get_case(case_id).get_constraints(scaled=False)[k])) + '\n')
f.write(' ' + '\n')

for i, k in enumerate(dvs.keys()):
f.write('DV: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + ' '.join(map(str,cr.get_case(case_id).get_design_vars(scaled=False)[k])) + '\n')
f.write(' ' + '\n')

f.write('run times, function\n')
for i in range(len(prob.model.remote.times_function)):
f.write(f'{prob.model.remote.times_function[i]}\n')
f.write(' ' + '\n')

for i, k in enumerate(cons.keys()):
f.write('constraint: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + ' '.join(map(str,cr.get_case(case_id).get_constraints(scaled=False)[k])) + '\n')
f.write('run times, gradient\n')
for i in range(len(prob.model.remote.times_gradient)):
f.write(f'{prob.model.remote.times_gradient[i]}\n')
f.write(' ' + '\n')

for i, k in enumerate(dvs.keys()):
f.write('DV: ' + k + '\n')
for j, case_id in enumerate(driver_cases):
f.write(str(j) + ' ' + ' '.join(map(str,cr.get_case(case_id).get_design_vars(scaled=False)[k])) + '\n')
f.write(' ' + '\n')

f.write('run times, function\n')
for i in range(len(prob.model.remote.times_function)):
f.write(f'{prob.model.remote.times_function[i]}\n')
f.write(' ' + '\n')

f.write('run times, gradient\n')
for i in range(len(prob.model.remote.times_gradient)):
f.write(f'{prob.model.remote.times_gradient[i]}\n')
f.write(' ' + '\n')
26 changes: 18 additions & 8 deletions examples/aerostructural/supersonic_panel/geometry_morph.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
import numpy as np
import openmdao.api as om
from mpi4py import MPI
from mphys import Builder
from mphys import Builder, MPhysVariables


X_AERO0_GEOM_INPUT = MPhysVariables.Aerodynamics.Surface.Geometry.COORDINATES_INPUT
X_AERO0_GEOM_OUTPUT = MPhysVariables.Aerodynamics.Surface.Geometry.COORDINATES_OUTPUT

X_STRUCT_GEOM_INPUT = MPhysVariables.Structures.Geometry.COORDINATES_INPUT
X_STRUCT_GEOM_OUTPUT = MPhysVariables.Structures.Geometry.COORDINATES_OUTPUT

# EC which morphs the geometry
class GeometryMorph(om.ExplicitComponent):
def initialize(self):
self.options.declare('names')
self.options.declare('n_nodes')

self.input_names = {'aero': X_AERO0_GEOM_INPUT, 'struct': X_STRUCT_GEOM_INPUT}
self.output_names = {'aero': X_AERO0_GEOM_OUTPUT, 'struct': X_STRUCT_GEOM_OUTPUT}

def setup(self):
self.add_input('geometry_morph_param')

for name, n_nodes in zip(self.options['names'], self.options['n_nodes']):
self.add_input(f'x_{name}_in', distributed=True, shape_by_conn=True)
self.add_output(f'x_{name}0', shape=n_nodes*3, distributed=True, tags=['mphys_coordinates'])
self.add_input(self.input_names[name], distributed=True, shape_by_conn=True, tags=['mphys_coordinates'])
self.add_output(self.output_names[name], shape=n_nodes*3, distributed=True, tags=['mphys_coordinates'])

def compute(self,inputs,outputs):
for name in self.options['names']:
outputs[f'x_{name}0'] = inputs['geometry_morph_param']*inputs[f'x_{name}_in']
outputs[self.output_names[name]] = inputs['geometry_morph_param']*inputs[self.input_names[name]]

def compute_jacvec_product(self, inputs, d_inputs, d_outputs, mode):
if mode == 'rev':
for name in self.options['names']:
if f'x_{name}0' in d_outputs:
if self.output_names[name] in d_outputs:
if 'geometry_morph_param' in d_inputs:
d_inputs['geometry_morph_param'] += self.comm.allreduce(np.sum(d_outputs[f'x_{name}0']*inputs[f'x_{name}_in']), op=MPI.SUM)
d_inputs['geometry_morph_param'] += self.comm.allreduce(np.sum(d_outputs[self.output_names[name]]*inputs[self.input_names[name]]), op=MPI.SUM)

if f'x_{name}_in' in d_inputs:
d_inputs[f'x_{name}_in'] += d_outputs[f'x_{name}0']*inputs['geometry_morph_param']
if self.input_names[name] in d_inputs:
d_inputs[self.input_names[name]] += d_outputs[self.output_names[name]]*inputs['geometry_morph_param']


# Builder
Expand Down
Loading
Loading