Connecting Variables#

To cause data to flow between two systems in a model, we must connect at least one output variable from one system to at least one input variable from the other. If the variables have units defined, then the framework will automatically perform the conversion. We can also connect only part of an array output to an input by specifying the indices of the entries that we want.

To connect two variables within a model, use the connect function.

Group.connect(src_name, tgt_name, src_indices=None, flat_src_indices=None)[source]

Connect source src_name to target tgt_name in this namespace.

Parameters:
src_namestr

Name of the source variable to connect.

tgt_namestr or [str, … ] or (str, …)

Name of the target variable(s) to connect.

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

The global indices of the source variable to transfer data from. 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

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

Usage#

1: Connect an output variable to an input variable, with an automatic unit conversion.

import numpy as np
import openmdao.api as om

p = om.Problem()

p.model.set_input_defaults('x', np.ones(5), units='ft')

exec_comp = om.ExecComp('y=sum(x)',
                        x={'val': np.zeros(5), 'units': 'inch'},
                        y={'units': 'inch'})

p.model.add_subsystem('comp1', exec_comp, promotes_inputs=['x'])

p.setup()
p.run_model()

print(p.get_val('x', units='ft'))
print(p.get_val('comp1.x'))
print(p.get_val('comp1.y'))
[1. 1. 1. 1. 1.]
[12. 12. 12. 12. 12.]
[60.]

2: Connect one output to many inputs.

p = om.Problem()

p.model.add_subsystem('C1', om.ExecComp('y=sum(x)*2.0', x=np.zeros(5)), promotes_inputs=['x'])
p.model.add_subsystem('C2', om.ExecComp('y=sum(x)*4.0', x=np.zeros(5)), promotes_inputs=['x'])
p.model.add_subsystem('C3', om.ExecComp('y=sum(x)*6.0', x=np.zeros(5)), promotes_inputs=['x'])

p.setup()
p.set_val('x', np.ones(5))
p.run_model()

print(p.get_val('C1.y'))
print(p.get_val('C2.y'))
print(p.get_val('C3.y'))
[10.]
[20.]
[30.]

3: Connect only part of an array output to an input of a smaller size.

p = om.Problem()

p.model.add_subsystem('indep', om.IndepVarComp('x', np.ones(5)))
p.model.add_subsystem('C1', om.ExecComp('y=sum(x)*2.0', x=np.zeros(3)))
p.model.add_subsystem('C2', om.ExecComp('y=sum(x)*4.0', x=np.zeros(2)))

# connect C1.x to the first 3 entries of indep.x
p.model.connect('indep.x', 'C1.x', src_indices=[0, 1, 2])

# connect C2.x to the last 2 entries of indep.x
# use -2 (same as 3 in this case) to show that negative indices work.
p.model.connect('indep.x', 'C2.x', src_indices=[-2, 4])

p.setup()
p.run_model()

print(p['C1.x'])
print(p['C1.y'])
print(p['C2.x'])
print(p['C2.y'])
[1. 1. 1.]
[6.]
[1. 1.]
[8.]

4: Connect only part of a non-flat array output to a non-flat array input.

p = om.Problem()

p.model.add_subsystem('indep', om.IndepVarComp('x', np.arange(12).reshape((4, 3))))
p.model.add_subsystem('C1', om.ExecComp('y=sum(x)*2.0', x=np.zeros((2, 2))))

# Connect C1.x to entries (0,0), (-1,1), (2,1), (1,1) of indep.x and give then a 2x2 shape.
# To do this, create 2x2 shaped arrays representing rows and columns. Note that the final
# src_indices is a tuple (not a list or array) containing the rows and cols arrays.
rows = [[0, -1],[2, 1]]
cols = [[0, 1], [1, 1]]
p.model.connect('indep.x', 'C1.x', src_indices=(rows, cols), flat_src_indices=False)

p.setup()
p.run_model()

print(p.get_val('indep.x'))
print(p.get_val('C1.x'))
print(p.get_val('C1.y'))
[[ 0.  1.  2.]
 [ 3.  4.  5.]
 [ 6.  7.  8.]
 [ 9. 10. 11.]]
