Visualizing The Structure of Your Model#
OpenMDAO models can have deep hierarchies of groups and components, with many connections between them. Often times, it is helpful to visualize the model structure. OpenMDAO provides a model visualization tool that is accessible via the openmdao command line tool.
For example, if you had built the Sellar problem with a missing connection, and you figured out that there was a missing connection by running check setup, then you might want to take a closer look at the model to help figure out what’s going on.
SellarDis1
class definition
class SellarDis1(om.ExplicitComponent):
"""
Component containing Discipline 1 -- no derivatives version.
"""
def __init__(self, units=None, scaling=None):
super().__init__()
self.execution_count = 0
self._units = units
self._do_scaling = scaling
def setup(self):
if self._units:
units = 'ft'
else:
units = None
if self._do_scaling:
ref = .1
else:
ref = 1.
# Global Design Variable
self.add_input('z', val=np.zeros(2), units=units)
# Local Design Variable
self.add_input('x', val=0., units=units)
# Coupling parameter
self.add_input('y2', val=1.0, units=units)
# Coupling output
self.add_output('y1', val=1.0, lower=0.1, upper=1000., units=units, ref=ref)
def setup_partials(self):
# Finite difference everything
self.declare_partials('*', '*', method='fd')
def compute(self, inputs, outputs):
"""
Evaluates the equation
y1 = z1**2 + z2 + x1 - 0.2*y2
"""
z1 = inputs['z'][0]
z2 = inputs['z'][1]
x1 = inputs['x']
y2 = inputs['y2']
outputs['y1'] = z1**2 + z2 + x1 - 0.2*y2
self.execution_count += 1
SellarDis2
class definition
class SellarDis2(om.ExplicitComponent):
"""
Component containing Discipline 2 -- no derivatives version.
"""
def __init__(self, units=None, scaling=None):
super().__init__()
self.execution_count = 0
self._units = units
self._do_scaling = scaling
def setup(self):
if self._units:
units = 'inch'
else:
units = None
if self._do_scaling:
ref = .18
else:
ref = 1.
# Global Design Variable
self.add_input('z', val=np.zeros(2), units=units)
# Coupling parameter
self.add_input('y1', val=1.0, units=units)
# Coupling output
self.add_output('y2', val=1.0, lower=0.1, upper=1000., units=units, ref=ref)
def setup_partials(self):
# Finite difference everything
self.declare_partials('*', '*', method='fd')
def compute(self, inputs, outputs):
"""
Evaluates the equation
y2 = y1**(.5) + z1 + z2
"""
z1 = inputs['z'][0]
z2 = inputs['z'][1]
y1 = inputs['y1']
# Note: this may cause some issues. However, y1 is constrained to be
# above 3.16, so lets just let it converge, and the optimizer will
# throw it out
if y1.real < 0.0:
y1 *= -1
outputs['y2'] = y1**.5 + z1 + z2
self.execution_count += 1
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2
class SellarMDAConnect(om.Group):
def setup(self):
cycle = self.add_subsystem('cycle', om.Group(), promotes_inputs=['x', 'z'])
cycle.add_subsystem('d1', SellarDis1(), promotes_inputs=['x', 'z'])
cycle.add_subsystem('d2', SellarDis2(), promotes_inputs=['z'])
cycle.connect('d1.y1', 'd2.y1')
######################################
# This is a "forgotten" connection!!
######################################
#cycle.connect('d2.y2', 'd1.y2')
cycle.set_input_defaults('x', 1.0)
cycle.set_input_defaults('z', np.array([5.0, 2.0]))
# Nonlinear Block Gauss Seidel is a gradient free solver
cycle.nonlinear_solver = om.NonlinearBlockGS()
self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',
z=np.array([0.0, 0.0]), x=0.0),
promotes_inputs=['x', 'z'])
self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'))
self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'))
self.connect('cycle.d1.y1', ['obj_cmp.y1', 'con_cmp1.y1'])
self.connect('cycle.d2.y2', ['obj_cmp.y2', 'con_cmp2.y2'])
prob = om.Problem()
prob.model = SellarMDAConnect()
prob.driver = om.ScipyOptimizeDriver()
prob.driver.options['optimizer'] = 'SLSQP'
prob.driver.options['tol'] = 1e-8
prob.set_solver_print(level=0)
prob.model.add_design_var('x', lower=0, upper=10)
prob.model.add_design_var('z', lower=0, upper=10)
prob.model.add_objective('obj_cmp.obj')
prob.model.add_constraint('con_cmp1.con1', upper=0)
prob.model.add_constraint('con_cmp2.con2', upper=0)
prob.setup()
prob.set_val('x', 2.0)
prob.set_val('z', [-1., -1.])
prob.run_driver()
print('minimum found at')
print(prob.get_val('x')[0])
print(prob.get_val('z'))
print('minumum objective')
print(prob.get_val('obj_cmp.obj')[0])
Optimization terminated successfully (Exit mode 0)
Current function value: 3.1870337508695563
Iterations: 9
Function evaluations: 10
Gradient evaluations: 9
Optimization Complete
-----------------------------------
minimum found at
0.0
[1.83303028 0. ]
minumum objective
3.1870337508695563
/usr/share/miniconda/envs/test/lib/python3.11/site-packages/openmdao/core/driver.py:689: DriverWarning:The following design variable initial conditions are out of their specified bounds:
z
val: [-1. -1.]
lower: 0.0
upper: 10.0
Set the initial value of the design variable to a valid value or set the driver option['invalid_desvar_behavior'] to 'ignore'.
You can generate a visualization of the model in \(N^2\) form with the following command:
openmdao n2 sellar.py
This diagram is a version of a design-structure matrix, with the model hierarchy displayed on the left side. On the diagonal is each input and output of each of the components. Off-diagonal blocks indicate data connections. Feed-forward connections are shown in the upper triangle, and feed-back connections (the kind that cause cycles) are shown in the lower triangle. If you hover over any of the blocks on the diagonal, the incoming and outgoing connections are highlighted with arrows.
The unconnected input mentioned above, cycle.d1.y2
, is highlighted in red to immediately draw attention to a possible problem.
You can also see that it’s a member of the cycle group (in this case, that was obvious by its name, but with variable promotion that will not always be the case).
Furthermore, you can see that the variable you would want to connect cycle.d1.y1
to, cycle.d2.y2
, is in the same group.
The lack of cycles in the model is made visually evident by the lack of any connections in the lower triangle of the diagram.
Collapse groups down by right-clicking on them, and zoom in and out of different parts of the hierarchy by left-clicking. These controls help show that the \(N^2\) diagram is a powerful tool for letting you inspect and understand large, complex models.
The right side of the diagram shows the solver hierarchy. Initially, it shows the linear solvers but by clicking on
the Toggle Solver Names
button in the toolbar, the names of the nonlinear solvers can be displayed.