ExecComp#

ExecComp is a component that provides a shortcut for building an ExplicitComponent that represents a set of simple mathematical relationships between inputs and outputs. The ExecComp automatically takes care of all of the component API methods, so you just need to instantiate it with an equation or a list of equations.

ExecComp Options#

OptionDefaultAcceptable ValuesAcceptable TypesDescription
always_optFalse[True, False]['bool']If True, force nonlinear operations on this component to be included in the optimization loop even if this component is not relevant to the design variables and responses.
derivs_methodN/A['jax', 'cs', 'fd', None]N/AThe method to use for computing derivatives
do_coloringTrue[True, False]['bool']If True (the default), compute the partial jacobian coloring for this component.
has_diag_partialsFalse[True, False]['bool']If True, treat all array/array partials as diagonal if both arrays have size > 1. All arrays with size > 1 must have the same flattened size or an exception will be raised.
run_root_onlyFalse[True, False]['bool']If True, call compute, compute_partials, linearize, apply_linear, apply_nonlinear, and compute_jacvec_product only on rank 0 and broadcast the results to the other ranks.
shapeN/AN/A['int', 'tuple', 'list']Shape to be assigned to all variables in this component. Default is None, which means shape may be provided for variables individually.
shape_by_connFalse[True, False]['bool']If True, shape all inputs and outputs based on their connection. Default is False.
unitsN/AN/A['str']Units to be assigned to all variables in this component. Default is None, which means units may be provided for variables individually.
use_jitTrue[True, False]['bool']If True, attempt to use jit on compute_primal, assuming jax or some other AD package is active.

ExecComp Constructor#

The call signature for the ExecComp constructor is:

ExecComp.__init__(exprs=[], **kwargs)[source]

Create a <Component> using only an expression string.

Given a list of assignment statements, this component creates input and output variables at construction time. All variables appearing on the left-hand side of an assignment are outputs, and the rest are inputs. Each variable is assumed to be of type float unless the initial value for that variable is supplied in **kwargs. Derivatives are calculated using complex step.

The following functions are available for use in expressions:

Function

Description

abs(x)

Absolute value of x

acos(x)

Inverse cosine of x

acosh(x)

Inverse hyperbolic cosine of x

arange(start, stop, step)

Array creation

arccos(x)

Inverse cosine of x

arccosh(x)

Inverse hyperbolic cosine of x

arcsin(x)

Inverse sine of x

arcsinh(x)

Inverse hyperbolic sine of x

arctan(x)

Inverse tangent of x

arctan2(y, x)

4-quadrant arctangent function of y and x

asin(x)

Inverse sine of x

asinh(x)

Inverse hyperbolic sine of x

atan(x)

Inverse tangent of x

cos(x)

Cosine of x

cosh(x)

Hyperbolic cosine of x

dot(x, y)

Dot product of x and y

e

Euler’s number

erf(x)

Error function

erfc(x)

Complementary error function

exp(x)

Exponential function

expm1(x)

exp(x) - 1

fmax(x, y)

Element-wise maximum of x and y

fmin(x, y)

Element-wise minimum of x and y

inner(x, y)

Inner product of arrays x and y

isinf(x)

Element-wise detection of np.inf

isnan(x)

Element-wise detection of np.nan

kron(x, y)

Kronecker product of arrays x and y

linspace(x, y, N)

Numpy linear spaced array creation

log(x)

Natural logarithm of x

log10(x)

Base-10 logarithm of x

log1p(x)

log(1+x)

matmul(x, y)

Matrix multiplication of x and y

maximum(x, y)

Element-wise maximum of x and y

minimum(x, y)

Element-wise minimum of x and y

ones(N)

Create an array of ones

outer(x, y)

Outer product of x and y

pi

Pi

power(x, y)

Element-wise x**y

prod(x)

The product of all elements in x

sin(x)

Sine of x

sinh(x)

Hyperbolic sine of x

sum(x)

The sum of all elements in x

tan(x)

Tangent of x

tanh(x)

Hyperbolic tangent of x

tensordot(x, y)

Tensor dot product of x and y

zeros(N)

Create an array of zeros

Notes

If a variable has an initial value that is anything other than 1.0, either because it has a different type than float or just because its initial value is != 1.0, you must use a keyword arg to set the initial value. For example, let’s say we have an ExecComp that takes an array ‘x’ as input and outputs a float variable ‘y’ which is the sum of the entries in ‘x’.

import numpy
import openmdao.api as om
excomp = om.ExecComp('y=sum(x)', x=numpy.ones(10, dtype=float))

In this example, ‘y’ would be assumed to be the default type of float and would be given the default initial value of 1.0, while ‘x’ would be initialized with a size 10 float array of ones.