[[ 0. 10.]
 [ 7.  4.]]
[42.]

Using om.slicer to connect non-scalar variables#

The om.slicer object was introduced to OpenMDAO to make connecting non-scalar variables easier by allowing the src_indices to be specified as slices. This way of thinking about src_indices is probably more intuitive for users who are experienced with numpy.

The following example connects a 2x2 portion of a 5x5 matrix, starting from indices [2, 3]:

p = om.Problem()

ivc = p.model.add_subsystem('ivc', om.IndepVarComp())
ivc.add_output('M', val=np.arange(25).reshape((5, 5)))

exec = p.model.add_subsystem('exec', om.ExecComp())

exec.add_expr('A = B', A={'shape': (2, 2)}, B={'shape': (2, 2)})

p.model.connect('ivc.M', 'exec.B', src_indices=om.slicer[2:4, 3:5])

p.setup()

p.run_model()

print('M')
print(p.get_val('ivc.M'))

print('A')
print(p.get_val('exec.A'))
M
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]
 [20. 21. 22. 23. 24.]]
A
[[13. 14.]
 [18. 19.]]

The following example connects the first 3 columns of rows 0, 2, and 3 of M to B:

p = om.Problem()

ivc = p.model.add_subsystem('ivc', om.IndepVarComp())
ivc.add_output('M', val=np.arange(25).reshape((5, 5)))

exec = p.model.add_subsystem('exec', om.ExecComp())

exec.add_expr('A = B', A={'shape': (3, 3)}, B={'shape': (3, 3)})

p.model.connect('ivc.M', 'exec.B', src_indices=om.slicer[[0, 2 ,3], :3])

p.setup()

p.run_model()

print('M')
print(p.get_val('ivc.M'))

print('A')
print(p.get_val('exec.A'))
M
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]
 [20. 21. 22. 23. 24.]]
A
[[ 0.  1.  2.]
 [10. 11. 12.]
 [15. 16. 17.]]

The meaning of flat_src_indices when using om.slicer#

When src_indices are provided as a slicer and flat_src_indices = True, OpenMDAO first flattens (in row-major order), the source variable and then applies the slicer. Consider connecting the 3x3 matrix M below to a target 3-vector. In this first case, we don’t flatten the matrix before indexing it (flat_src_indices=False), so in this case om.slicer[:1] refers to the first element in the first dimension (the first row).

p = om.Problem()

ivc = p.model.add_subsystem('ivc', om.IndepVarComp())
M_np = np.arange(9)[::-1].reshape((3, 3))
ivc.add_output('M', val=M_np)

exec = p.model.add_subsystem('exec', om.ExecComp())

exec.add_expr('A = B', A={'shape': (1, 3)}, B={'shape': (3,)})

p.model.connect('ivc.M', 'exec.B', src_indices=om.slicer[:1])

p.setup()

p.run_model()

print('M')
print(p.get_val('ivc.M'))
print()
print('A')
print(p.get_val('exec.A'))
M
[[8. 7. 6.]
 [5. 4. 3.]
 [2. 1. 0.]]

A
[[8. 7. 6.]]

If, on the other hand, we specify flat_src_indices=True in the connection, then we will first flatten M, and pass the first element of the flattened array to exec.B.

p = om.Problem()

ivc = p.model.add_subsystem('ivc', om.IndepVarComp())
ivc.add_output('M', val=np.arange(9)[::-1].reshape((3, 3)))

exec = p.model.add_subsystem('exec', om.ExecComp())

exec.add_expr('A = B', A={'shape': (1,)}, B={'shape': (1,)})

p.model.connect('ivc.M', 'exec.B', src_indices=om.slicer[:1], flat_src_indices=True)

p.setup()

p.run_model()

print('M')
print(p.get_val('ivc.M'))

print('A')
print(p.get_val('exec.A'))
M
[[8. 7. 6.]
 [5. 4. 3.]
 [2. 1. 0.]]
A
[8.]