Component Options (Arguments to Components)

The primary jobs of a component, whether explicit or implicit, are to define inputs and outputs and to do the mapping that computes the outputs given the inputs. Often, however, there are incidental parameters that affect the behavior of the component, but which are not considered input variables in the sense of being computed as an output of another component.

OpenMDAO provides a way of declaring these parameters, which are contained in an OptionsDictionary named options that is available in every component. Options associated with a particular component must be declared in the initialize method of the component definition. A default value can be provided as well as various checks for validity, such as a list of acceptable values or types.

The attributes that can be specified when declaring an option are enumerated and described below:

OptionsDictionary.declare(name, default=UNDEFINED, values=None, types=None, desc='', upper=None, lower=None, check_valid=None, allow_none=False, recordable=True, deprecation=None)[source]

Declare an option.

The value of the option must satisfy the following: 1. If values only was given when declaring, value must be in values. 2. If types only was given when declaring, value must satisfy isinstance(value, types). 3. It is an error if both values and types are given.

Parameters
namestr

Name of the option.

defaultobject or Null

Optional default value that must be valid under the above 3 conditions.

valuesset or list or tuple or None

Optional list of acceptable option values.

typestype or tuple of types or None

Optional type or list of acceptable option types.

descstr

Optional description of the option.

upperfloat or None

Maximum allowable value.

lowerfloat or None

Minimum allowable value.

check_validfunction or None

User-supplied function with arguments (name, value) that raises an exception if the value is not valid.

allow_nonebool

If True, allow None as a value regardless of values or types.

recordablebool

If True, add to recorder

deprecationstr or None

If None, it is not deprecated. If a str, use as a DeprecationWarning during __setitem__ and __getitem__

When using the check_valid argument, the expected function signature is:

options_dictionary.check_valid(value)

Check the validity of value for the option with name.

Parameters
namestr

name of the option

valueany

value for the option

Raises
ValueError

if value is not valid for option

Option values are typically passed at component instantiation time as keyword arguments, which are automatically assigned into the option dictionary. The options are then available for use in the component’s other methods, such as setup and compute.

Alternatively, values can be set at a later time, in another method of the component (except for initialize) or outside of the component definition after the component is instantiated.

A Simple Example

Options are commonly used to specify the shape or size of the component’s input and output variables, such as in this simple example.

"""
A component that multiplies a vector by 2, where the
size of the vector is given as an option of type 'int'.
"""
import numpy as np

import openmdao.api as om


class VectorDoublingComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('size', types=int)

    def setup(self):
        size = self.options['size']

        self.add_input('x', shape=size)
        self.add_output('y', shape=size)

    def setup_partials(self):
        size = self.options['size']
        self.declare_partials('y', 'x', val=2.,
                              rows=np.arange(size),
                              cols=np.arange(size))

    def compute(self, inputs, outputs):
        outputs['y'] = 2 * inputs['x']
import openmdao.api as om

prob = om.Problem()
prob.model.add_subsystem('double', VectorDoublingComp(size=3))  # 'size' is an option

prob.setup()

prob.set_val('double.x', [1., 2., 3.])

prob.run_model()
print(prob.get_val('double.y'))
[2. 4. 6.]

Not setting a default value when declaring an option implies that the value must be set by the user.

In this example, ‘size’ is required; We would have gotten an error if we:

  1. Did not pass in ‘size’ when instantiating VectorDoublingComp and

  2. Did not set its value in the code for VectorDoublingComp.

import openmdao.api as om
from openmdao.test_suite.components.options_feature_vector import VectorDoublingComp

prob = om.Problem()
prob.model.add_subsystem('double', VectorDoublingComp())  # 'size' not specified

try:
    prob.setup()
except RuntimeError as err:
    print(str(err))
'double' <class VectorDoublingComp>: Option 'size' is required but has not been set.

Option Types

Options are not limited to simple types like int. In the following example, the component takes a Numpy array as an option:

"""
A component that multiplies an array by an input value, where
the array is given as an option of type 'numpy.ndarray'.
"""
import numpy as np

import openmdao.api as om


class ArrayMultiplyComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('array', types=np.ndarray)

    def setup(self):
        array = self.options['array']

        self.add_input('x', 1.)
        self.add_output('y', shape=array.shape)

    def setup_partials(self):
        self.declare_partials(of='*', wrt='*')

    def compute(self, inputs, outputs):
        outputs['y'] = self.options['array'] * inputs['x']
import numpy as np

import openmdao.api as om

prob = om.Problem()
prob.model.add_subsystem('a_comp', ArrayMultiplyComp(array=np.array([1, 2, 3])))

prob.setup()

prob.set_val('a_comp.x', 5.)

prob.run_model()
print(prob.get_val('a_comp.y'))
[ 5. 10. 15.]

It is even possible to provide a function as an option:

"""
A component that computes y = func(x), where func
is a function given as an option.
"""

from types import FunctionType

import openmdao.api as om


class UnitaryFunctionComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('func', types=FunctionType, recordable=False)

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

    def setup_partials(self):
        self.declare_partials('y', 'x', method='fd')

    def compute(self, inputs, outputs):
        func = self.options['func']
        outputs['y'] = func(inputs['x'])
import openmdao.api as om

def my_func(x):
    return x*2

prob = om.Problem()
prob.model.add_subsystem('f_comp', UnitaryFunctionComp(func=my_func))

prob.setup()

prob.set_val('f_comp.x', 5.)

prob.run_model()
print(prob.get_val('f_comp.y'))
[10.]

Providing Default Values

One reason why using options is convenient is that a default value can be specified, making it optional to pass the value in during component instantiation.

"""
A component that computes y = a*x + b, where a and b
are given as an option of type 'numpy.ScalarType'.
"""
import numpy as np

import openmdao.api as om


class LinearCombinationComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('a', default=1., types=np.ScalarType)
        self.options.declare('b', default=1., types=np.ScalarType)

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

    def setup_partials(self):
        self.declare_partials('y', 'x', val=self.options['a'])

    def compute(self, inputs, outputs):
        outputs['y'] = self.options['a'] * inputs['x'] + self.options['b']
import openmdao.api as om

prob = om.Problem()
prob.model.add_subsystem('linear', LinearCombinationComp(a=2.))  # 'b' not specified

prob.setup()

prob.set_val('linear.x', 3)

prob.run_model()
print(prob.get_val('linear.y'))
[7.]

In this example, both ‘a’ and ‘b’ are optional, so it is valid to pass in ‘a’, but not ‘b’.

Specifying Values or Types

The parameters available when declaring an option allow a great deal of flexibility in specifying exactly what types and values are acceptable.

As seen above, the allowed types can be specified using the types parameter. If an option is more limited, then the set of allowed values can be given with values:

import numpy as np
import openmdao.api as om

class VectorDoublingComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('size', values=[2, 4, 6, 8])

    def setup(self):
        size = self.options['size']

        self.add_input('x', shape=size)
        self.add_output('y', shape=size)
        self.declare_partials('y', 'x', val=2.,
                              rows=np.arange(size),
                              cols=np.arange(size))

    def compute(self, inputs, outputs):
        outputs['y'] = 2 * inputs['x']

prob = om.Problem()
prob.model.add_subsystem('double', VectorDoublingComp(size=4))

prob.setup()

prob.set_val('double.x', [1., 2., 3., 4.])

prob.run_model()
print(prob.get_val('double.y'))
[2. 4. 6. 8.]

Warning

It is an error to attempt to specify both a list of acceptable values and a list of acceptable types.

Alternatively, the allowable values can be set using bounds and/or a validation function.

import numpy as np
import openmdao.api as om

def check_even(name, value):
    if value % 2 != 0:
        raise ValueError(f"Option '{name}' with value {value} must be an even number.")

class VectorDoublingComp(om.ExplicitComponent):

    def initialize(self):
        self.options.declare('size', types=int, lower=2, upper=8, check_valid=check_even)

    def setup(self):
        size = self.options['size']

        self.add_input('x', shape=size)
        self.add_output('y', shape=size)
        self.declare_partials('y', 'x', val=2.,
                              rows=np.arange(size),
                              cols=np.arange(size))

    def compute(self, inputs, outputs):
        outputs['y'] = 2 * inputs['x']

try:
    comp = VectorDoublingComp(size=5)
except Exception as err:
    msg = str(err)
    print(msg)
Option 'size' with value 5 must be an even number.