Constrained Optimization#
Solve a problem with constraints on the design variables using saealib.
With minimize(), you can pass a string or instance for the algorithm, surrogate, and evaluation strategy.
The examples on this page add only problem-specific settings and use defaults for everything else.
What you’ll be able to do
By the end of this page, you’ll be able to define constraints on a Problem, check feasibility, and choose a ConstraintHandler.
For a simple objective-function optimization, use Single-objective optimization. To implement custom constraint handling, see Custom components.
This page covers defining constraints on a Problem, checking feasibility, and choosing a ConstraintHandler.
Set up the problem#
Assume a problem that, in addition to the objective function, has an inequality constraint g(x) <= 0 that the solution must satisfy.
import numpy as np
def expensive_func(x):
return np.sum(x**2)
def g1(x):
# require the sum of the design variables to be at least 1
return 1.0 - np.sum(x)
DIM = 5
LB = [-5.0] * DIM
UB = [5.0] * DIM
Only solutions satisfying g1(x) <= 0 are feasible.
Define constraints#
A constraint is defined with InequalityConstraint(func, threshold=0.0) and passed to Problem’s constraints argument.
from saealib import InequalityConstraint, Problem, minimize
constraint = InequalityConstraint(g1, threshold=0.0)
problem = Problem(
func=expensive_func,
dim=DIM,
n_obj=1,
direction=np.array([-1.0]),
lb=LB,
ub=UB,
constraints=[constraint],
)
result = minimize(problem, max_fe=1000, seed=0)
print(result.x, result.f)
print(constraint.violation(result.x)) # 0.0 means the constraint is satisfied
To impose a constraint of the form g(x) >= threshold, pass func with its sign flipped.
For an equality constraint h(x) = 0, use EqualityConstraint(func, tolerance=1e-6) instead of combining two sign-flipped InequalityConstraints.
from saealib import EqualityConstraint
def h1(x):
# require the sum of the design variables to be exactly 1
return np.sum(x) - 1.0
equality = EqualityConstraint(h1, tolerance=1e-6)
EqualityConstraint treats solutions satisfying |h(x)| <= tolerance as feasible.
Check feasibility#
A solution is feasible if its constraint violation cv is at most Problem’s eps_cv (default 1e-6).
archive_cv = result.ctx.archive.get_array("cv")
feasible = archive_cv <= problem.eps_cv
print(f"feasible: {feasible.sum()} / {len(archive_cv)}")
Customize constraint handling with ConstraintHandler#
How cv is aggregated from multiple constraints, and how feasibility is judged from that value, is decided by ConstraintHandler.
If Problem’s handler argument is omitted, StaticToleranceHandler(eps_cv=problem.eps_cv) is used, which judges feasibility by whether cv <= eps_cv.
Class |
Behavior |
|---|---|
|
Judges feasibility with a fixed tolerance |
|
Shrinks the tolerance toward 0 over generations, gradually driving solutions into the feasible region |
|
Repairs infeasible solutions using the constraint gradient |
EpsilonConstraintHandler takes a function that receives the generation number and returns the tolerance.
from saealib import EpsilonConstraintHandler
def schedule(gen):
return max(0.0, 1.0 - gen * 0.05)
problem = Problem(
func=expensive_func,
dim=DIM,
n_obj=1,
direction=np.array([-1.0]),
lb=LB,
ub=UB,
constraints=[constraint],
handler=EpsilonConstraintHandler(schedule),
)
result = minimize(problem, max_fe=1000, seed=0)