PySCIPOpt Adapterで特殊制約をそのまま解く

PySCIPOpt Adapterで特殊制約をそのまま解く#

OMMXでは、条件付きの制約や変数の組合せに対する制約を「特殊制約」として表現できます。各制約の意味はOMMXが定義します。対応するAdapterを使えば、通常の制約へ書き換えず、そのままソルバーへ渡せます。

このチュートリアルでは、次の2種類を含む小さなモデルを作り、PySCIPOpt Adapterで解きます。

  • Indicator制約: 指定したBinary変数が1のときだけ有効になる制約

  • SOS1制約: 指定した変数のうち、高々1つだけが非ゼロになる制約

実行環境#

Python SDKとAdapterのインストールで用意したv3の環境を使います。このページではPySCIPOpt Adapterを使います。

特殊制約を持つInstanceを作る#

次のモデルには4つのBinary変数があります。enabled = 1 のときだけ option_a + option_b <= 1 を課し、さらに option_b と option_c のうち高々1つだけを選べるようにします。

from ommx import Instance, Sos1Constraint

source = Instance.maximize()
enabled = source.new_binary("enabled")
option_a = source.new_binary("option_a")
option_b = source.new_binary("option_b")
option_c = source.new_binary("option_c")

source.objective = (
    10 * enabled
    + 6 * option_a
    + 5 * option_b
    + 4 * option_c
)

# enabled = 1 のときだけ option_a + option_b <= 1
source.add_indicator_constraint(
    (option_a + option_b <= 1).with_indicator(enabled)
)

# option_b と option_c のうち、高々1つだけが非ゼロ
source.add_sos1_constraint(
    Sos1Constraint(
        variables=[option_b, option_c],
        name="exclusive_options",
    )
)

assert len(source.indicator_constraints) == 1
assert len(source.sos1_constraints) == 1
source

Instance.maximize() で空の最大化問題を作り、new_binary() と add_*_constraint() でモデルを順に組み立てています。

PySCIPOpt Adapterで解く#

PySCIPOpt Adapterは、線形なIndicator制約とSOS1制約を直接受け取れます。前のナップサック問題と同じく、作ったInstanceをsolve()へ渡します。

from ommx_pyscipopt_adapter import OMMXPySCIPOptAdapter

solution = OMMXPySCIPOptAdapter.solve(source)

assert solution.feasible
assert abs(solution.objective - 20.0) < 1e-8
solution.decision_variables_df()

この解では enabled = 1、option_a = 1、option_b = 0、option_c = 1 となり、Indicator制約とSOS1制約をどちらも満たします。呼び出し前に通常制約へ書き換える必要はありませんでした。

一方、Indicator制約やSOS1制約を直接扱わないソルバーもあります。次のAdapter向けにInstanceを準備するでは、同じモデルをHiGHSで解き、OMMXが通常制約への変換を担う例を見ます。各特殊制約の意味と個別APIの参照先は特殊制約型にまとめています。