Sanity Checking Your Model#
In the first two tutorials we showed you the basics of how to build up a model from a set of components, group them together, connect them together, and optimize them.
Sometimes you put your model together and things don’t work quite the way you would expect. When this happens, OpenMDAO has a number of debugging features to help you understand the structure of your model better and sort out the issue. Many debugging features are all accessed via a command line script that is installed along with OpenMDAO itself. There are a lot of different tools that are accessible from that script, but in this tutorial we’ll focus on the most important one: check setup.
Check Setup#
Check setup runs through a host of different tests to make sure your model is setup correctly and warn you about things that commonly cause problems. It will:
identify any unconnected inputs (forgetting to connect things is one of the most common mistakes).
look for any cycles in your model that indicate the need for solvers (did you mean to create that cycle?).
recurse down the model hierarchy and give every group and component a chance to perform its own custom checks.
For example, if you tried to build the sellar problem using connections, but forgot to issue one of the connections then your problem wouldn’t run correctly and you’d get the wrong answer.
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['maxiter'] = 100
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'.
If you are in colab, the shell command will not find the file because it is a single notebook without the included file.
!openmdao check -c all sellar.py
INFO: checking all_unserializable_options...
INFO: all_unserializable_options check complete (0.000083 sec).
INFO: checking auto_ivc_warnings...
INFO: auto_ivc_warnings check complete (0.000001 sec).
INFO: checking comp_has_no_outputs...
INFO: comp_has_no_outputs check complete (0.000015 sec).
INFO: checking cycles...
INFO: cycles check complete (0.000364 sec).
INFO: checking dup_inputs...
INFO: dup_inputs check complete (0.000021 sec).
INFO: checking missing_recorders...
WARNING: The Problem has no recorder of any kind attached
INFO: missing_recorders check complete (0.000060 sec).
INFO: checking out_of_order...
INFO: out_of_order check complete (0.000126 sec).
INFO: checking promotions...
INFO: promotions check complete (0.000014 sec).
INFO: checking solvers...
INFO: solvers check complete (0.000072 sec).
INFO: checking system...
INFO: system check complete (0.000007 sec).
INFO: checking unconnected_inputs...
WARNING: The following inputs are not connected:
cycle.d1.y2 (cycle.d1.y2)
INFO: unconnected_inputs check complete (0.000030 sec).
This output tells you several things:
You have an unconnected input:
cycle.d1.y2
There are no reported cycles in your model, but there should be because this is supposed to be a coupled model!
Whenever you encounter a problem, before you look at anything else you should always run this check first and look over the output carefully.