クソコード製造機

数理最適化とかPythonとか

Gurobiの階層型多目的最適化の制御

Gurobiで多目的最適化をする際に、それぞれの目的関数における振る舞いを制御したいときがある。

今回は例として、目的関数が3つの多目的最適化について、優先度を設定して順々に最適化することを考える。

import gurobipy as gp

model = gp.Model()

# モデルをがんばって定義する

# obj0, obj1, obj2にはそれぞれ目的関数を表すLinExprオブジェクトを入れる

model.setObjectiveN(obj0, 0, 2)
model.setObjectiveN(obj1, 1, 1)
model.setObjectiveN(obj2, 2, 0)

こうすることで、obj0, obj1, obj2の順で最適化が行われる。第3引数が優先度であり、この値が大きい目的関数が優先して最適化される。第2引数はindexであり、後ほど目的関数を指定するときに使う。

今回は次のようにソルバーを制御することを考える。

  • 各目的関数の時間制限として、10分を設定する。
  • 各目的関数において、分枝限定法の前にヒューリスティクスにおける解の探索(NoRelHeur)を行う。これはobj0では300s、他2つでは200s行うことにする。
  • 相対許容誤差(MIPGap)を設定し、誤差以内になったら求解を終了する。許容誤差は、obj0とobj2では1%、obj1では5%とする。

このような場合には、次のように設定すれば良い。

# 全体共通の設定
model.setParam("TimeLimit", 2000)
model.setParam("NoRelHeurTime", 200)
model.setParam("MIPGap", 0.01)

# 各目的関数における設定
env0 = model.getMultiobjEnv(0)
env0.setParam("TimeLimit", 600)
env0.setParam("NoRelHeurTime", 300)

env1 = model.getMultiobjEnv(1)
env1.setParam("TimeLimit", 600)
env1.setParam("MIPGap", 0.05)

env2 = model.getMultiobjEnv(2)
env2.setParam("TimeLimit", 600)

基本的には、目的関数個別に設定されていないパラメータについては、全体のパラメータが参照される。

注意が必要なのがTimeLimit(WorkLimitを使う場合も)であり、全体のモデルに適用した時間制限は、すべての目的関数を合わせた制限時間となる。たとえば、ここで

model.setParam("TimeLimit", 600)

と設定してしまうと、600s経過した時点ですべての最適化が打ち切られてしまうため、obj1の最適化が一切行われない可能性がある。また、TimeLimitを600sに設定していても、実際は600sを少しオーバーすることがあるので、1800sから少し余裕を持たせている。

今回は例示のために、設定情報をマジックナンバーとしてコードに埋め込んでいるが、実際のシステムでは、yamlファイルなど別ファイルに書き出したものを読み取る形にするのが、保守性的には良いと思われる。