MuxComp and DemuxComp
Contents
MuxComp and DemuxComp¶
Note
DemuxComp is being deprecated in favor of om.slicer
for a safer and more robust solution. Below is an example of how to use om.slicer
to achieve the same result.
DemuxComp
and MuxComp
work together to break up inputs into multiple values (demux) or combine
multiple inputs into a single value (mux). This can be useful in situations where scalar outputs
from multiple components need to be fed into a single vectorized component.
DemuxComp
takes a single input of arbitary shape (the size of at least one axis must be equal
to vec_size
). It can then be broken along that axis, resulting in vec_size
outputs.
MuxComp
combines two or more inputs into a single output by stacking them along an axis.
MuxComp and DemuxComp Options¶
These components have a single option, vec_size
, which provides the number of inputs to be
combined into a single output (for MuxComp
) or the number of outputs into which an input is
to be broken (for DemuxComp
). The default value of vec_size
is 2.
Adding Variables¶
A single MuxComp
or DemuxComp
can mux or demux multiple variables, so long as all variables
are compatible with the given vec_size
. Variables are added via the add_var
method.
The axis along which the muxing/demuxing is to occur is given via the axis argument.
For DemuxComp, the specified axis index must be the index of one of the input dimensions (you cannot demux along axis 3 of a 2D input).
In addition, the axis on which the Demuxing is to be done must have length vec_size
.
For MuxComp, the variables are joined along a new dimension, the index of which is given by axis.
The specified axis follows the convention used by the numpy.stack
function.
Giving axis = 0
will stack the inputs along the first axis (vertically).
Giving axis = 1
will stack the inputs along the second axis (horizontally).
Giving axis = -1
will stack the inputs along the last axis, and so is dependent on the shape of the inputs.
Due to the axis convention of numpy.stack
, the axis index is only valid if it is less than or
equal to the number of dimensions in the inputs.
For example, 1D arrays can be stacked vertically (axis = 0
) or horizontally (axis = 1
), but not
depth-wise (axis = 2
).
For DemuxComp, the name of the given variable is the input. It is demuxed into variables whose
names are appended with _n
where n
is an integer from 0 through vec_size
-1.
Conversely, for MuxComp, the given variable name is the output, and each input is appended with _n
.
- MuxComp.add_var(name, val=1.0, shape=None, units=None, desc='', axis=0)[source]
Add an output variable to be muxed, and all associated input variables.
- Parameters
- namestr
Name of the variable in this component’s namespace.
- valfloat or list or tuple or ndarray or Iterable
The initial value of the variable being added in user-defined units. Default is 1.0.
- shapeint or tuple or list or None
Shape of the input variables to be muxed, only required if val is not an array. Default is None.
- unitsstr or None
Units in which this input variable will be provided to the component during execution. Default is None, which means it is unitless.
- descstr
Description of the variable.
- axisint
The axis along which the elements will be stacked. Note that N-dimensional inputs cannot be stacked along an axis greater than N.
- DemuxComp.add_var(name, val=1.0, shape=None, units=None, desc='', axis=0)[source]
Add an input variable to be demuxed, and all associated output variables.
- Parameters
- namestr
Name of the variable in this component’s namespace.
- valfloat or list or tuple or ndarray or Iterable
The initial value of the variable being added in user-defined units. Default is 1.0.
- shapeint or tuple or list or None
Shape of this variable, only required if val is not an array. Default is None.
- unitsstr or None
Units in which this input variable will be provided to the component during execution. Default is None, which means it is unitless.
- descstr
Description of the variable.
- axisint
The axis along which the elements will be selected. Note the axis must have length vec_size, otherwise a RuntimeError is raised at setup.
Example: Demuxing a 3-column matrix into constituent vectors¶
This example is contrived and could be achieved with a single vectorized component, but it serves to give an example to the capabilities of the Demux component. Given a position vector in the Earth-centered, Earth-fixed (ECEF) frame (n x 3), extract the three (n x 1) columns from the matrix and use the first two to compute the longitude at the given position vector.
import numpy as np
import openmdao.api as om
# The number of elements to be demuxed
n = 3
# The size of each element to be demuxed
m = 100
p = om.Problem()
demux_comp = p.model.add_subsystem(name='demux', subsys=om.DemuxComp(vec_size=n),
promotes_inputs=['pos_ecef'])
demux_comp.add_var('pos_ecef', shape=(m, n), axis=1, units='km')
p.model.add_subsystem(name='longitude_comp',
subsys=om.ExecComp('long = atan(y/x)',
x={'val': np.ones(m), 'units': 'km'},
y={'val': np.ones(m), 'units': 'km'},
long={'val': np.ones(m), 'units': 'rad'}))
p.model.connect('demux.pos_ecef_0', 'longitude_comp.x')
p.model.connect('demux.pos_ecef_1', 'longitude_comp.y')
p.setup()
p.set_val('pos_ecef', 6378 * np.cos(np.linspace(0, 2*np.pi, m)), indices=om.slicer[:, 0])
p.set_val('pos_ecef', 6378 * np.sin(np.linspace(0, 2*np.pi, m)), indices=om.slicer[:, 1])
p.set_val('pos_ecef', 0.0, indices=om.slicer[:, 2])
p.run_model()
expected = np.arctan(p.get_val('pos_ecef', indices=om.slicer[:, 1]) / p.get_val('pos_ecef', indices=om.slicer[:, 0]))
print(p.get_val('longitude_comp.long'))
[ 0.00000000e+00 6.34665183e-02 1.26933037e-01 1.90399555e-01
2.53866073e-01 3.17332591e-01 3.80799110e-01 4.44265628e-01
5.07732146e-01 5.71198664e-01 6.34665183e-01 6.98131701e-01
7.61598219e-01 8.25064737e-01 8.88531256e-01 9.51997774e-01
1.01546429e+00 1.07893081e+00 1.14239733e+00 1.20586385e+00
1.26933037e+00 1.33279688e+00 1.39626340e+00 1.45972992e+00
1.52319644e+00 -1.55492970e+00 -1.49146318e+00 -1.42799666e+00
-1.36453014e+00 -1.30106362e+00 -1.23759711e+00 -1.17413059e+00
-1.11066407e+00 -1.04719755e+00 -9.83731033e-01 -9.20264515e-01
-8.56797996e-01 -7.93331478e-01 -7.29864960e-01 -6.66398442e-01
-6.02931923e-01 -5.39465405e-01 -4.75998887e-01 -4.12532369e-01
-3.49065850e-01 -2.85599332e-01 -2.22132814e-01 -1.58666296e-01
-9.51997774e-02 -3.17332591e-02 3.17332591e-02 9.51997774e-02
1.58666296e-01 2.22132814e-01 2.85599332e-01 3.49065850e-01
4.12532369e-01 4.75998887e-01 5.39465405e-01 6.02931923e-01
6.66398442e-01 7.29864960e-01 7.93331478e-01 8.56797996e-01
9.20264515e-01 9.83731033e-01 1.04719755e+00 1.11066407e+00
1.17413059e+00 1.23759711e+00 1.30106362e+00 1.36453014e+00
1.42799666e+00 1.49146318e+00 1.55492970e+00 -1.52319644e+00
-1.45972992e+00 -1.39626340e+00 -1.33279688e+00 -1.26933037e+00
-1.20586385e+00 -1.14239733e+00 -1.07893081e+00 -1.01546429e+00
-9.51997774e-01 -8.88531256e-01 -8.25064737e-01 -7.61598219e-01
-6.98131701e-01 -6.34665183e-01 -5.71198664e-01 -5.07732146e-01
-4.44265628e-01 -3.80799110e-01 -3.17332591e-01 -2.53866073e-01
-1.90399555e-01 -1.26933037e-01 -6.34665183e-02 -2.44929360e-16]
/usr/share/miniconda/envs/test/lib/python3.10/site-packages/openmdao/components/demux_comp.py:39: OMDeprecationWarning:DemuxComp is being deprecated. This same functionality can be achieved directly in the connect/promotes indices arg using om.slicer.
Example: Using om.slicer
to reduce a 3-column matrix into constituent vectors¶
# The number of elements to be demuxed
n = 3
arr_5x3 = np.array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11, 12],
[13, 14, 15],
])
p = om.Problem()
p.model.add_subsystem('indep', om.IndepVarComp('x', arr_5x3, units='km'), promotes=['*'])
p.model.add_subsystem('indep2', om.IndepVarComp('y', arr_5x3, units='km'), promotes=['*'])
p.model.add_subsystem(name='longitude_comp',
subsys=om.ExecComp('long = atan(y/x)',
x={'val': np.ones(n), 'units': 'km'},
y={'val': np.ones(n), 'units': 'km'},
long={'val': np.ones(n), 'units': 'rad'}))
# Use the src_indices arg in promotes to perform the demuxing
p.model.promotes('longitude_comp', inputs=['x'], src_indices=om.slicer[0, :])
p.model.promotes('longitude_comp', inputs=['y'], src_indices=om.slicer[1, :])
p.setup()
p.run_model()
print(p.get_val('longitude_comp.x'))
print(p.get_val('longitude_comp.y'))
print(p.get_val('longitude_comp.long'))
[1. 2. 3.]
[4. 5. 6.]
[1.32581766 1.19028995 1.10714872]
Example: Muxing 3 (n x 1) columns into a single (n x 3) matrix¶
In this example we start with three (n x 1) column vectors (x
, y
, and z
) and
combine them into a single position vector r
(n x 3). This is achieved by stacking the vectors
along axis = 1
. Like the previous example, this is somewhat contrived but is intended to demonstrate
the capabilities of the MuxComp.
# The number of elements to be muxed
n = 3
# The size of each element to be muxed
m = 100
p = om.Problem()
mux_comp = p.model.add_subsystem(name='mux', subsys=om.MuxComp(vec_size=n))
mux_comp.add_var('r', shape=(m,), axis=1, units='m')
p.model.add_subsystem(name='vec_mag_comp',
subsys=om.VectorMagnitudeComp(vec_size=m, length=n, in_name='r',
mag_name='r_mag', units='m'))
p.model.connect('mux.r', 'vec_mag_comp.r')
p.setup()
p.set_val('mux.r_0', 1 + np.random.rand(m))
p.set_val('mux.r_1', 1 + np.random.rand(m))
p.set_val('mux.r_2', 1 + np.random.rand(m))
p.run_model()
# Verify the results against numpy.dot in a for loop.
for i in range(n):
r_i = [p.get_val('mux.r_0')[i], p.get_val('mux.r_1')[i], p.get_val('mux.r_2')[i]]
expected_i = np.sqrt(np.dot(r_i, r_i))
print(p.get_val('vec_mag_comp.r_mag')[i])
2.6730309139229473
2.956124332983684
2.6741510008627505