If you want to assign certain metadata for ‘x’ in addition to its initial value, you can do it as follows:

excomp = ExecComp('y=sum(x)',
                  x={'val': numpy.ones(10, dtype=float),
                     'units': 'ft'})

The values of the kwargs can be dicts which define the initial value for the variables along with other metadata. For example,

    ExecComp('xdot=x/t', x={'units': 'ft'}, t={'units': 's'}, xdot={'units': 'ft/s')

Here is a list of the possible metadata that can be assigned to a variable in this way. The Applies To column indicates whether the metadata is appropriate for input variables, output variables, or both.

Name

Description

Valid Types

Applies To

Default

value

Initial value in user-defined units

float, list, tuple, ndarray

input & output

1

shape

Variable shape, only needed if not an array

int, tuple, list, None

input & output

None

shape_by_conn

Determine variable shape based on its connection

bool

input & output

False

copy_shape

Determine variable shape based on named variable

str

input & output

None

units

Units of variable

str, None

input & output

None

desc

Description of variable

str

input & output

“”

res_units

Units of residuals

str, None

output

None

ref

Value of variable when scaled value is 1

float, ndarray

output

1

ref0

Value of variable when scaled value is 0

float, ndarray

output

0

res_ref

Value of residual when scaled value is 1

float, ndarray

output

1

lower

Lower bound of variable

float, list, tuple, ndarray, Iterable, None

output

None

upper

Lower bound of variable

float, list, tuple, ndarray, Iterable, None

output

None

src_indices

Global indices of the variable

int, list of ints, tuple of ints, int ndarray, Iterable, None

input

None

flat_src_indices

If True, src_indices are indices into flattened source

bool

input

None

tags

Used to tag variables for later filtering

str, list of strs

input & output

None

These metadata are passed to the Component methods add_input and add_output. For more information about these metadata, see the documentation for the arguments to these methods on Component.

Registering User Functions#

To get your own functions added to the internal namespace of ExecComp so you can call them from within an ExecComp expression, you can use the ExecComp.register function.

classmethod ExecComp.register(name, callable_obj, complex_safe)[source]

Register a callable to be usable within ExecComp expressions.

Parameters:
namestr

Name of the callable.

callable_objcallable

The callable.

complex_safebool

If True, the given callable works correctly with complex numbers.

Note that you’re required, when registering a new function, to indicate whether that function is complex safe or not.

ExecComp Example: Simple#

For example, here is a simple component that takes the input and adds one to it.

import openmdao.api as om

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', om.ExecComp('y=x+1.'))

model.set_input_defaults('comp.x', 2.0)

prob.setup()

prob.run_model()

print(prob.get_val('comp.y'))
[3.]

ExecComp Example: Multiple Outputs#

You can also create an ExecComp with multiple outputs by placing the expressions in a list.

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', om.ExecComp(['y1=x+1.', 'y2=x-1.']), promotes=['x'])

prob.setup()

prob.set_val('x', 2.0)

prob.run_model()

print(prob.get_val('comp.y1'))
print(prob.get_val('comp.y2'))
[3.]
[1.]

ExecComp Example: Arrays#

You can declare an ExecComp with arrays for inputs and outputs, but when you do, you must also pass in a correctly-sized array as an argument to the ExecComp call, or set the ‘shape’ metadata for that variable as described earlier. If specifying the value directly, it can be the initial value in the case of unconnected inputs, or just an empty array with the correct size.

import numpy as np

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', om.ExecComp('y=x[1]',
                                        x=np.array([1., 2., 3.]),
                                        y=0.0))

prob.setup()

prob.run_model()

print(prob.get_val('comp.y'))
[2.]

ExecComp Example: Math Functions#

Functions from the math library are available for use in the expression strings.

import numpy as np

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', om.ExecComp('z = sin(x)**2 + cos(y)**2'))

prob.setup()

prob.set_val('comp.x', np.pi/2.0)
prob.set_val('comp.y', np.pi/2.0)

prob.run_model()

print(prob.get_val('comp.z'))
[1.]

ExecComp Example: Variable Properties#

You can also declare properties like ‘units’, ‘upper’, or ‘lower’ on the inputs and outputs. In this example we declare all our inputs to be inches to trigger conversion from a variable expressed in feet in one connection source.

prob = om.Problem()
model = prob.model

model.add_subsystem('comp', om.ExecComp('z=x+y',
                                        x={'val': 0.0, 'units': 'inch'},
                                        y={'val': 0.0, 'units': 'inch'},
                                        z={'val': 0.0, 'units': 'inch'}))

prob.setup()

