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:

  1. identify any unconnected inputs (forgetting to connect things is one of the most common mistakes).

  2. look for any cycles in your model that indicate the need for solvers (did you mean to create that cycle?).

  3. 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.

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.000088 sec).
INFO: checking auto_ivc_warnings...
INFO:     auto_ivc_warnings check complete (0.000002 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.000373 sec).
INFO: checking dup_inputs...
INFO:     dup_inputs check complete (0.000020 sec).
INFO: checking missing_recorders...
WARNING: The Problem has no recorder of any kind attached
INFO:     missing_recorders check complete (0.000036 sec).
INFO: checking out_of_order...
INFO:     out_of_order check complete (0.000120 sec).
INFO: checking promotions...
INFO:     promotions check complete (0.000016 sec).
INFO: checking solvers...
INFO:     solvers check complete (0.000076 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.000044 sec).

This output tells you several things:

  1. You have an unconnected input: cycle.d1.y2

  2. 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.