Declaring Continuous Variables#

Calling add_input and add_output#

Every component in an OpenMDAO model is an instance of ExplicitComponent, ImplicitComponent, or a subclass of one of these classes. Regardless of the type, each component has input variables and output variables that it must declare.

In explicit and implicit components, the user must call add_input and add_output to declare variables in the setup method. An example is given below.

import openmdao.api as om


class TestExplCompSimple(om.ExplicitComponent):

    def setup(self):
        self.add_input('length', val=1., desc='length of rectangle')
        self.add_input('width', val=1., desc='width of rectangle')
        self.add_output('area', val=1., desc='area of rectangle')

    def setup_partials(self):
        self.declare_partials('*', '*')

    def compute(self, inputs, outputs):
        outputs['area'] = inputs['length'] * inputs['width']

Note

Variable names have few restrictions, but the following characters are not allowed in a variable name: ‘.’, ‘*’, ‘?’, ‘!’, ‘[‘, ‘]’.

Method Signatures#

Component.add_input(name, val=1.0, shape=None, units=None, desc='', tags=None, shape_by_conn=False, copy_shape=None, compute_shape=None, distributed=None)[source]

Add an input variable to the component.

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.

tagsstr or list of strs

User defined tags that can be used to filter what gets listed when calling list_inputs and list_outputs.

shape_by_connbool

If True, shape this input to match its connected output.

copy_shapestr or None

If a str, that str is the name of a variable. Shape this input to match that of the named variable.

compute_shapefunction

A function taking a dict arg containing names and shapes of this component’s outputs and returning the shape of this input.

distributedbool

If True, this variable is a distributed variable, so it can have different sizes/values across MPI processes.

Returns:
dict

Metadata for added variable.

Component.add_output(name, val=1.0, shape=None, units=None, res_units=None, desc='', lower=None, upper=None, ref=1.0, ref0=0.0, res_ref=None, tags=None, shape_by_conn=False, copy_shape=None, compute_shape=None, distributed=None)[source]

Add an output variable to the component.

Parameters:
namestr

Name of the variable in this component’s namespace.

valfloat or list or tuple or ndarray

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 the output variables will be provided to the component during execution. Default is None, which means it has no units.

res_unitsstr or None

Units in which the residuals of this output will be given to the user when requested. Default is None, which means it has no units.

descstr

Description of the variable.

lowerfloat or list or tuple or ndarray or Iterable or None

Lower bound(s) in user-defined units. It can be (1) a float, (2) an array_like consistent with the shape arg (if given), or (3) an array_like matching the shape of val, if val is array_like. A value of None means this output has no lower bound. Default is None.

upperfloat or list or tuple or ndarray or or Iterable None

Upper bound(s) in user-defined units. It can be (1) a float, (2) an array_like consistent with the shape arg (if given), or (3) an array_like matching the shape of val, if val is array_like. A value of None means this output has no upper bound. Default is None.

reffloat or ndarray

Scaling parameter. The value in the user-defined units of this output variable when the scaled value is 1. Default is 1.

ref0float or ndarray

Scaling parameter. The value in the user-defined units of this output variable when the scaled value is 0. Default is 0.

res_reffloat or ndarray

Scaling parameter. The value in the user-defined res_units of this output’s residual when the scaled value is 1. Default is 1.

tagsstr or list of strs or set of strs

User defined tags that can be used to filter what gets listed when calling list_inputs and list_outputs.

shape_by_connbool

If True, shape this output to match its connected input(s).

copy_shapestr or None

If a str, that str is the name of a variable. Shape this output to match that of the named variable.

compute_shapefunction

A function taking a dict arg containing names and shapes of this component’s inputs and returning the shape of this output.

distributedbool

If True, this variable is a distributed variable, so it can have different sizes/values across MPI processes.

Returns:
dict

Metadata for added variable.

Usage#

1. Declaring with only the default value.

import numpy as np