prob.set_val('comp.x', 12.0, units='inch')
prob.set_val('comp.y', 1.0, units='ft')

prob.run_model()

print(prob.get_val('comp.z'))
[24.]

ExecComp Example: Diagonal Partials#

If all of your ExecComp’s array inputs and array outputs are the same size and happen to have diagonal partials, you can make computation of derivatives for your ExecComp faster by specifying a has_diag_partials=True argument to __init__ or via the component options. This will cause the ExecComp to solve for its partials by complex stepping all entries of an array input at once instead of looping over each entry individually.

import numpy as np

p = om.Problem()
model = p.model

model.add_subsystem('comp', om.ExecComp('y=3.0*x + 2.5',
                                        has_diag_partials=True,
                                        x=np.ones(5), y=np.ones(5)))

p.setup()

p.set_val('comp.x', np.ones(5))

p.run_model()

J = p.compute_totals(of=['comp.y'], wrt=['comp.x'], return_format='array')

print(J)
[[ 3. -0. -0. -0. -0.]
 [-0.  3. -0. -0. -0.]
 [-0. -0.  3. -0. -0.]
 [-0. -0. -0.  3. -0.]
 [-0. -0. -0. -0.  3.]]

ExecComp Example: Options#

Other options that can apply to all the variables in the component are variable shape and units. These can also be set as a keyword argument in the constructor or via the component options. In the following example the variables all share the same shape, which is specified in the constructor, and common units that are specified by setting the option.

model = om.Group()

xcomp = model.add_subsystem('comp', om.ExecComp('y=2*x', shape=(2,)))

xcomp.options['units'] = 'm'

prob = om.Problem(model)
prob.setup()

prob.set_val('comp.x', [100., 200.], units='cm')

prob.run_model()

print(prob.get_val('comp.y'))
[2. 4.]

ExecComp Example: User function registration#

If the function is complex safe, then you don’t need to do anything differently than you would for any other ExecComp.

try: 
    om.ExecComp.register("myfunc", lambda x: x * x, complex_safe=True)
except NameError:
    pass
p = om.Problem()
comp = p.model.add_subsystem("comp", om.ExecComp("y = 2 * myfunc(x)"))

p.setup()
p.run_model()
J = p.compute_totals(of=['comp.y'], wrt=['comp.x'])
print(J['comp.y', 'comp.x'][0][0])
4.0

ExecComp Example: Complex unsafe user function registration#

If the function isn’t complex safe, then derivatives involving that function will have to be computed using finite difference instead of complex step. The way to specify that fd should be used for a given derivative is to call declare_partials.

try:
    om.ExecComp.register("unsafe", lambda x: x * x, complex_safe=False)
except NameError:
    pass
p = om.Problem()
comp = p.model.add_subsystem("comp", om.ExecComp("y = 2 * unsafe(x)"))

# because our function is complex unsafe, we must declare that the partials
# with respect to 'x' use 'fd' instead of 'cs'
comp.declare_partials('*', 'x', method='fd')

p.setup()
p.run_model()
J = p.compute_totals(of=['comp.y'], wrt=['comp.x'])
print(J['comp.y', 'comp.x'][0][0])
4.000001999848735

ExecComp Example: Adding Expressions#

You can add additional expressions to an ExecComp with the “add_expr” method.

import numpy as np

class ConfigGroup(om.Group):
    def setup(self):
        excomp = om.ExecComp('y=x',
                             x={'val' : 3.0, 'units' : 'mm'},
                             y={'shape' : (1, ), 'units' : 'cm'})

        self.add_subsystem('excomp', excomp, promotes=['*'])

    def configure(self):
        self.excomp.add_expr('z = 2.9*x',
                             z={'shape' : (1, ), 'units' : 's'})

p = om.Problem()
p.model.add_subsystem('sub', ConfigGroup(), promotes=['*'])
p.setup()
p.run_model()

print(p.get_val('z'))
print(p.get_val('y'))
[8.7]
[3.]

ExecComp Example: Constants#

You can define variables in ExecComp to be constants. Here is a simple model in which no constants are used. Notice the names of the inputs that are printed in the two models.

prob = om.Problem()
C1 = prob.model.add_subsystem('C1', om.ExecComp('x = a + b'))
prob.setup()
prob.set_solver_print(level=0)
prob.run_model()
print(list(C1._inputs._names))
['C1.a', 'C1.b']
prob = om.Problem()
C1 = prob.model.add_subsystem('C1', om.ExecComp('x = a + b', a={'val': 6, 'constant':True}))
prob.setup()
prob.set_solver_print(level=0)
prob.run_model()
print(list(C1._inputs._names))
['C1.b']