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:466: 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
/usr/share/miniconda/envs/test/lib/python3.11/pty.py:89: RuntimeWarning: os.fork() was called. os.fork() is incompatible with multithreaded code, and JAX is multithreaded, so this will likely lead to a deadlock.
  pid, fd = os.forkpty()
INFO: checking all_unserializable_options
INFO: checking auto_ivc_warnings
INFO: checking comp_has_no_outputs
INFO: checking cycles
INFO: checking dup_inputs
INFO: checking missing_recorders
WARNING: The Problem has no recorder of any kind attached
INFO: checking out_of_order
INFO: checking promotions
INFO: checking solvers
INFO: checking system
INFO: checking unconnected_inputs
WARNING: The following inputs are not connected:
  cycle.d1.y2 (cycle.d1.y2)

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.