Constraint Conditions¶
In Amplify-BBOpt, you can incorporate constraint conditions on decision variables. Constraints define the conditions that the values of decision variables must satisfy in an optimization problem. In this section, we explain how to handle constraints in Amplify-BBOpt using the following example black-box function introduced in Defining the Objective Function: a function for “maximizing lift and minimizing drag in wing design.”
import WingSimulator # Example simulator
from amplify_bbopt import RealVariable, blackbox
@blackbox
def my_blackbox_func(
width: float = RealVariable(bounds=(1, 20)),
height: float = RealVariable(bounds=(1, 5)),
angle: float = RealVariable(bounds=(0, 45)),
) -> float:
"""Returns the negative value of the lift-to-drag ratio obtained from the simulation."""
simulator = WingSimulator(width, height, angle)
lift_force, drag_force = simulator.simulate()
return -lift_force / drag_force
In this black-box function, three decision variables are considered: width, height, and angle. The range of each variable is defined at the time of declaration.
When performing optimization, you can impose constraints on functions of these decision variables or on polynomial expressions constructed from them. For example, constraints can take the form of a polynomial expression, such as width + 2 * height <= 25,
or a non-polynomial (black-box) functional constraint, such as g(width, height, angle) >= 0. Depending on the nature and purpose of the constraint, Amplify-BBOpt supports two implementation types described below: hard constraints and soft constraints.
Hard constraints¶
When constraints can be expressed as equalities/inequalities of polynomials of the decision variables , they can be treated as hard constraints. They are called “hard” because a solution is not accepted unless all constraints are satisfied, and conversely, any solution returned is guaranteed (by the backend Amplify SDK) to satisfy all constraints. In Amplify-BBOpt, such hard constraints are handled using the Amplify SDK’s constraint helper functions.
Below is a list of the Amplify SDK constraint helper functions available in Amplify-BBOpt. Here, left denotes a polynomial of the decision variables, and right denotes a value (a tuple of values for clamp).
Amplify SDK’s helper functions |
Constraint form |
|---|---|
|
|
|
|
|
|
|
|
|
|
Using these helper functions, the aforementioned constraint width + 2 * height <= 25, which should be considered together with the black-box function above, can be added when instantiating the Optimizer. The decision variable objects that appear in the polynomial on the left-hand side of the constraint can be obtained as attributes of the black-box function class, as shown below.
Note
In the code below, trainer and client refer to the previously defined surrogate model function and the Amplify SDK solver client, respectively. For how to set these, see “Running the Optimization”.
import WingSimulator
import amplify
from amplify_bbopt import Optimizer, RealVariable, blackbox
@blackbox
def my_blackbox_func(
width: float = RealVariable(bounds=(1, 20)),
height: float = RealVariable(bounds=(1, 5)),
angle: float = RealVariable(bounds=(0, 45)),
) -> float:
"""Returns the negative value of the lift-to-drag ratio obtained from the simulation."""
simulator = WingSimulator(width, height, angle)
lift_force, drag_force = simulator.simulate()
return -lift_force / drag_force
# Retrieve decision variables as attributes of the black-box function class
width = my_blackbox_func.variables.width
height = my_blackbox_func.variables.height
# Define the constraint
constraint = amplify.less_equal(width + 2 * height, 25)
print(constraint) # Output: width + 2 height <= 25 (weight: 1)
# Instantiate the optimizer class
optimizer = Optimizer(
my_blackbox_func,
trainer,
client,
constraints = constraint, # Add the constraint
)
Soft constraints¶
You can indirectly account for the constraint by adding a penalty to the original black-box objective value -lift_force / drag_force if the given decision variables do not satisfy a pre-defined constraint. This approach is often called a soft constraint, and is useful when the constraint cannot be expressed as a polynomial (i.e., hard constraints are not applicable).
Unlike aforementioned hard constraints, soft constraints still treat infeasible solutions as feasible from Amplify-BBOpt’s perspective. The constraint is not explicitly defined in Amplify-BBOpt; rather, it is handled as part of the objective function.
Below is an example black-box function that applies a soft constraint with a penalty of magnitude 1. There are many ways to implement soft constraints, including how to estimate the penalty value. Note, however, that the penalty magnitude should be set appropriately relative to the scale of the original objective value.
@blackbox
def my_blackbox_func(
width: float = RealVariable(bounds=(1, 20)),
height: float = RealVariable(bounds=(1, 5)),
angle: float = RealVariable(bounds=(0, 45)),
) -> float:
"""Returns the negative value of the lift-to-drag ratio from the simulation plus a constraint penalty."""
simulator = WingSimulator(width, height, angle)
lift_force, drag_force = simulator.simulate()
# Add a penalty of 1 if the constraint is violated
penalty = 0
if g(width, height, angle) < 0:
penalty = 1
return -lift_force / drag_force + penalty
Tip
If the simulation (WingSimulator) is expensive-to-evaluate, you can return only the penalty before running the simulation for infeasible inputs.
@blackbox
def my_blackbox_func(
width: float = RealVariable(bounds=(1, 20)),
height: float = RealVariable(bounds=(1, 5)),
angle: float = RealVariable(bounds=(0, 45)),
) -> float:
"""Returns the negative value of the lift-to-drag ratio from the simulation"""
# If the constraint is violated, return only the penalty value.
if g(width, height, angle) < 0:
return 1
simulator = WingSimulator(width, height, angle)
lift_force, drag_force = simulator.simulate()
return -lift_force / drag_force