Adding Subsystems to a Group and Promoting Variables#

To add a Component or another Group to a Group, use the add_subsystem method.

Group.add_subsystem(name, subsys, promotes=None, promotes_inputs=None, promotes_outputs=None, min_procs=1, max_procs=None, proc_weight=1.0, proc_group=None)[source]

Add a subsystem.

Parameters:
namestr

Name of the subsystem being added.

subsys<System>

An instantiated, but not-yet-set up system object.

promotesiter of (str or tuple), optional

A list of variable names specifying which subsystem variables to ‘promote’ up to this group. If an entry is a tuple of the form (old_name, new_name), this will rename the variable in the parent group.

promotes_inputsiter of (str or tuple), optional

A list of input variable names specifying which subsystem input variables to ‘promote’ up to this group. If an entry is a tuple of the form (old_name, new_name), this will rename the variable in the parent group.

promotes_outputsiter of (str or tuple), optional

A list of output variable names specifying which subsystem output variables to ‘promote’ up to this group. If an entry is a tuple of the form (old_name, new_name), this will rename the variable in the parent group.

min_procsint

Minimum number of MPI processes usable by the subsystem. Defaults to 1.

max_procsint or None

Maximum number of MPI processes usable by the subsystem. A value of None (the default) indicates there is no maximum limit.

proc_weightfloat

Weight given to the subsystem when allocating available MPI processes to all subsystems. Default is 1.0.

proc_groupstr or None

Name of a processor group such that any system with that processor group name within the same parent group will be allocated on the same mpi process(es). If this is not None, then any other systems sharing the same proc_group must have identical values of min_procs, max_procs, and proc_weight or an exception will be raised.

Returns:
<System>

The subsystem that was passed in. This is returned to enable users to instantiate and add a subsystem at the same time, and get the reference back.

Usage#

Add a Component to a Group#

import openmdao.api as om

p = om.Problem()
p.model.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))

p.setup();
print(p.get_val('comp1.a'))
print(p.get_val('comp1.b'))
[3.]
[6.]

Note

Group names must be Pythonic, so they can only contain alphanumeric characters plus the underscore. In addition, the first character in the group name must be a letter of the alphabet. Also, the system name should not duplicate any method or attribute of the System API.

Promote the input and output of a Component#

Because the promoted names of indep.a and comp.a are the same, indep.a is automatically connected to comp1.a.

Note

Inputs are always accessed using unpromoted names even when they are promoted, because promoted input names may not be unique. The unpromoted name is the full system path to the variable from the point of view of the calling system. Accessing the variables through the Problem as in this example means that the unpromoted name and the full or absolute pathname are the same.

p = om.Problem()
p.model.add_subsystem('indep', om.IndepVarComp('a', 3.0),
                      promotes_outputs=['a'])
p.model.add_subsystem('comp1', om.ExecComp('b=2.0*a'),
                      promotes_inputs=['a'])

p.setup()
p.run_model() 

print(p.get_val('a'))
print(p.get_val('comp1.b'))
[3.]
[6.]

Add two Components to a Group nested within another Group#

p = om.Problem()
p.model.add_subsystem('G1', om.Group())
p.model.G1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))
p.model.G1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0))

p.setup()
print(p.get_val('G1.comp1.a'))
print(p.get_val('G1.comp1.b'))
print(p.get_val('G1.comp2.a'))
print(p.get_val('G1.comp2.b'))
[3.]
[6.]
[4.]
[12.]

Promote the input and output of Components to subgroup level#

In this example, there are two inputs promoted to the same name, so the promoted name G1.a is not unique.

# promotes from bottom level up 1
p = om.Problem()
g1 = p.model.add_subsystem('G1', om.Group())
g1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0),
                 promotes_inputs=['a'], promotes_outputs=['b'])
g1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0),
                 promotes_inputs=['a'])
g1.set_input_defaults('a', val=3.5)
p.setup()

# output G1.comp1.b is promoted
print(p.get_val('G1.b'))
# output G1.comp2.b is not promoted
print(p.get_val('G1.comp2.b'))

# use unpromoted names for the following 2 promoted inputs
print(p.get_val('G1.comp1.a'))
print(p.get_val('G1.comp2.a'))
[6.]
[12.]
[3.5]
[3.5]

Promote the input and output of Components from subgroup level up to top level#

# promotes up from G1 level
p = om.Problem()
g1 = om.Group()
g1.add_subsystem('comp1', om.ExecComp('b=2.0*a', a=3.0, b=6.0))
g1.add_subsystem('comp2', om.ExecComp('b=3.0*a', a=4.0, b=12.0))

# use glob pattern 'comp?.a' to promote both comp1.a and comp2.a
# use glob pattern 'comp?.b' to promote both comp1.b and comp2.b
p.model.add_subsystem('G1', g1,
                      promotes_inputs=['comp?.a'],
                      promotes_outputs=['comp?.b'])
p.setup()

# output G1.comp1.b is promoted
print(p.get_val('comp1.b'), 6.0)
# output G1.comp2.b is promoted
print(p.get_val('comp2.b'), 12.0)

# access both promoted inputs using unpromoted names.
print(p.get_val('G1.comp1.a'), 3.0)
print(p.get_val('G1.comp2.a'), 4.0)
[6.] 6.0
[12.] 12.0
[3.] 3.0
[4.] 4.0

Promote with an alias to connect an input to a source#

p = om.Problem()
p.model.add_subsystem('indep', om.IndepVarComp('aa', 3.0),
                      promotes=['aa'])
