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¶
Option |
Default |
Acceptable Values |
Acceptable Types |
Description |
---|---|---|---|---|
distributed |
False |
[True, False] |
[‘bool’] |
True if the component has variables that are distributed across multiple processes. |
has_diag_partials |
False |
[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. |
shape |
N/A |
N/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_conn |
False |
[True, False] |
[‘bool’] |
If True, shape all inputs and outputs based on their connection. Default is False. |
units |
N/A |
N/A |
[‘str’] |
Units to be assigned to all variables in this component. Default is None, which means units may be provided for variables individually. |
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
factorial(x)
Factorial of all numbers in x (DEPRECATED, not available with SciPy >=1.5)
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
- Parameters
- exprsstr, tuple of str or list of str
An assignment statement or iter of them. These express how the outputs are calculated based on the inputs. In addition to standard Python operators, a subset of numpy and scipy functions is supported.
- **kwargsdict of named args
Initial values of variables can be set by setting a named arg with the var name. If the value is a dict it is assumed to contain metadata. To set the initial value in addition to other metadata, assign the initial value to the ‘value’ entry of the dict.
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={'value': numpy.ones(10, dtype=float), 'units': 'ft'})
ExecComp Variable Metadata¶
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 Component methods:
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.set_solver_print(level=0)
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.
import openmdao.api as om
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.set_solver_print(level=0)
prob.run_model()
print(prob.get_val('comp.y1'))
[3.]
print(prob.get_val('comp.y2'))
[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
import openmdao.api as om
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.set_solver_print(level=0)
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
import openmdao.api as om
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.set_solver_print(level=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.
import openmdao.api as om
prob = om.Problem()
model = prob.model
model.add_subsystem('comp', om.ExecComp('z=x+y',
x={'value': 0.0, 'units': 'inch'},
y={'value': 0.0, 'units': 'inch'},
z={'value': 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.set_solver_print(level=0)
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
import openmdao.api as om
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.
import openmdao.api as om
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.
import openmdao.api as om
om.ExecComp.register("myfunc", lambda x: x * x, complex_safe=True)
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
.
import openmdao.api as om
# the following function isn't really complex unsafe, but we'll call it unsafe anyway
# for demonstration purposes
om.ExecComp.register("unsafe", lambda x: x * x, complex_safe=False)
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