class CompAddWithDefault(om.ExplicitComponent):
    """Component for tests for declaring only default value."""

    def setup(self):
        self.add_input('x_a')
        self.add_input('x_b', val=3.)
        self.add_input('x_c', val=(3., 3.))
        self.add_input('x_d', val=[3., 3.])
        self.add_input('x_e', val=3. * np.ones((2, 2)))

        self.add_output('y_a')
        self.add_output('y_b', val=6.)
        self.add_output('y_c', val=(6., 6., 6.))
        self.add_output('y_d', val=[6., 6., 6.])
        self.add_output('y_e', val=6. * np.ones((3, 2)))
"""Test declaring only default value."""
p = om.Problem()
p.model.add_subsystem('comp', CompAddWithDefault(), promotes=['*'])
p.setup()

print(p.get_val('x_a'))
[1.]
print(p.get_val('x_b'))
[3.]
print(p.get_val('x_c'))
[3. 3.]
print(p.get_val('x_d'))
[3. 3.]
print(p.get_val('x_e'))
[[3. 3.]
 [3. 3.]]
print(p.get_val('y_a'))
[1.]
print(p.get_val('y_b'))
[6.]
print(p.get_val('y_c'))
[6. 6. 6.]
print(p.get_val('y_d'))
[6. 6. 6.]
print(p.get_val('y_e'))
[[6. 6.]
 [6. 6.]
 [6. 6.]]

2. Declaring with only the shape argument.

class CompAddWithShape(om.ExplicitComponent):
    """Component for tests for declaring only shape."""

    def setup(self):
        self.add_input('x_a', shape=2)
        self.add_input('x_b', shape=(2, 2))
        self.add_input('x_c', shape=[2, 2])

        self.add_output('y_a', shape=3)
        self.add_output('y_b', shape=(3, 3))
        self.add_output('y_c', shape=[3, 3])
"""Test declaring only shape."""
p = om.Problem()
p.model.add_subsystem('comp', CompAddWithShape(), promotes=['*'])
p.setup()

print(p.get_val('x_a'))
[1. 1.]
print(p.get_val('x_b'))
[[1. 1.]
 [1. 1.]]
print(p.get_val('x_c'))
[[1. 1.]
 [1. 1.]]
print(p.get_val('y_a'))
[1. 1. 1.]
print(p.get_val('y_b'))
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]
print(p.get_val('y_c'))
[[1. 1. 1.]
 [1. 1. 1.]
 [1. 1. 1.]]

4. Declaring an array variable with a scalar default value.

class CompAddArrayWithScalar(om.ExplicitComponent):
    """Component for tests for declaring a scalar val with an array variable."""

    def setup(self):
        self.add_input('x_a', val=2.0, shape=(6))
        self.add_input('x_b', val=2.0, shape=(3, 2))
        self.add_output('y_a', val=3.0, shape=(6))
        self.add_output('y_b', val=3.0, shape=(3, 2))
"""Test declaring a scalar val with an array variable."""
p = om.Problem()
p.model.add_subsystem('comp', CompAddArrayWithScalar(), promotes=['*'])
p.setup()
<openmdao.core.problem.Problem at 0x7f804de4fd50>
print(p.get_val('x_a'))
[2. 2. 2. 2. 2. 2.]
print(p.get_val('x_b'))
[[2. 2.]
 [2. 2.]
 [2. 2.]]
print(p.get_val('y_a'))
[3. 3. 3. 3. 3. 3.]
print(p.get_val('y_b'))
[[3. 3.]
 [3. 3.]
 [3. 3.]]

6. Declaring an output with bounds, using upper and/or lower arguments.

class CompAddWithBounds(om.ExplicitComponent):
    """Component for tests for declaring bounds."""

    def setup(self):
        self.add_input('x')

        self.add_output('y_a', val=2.0, lower=0.)
        self.add_output('y_b', val=2.0, lower=0., upper=10.)
        self.add_output('y_c', val=2.0 * np.ones(6),  lower=np.zeros(6), upper=10.)
        self.add_output('y_d', val=2.0 * np.ones(6), lower=0., upper=[12, 10, 10, 10, 10, 12])
        self.add_output('y_e', val=2.0 * np.ones((3, 2)), lower=np.zeros((3, 2)))