p.model.add_subsystem('comp1', om.ExecComp('b=2.0*aa'),
                      promotes_inputs=['aa'])

# here we alias 'a' to 'aa' so that it will be automatically
# connected to the independent variable 'aa'.
p.model.add_subsystem('comp2', om.ExecComp('b=3.0*a'),
                      promotes_inputs=[('a', 'aa')])

p.setup()
p.run_model()

print(p.get_val('comp1.b'))
print(p.get_val('comp2.b'))
[6.]
[9.]

Promote Inputs and Outputs After Adding Subsystems#

It is also possible to promote inputs and outputs after a subsystem has been added to a Group using the promotes method.

Group.promotes(subsys_name, any=None, inputs=None, outputs=None, src_indices=None, flat_src_indices=None, src_shape=None)[source]

Promote a variable in the model tree.

Parameters:
subsys_namestr

The name of the child subsystem whose inputs/outputs are being promoted.

anySequence of str or tuple

A Sequence of variable names (or tuples) to be promoted, regardless of if they are inputs or outputs. This is equivalent to the items passed via the promotes= argument to add_subsystem. If given as a tuple, we use the “promote as” standard of “(‘real name’, ‘promoted name’)*[]:”.

inputsSequence of str or tuple

A Sequence of input names (or tuples) to be promoted. Tuples are used for the “promote as” capability.

outputsSequence of str or tuple

A Sequence of output names (or tuples) to be promoted. Tuples are used for the “promote as” capability.

src_indicesint or list of ints or tuple of ints or int ndarray or Iterable or None

This argument applies only to promoted inputs. The global indices of the source variable to transfer data from. A value of None implies this input depends on all entries of source. Default is None. The shapes of the target and src_indices must match, and form of the entries within is determined by the value of ‘flat_src_indices’.

flat_src_indicesbool

This argument applies only to promoted inputs. If True, each entry of src_indices is assumed to be an index into the flattened source. Otherwise each entry must be a tuple or list of size equal to the number of dimensions of the source.

src_shapeint or tuple

Assumed shape of any connected source or higher level promoted input.

Usage#

Promote any subsystem inputs and outputs from the configure function#

class SimpleGroup(om.Group):

    def setup(self):
        self.add_subsystem('comp1', om.IndepVarComp('x', 5.0))
        self.add_subsystem('comp2', om.ExecComp('b=2*a'))

    def configure(self):
        self.promotes('comp1', any=['*'])

top = om.Problem(model=SimpleGroup())
top.setup()

print(top.get_val('x'))
[5.]

Promote specific inputs and outputs from the configure function#

class SimpleGroup(om.Group):

    def setup(self):
        self.add_subsystem('comp1', om.IndepVarComp('x', 5.0))
        self.add_subsystem('comp2', om.ExecComp('b=2*a'))

    def configure(self):
        self.promotes('comp2', inputs=['a'], outputs=['b'])

top = om.Problem(model=SimpleGroup())
top.setup()

print(top.get_val('a'))
print(top.get_val('b'))
[1.]
[1.]

Specifying source shape and source indices for promoted inputs of a group#

The arg src_shape can be passed to promotes or set_input_defaults calls in order to specify the shape of the source that the input is expecting. This allows an output having a different shape to be connected to an input by specifying src_indices in the connect or promotes call, even if there are other src_indices specified at lower levels in the system tree for the same input(s). This basically allows you to specify the ‘connection interface’ for a given Group, making it easier to use that Group in other models without having to modify its internal src_indices based on the shape of whatever sources are connected to its inputs in a given model.

Note that if multiple inputs are promoted to the same name then their src_shape must match, but their src_indices may be different.

Below is an example of applying multiple src_indices to the same promoted input at different system tree levels.

import numpy as np 

p = om.Problem()
G = p.model.add_subsystem('G', om.Group())

# At the top level, we assume that the source has a shape of (3,3), and after we
# slice it with [:,:-1], lower levels will see their source having a shape of (3,2)
p.model.promotes('G', inputs=['x'], src_indices=om.slicer[:,:-1], src_shape=(3, 3))

# This specifies that G.x assumes a source shape of (3,2)
G.set_input_defaults('x', src_shape=(3, 2))

g1 = G.add_subsystem('g1', om.Group(), promotes_inputs=['x'])
g1.add_subsystem('C1', om.ExecComp('y = 3*x', shape=3))

# C1.x has a shape of 3, so we apply a slice of [:, 1] to our source which has a shape
# of (3,2) to give us our final shape of 3.
g1.promotes('C1', inputs=['x'], src_indices=om.slicer[:, 1], src_shape=(3, 2))

g2 = G.add_subsystem('g2', om.Group(), promotes_inputs=['x'])
g2.add_subsystem('C2', om.ExecComp('y = 2*x', shape=2))

# C2.x has a shape of 2, so we apply flat source indices of [1,5] to our source which has
# a shape of (3,2) to give us our final shape of 2.
g2.promotes('C2', inputs=['x'], src_indices=[1, 5], src_shape=(3, 2), flat_src_indices=True)

p.setup()

inp = np.arange(9).reshape((3,3)) + 1.

p.set_val('x', inp)
p.run_model()

print(p['x'])
print(p['G.g1.C1.y'])
print(p['G.g2.C2.y'])
[[1. 2. 3.]
 [4. 5. 6.]
 [7. 8. 9.]]
[ 6. 15. 24.]
[ 4. 16.]