ConstraintHandler#
制約の基本的な定義方法(InequalityConstraint/EqualityConstraint)、Problemのhandler引数の指定方法、組み込み3種のハンドラの選び方は制約付き最適化で扱っています。
このページでは、それらを踏まえた上で独自のConstraintHandlerを書きたい場合に絞って説明します。
ConstraintHandlerの役割#
ConstraintHandlerは、制約の処理方式を差し替え可能なライフサイクルフックの集合として公開しています。
Ask -> [repair(x, constraints, lb, ub)]
-> evaluate f, g
-> [compute_cv(constraints, x, g)] -> cv
-> [augment_objective(f, constraints, x, g)] -> f'
Tell -> Comparator(f', cv) with eps_cv = feasibility_threshold
Generation end -> [on_generation_end(gen, population)]
抽象メソッドはcompute_cvだけであり、残りのフックはいずれも既定実装を持ちます。
独自のConstraintHandlerは、必要なフックだけをオーバーライドすればよいです。
repair(x, constraints, lb, ub, **kwargs):交叉と突然変異の後、評価の前に呼ばれます。
既定はnp.clip(x, lb, ub)で、境界にクリップするだけです。
compute_cv(constraints, x, g)(抽象):制約群を単一のcv値へ集約します。
augment_objective(f, constraints, x, g):目的関数値を制約情報で変換します。
既定は恒等関数(変換しない)で、ペナルティ関数法やaugmented Lagrangian法を実装する際にオーバーライドする場所です。
組み込み3種のハンドラはいずれもこのフックをオーバーライドしていないため、現状は将来の拡張点として空いています。
feasibility_threshold(プロパティ):既定1e-6。
この値がComparatorのeps_cvとして使われ、Optimizer実行中は毎世代同期されます。
on_generation_end(gen, population):世代の終わりに呼ばれます。
既定はno-opで、EpsilonConstraintHandlerのようにε値を世代ごとに更新する内部状態を持つハンドラが使います。
組み込みハンドラがオーバーライドするフック#
クラス |
オーバーライドするフック |
|---|---|
|
|
|
|
|
|
EpsilonConstraintHandler[1]は、schedule(gen) -> floatという関数でεを世代ごとに更新します。
linear_epsilon_schedule(eps0, n_gen)/exponential_epsilon_schedule(eps0, decay)という組み込みのスケジュール生成関数も公開されています。
GradientRepairHandler[2]は、現在違反している等式制約と不等式制約に対して、Moore-Penrose擬似逆行列による同時更新を行うrepair()を実装します。
gradient()がNoneを返す制約は、スキップせず前進差分による数値近似へフォールバックします。
InequalityConstraint/EqualityConstraintの拡張点#
InequalityConstraint自体にも2つの拡張点があります。
gradient(x):既定はNoneを返します。
オーバーライドして解析的な勾配ベクトルを返さない場合、GradientRepairHandlerは数値近似へフォールバックします。
violation_from_value(g):生の制約値g(x)から違反量への変換を定義します。
既定はmax(0, g - threshold)です。
EqualityConstraintはこのメソッドだけをオーバーライドし、max(0, |h(x)| - tolerance)という独自の変換を定義しています。
独自ConstraintHandlerの実装方法#
独自の制約処理戦略が必要な場合は、ConstraintHandlerを継承してcompute_cv()だけを実装すればよいです。
次の例は、制約違反量を目的関数値へ加算するペナルティ関数法です。
from saealib import ConstraintHandler
class PenaltyHandler(ConstraintHandler):
"""Adds the violation amount to the objective value as a penalty."""
def __init__(self, penalty_coeff: float = 1e3):
self.penalty_coeff = penalty_coeff
def compute_cv(self, constraints, x, g):
return sum(max(0.0, gi) for gi in g)
def augment_objective(self, f, constraints, x, g):
cv = self.compute_cv(constraints, x, g)
return f + self.penalty_coeff * cv
augment_objectiveをオーバーライドしてペナルティを目的関数値に加算する一方で、compute_cvは制約違反として0を返す実装にすれば、ペナルティ関数法だけで実行可能性を扱わない(全解を実行可能とみなす)構成にもできます。
関連コンポーネント#
制約付き最適化:制約の定義と組み込みハンドラの基本的な使い方
Problem:
handler引数の渡し方Comparator:
feasibility_thresholdがeps_cvとして同期される先