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 |
---|---|---|---|---|
always_opt | False | [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_method | N/A | ['jax', 'cs', 'fd', None] | N/A | The method to use for computing derivatives |
do_coloring | True | [True, False] | ['bool'] | If True (the default), compute the partial jacobian coloring for this component. |
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. |
run_root_only | False | [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. |
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. |
use_jit | True | [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']