EQConstraintComp#
EQConstraintComp
is a specialized component that provides a simple way to implement
equality constraints.
You can add one or more outputs to an EQConstraintComp
that compute the difference
between a pair of input values for the purposes of driving the two inputs to equality. It
computes the output value as:
The normalization function \(f_{norm}(name_{rhs})\) takes one of the following forms:
The following inputs and outputs are associated with each output variable.
Name
I/O
Description
{name}
output
output variable
lhs:{name}
input
left-hand side of difference equation
rhs:{name}
input
right-hand side of difference equation
mult:{name}
input
left-hand side multiplier of difference equation
The default value for the rhs:{name}
input can be set to via the
rhs_val
argument (see arguments below). If the rhs value is fixed (e.g. 0),
then the input can be left unconnected. The lhs:{name}
input must always have
something connected to it.
The multiplier is optional and will default to 1.0 if not connected.
EQConstraintComp
supports vectorized outputs. Simply provide a default
value or shape when adding the output that reflects the correct shape.
You can provide the arguments to create an output variable when instantiating an
EQConstraintComp
or you can use the add_eq_output
method to create one
or more outputs after instantiation. The constructor accepts all the same arguments
as the add_eq_output
method:
- EQConstraintComp.add_eq_output(name, eq_units=None, lhs_name=None, rhs_name=None, rhs_val=0.0, use_mult=False, mult_name=None, mult_val=1.0, normalize=True, add_constraint=False, ref=None, ref0=None, adder=None, scaler=None, linear=False, indices=None, cache_linear_solution=False, flat_indices=False, alias=None, **kwargs)[source]
Add a new output variable computed via the difference equation.
This will create new inputs lhs:name, rhs:name, and mult:name that will define the left and right sides of the difference equation, and a multiplier for the left-hand-side.
- Parameters:
- namestr
The name of the output variable to be created.
- eq_unitsstr or None
Units for the left-hand-side and right-hand-side of the difference equation.
- lhs_namestr or None
Optional name for the LHS variable associated with the difference equation. If None, the default will be used: ‘lhs:{name}’.
- rhs_namestr or None
Optional name for the RHS variable associated with the difference equation. If None, the default will be used: ‘rhs:{name}’.
- rhs_valint, float, or np.array
Default value for the RHS. Must be compatible with the shape (optionally) given by the val or shape option in kwargs.
- use_multbool
Specifies whether the LHS multiplier is to be used. If True, then an additional input mult_name is created, with the default value given by mult_val, that multiplies lhs. Default is False.
- mult_namestr or None
Optional name for the LHS multiplier variable associated with the output variable. If None, the default will be used: ‘mult:{name}’.
- mult_valint, float, or np.array
Default value for the LHS multiplier. Must be compatible with the shape (optionally) given by the val or shape option in kwargs.
- normalizebool
Specifies whether or not the resulting output should be normalized by a quadratic function of the RHS. When this option is True, the user-provided ref/ref0 scaler/adder options below are typically unnecessary.
- add_constraintbool
Specifies whether to add an equality constraint.
- reffloat or ndarray, optional
Value of response variable that scales to 1.0 in the driver. This option is only meaningful when add_constraint=True.
- ref0float or ndarray, optional
Value of response variable that scales to 0.0 in the driver. This option is only meaningful when add_constraint=True.
- adderfloat or ndarray, optional
Value to add to the model value to get the scaled value for the driver. adder is first in precedence. This option is only meaningful when add_constraint=True.
- scalerfloat or ndarray, optional
Value to multiply the model value to get the scaled value for the driver. scaler is second in precedence. This option is only meaningful when add_constraint=True.
- linearbool
Set to True if constraint is linear. Default is False.
- indicessequence of int, optional
If variable is an array, these indicate which entries are of interest for this particular response. These may be positive or negative integers.
- cache_linear_solutionbool
If True, store the linear solution vectors for this variable so they can be used to start the next linear solution with an initial guess equal to the solution from the previous linear solve.
- flat_indicesbool
If True, interpret specified indices as being indices into a flat source array.
- aliasstr
Alias for this response. Necessary when adding multiple constraints on different indices or slices of a single variable.
- **kwargsdict
Additional arguments to be passed for the creation of the output variable. (see add_output method).
Note that the kwargs
arguments can include any of the keyword arguments normally available
when creating an output variable with the
add_output
method of a Component.
Example: Sellar IDF#
The following example shows an Individual Design Feasible (IDF) architecture for the
Sellar problem that demonstrates the use of an EQConstraintComp
.
In IDF, the direct coupling between the disciplines is removed and the coupling variables
are added to the optimizer’s design variables. The algorithm calls for two new equality
constraints that enforce the coupling between the disciplines. This ensures that the final
optimized solution is feasible, though it is achieved through the optimizer instead of
using a solver. The two new equality constraints are implemented in this example with
an EQConstraintComp
.
import numpy as np
import openmdao.api as om
from openmdao.test_suite.components.sellar import SellarDis1withDerivatives, SellarDis2withDerivatives
class SellarIDF(om.Group):
"""
Individual Design Feasible (IDF) architecture for the Sellar problem.
"""
def setup(self):
# construct the Sellar model with `y1` and `y2` as independent variables
self.set_input_defaults('x', 5.)
self.set_input_defaults('y1', 5.)
self.set_input_defaults('y2', 5.)
self.set_input_defaults('z', np.array([2., 0.]))
self.add_subsystem('d1', SellarDis1withDerivatives(), promotes_inputs=['x', 'z', 'y2'])
self.add_subsystem('d2', SellarDis2withDerivatives(), promotes_inputs=['y1', 'z'])
self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',
x=0., z=np.array([0., 0.])), promotes_inputs=['x', 'z', 'y1', 'y2'])
self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes_inputs=['y1'])
self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes_inputs=['y2'])
# rather than create a cycle by connecting d1.y1 to d2.y1 and d2.y2 to d1.y2
# we will constrain y1 and y2 to be equal for the two disciplines
equal = om.EQConstraintComp()
self.add_subsystem('equal', equal, promotes_inputs=[('lhs:y1', 'y1'), ('lhs:y2', 'y2')])
equal.add_eq_output('y1', add_constraint=True)
equal.add_eq_output('y2', add_constraint=True)
self.connect('d1.y1', 'equal.rhs:y1')
self.connect('d2.y2', 'equal.rhs:y2')
# the driver will effectively solve the cycle
# by satisfying the equality constraints
self.add_design_var('x', lower=0., upper=5.)
self.add_design_var('y1', lower=0., upper=5.)
self.add_design_var('y2', lower=0., upper=5.)
self.add_design_var('z', lower=np.array([-5., 0.]), upper=np.array([5., 5.]))
self.add_objective('obj_cmp.obj')
self.add_constraint('con_cmp1.con1', upper=0.)
self.add_constraint('con_cmp2.con2', upper=0.)
SellarIDF
class definition
class SellarIDF(om.Group):
"""
Individual Design Feasible (IDF) architecture for the Sellar problem.
"""
def setup(self):
# construct the Sellar model with `y1` and `y2` as independent variables
self.set_input_defaults('x', 5.)
self.set_input_defaults('y1', 5.)
self.set_input_defaults('y2', 5.)
self.set_input_defaults('z', np.array([2., 0.]))
self.add_subsystem('d1', SellarDis1withDerivatives(), promotes_inputs=['x', 'z', 'y2'])
self.add_subsystem('d2', SellarDis2withDerivatives(), promotes_inputs=['y1', 'z'])
self.add_subsystem('obj_cmp', om.ExecComp('obj = x**2 + z[1] + y1 + exp(-y2)',
x=0., z=np.array([0., 0.])), promotes_inputs=['x', 'z', 'y1', 'y2'])
self.add_subsystem('con_cmp1', om.ExecComp('con1 = 3.16 - y1'), promotes_inputs=['y1'])
self.add_subsystem('con_cmp2', om.ExecComp('con2 = y2 - 24.0'), promotes_inputs=['y2'])
# rather than create a cycle by connecting d1.y1 to d2.y1 and d2.y2 to d1.y2
# we will constrain y1 and y2 to be equal for the two disciplines
equal = om.EQConstraintComp()
self.add_subsystem('equal', equal, promotes_inputs=[('lhs:y1', 'y1'), ('lhs:y2', 'y2')])
equal.add_eq_output('y1', add_constraint=True)
equal.add_eq_output('y2', add_constraint=True)
self.connect('d1.y1', 'equal.rhs:y1')
self.connect('d2.y2', 'equal.rhs:y2')
# the driver will effectively solve the cycle
# by satisfying the equality constraints
self.add_design_var('x', lower=0., upper=5.)
self.add_design_var('y1', lower=0., upper=5.)
self.add_design_var('y2', lower=0., upper=5.)
self.add_design_var('z', lower=np.array([-5., 0.]), upper=np.array([5., 5.]))
self.add_objective('obj_cmp.obj')
self.add_constraint('con_cmp1.con1', upper=0.)
self.add_constraint('con_cmp2.con2', upper=0.)
from openmdao.test_suite.components.sellar_feature import SellarIDF
prob = om.Problem(model=SellarIDF())
prob.driver = om.ScipyOptimizeDriver(optimizer='SLSQP', disp=True)
prob.setup()
prob.run_driver();
Optimization terminated successfully (Exit mode 0)
Current function value: 3.1833939516939864
Iterations: 6
Function evaluations: 6
Gradient evaluations: 6
Optimization Complete
-----------------------------------
print(prob.get_val('x'))
[0.]
print([prob.get_val('y1'), prob.get_val('d1.y1')])
print([prob.get_val('y2'), prob.get_val('y2')])
[array([3.16]), array([3.15999999])]
[array([3.75527776]), array([3.75527776])]
print(prob.get_val('z'))
[1.97763888 0. ]
print(prob.get_val('obj_cmp.obj'))
[3.18339395]