Implicit Relationships with SubsystemsΒΆ

Residual variables may depend on the result of a subsystem. For example, the solution to a quadratic equation depends on the coefficients, but one of those coefficients may not be constant, and may depend on a subsystem.

In this example, we solve \(ax^2+bx+c=0\), but \(a\) is a fixed point of \((3 + a - 2a^2)^\frac{1}{4}\).

In order to compute \(a\), omtools creates a new openmdao.Problem instance within an ImplicitComponent. The Problem instance contains a model, which is an instance of ot.Group. The ImplicitComponent class provides acces to this ot.Group object with self.group. Residuals that depend on subsystems may be defined with calls to self.group.add_subsystem.

Subsystems added by calling ``self.group.add_subsystem`` are part of the residual(s), not the ``ImplicitComponent``.

All inputs to the ``ImplicitComponent`` must be declared using calls to ``self.group.declare_input`` at the beginning of ``self.setup``, before any calls to ``self.group.add_subsystem``.

In this example, the only input is c. Both a and b are outputs of subsystems used to define the residual.

from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np


class ExampleWithSubsystems(ImplicitComponent):
    def setup(self):
        g = self.group

        # define a subsystem (this is a very simple example)
        group = Group()
        p = group.create_indep_var('p', val=7)
        q = group.create_indep_var('q', val=8)
        r = p + q
        group.register_output('r', r)

        # add child system
        g.add_subsystem('R', group, promotes=['*'])
        # declare output of child system as input to parent system
        r = g.declare_input('r')

        c = g.declare_input('c', val=18)

        # a == (3 + a - 2 * a**2)**(1 / 4)
        group = Group()
        a = group.create_output('a')
        a.define((3 + a - 2 * a**2)**(1 / 4))
        group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)
        g.add_subsystem('coeff_a', group, promotes=['*'])

        a = g.declare_input('a')

        group = Group()
        group.create_indep_var('b', val=-4)
        g.add_subsystem('coeff_b', group, promotes=['*'])

        b = g.declare_input('b')
        y = g.create_implicit_output('y')
        z = a * y**2 + b * y + c - r
        y.define_residual(z)
        self.linear_solver = ScipyKrylov()
        self.nonlinear_solver = NewtonSolver(
            solve_subsystems=False,
            maxiter=100,
        )


prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystems())
prob.setup(force_alloc_complex=True)
prob.run_model()

============
comp_example
============
NL: Newton Converged in 3 iterations

Note that calls to ImplicitComponent.add_subsystem result in adding a subsystem to the internal Problem instance (not shown), and not the ImplicitComponent itself.

Here is the n2 diagram generated by passing n2=True to the constructor for ImplicitOutput to verify that the model structure for the residual is correct.

Just as with residuals that do not require subsystems to converge, bracketing solutions is an option as well.

from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np


class ExampleWithSubsystemsBracketedScalar(ImplicitComponent):
    def setup(self):
        g = self.group

        # define a subsystem (this is a very simple example)
        group = Group()
        p = group.create_indep_var('p', val=7)
        q = group.create_indep_var('q', val=8)
        r = p + q
        group.register_output('r', r)

        # add child system
        g.add_subsystem('R', group, promotes=['*'])
        # declare output of child system as input to parent system
        r = g.declare_input('r')

        c = g.declare_input('c', val=18)

        # a == (3 + a - 2 * a**2)**(1 / 4)
        with g.create_group('coeff_a') as group:
            a = group.create_output('a')
            a.define((3 + a - 2 * a**2)**(1 / 4))
            group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)

        a = g.declare_input('a')

        with g.create_group('coeff_b') as group:
            group.create_indep_var('b', val=-4)

        b = g.declare_input('b')
        y = g.create_implicit_output('y')
        z = a * y**2 + b * y + c - r
        y.define_residual_bracketed(z, x1=0, x2=2)


prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystemsBracketedScalar())
prob.setup(force_alloc_complex=True)
prob.run_model()

print('y', prob['y'].shape)
print(prob['y'])
y (1,)
[1.07440944]

Brackets may also be specified for multidimensional array values.

from openmdao.api import Problem
from openmdao.api import ScipyKrylov, NewtonSolver, NonlinearBlockGS
from omtools.api import Group, ImplicitComponent
import numpy as np


class ExampleWithSubsystemsBracketedArray(ImplicitComponent):
    def setup(self):
        g = self.group

        # define a subsystem (this is a very simple example)
        group = Group()
        p = group.create_indep_var('p', val=[7, -7])
        q = group.create_indep_var('q', val=[8, -8])
        r = p + q
        group.register_output('r', r)

        # add child system
        g.add_subsystem('R', group, promotes=['*'])
        # declare output of child system as input to parent system
        r = g.declare_input('r', shape=(2, ))

        c = g.declare_input('c', val=[18, -18])

        # a == (3 + a - 2 * a**2)**(1 / 4)
        with g.create_group('coeff_a') as group:
            a = group.create_output('a')
            a.define((3 + a - 2 * a**2)**(1 / 4))
            group.nonlinear_solver = NonlinearBlockGS(iprint=0, maxiter=100)

        # store positive and negative values of `a` in an array
        ap = g.declare_input('a')
        an = -ap
        a = g.create_output('vec_a', shape=(2, ))
        a[0] = ap
        a[1] = an

        with g.create_group('coeff_b') as group:
            group.create_indep_var('b', val=[-4, 4])

        b = g.declare_input('b', shape=(2, ))
        y = g.create_implicit_output('y', shape=(2, ))
        z = a * y**2 + b * y + c - r
        y.define_residual_bracketed(
            z,
            x1=[0, 2.],
            x2=[2, np.pi],
        )


prob = Problem()
prob.model = Group()
prob.model.add_subsystem('example', ExampleWithSubsystemsBracketedArray())
prob.setup(force_alloc_complex=True)
prob.run_model()

print('y', prob['y'].shape)
print(prob['y'])
y (2,)
[1.07440944 2.48391993]