Simple Explicit Expressions with Subsystems

In addition to generating Component objects from expressions, omtools supports adding subsystems to a Group class as in OpenMDAO. This means that OpenMDAO System objects, including omtools.Group objects, can be added to a model built with omtools.

Note that, while omtools determines execution order of Component objects, the user is responsible for calling Group.add_subsystem after the parent Group’s outputs are registered, and before the parent Group’s inputs are declared.

In this example, 'subsystem' declares 'x1' as an input, so the user must create an 'x1' output in the parent Group prior to the call to Group.add_subsystem in order to update the input values in 'sys'.

Likewise, if the parent Group is to use an output registered in 'subsystem', such as 'x2', then the user must call Group.declare_input after Group.add_subsystem for that variable.

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


class ExampleWithSubsystems(Group):
    def setup(self):
        # Create independent variable
        x1 = self.create_indep_var('x1', val=40)

        # Powers
        y4 = x1**2

        # Create subsystem that depends on previously created
        # independent variable
        subgroup = Group()

        # This value is overwritten by connection from the main group
        a = subgroup.declare_input('x1', val=2)
        b = subgroup.create_indep_var('x2', val=12)
        subgroup.register_output('prod', a * b)
        self.add_subsystem('subsystem', subgroup, promotes=['*'])

        # declare inputs with default values
        # This value is overwritten by connection
        # from the subgroup
        x2 = self.declare_input('x2', val=3)

        # Simple addition
        y1 = x2 + x1
        self.register_output('y1', y1)

        # Simple subtraction
        self.register_output('y2', x2 - x1)

        # Simple multitplication
        self.register_output('y3', x1 * x2)

        # Powers
        y5 = x2**2

        # register outputs in reverse order to how they are defined
        self.register_output('y5', y5)
        self.register_output('y6', y1 + y5)
        self.register_output('y4', y4)


prob = Problem()
prob.model = ExampleWithSubsystems()
prob.setup(force_alloc_complex=True)
prob.run_model()

print('prod', prob['prod'].shape)
print(prob['prod'])
print('y1', prob['y1'].shape)
print(prob['y1'])
print('y2', prob['y2'].shape)
print(prob['y2'])
print('y3', prob['y3'].shape)
print(prob['y3'])
print('y4', prob['y4'].shape)
print(prob['y4'])
print('y5', prob['y5'].shape)
print(prob['y5'])
print('y6', prob['y6'].shape)
print(prob['y6'])
prod (1,)
[480.]
y1 (1,)
[52.]
y2 (1,)
[-28.]
y3 (1,)
[480.]
y4 (1,)
[1600.]
y5 (1,)
[144.]
y6 (1,)
[196.]

Below is an n2 diagram for a Group with simple binary expressions and a subsystem. The Component objects added to the model are guaranteed to be connected such that there are no unnecessary feedbacks, regardless of the order in which each output is defined or registered.