"""Test declaring bounds."""
p = om.Problem()
p.model.add_subsystem('comp', CompAddWithBounds(), promotes=['*'])
p.setup()

print(p.get_val('y_a'))
[2.]
print(p.get_val('y_b'))
[2.]
print(p.get_val('y_c'))
[2. 2. 2. 2. 2. 2.]
print(p.get_val('y_d'))
[2. 2. 2. 2. 2. 2.]
print(p.get_val('y_e'))
[[2. 2.]
 [2. 2.]
 [2. 2.]]

7. Adding tags to input and output variables. These tags can then be used to filter what gets returned from the System.get_io_metadata method and displayed/returned from the System.list_inputs and System.list_outputs methods.

class RectangleCompWithTags(om.ExplicitComponent):
    """
    A simple Explicit Component that also has input and output with tags.
    """

    def setup(self):
        self.add_input('length', val=1., tags=["tag1", "tag2"])
        self.add_input('width', val=1., tags=["tag2"])
        self.add_output('area', val=1., tags="tag1")

    def setup_partials(self):
        self.declare_partials('*', '*')

    def compute(self, inputs, outputs):
        outputs['area'] = inputs['length'] * inputs['width']

prob = om.Problem()
prob.model.add_subsystem('comp', RectangleCompWithTags(), promotes=['*'])
prob.setup(check=False)
prob.run_model()

# Inputs no tags
inputs = prob.model.list_inputs(val=False, out_stream=None)
print(sorted(inputs))
[('comp.length', {'prom_name': 'length'}), ('comp.width', {'prom_name': 'width'})]
# Inputs with tags
inputs = prob.model.list_inputs(val=False, out_stream=None, tags="tag1")
print(sorted(inputs))
[('comp.length', {'prom_name': 'length'})]
# Inputs with multiple tags
inputs = prob.model.list_inputs(val=False, out_stream=None, tags=["tag1", "tag2"])
print(sorted(inputs))
[('comp.length', {'prom_name': 'length'}), ('comp.width', {'prom_name': 'width'})]
# Inputs with tag that does not match
inputs = prob.model.list_inputs(val=False, out_stream=None, tags="tag3")
print(sorted(inputs))
[]
# Outputs no tags
outputs = prob.model.list_outputs(val=False, out_stream=None)
print(sorted(outputs))
[('comp.area', {'prom_name': 'area'})]
# Outputs with tags
outputs = prob.model.list_outputs(val=False, out_stream=None, tags="tag1")
print(sorted(outputs))
[('comp.area', {'prom_name': 'area'})]
# Outputs with multiple tags
outputs = prob.model.list_outputs(val=False, out_stream=None, tags=["tag1", "tag3"])
print(sorted(outputs))
[('comp.area', {'prom_name': 'area'})]
# Outputs with tag that does not match
outputs = prob.model.list_outputs(val=False, out_stream=None, tags="tag3")
print(sorted(outputs))
[]

Calling add_residual#

By default, residuals and outputs have the same names, but there are times when writing an ImplicitComponent that this might be confusing. OpenMDAO POEM 69 has specific examples that demonstrate the confusion.

To address this, the add_residual method was added to ImplicitComponent.

ImplicitComponent.add_residual(name, shape=(1,), units=None, desc='', ref=None)[source]

Add a residual variable to the component.

Note that the total size of the residual vector must match the total size of the outputs vector for this component.

Parameters:
namestr

Name of the residual in this component’s namespace.

shapeint or tuple

Shape of this residual.

unitsstr or None

Units in which this residual will be given to the user when requested. Default is None, which means it has no units.

descstr

Description of the residual.

reffloat or ndarray or None

Scaling parameter. The value in the user-defined units of this residual when the scaled value is 1. Default is 1.

Returns:
dict

Metadata for the added residual.

Note that if you make any calls to add_residual, you must use it to add all of the residuals, so that the size of the residuals vector is the same size as the outputs vector for that component.

Also note that this residual renaming is only active inside of the component, and the rest of the framework will still deal with residuals as if they map 1-to-1 with outputs.