Linking Variables with Promotion vs. Connection#

In the previous tutorial, we built up a model of the Sellar problem using two disciplinary components and a few ExecComps. In order to get OpenMDAO to pass the data between all the components, we linked everything up using promoted variables so that data passed from outputs to inputs with the same promoted name.

Promoting variables is often a convenient way to establish the data passing links from outputs to inputs. However, you can also use calls to the connect method in order to link outputs to inputs without having to promote anything. Here is how you would define the same Sellar model using:

  1. Variable promotion

  2. Connect statements

  3. Both variable promotion and connect statements

All three will give the exact same answer, but the way you address the variables will be slightly different in each one.

Variable Promotion#

Input and output variables can be promoted when a subsystem is added:

import numpy as np
import openmdao.api as om

from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2

class SellarMDA(om.Group):
    """
    Group containing the Sellar MDA.
    """

    def setup(self):
        cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])
        cycle.add_subsystem('d1', SellarDis1(),
                            promotes_inputs=['x', 'z', 'y2'],
                            promotes_outputs=['y1'])
        cycle.add_subsystem('d2', SellarDis2(),
                            promotes_inputs=['z', 'y1'],
                            promotes_outputs=['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=['x', 'z', 'y1', 'y2', 'obj'])

        self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'),
                           promotes=['con1', 'y1'])
        self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'),
                           promotes=['con2', 'y2'])


prob = om.Problem()
prob.model = SellarMDA()

prob.setup()

prob.set_val('x', 2.0)
prob.set_val('z', [-1., -1.])

prob.run_model()
=====
cycle
=====
NL: NLBGS Converged in 10 iterations

Alternatively, variables can be promoted when a group is configured:

import numpy as np

from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2

class SellarMDA(om.Group):
    """
    Group containing the Sellar MDA.
    """

    def setup(self):
        # set up model hierarchy
        cycle = self.add_subsystem('cycle', om.Group())
        cycle.add_subsystem('d1', SellarDis1())
        cycle.add_subsystem('d2', SellarDis2())

        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))

        self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'))
        self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'))

    def configure(self):
        # connect everything via promotes
        self.cycle.promotes('d1', inputs=['x', 'z', 'y2'], outputs=['y1'])
        self.cycle.promotes('d2', inputs=['z', 'y1'], outputs=['y2'])

        self.promotes('cycle', any=['*'])

        self.promotes('obj_cmp', any=['x', 'z', 'y1', 'y2', 'obj'])
        self.promotes('con_cmp1', any=['con1', 'y1'])
        self.promotes('con_cmp2', any=['con2', 'y2'])


prob = om.Problem()
prob.model = SellarMDA()

prob.setup()

prob.set_val('x', 2.0)
prob.set_val('z', [-1., -1.])

prob.run_model()
=====
cycle
=====
NL: NLBGS Converged in 10 iterations

There are a few important details to note:

  • The promoted name of an output has to be unique within that level of the hierarchy (i.e. you can’t have two outputs with the same name)

  • You are allowed to have multiple inputs promoted to the same name.

  • You can use glob patterns to promote lots of variables without specifying them all, but try to limit your usage of promotes=['*']. Though it may seem like a convenient way to do things, it can make it difficult for other people who are reading your code to understand which variables are connected to each other. It is acceptable to use promotes=['*'] in cases where it won’t cause confusion, for example with cycle, which only exists to allow for the nonlinear solver to converge the two components. Another example of when it would be safe to use promotes=['*'] would be if you have ExecComps that make it clear what the I/O of that component is anyway.

Note

For a more detailed set of examples for how to promote variables, check out the feature doc on adding sub-systems to a group.There are some more advanced things you can do, such as variable name aliasing and connecting a sub-set of indices from the output array of one component to the input of another.

Connect Statements#

The exact same model results can be achieved using connect statements instead of promotions. However, take careful note of how the variables are addressed in those connect and print statements.

import numpy as np

from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2

class SellarMDAConnect(om.Group):
    """
    Group containing the Sellar MDA. This version uses the disciplines without derivatives.
    """

    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')
        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.setup()

prob.set_val('x', 2.0)
prob.set_val('z', [-1., -1.])

prob.run_model()
=====
cycle
=====
NL: NLBGS Converged in 10 iterations

Variable Promotion and Connect Statements#

It is also possible to combine promotion and connection in a single model. Here, notice that we do not have to add “cycle” in front of anything, because we promoted all the variables up from that group.

import numpy as np

from openmdao.test_suite.components.sellar import SellarDis1, SellarDis2

class SellarMDAPromoteConnect(om.Group):
    """
    Group containing the Sellar MDA. This version uses the disciplines without derivatives.
    """

    def setup(self):
        cycle = self.add_subsystem('cycle', om.Group(), promotes=['*'])
        cycle.add_subsystem('d1', SellarDis1(),
                            promotes_inputs=['x', 'z'])
        cycle.add_subsystem('d2', SellarDis2(),
                            promotes_inputs=['z'])
        cycle.connect('d1.y1', 'd2.y1')
        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('d1.y1', ['con_cmp1.y1', 'obj_cmp.y1'])
        self.connect('d2.y2', ['con_cmp2.y2', 'obj_cmp.y2'])


prob = om.Problem()
prob.model = SellarMDAPromoteConnect()

prob.setup()

prob.set_val('x', 2.0)
prob.set_val('z', [-1., -1.])

prob.run_model()
=====
cycle
=====
NL: NLBGS Converged in 10 iterations