3. Constraint¶
Let us recap the below example mentioned in “2. Black-Box Function”.
Numerical simulations
You want to design an optimal wing profile to maximize the lift and minimize the drag. In this case, your black-box function would be a fluid flow simulation that returns the negative lift-drag ratio of a given wing profile setting.
In this example, the objective is to find an input set that minimizes the negative lift-drag ratio, maximizing the lift while reducing the drag.
The objective function for this problem may be implemented as follows. Note that the below code defines the three decision variables using the “@blackbox
” decorator as described in “2. Black-Box Function”.
from amplify_bbopt import RealVariable, blackbox
from utils.pseudo_simulators import (
pseudo_wing_simulator as wing_simulator,
)
@blackbox
def objective_lift_drag(
wing_width: float = RealVariable(bounds=(1, 20), nbins=100),
wing_height: float = RealVariable(bounds=(1, 5), nbins=20),
wing_angle: float = RealVariable(bounds=(0, 45), nbins=20),
) -> float:
"""
This black-box function executes wing_simulator() and returns
the negative lift-drag ratio for a given wing's width, height, and angle.
"""
lift, drag = wing_simulator(wing_width, wing_height, wing_angle)
return -lift / drag # value to minimize
Often, you may need to impose some constraints on the set variables during black-box optimization. In the black-box optimization problem considering the black-box function above, for example, the decision variables may need to meet the following constraints:
wing_width + wing_height
must be less than or equal to 10.wing_width - wing_height
must be greater than or equal to 4.
There are two ways to implement such constraints: “soft” and “hard” constraints.
Soft constraint¶
The first method is to use a soft constraint. In the above black-box function, instead of returning -lift / drag
directly, you can add a penalty to the objective function when the given decision variables do not meet the constraints.
This method is called “soft” since the solutions violating the constraints are still regarded as feasible from an optimizer perspective — the constraints are a part of the objective function and not explicitly defined as constraints with Amplify-BBOpt. Note that in this method, the intensity of the penalty needs to be set appropriately.
Below is an example of the same black-box function with such soft constraints and unity penalty.
@blackbox
def objective_lift_drag_soft_constraint(
wing_width: float = RealVariable(bounds=(1, 20), nbins=100),
wing_height: float = RealVariable(bounds=(1, 5), nbins=20),
wing_angle: float = RealVariable(bounds=(0, 45), nbins=20),
) -> float:
lift, drag = wing_simulator(wing_width, wing_height, wing_angle)
value = -lift / drag
# Add a penalty (=1) when the given variables violate the constraint.
if wing_width + wing_height > 10:
value += 1
# Add a penalty (=1) when the given variables violate the constraint.
if wing_width - wing_height < 4:
value += 1
return value
Hard constraint¶
The second method is called hard constraint. In this method, you do not modify your black-box function. Instead, you additionally specify and add the constraints to your black-box function class instance using the following Amplify-BBOpt functions.
Method |
Constraint |
---|---|
|
|
|
|
|
|
|
Here, left
is the left-hand side of the equality and inequality constraints and consists of a polynomial based on the decision variables. right
is the right-hand side of the constraints and is always a value (a tuple for clamp
to specify lower and upper bounds of an inequality equation).
In the above example of the wing simulation, the constraints can be implemented as follows:
from amplify_bbopt import greater_equal, less_equal
variables = objective_lift_drag.variables
constraint_sum = less_equal(variables.wing_width + variables.wing_height, 10)
constraint_sub = greater_equal(variables.wing_width - variables.wing_height, 4)
The defined constraints are the instances of Constraint
. You can check the created constraints as follows:
print(constraint_sum)
print(constraint_sub)
constraint: wing_width + wing_height <= 10
constraint: wing_width - wing_height >= 4
You can add these constraints to the black-box function class instance using the add_constraint
method. You can add more than one constraint by using add_constraint
repeatedly. You can also view the added constraints associated with the black-box function class instance.
# Add constraints to the black-box function class instance
objective_lift_drag.add_constraint(constraint_sum)
objective_lift_drag.add_constraint(constraint_sub)
# Display all constraints associated with the black-box function class instance
print(objective_lift_drag.constraints)
--------------------
constraint: wing_width + wing_height <= 10 (weight: 1.0)
constraint: wing_width - wing_height >= 4 (weight: 1.0)
Now, Amplify-BBOpt knows that these variables must hold the added constraints for your black-box function, and any input set violating the constraint is regarded as infeasible. Hence, we call this type of constraint a “hard” constraint.
Attention
Currently, you can only specify a first-order polynomial of the decision variables as left
of the hard constraint. For example, wing_width + wing_height
is a first-order polynomial and can be a left
in the above constraint functions (you can also multiply a value). However, wing_width * wing_height
is second order, and Amplify-BBOpt cannot handle such a high order polynomial as left
for now. If you need to consider a second or higher order polynomial, use the “soft” constraint approach or manually construct your constraint instead.
Caution
Using an equal_to
constraint for real variables requires some care. Most likely, Amplify-BBOpt discretizes between the bounds a real variable can take into a number of small bins. This means the value you expect the real variable would yield may not be included in the discretized values of the variable.
real = RealVariable(bounds=[0, 1], nbins=3) print(real) c = equal_to(2 * real, 0.2) # this would never satisfied.
To circumvent this situation, you can:
Adjust your variable
bounds
ornbins
appropriately, orUse the soft constraint approach with an allowable error.
Caution
Using inequality constraints (less_equal
, greater_equal
and clamp
) for real variables requires some care. Most likely, Amplify-BBOpt passes such inequality constraints to Amplify SDK as is, which, as of today, cannot take inequality constraints with real variables. If this happens, Amplify SDK spits the following error message:
ValueError: Model conversion failed: conversion from real to other types of variables is not supported.
Should this happen, you can set the penalty_formulation
property of the corresponding constraint as “Relaxation”:
real = RealVariable(bounds=[0, 2], nbins=5) print(real) c = less_equal(real, 1) c.penalty_formulation = "Relaxation"