# 実験的 MILP ソルバー — SCIP, HiGHS, GLPK, CBC :::{container} prog-cpp Hi-QUBO は複数のサードパーティ製**厳密 MILP ソルバー**で QUBO 式を解くことが できます.これらは共通インタフェースを持つヘッダオンリーのソルバーとして ラップされており,**クラス名を変えるだけ**で互いに,また `qbpp::ABS3Solver` とも切り替えられます. これらは**線形**の目的関数を最小化するため,二次の QUBO は渡す前に **線形化**する必要があります(後述).これがこのページの判定基準です. 二次目的関数を**直接**受け取れるソルバー(Gurobi, IBM CPLEX — いずれも MIQP)は ここには含まれず,[QUBO/HUBO ソルバー](../solver/experimental-solvers.md) にまとめています. 制約プログラミングエンジンの OR-Tools CP-SAT は [CP ソルバー](../solver/cp-solvers.md) を 参照してください. > **実験的機能.** これらは実験・ベンチマーク用途で提供されます.API は予告なく > 変更される可能性があり,各ソルバーは別途インストールが必要です([セットアップ](#setup)参照). > 対応は **QUBO(次数 ≤ 2)のみ**です.HUBO は事前に QUBO へ削減するか,任意次数に > 対応する `qbpp::ABS3Solver` / `qbpp::EasySolver` を使ってください. 内部では各二次項 `x·y` を補助変数+線形リンク制約に置き換え(Fortet 線形化), QUBO を純粋な MILP としてソルバーに渡します.返される解のエネルギーは,ソルバーの 浮動小数点目的値とは独立に,元の QUBO から常に厳密に再計算されます. | ソルバー | クラス | ライセンス | 備考 | |----|----|----|----| | [SCIP](https://www.scipopt.org) | `qbpp::ScipSolver` | Apache-2.0 (OSS) | linearize / quadratic 定式化 | | [HiGHS](https://highs.dev) | `qbpp::HighsSolver` | MIT (OSS) | 高速な OSS MILP | | [GLPK](https://www.gnu.org/software/glpk/) | `qbpp::GlpkSolver` | GPL (OSS) | 軽量 | | [CBC](https://github.com/coin-or/Cbc) | `qbpp::CbcSolver` | EPL (OSS) | COIN-OR branch & cut | 二次目的関数を直接受け取れる商用の厳密ソルバー(Gurobi, IBM CPLEX)は [QUBO/HUBO ソルバー](../solver/experimental-solvers.md) を参照してください(Gurobi は C++・PyQBPP 両対応,CPLEX は PyQBPP のみ). ## 使い方 4 つのソルバーはすべて同じインタフェースです.次のプログラムは SCIP で数分割問題を 解きます.`qbpp::ScipSolver` を `qbpp::HighsSolver` / `qbpp::GlpkSolver` / `qbpp::CbcSolver` に置き換える(と対応ヘッダを include する)だけで別のソルバーを 使えます: ```{literalinclude} /../programFiles/cppPrograms/solver/experimental-solvers-program1.cpp :language: cpp :caption: experimental-solvers-program1.cpp ``` エネルギーが下界 `sol.info("bound")` と一致すれば,その解は最適であることが 保証されます: ```{include} /../programFiles/markDown/solver/experimental-solvers.md :start-after: :end-before: ``` ソルバーオブジェクトは式から生成され,構築時に(simplify 済みの)QUBO がソルバー 内部の MILP モデルへ線形化されます.式が高次(HUBO)項を含む場合,コンストラクタは 例外を送出します. ## パラメータ パラメータは `search()` にキー/値のペアの初期化子リストで渡します.すべての ラッパーが解釈する共通キーは次の通りです: | キー | 値 | 説明 | |----|----|----| | **`time_limit`** | 秒 | 制限時間に達したら停止 | | **`target_energy`** | エネルギー | この値以下の解を見つけたら停止 | | **`callback_timer_interval`** | 秒 | `Timer` イベントの初期間隔 | | **`enable_default_callback`** | `1` | 新しい incumbent ごとにエネルギーと TTS を表示 | | **`thread_count`** | スレッド数 | ワーカースレッド数(SCIP/HiGHS.GLPK/CBC は無視) | | **`topk_sols`** | K | 最大 K 個の解を返す(ベストエフォート,下記参照) | | **`gap_limit`** | gap | 相対 MIP gap による停止(SCIP/HiGHS) | | **`output_flag`** | `1` | ソルバー自身のログを表示(SCIP/HiGHS) | ソルバー固有の追加: - **SCIP** — 未知のキーは SCIP にそのまま転送されます(例 `"limits/gap"`, `"lp/threads"`).`formulation`(linearize / quadratic)は **コンストラクタ**の オプションであり,`search()` のキーではありません(下記参照). - **HiGHS** — 未知のキーは HiGHS の `setOptionValue` に転送されます (例 `"presolve"`, `"mip_rel_gap"`). > `topk_sols` はベストエフォートです.Gurobi の解プールと異なり,これらのソルバーは > 内部ストレージに残る相異なる解(多くの場合 incumbent のみ)を返します. ## SCIP: linearize と quadratic 定式化 `qbpp::ScipSolver` は QUBO を 2 通りの方法で SCIP に渡せます: - **`"linearize"`(既定)** — Fortet 線形化で純粋な MILP にする(他のソルバーと共通の 変換).SCIP に締まった LP 緩和を与えます. - **`"quadratic"`** — SCIP の目的関数は線形のみのため,目的変数 `t` を 1 つ追加し, **2 次(非線形)制約** `t == const + Σ qᵢⱼ·xᵢ·xⱼ` を 1 本張って `t` を最小化します. 二次項は SCIP の非線形制約ハンドラが内部で再定式化するため,Fortet 補助変数は 追加されません.ただし項ごとの(McCormick)緩和は緩く,密なペナルティ QUBO では 通常遅くなります.比較用に提供しています. どちらの定式化でも同じ最適解に到達します.定式化は**構築時に固定**されます. 別の定式化を使うにはソルバーオブジェクトを作り直してください(`search()` の パラメータではありません): ```{include} /../programFiles/markDown/solver/experimental-solvers.md :start-after: :end-before: ``` このオプションは SCIP 固有です.HiGHS・GLPK・CBC は常に線形化 MILP を使います. ## Solver Info `sol.info()` はソルバーが生成した文字列を保持します: | キー | 説明 | |----|----| | `status` | `OPTIMAL`, `TIME_LIMIT`, `INFEASIBLE`, …(綴りはソルバー依存) | | `bound` | 最良の双対下界 | | `mip_gap` | 最終的な相対 MIP gap(SCIP/HiGHS) | | `node_count` | 分枝限定ノード数 | | `solution_count` | 解が得られたら `1`,なければ `0` | | `_version` | `scip_version` / `highs_version` / `glpk_version` | | `run_time` | 実時間の求解時間(秒) | ## カスタムコールバック コールバック API は `qbpp::ABS3Solver` / `qbpp::GurobiSolver` と同一です.ソルバーを 継承し `callback()` 仮想メソッドを override します: | イベント | 説明 | |-------|-------------| | `CallbackEvent::Start` | `search()` の開始時に 1 度呼ばれる | | `CallbackEvent::BestUpdated` | 新しい incumbent が見つかるたびに呼ばれる | | `CallbackEvent::Timer` | `timer(seconds)` で設定した間隔で定期的に呼ばれる | コールバック内では **`event()`**,**`best_sol()`**(現在の最良 `qbpp::Sol`), **`bound()`**(現在の双対下界),**`timer(seconds)`**(タイマー設定/無効化), **`terminate()`**(次の安全点で探索を停止)が使えます.`hint(sol)` は SCIP と HiGHS を ウォームスタートします(GLPK・CBC では何もしません). ```{literalinclude} /../programFiles/cppPrograms/solver/experimental-solvers-program2.cpp :language: cpp :caption: experimental-solvers-program2.cpp ``` ## セットアップ {#setup} 各ソルバーは別途インストールが必要です.ヘッダは独立しています (``, ``, ``, ``). 使うものだけ include してください.qbpp 本体は `dlopen` で読み込まれるため `-lqbpp` は不要です.4 つすべてを手軽に入れるには [conda-forge](https://conda-forge.org) が便利です: ```{include} /../programFiles/markDown/solver/experimental-solvers.md :start-after: :end-before: ``` ソルバー別のビルドフラグ(他の qbpp プログラム同様 `-ldl -pthread` を付与): | ソルバー | リンクフラグ | ヘッダパス(conda) | |----|----|----| | SCIP | `-lscip` | (システム include か `-I$PREFIX/include`) | | HiGHS | `-lhighs` | `-isystem $PREFIX/include/highs` | | GLPK | `-lglpk` | (システム include か `-I$PREFIX/include`) | | CBC | `-lCbc -lCbcSolver -lCgl -lOsiClp -lClp -lOsi -lCoinUtils` | `-isystem $PREFIX/include/coin` | 例(conda を `$CONDA_PREFIX` に導入した場合): ```{include} /../programFiles/markDown/solver/experimental-solvers.md :start-after: :end-before: ``` SCIP は apt/deb(`SCIPOptSuite-*.deb`)で導入するとヘッダと `libscip.so` が既定の パスに置かれるため,`-lscip` だけで済みます. ::: :::{container} prog-python PyQBPP は複数のサードパーティ製**厳密 MILP ソルバー**で QUBO 式を解くことが できます.これらは共通インタフェースを持ち,**クラス名を変えるだけ**で互いに, また `pyqbpp.ABS3Solver` とも切り替えられます. これらは**線形**の目的関数を最小化するため,二次の QUBO は渡す前に **線形化**する必要があります(後述).二次目的関数を**直接**受け取れる ソルバー(Gurobi, IBM CPLEX — いずれも MIQP)はここには含まれず, [QUBO/HUBO ソルバー](../solver/experimental-solvers.md) にまとめています.制約プログラミング エンジンの OR-Tools CP-SAT は [CP ソルバー](../solver/cp-solvers.md) を参照してください. > **実験的機能.** これらは実験・ベンチマーク用途で提供されます.API は予告なく > 変更される可能性があり,各ソルバーの Python バインディングは別途インストールが > 必要です([セットアップ](#setup)参照).対応は **QUBO(次数 ≤ 2)のみ**です. > HUBO は事前に QUBO へ削減するか,任意次数に対応する `pyqbpp.ABS3Solver` / > `pyqbpp.EasySolver` を使ってください. 内部では各二次項を補助変数+線形リンク制約に置き換え(Fortet 線形化),QUBO を 純粋な MILP としてソルバーに渡します.返される解のエネルギーは元の QUBO から常に 厳密に再計算されます. | ソルバー | クラス | Python バインディング | ライセンス | |----|----|----|----| | [SCIP](https://www.scipopt.org) | `pyqbpp.ScipSolver` | PySCIPOpt | Apache-2.0 | | [HiGHS](https://highs.dev) | `pyqbpp.HighsSolver` | highspy | MIT | | [GLPK](https://www.gnu.org/software/glpk/) | `pyqbpp.GlpkSolver` | swiglpk | GPL | | [CBC](https://github.com/coin-or/Cbc) | `pyqbpp.CbcSolver` | python-mip | EPL | 二次目的関数を直接受け取れる商用の厳密ソルバーは [Gurobi と IBM CPLEX](../solver/experimental-solvers.md) を参照してください. ## 使い方 4 つのソルバーはすべて同じインタフェースです.次のプログラムは SCIP で数分割問題を 解きます.`qbpp.ScipSolver` を `qbpp.HighsSolver` / `qbpp.GlpkSolver` / `qbpp.CbcSolver` に置き換えるだけで別のソルバーを使えます: ```{literalinclude} /../programFiles/pythonPrograms/solver/experimental-solvers-program1.py :language: python :caption: experimental-solvers-program1.py ``` エネルギーが下界と一致すれば最適が保証されます.ソルバーオブジェクトは式から 生成され,構築時に QUBO がソルバー内部の MILP モデルへ線形化されます.高次(HUBO) の式は例外を送出します. ## パラメータ パラメータは `search()` にキーワード引数で渡します(dict も可).すべてのラッパーが 解釈する共通キーは次の通りです: | キー | 値 | 説明 | |----|----|----| | **`time_limit`** | 秒 | 制限時間に達したら停止 | | **`target_energy`** | エネルギー | この値以下の解を見つけたら停止 | | **`callback_timer_interval`** | 秒 | `Timer` イベントの初期間隔 | | **`enable_default_callback`** | `1` | 新しい incumbent ごとにエネルギーと TTS を表示 | | **`thread_count`** | スレッド数 | ワーカースレッド数(SCIP/HiGHS/CBC) | | **`topk_sols`** | K | 最大 K 個の解を返す(ベストエフォート) | | **`gap_limit`** | gap | 相対 MIP gap による停止(SCIP/HiGHS) | | **`output_flag`** | `1` | ソルバー自身のログを表示(SCIP/HiGHS) | ソルバー固有の追加: - **SCIP** — 未知のキーは SCIP にそのまま転送されます(例 `solver.search({"limits/gap": 0.0})`).`formulation`(linearize / quadratic)は **コンストラクタ**の引数であり,`search()` のキーではありません(下記参照). - **HiGHS** — 未知のキーは HiGHS の `setOptionValue` に転送されます(例 `presolve="on"`). ## SCIP: linearize と quadratic 定式化 `ScipSolver` は QUBO を 2 通りの方法で SCIP に渡せます: - **`"linearize"`(既定)** — Fortet 線形化で純粋な MILP にする(他のソルバーと共通の 変換).SCIP に締まった LP 緩和を与えます. - **`"quadratic"`** — SCIP の目的関数は線形のみのため,目的変数を 1 つ追加し,SCIP の 非線形制約ハンドラが内部で再定式化する **2 次(非線形)制約**を 1 本張ります. Fortet 補助変数は追加されません.項ごとの緩和は緩いため,密なペナルティ QUBO では 通常遅くなります.比較用に提供しています. どちらも同じ最適解に到達します.定式化は**構築時に固定**されます.別の定式化を 使うにはソルバーオブジェクトを作り直してください(`search()` のキーワードでは ありません): ```{include} /../programFiles/markDown/solver/experimental-solvers.md :start-after: :end-before: ``` このオプションは SCIP 固有です.HiGHS・GLPK・CBC は常に線形化 MILP を使います. ## Solver Info `sol.info` はソルバーが生成した文字列を保持します: `status`, `bound`, `mip_gap` (SCIP/HiGHS), `node_count`, `solution_count`, `_version` (`scip_version` / `highs_version` / `glpk_version`), `run_time`. ## カスタムコールバック ソルバーを継承し `callback()` を override します.内部では `self.event()`, `self.best_sol()`(`pyqbpp.Sol`),`self.bound()`,`self.timer(seconds)`, `self.terminate()` が使えます.イベント定数はクラス属性 `EVENT_START` / `EVENT_BEST_UPDATED` / `EVENT_TIMER` です. ```{literalinclude} /../programFiles/pythonPrograms/solver/experimental-solvers-program2.py :language: python :caption: experimental-solvers-program2.py ``` > **ライブコールバックの対応はバインディング依存です.** `ScipSolver` と > `HighsSolver` は求解中に `BestUpdated`/`Timer` を発火します.**`GlpkSolver` と > `CbcSolver` は `EVENT_START` のみ**です(swiglpk は Python コールバックを設定でき > ず,python-mip の incumbent コールバックも一般的なビルドでは呼ばれないため). > C++ の `qbpp::GlpkSolver` / `qbpp::CbcSolver` はライブイベントを発火します. > `hint(sol)` は SCIP/HiGHS をウォームスタートします(GLPK/CBC では何もしません). > 求解時間は `time_limit` で制限してください. ## セットアップ {#setup} 使うバインディングだけ入れてください: | ソルバー | インストール | |----|----| | SCIP | `conda install -c conda-forge pyscipopt` | | HiGHS | `pip install highspy`(または `conda install -c conda-forge highspy`) | | GLPK | `conda install -c conda-forge swiglpk` | | CBC | `pip install mip` | バインディングは遅延 import されるため,未インストールでも `import pyqbpp` は成功 します.エラーはそのソルバーを生成した時点で初めて送出されます. :::