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.8/site-packages/openmdao/components/demux_comp.py:38: 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.40279130766533
2.3060511116445093
3.229612149919139