制約付き最適化#
設計変数に制約がある問題を、saealibで解きます。
minimize()では、アルゴリズム、代理モデル、評価戦略に文字列またはインスタンスを指定できます。 このページの例では問題固有の設定だけを追加し、それ以外にはデフォルトを使います。
このページでできるようになること
このページを終えると、Problemに制約を定義し、実行可能性を確認しながらConstraintHandlerを選べます。
単純な目的関数の最適化には単目的最適化を使い、独自の制約処理を実装する場合は独自コンポーネントを参照してください。このページでは、Problemへの制約定義、実行可能性の確認、ConstraintHandlerの選択を扱います。
Problemを準備する#
目的関数に加えて、解が満たすべき不等式制約g(x) <= 0を持つ問題を想定します。
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
g1(x) <= 0を満たす解だけが実行可能解です。
制約を定義する#
制約はInequalityConstraint(func, threshold=0.0)で定義し、Problemのconstraints引数に渡します。
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
g(x) >= thresholdの形で制約を課したい場合は、funcの符号を反転させて渡します。
等式制約h(x) = 0は、InequalityConstraintを符号反転で組み合わせる代わりにEqualityConstraint(func, tolerance=1e-6)を使います。
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は、|h(x)| <= toleranceを満たす解を実行可能解として扱います。
実行可能性を確認する#
Problemのeps_cv(既定値1e-6)以下の制約違反量cvを持つ解が実行可能解です。
archive_cv = result.ctx.archive.get_array("cv")
feasible = archive_cv <= problem.eps_cv
print(f"feasible: {feasible.sum()} / {len(archive_cv)}")
ConstraintHandlerで制約処理をカスタマイズする#
複数の制約からcvを集約する方法と、その値をもとに実行可能性を判定する方法は、ConstraintHandlerが決めます。
Problemのhandler引数を省略すると、StaticToleranceHandler(eps_cv=problem.eps_cv)が使われ、cv <= eps_cvかどうかで実行可能性を判定します。
クラス |
動作 |
|---|---|
|
固定の許容誤差 |
|
許容誤差を世代とともに0へ近づけ、徐々に実行可能領域へ解を追い込む |
|
制約の勾配を使って実行不可能解を修復する |
EpsilonConstraintHandlerには、世代番号を受け取り許容誤差を返す関数を渡します。
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)