Python

OR-Tools MathOptとは?pywraplpとの違い・対応ソルバーとPythonでの使い方【v9.15】

OR-Tools MathOptとは?pywraplpとの違い・対応ソルバーとPythonでの使い方【v9.15】

MathOptは、Googleの最適化ライブラリOR-Toolsに含まれるモデリングAPIです。変数・制約・目的関数を1度書けば、GLOP、SCIP、HiGHS、CP-SAT、Gurobiなどのソルバーを引数1つで切り替えて解けます。OR-Toolsのリポジトリにある説明文は、新しく書くコードでは従来のMPSolver(Pythonではpywraplp)よりMathOptを優先するよう求めています。

この記事では、ortools 9.15.6755(2026年1月14日公開・2026年10月時点のPyPI最新版)をPython 3.14に入れ、MathOptのコードを実行して確かめた結果をもとに、pip版でそのまま動くソルバー、ソルバーごとに使える機能、移行のときにつまずく点を整理します。OR-Tools全体の入門とインストールはGoogle OR-Toolsとは?Pythonでの使い方とインストール、CP-SAT・配送ルートの実行例【v9.15】で扱っています。

まとめ:MathOptの位置づけと使い分けの要点

  • 位置づけ:OR-Toolsの線形計画・混合整数計画向けの新しいモデリングAPIで、非推奨になったMPSolver(pywraplp)の後継です。公式ドキュメントのクライアントはC++とPythonで、.NETは対象外です。
  • pip版で追加導入なしに動くソルバー:GSCIP(SCIP)・GLOP・CP_SAT・PDLP・HIGHSの5つ。GLPK・OSQP・ECOS・SCS・SANTORINIは列挙値にあってもpip版には組み込まれていません。Gurobiは共有ライブラリとライセンスを別途用意します。
  • 機能はソルバー次第:インジケーター制約と二次制約を扱える同梱ソルバーはSCIPだけです。IIS(実行不能の原因となる制約の特定)と多目的最適化は、同梱ソルバーのどれも対応していません。
  • 最大の落とし穴:CP_SATの連続変数対応は実験的な離散化で、既定設定では整数刻みになります。掲載例のLPでは真の最適値2.8ではなく2.0が「OPTIMAL」で返りました。CP_SATは全変数が整数のモデルに限って使うのが安全です。
  • 移行の判断:pywraplpは9.15でも動きますが、CBC・CLPに頼るコードは、MathOptに移すとソルバーごと変わります。先にHiGHSかSCIPで解き直し、結果が変わらないか確かめてから移します。

MathOptとpywraplp・model_builder・CP-SATの関係

OR-ToolsのPythonパッケージには、線形・整数計画のモデルを組むAPIが3つあり、さらに制約プログラミング用のCP-SAT専用APIがあります。旧版のこの記事ではpywraplpを「ModelBuilder」と呼んでいましたが、両者は別のAPIです。

API モジュール 主な用途 Pythonで使えるソルバー
MathOpt ortools.math_opt.python.mathopt LP・MIP・二次計画(新規開発向け) GSCIP・GLOP・CP_SAT・PDLP・HIGHS(Gurobiは別途)
pywraplp(MPSolver) ortools.linear_solver.pywraplp LP・MIP(従来API) SCIP・GLOP・CP-SAT・PDLP・HiGHS・CBC・CLP
model_builder ortools.linear_solver.python.model_builder LP・MIPの単発求解、MPS・LP形式の入出力 SCIP・GLOP・CP-SAT・PDLP・HiGHS
CP-SAT(cp_model) ortools.sat.python.cp_model スケジューリング等の組合せ最適化 CP-SATのみ

ソルバー欄は、9.15.6755をmacOS(x86_64)に入れ、各APIからソルバーを生成できたかを確かめた結果です。pywraplpだけがCBCとCLPを持っている点が、移行の判断に効いてきます。model_builderはPythonのほかJava・.NETからも使える単発求解向けのAPIで、MPS・LP形式のファイルを読み書きできます。モデルを更新するとソルバーの内部状態は失われるため、何度も解き直す用途はMathOptの担当です。

OR-Toolsのリポジトリにあるmath_optディレクトリの説明文は、MathOptを「GLOP、CP-SAT、SCIP、Gurobiなどのソルバーを1つのAPIでおおむね相互運用できるようにするもの」と位置づけ、New code should prefer MathOpt to `MPSolver`(新しいコードはMPSolverよりMathOptを優先すべき)と書いています。メンテナーのLaurent Perron氏はGitHub Discussions(#3992、2023年11月18日)でMPSolver is deprecated.と告知し、コードは少なくとも次の1メジャーリリースの間(年単位)は残すとしました。同じ投稿で、MathOptの目的をMPSolverを包含し、インクリメンタルソルブなどの高度な手法を支えることとし、C#(.NET)版はおそらく実装しないと述べています。9.15.6755でもpywraplpは同梱され、既存のコードはそのまま動きます。

名前が似ているMathOptInterfaceは、Julia言語のモデリング環境JuMPが使う別のライブラリです。OR-ToolsのMathOptとは無関係で、互換性もありません。

MathOptのインストールと最初のモデル

MathOptはortoolsパッケージに含まれているので、別のパッケージは要りません。9.15.6755の要件はPython 3.9以上で、Windows(x64)、macOS(x86_64・arm64)、Linux(x86_64・aarch64)向けのwheelが公開されています。

pip install ortools==9.15.6755
python -c "import ortools; print(ortools.__version__)"
# 9.15.6755

例として、2種類の製品の生産量を決める問題を解きます。製品Aは1個30円・製品Bは1個20円の利益で、機械時間は「2×A+B」が100時間以内、人手は「A+B」が80時間以内、製品Aは40個までです。変数・制約・目的関数の考え方は決定変数・目的関数・制約条件とは?違いと定式化の手順を例題で解説にまとめています。

from ortools.math_opt.python import mathopt

model = mathopt.Model(name="production_plan")
x = model.add_variable(lb=0, ub=40, name="x")  # 製品Aの生産量
y = model.add_variable(lb=0, name="y")         # 製品Bの生産量
model.add_linear_constraint(2 * x + y <= 100, name="machine_hours")
model.add_linear_constraint(x + y <= 80, name="labor_hours")
model.maximize(30 * x + 20 * y)

result = mathopt.solve(model, mathopt.SolverType.GLOP)
print(result.termination.reason.name)
print("利益:", result.objective_value())
print("x =", round(result.variable_values(x), 6), "y =", round(result.variable_values(y), 6))
for c, v in result.dual_values().items():
    print(c.name, "の双対価格:", round(v, 3))

実行結果は次のとおりです。

OPTIMAL
利益: 1800.0
x = 20.0 y = 60.0
machine_hours の双対価格: 10.0
labor_hours の双対価格: 10.0

結果の読み方:termination・objective_value・dual_values

mathopt.solve()はSolveResultを返します。最初に見るのはresult.termination.reasonで、OPTIMAL以外(INFEASIBLE、UNBOUNDED、時間切れのFEASIBLEやNO_SOLUTION_FOUNDなど)なら、目的関数値を信じる前に原因を調べます。上限に達して停止した場合、実行可能解が返ればreasonはFEASIBLE、解が見つからなければNO_SOLUTION_FOUNDとなり、どの上限かはresult.termination.limit(TIME・SOLUTIONなど)でわかります。解が1つも無いのにobjective_value()を呼ぶと、9.15ではValueError: No primal feasible solution available.で止まります。値を取り出す前にresult.has_primal_feasible_solution()で確かめると、実行不能なデータが紛れ込んだ日でもバッチ処理を落とさずに済みます。

dual_values()は制約ごとの双対価格(シャドープライス)を返します。上の例では機械時間・人手とも10.0なので、どちらかを1時間増やすと利益が10円増える見込みです。双対値は、対応ソルバーで解いたLPや凸二次計画などの連続凸最適化問題で取得できます。整数計画では通常返されず、連続問題でもソルバーと終了状態によって取得可否が異なります。

pip版で使えるソルバーと対応機能の実測表

mathopt.SolverTypeには11個の値が並んでいますが、pip版のortoolsで実際に動くのは一部です。9.15.6755をmacOS(x86_64)に入れ、同じモデルを各ソルバーで解いて結果を確かめました。

SolverType LP 整数変数 二次目的 二次制約 インジケーター コールバック
GSCIP ○ ○ ○ ○ ○ MIP_SOLUTION・MIP_NODE
HIGHS ○ ○ × × × 本文参照
CP_SAT △(離散化) ○ × × × MIP_SOLUTION・MIP
GLOP ○ × × × × なし
PDLP ○ × △(凸・対角のみ) × × なし
GUROBI 共有ライブラリとライセンスが必要(未導入なら実行時エラー)
GLPK・OSQP・ECOS・SCS・SANTORINI pip版には組み込まれていない(not registered エラー)

×の組み合わせでは、黙って無視されるのではなく例外で止まります。たとえばHiGHSにインジケーター制約を渡すとHighs does not support indicator constraints、CP_SATに二次目的を渡すとMathOpt does not currently support CP-SAT models with quadratic objectivesが出ます。例外で止まらないのは次章のCP_SATと連続変数の組み合わせだけです。HiGHSはどのイベントのコールバックも登録は通りましたが、ナップサック問題(変数60個)では1度も呼ばれませんでした。

同梱ソルバーでは使えない機能:IISと多目的最適化

MathOptには、実行不能なモデルから原因となる制約の組(IIS)を取り出すcompute_infeasible_subsystem()と、優先度付きの多目的最適化を組むadd_auxiliary_objective()があります。どちらもAPIとしては存在しますが、同梱の5ソルバーで呼ぶと、IISはUNIMPLEMENTED(例:SCIP does not provide a method to compute an infeasible subsystem)、多目的はINVALID_ARGUMENT(例:Highs does not support multiple objectives)で止まります。公式のMathOptの機能一覧でも、IISは「Gurobi only」と明記されています。旧版で紹介していた「IISによる原因解析」は、pip版だけでは試せません。実行不能の原因を探るときは、制約を1本ずつ外して解き直すか、Gurobiを使います。

Python APIの制限:SOS制約・二次錐制約の専用追加メソッド

公式のMathOptトップページは、モデルに入れられる要素としてSOS制約(Special Ordered Set)と二次錐制約(second order cone)を挙げています。ただしこれはC++の話です。9.15のC++のModelにはAddSos1Constraint・AddSos2Constraint・AddSecondOrderConeConstraintがありますが、Pythonのmathopt.Modelにあるadd_で始まるメソッドは、変数・線形制約・二次制約・インジケーター制約・目的関数の追加だけでした(2026年10月時点のmainブランチでも同じです)。区分線形の費用関数をSOS2で書きたい、ポートフォリオのリスク制約を二次錐で書きたい、という場合、SOS2の区分線形関数は二値変数を使った定式化に置き換えます。二次錐制約は、ノルムの上限を表す変数tを非負にしたうえで「x²+y²-t²≦0」という二次制約に直せば、add_quadratic_constraint()でSCIPに渡せます。x+y≧2の下でtを最小化する例では、SCIPが√2(1.4142)を返しました。二次錐を専用の制約として直接書きたいなら、gurobipyのような別のAPIを使います。目的関数や制約に一般の非線形関数(指数・対数など)を入れる手段もMathOptには無く、非線形計画はIpoptのような別のソルバーの領域です。

公式ページにあるFICO Xpressの扱い

英語版のMathOptトップページ(最終更新日:2026年2月16日)は対応ソルバーにFICO Xpress(要ライセンス)を掲載していますが、日本語版(最終更新日:2024年8月9日)の一覧にはまだありません。9.15.6755のPythonではmathopt.SolverTypeにXPRESSがなく、指定できません。GitHubのmainブランチには2026年2月10日に「Add XPRESS to Python MathOpt SolverType enum」という変更が入っているので、PythonからXpressを選べるのは9.15より後のリリースになる見込みです。

Gurobiを使う条件

SolverType.GUROBIを指定すると、MathOptは実行時にGurobiの共有ライブラリを探して読み込みます。Gurobi本体を入れていない環境では、MathOptのsolve()はRuntimeError: std::exceptionという原因の読み取れない例外で止まりました。同じ環境でpywraplpからGurobiを生成しようとするとCould not find the Gurobi shared libraryとログに出るので、原因はこちらで確かめられます。ライセンスの種類と価格はGurobiとは?Pythonでの使い方・インストール・ライセンスと価格【gurobipy 13.0】を参照してください。モデルのコードはSolverTypeを書き換えるだけで済み、これがMathOptを選ぶ一番の理由になります。

CP-SATの落とし穴:連続変数の離散化と「OPTIMAL」判定

ソルバーを引数で切り替えられることには裏があります。MathOptの公式リファレンスはCP-SATを「全変数が整数で有界な問題に対応する」ソルバーとし、連続変数を含む問題にはExperimental support to rescale and discretize problems with continuous variables(再スケーリングと離散化による実験的な対応)と書いています。既定設定のまま連続変数だけのLPを解くと、例外を出さずに、離散化した問題の解がOPTIMALとして返ってきます。

from ortools.math_opt.python import mathopt

model = mathopt.Model()
x = model.add_variable(lb=0, ub=10, name="x")  # 連続変数
y = model.add_variable(lb=0, ub=10, name="y")  # 連続変数
model.add_linear_constraint(3 * x + 2 * y <= 7)
model.add_linear_constraint(2 * x + 3 * y <= 7)
model.maximize(x + y)

for solver in (mathopt.SolverType.GLOP, mathopt.SolverType.HIGHS, mathopt.SolverType.CP_SAT):
    r = mathopt.solve(model, solver)
    print(solver.name, r.termination.reason.name, round(r.objective_value(), 3),
          round(r.variable_values(x), 3), round(r.variable_values(y), 3))
GLOP OPTIMAL 2.8 1.4 1.4
HIGHS OPTIMAL 2.8 1.4 1.4
CP_SAT OPTIMAL 2.0 2.0 0.0

正しい最適値はx=y=1.4のときの2.8ですが、CP_SATは2.0を返しました。xとyの組は実行のたびに変わり、2026年10月24日に同じコードを7回実行すると(2, 0)が5回、(0, 2)と(1, 1)が1回ずつでした。目的関数値はどれも2.0です。msg_cbでCP-SATのログを取るとScaling to pure integer problem.とWarning: 2 continuous variable domain with fewer than 1000 values.が出ており、CP-SATが連続変数を整数刻みに置き換えて解いたことがわかります。ログを読まなければ気づけません。

刻みを細かくするには、連続変数に掛ける倍率であるCP-SATのパラメータmip_var_scaling(既定値1.0)を上げます。

params = mathopt.SolveParameters()
params.cp_sat.mip_var_scaling = 10   # 連続変数を10倍して0.1刻みで扱う
r = mathopt.solve(model, mathopt.SolverType.CP_SAT, params=params)
print(round(r.objective_value(), 3), round(r.variable_values(x), 3), round(r.variable_values(y), 3))
# 2.8 1.4 1.4

倍率10なら0.1刻みになり、この例では正しい2.8が返りました。ただし刻みより細かい値は表せないので、最適解の桁数がわからない実務のモデルでは当てになりません。

この記事としての結論は、MathOptでCP_SATを選ぶのは全変数が整数か二値のモデルに限る、です。連続変数が1つでも入るなら、LPはGLOPかHiGHS、MIPはSCIPかHiGHSを使います。

CP-SATを直接使うべき場面

MathOptから使えるCP-SATは、線形の制約と目的関数に限られます。AddAllDifferent(全変数が異なる値)、区間変数とAddNoOverlap(作業の重なり禁止)のような、CP-SATの強みである制約は、ortools.sat.python.cp_modelを直接使わないと書けません。シフト表、ジョブショップ・スケジューリング、配送の時間枠といった組合せ問題はcp_modelで組むのが近道です。CP-SATで配送ルートを解く実行例はGoogle OR-Toolsとは?Pythonでの使い方とインストール、CP-SAT・配送ルートの実行例【v9.15】に載せています。

整数計画とインジケーター制約の書き方

整数変数はadd_integer_variable()、0か1の二値変数はadd_binary_variable()で作ります。整数変数はadd_variable(is_integer=True)でも作れ、二値変数はそれにlb=0, ub=1を加えたものと同じです。「拠点を開設しなければ出荷量は0」のような条件付きの制約は、従来はBig-M(十分大きな定数)で書いていましたが、MathOptではadd_indicator_constraint()で直接書けます。

from ortools.math_opt.python import mathopt

model = mathopt.Model(name="facility")
open_ = model.add_binary_variable(name="open")         # 拠点を開設するか
ship = model.add_variable(lb=0, ub=100, name="ship")   # 出荷量
# open_ == 0 のときは出荷できない
model.add_indicator_constraint(indicator=open_, activate_on_zero=True,
                               implied_constraint=ship <= 0, name="closed_no_ship")
model.add_linear_constraint(ship >= 30, name="demand")
model.minimize(50 * open_ + 1 * ship)                  # 固定費50 + 変動費

for solver in (mathopt.SolverType.GSCIP, mathopt.SolverType.HIGHS):
    try:
        r = mathopt.solve(model, solver)
        print(solver.name, r.termination.reason.name, r.objective_value())
    except RuntimeError as e:
        print(solver.name, "エラー:", str(e).splitlines()[0])
GSCIP OPTIMAL 80.0
HIGHS エラー: INVALID_ARGUMENT: Highs does not support indicator constraints

需要30を満たすには拠点を開く(固定費50)しかなく、最適値は50+30=80です。同じモデルでもHiGHSは例外で止まります。インジケーター制約を使うなら、同梱ソルバーではSCIP一択です。HiGHSでも解きたいなら、Big-M(例:ship <= 100 * open_)で書き直します。Mには出荷量の上限100のような、取りうる最大値を入れます。大きすぎるMは数値誤差の原因になります。

SCIP自体のライセンスや単体での使い方はSCIPソルバーとは?PySCIPOptでPythonから使う手順とCBC・HiGHSとの比較【SCIP 10.1】にまとめています。

応用機能:インクリメンタルソルブ・コールバック・パラメータ

IncrementalSolverによるモデル更新と再求解

IncrementalSolverは、ソルバーをモデルに結び付けたまま保持し、モデルを書き換えたあとの再計算では差分だけをソルバーへ送ります。対応していないソルバーや変更の種類では、内部でソルバーを作り直して解きます。「機械時間を増やしたら利益はどこまで増えるか」のような感度分析に向いています。

from ortools.math_opt.python import mathopt

model = mathopt.Model(name="production_plan")
x = model.add_variable(lb=0, ub=40, name="x")
y = model.add_variable(lb=0, name="y")
machine = model.add_linear_constraint(2 * x + y <= 100, name="machine_hours")
model.add_linear_constraint(x + y <= 80, name="labor_hours")
model.maximize(30 * x + 20 * y)

solver = mathopt.IncrementalSolver(model, mathopt.SolverType.GLOP)
print("機械100時間:", solver.solve().objective_value())
for hours in (110, 120, 130):
    machine.upper_bound = hours          # モデルを書き換えると差分だけソルバーへ送られる
    print(f"機械{hours}時間:", round(solver.solve().objective_value(), 6))
solver.close()
機械100時間: 1800.0
機械110時間: 1900.0
機械120時間: 2000.0
機械130時間: 2000.0

120時間で利益が頭打ちになるのは、人手80時間と製品Aの上限40個が先に効くためです。設備投資の検討なら、機械時間を120時間より増やしても利益は増えない、という判断材料になります。

SolveParametersの時間制限とコールバックのイベント登録

時間制限や最適性ギャップはSolveParametersにソルバー共通の名前で指定します。pywraplpのSetTimeLimit()がミリ秒の整数だったのに対し、MathOptはdatetime.timedeltaで渡します。コールバックはCallbackRegistrationで受け取るイベントを登録し、cbに関数を渡します。次の例は全変数が二値のナップサック問題なので、CP_SATで解いても連続変数の離散化の影響を受けません。

import datetime
import random
from ortools.math_opt.python import mathopt

# 60品目のナップサック問題(全変数が二値なのでCP_SATで解ける)
random.seed(0)
w = [random.randint(10, 60) for _ in range(60)]
v = [wi + random.randint(-5, 15) for wi in w]
model = mathopt.Model(name="knapsack")
xs = [model.add_binary_variable(name=f"x{i}") for i in range(60)]
model.add_linear_constraint(sum(wi * xi for wi, xi in zip(w, xs)) <= sum(w) // 2)
model.maximize(sum(vi * xi for vi, xi in zip(v, xs)))

params = mathopt.SolveParameters(
    time_limit=datetime.timedelta(seconds=10),  # 打ち切り時間
    relative_gap_tolerance=0.0,                 # 最適性ギャップ0まで探す
    enable_output=False,
)

def on_solution(data: mathopt.CallbackData) -> mathopt.CallbackResult:
    print("改善解:", data.event.name)
    return mathopt.CallbackResult()

result = mathopt.solve(
    model, mathopt.SolverType.CP_SAT, params=params,
    callback_reg=mathopt.CallbackRegistration(events={mathopt.Event.MIP_SOLUTION}),
    cb=on_solution,
)
print(result.termination.reason.name, result.objective_value())

受け付けるイベントはソルバーごとに違い、実測ではSCIPがMIP_SOLUTIONとMIP_NODE、CP_SATがMIP_SOLUTIONとMIPでした。対応しないイベントを登録するとevent CALLBACK_EVENT_PRESOLVE is not supportedのように例外で止まります。なお、SCIPでMIP_SOLUTIONを登録すると、解は正しく返る一方でSCIPcatchEvent does not support variable or row change eventsというエラーログが標準エラーに出ました。ソルバーのログはmsg_cbに関数を渡すと受け取れます。関数にはログ行のリスト(Sequence[str])が渡されるので、msg_cb=lambda lines: print("\n".join(lines))のように1行ずつ出力します。

ModelSolveParametersによる解のヒント(ウォームスタート)の指定

前日の計画や手早いヒューリスティックで作った解を、探索の出発点としてソルバーに渡せます。公式の機能一覧では「Warm starts」とだけ書かれている機能で、Pythonではmathopt.SolutionHintに変数の値を入れ、ModelSolveParameters(solution_hints=[...])としてsolve()のmodel_paramsに渡します。直前のコード例のナップサック問題に、貪欲法で作った解をヒントとして渡し、最初に見つかった解で止めて比べました。

# 前の例の model・xs・w・v をそのまま使う
cap = sum(w) // 2
chosen = set()
for i in sorted(range(len(w)), key=lambda i: -v[i] / w[i]):  # 価値÷重さの大きい順に詰める貪欲法
    if w[i] <= cap:
        chosen.add(i)
        cap -= w[i]
print("貪欲法の解:", sum(v[i] for i in chosen))
hint = mathopt.SolutionHint(
    variable_values={x: 1.0 if i in chosen else 0.0 for i, x in enumerate(xs)})
first_only = mathopt.SolveParameters(solution_limit=1)  # 最初に見つけた解で止める
for solver in (mathopt.SolverType.GSCIP, mathopt.SolverType.HIGHS):
    for mp in (None, mathopt.ModelSolveParameters(solution_hints=[hint])):
        r = mathopt.solve(model, solver, params=first_only, model_params=mp)
        print(solver.name, "ヒントあり" if mp else "ヒントなし",
              r.termination.reason.name, r.termination.limit.name, r.objective_value())
貪欲法の解: 1380
GSCIP ヒントなし FEASIBLE SOLUTION 0.0
GSCIP ヒントあり FEASIBLE SOLUTION 1380.0
HIGHS ヒントなし FEASIBLE SOLUTION 1357.0
HIGHS ヒントあり FEASIBLE SOLUTION 1382.0

SCIPはヒントが無いと最初の解が「何も入れない」(目的関数値0)でしたが、ヒントを渡すと貪欲法の1380から始まりました。HiGHSはヒントを足場に、最初の解の時点で最適値の1382に届いています。時間制限の厳しい日次バッチでは、前回の解をヒントに渡すだけで打ち切り時点の解の質が上がる見込みがあります。CP_SATはこの規模だとsolution_limit=1でも最適まで解き切ったため、差は見えませんでした。容量を超える解(全品目を入れる)をヒントに渡しても例外にはならず、結果はヒントなしと同じでした。ヒントが誤っていても気づけないので、渡す前に制約を満たしているかを自分で確かめます。同じModelSolveParametersのbranching_prioritiesで変数ごとの分岐優先度も渡せ、SCIP・HiGHS・CP_SATのどれも例外を出さずに受け付けました(この規模の問題では効果の有無までは見分けられません)。

リモート解法(MathOptサービス)の現状

MathOptのモデルは、自分のマシンではなくGoogleのOR APIに送って解くこともできます。公式のMathOptサービスの概要では、MathOptサービスはexperimental(試験提供)のAPI群で、エンドポイントはhttps://optimization.googleapis.com/v1/mathopt:solveMathOptModelです。使えるソルバーはGLOP・PDLP・CP-SAT・SCIP・GLPK・OSQP・HiGHSの7つで、pip版に入っていないGLPKとOSQPもリモートなら選べます。

# pip install ortools==9.15.6755 requests
# model は「最初のモデル」の生産計画の例で作成済みとする
from ortools.math_opt.python import mathopt
from ortools.math_opt.python.ipc import remote_http_solve

result, logs = remote_http_solve.remote_http_solve(
    model, mathopt.SolverType.HIGHS,
    api_key="YOUR_API_KEY",   # Google CloudでOR APIを有効化して発行したキーに置き換える
    deadline_sec=10,          # 既定値は10秒
)
print(result.termination.reason.name, result.objective_value())

利用にはOR APIのAPIキーが必要で、クライアントはOR-Tools 9.9以降に入っています。Pythonではrequestsパッケージも必要です(入っていないとremote_http_solveのimportで止まります)。無効なキーで呼ぶとstatus code 400: API key not validが返りました。公式はCallbacks, incrementalism, and interruption are not yet supported.としており、コールバックとIncrementalSolverは使えません。deadline_secの既定値は10秒です。試験提供でSLAの記載もないため、本番の業務処理はローカル実行を基本にし、リモートは手元にないソルバーを試す用途に留めるのが無難です。

pywraplpからMathOptへの移行判断

書き換えの対応関係は次のとおりです。モデルを作る部分はほぼ1対1で置き換えられます。

処理 pywraplp MathOpt
モデル作成 Solver.CreateSolver("SCIP") mathopt.Model()
連続変数 NumVar(lb, ub, name) add_variable(lb=, ub=, name=)
整数・二値変数 IntVar・BoolVar add_integer_variable・add_binary_variable
制約 Add(式) add_linear_constraint(式)
目的関数 Maximize(式) maximize(式)
求解 Solve() mathopt.solve(model, SolverType.GSCIP)
変数の値 x.solution_value() result.variable_values(x)
時間制限 SetTimeLimit(ミリ秒) SolveParameters(time_limit=timedelta)

大きな違いは、pywraplpではソルバーを先に選んでからモデルを組むのに対し、MathOptはモデルを組んでからsolve()でソルバーを選ぶ点です。ソルバー名も変わり、pywraplpの"SCIP"はMathOptではGSCIP(GoogleのSCIPラッパー)です。

今すぐ書き換えなくても、9.15のpywraplpは動きます。ただし、CBCかCLPで本番運用しているコードは早めに検証を始めます。上記の投稿でPerron氏は「CLP・CBCを既定のビルドから外したい(CBCは確実に)」とも書いており、MathOptにはCBCもCLPもありません。移行するとソルバーごと変わり、同じモデルでも別の最適解(目的関数値は同じでも変数の値が違う解)が返ることがあります。HiGHSかSCIPで過去の入力を解き直し、目的関数値と業務上の指標が変わらないことを確かめてから切り替えます。逆に、商用ソルバーへの切り替えを見込んでいる、インジケーター制約や二次制約を使いたい、感度分析で何度も解き直す、のどれかに当てはまるなら、新規コードはMathOptで書く価値があります。最適化モデルを業務システムに組み込む開発や移行後の検証を外部に任せる場合は、フルスクラッチ開発の相談窓口から依頼できます。

ほかの最適化ライブラリやソルバーと比べて選びたい場合はORツールとは?OR-Tools・Gurobiなど主要6ツールの比較と選び方【無償版の上限を実測】を参照してください。

よくある質問

OR-ToolsのMathOptとは何ですか?

Googleの最適化ライブラリOR-Toolsに含まれる、線形計画・混合整数計画・二次計画のモデリングAPIです。モデルを1度書けば、GLOP・SCIP・HiGHS・CP-SAT・GurobiなどのソルバーをSolverTypeの指定だけで切り替えて解けます。公式ドキュメントの対象言語はC++とPythonです。

pywraplpはもう使えないのですか?

使えますが非推奨です。メンテナーは2023年11月にMPSolver(pywraplp)を非推奨と告知し、少なくとも1メジャーリリースの間はコードを残すとしています。9.15.6755でもpywraplpは同梱され、CBCやCLPを含むソルバーで動きます。新しく書くコードはMathOptが勧められています。

CP-SATはMathOptから使えますか?

使えます(SolverType.CP_SAT)。ただし線形の制約と目的関数に限られ、連続変数への対応は再スケーリングと離散化による実験的なものです(既定設定では整数刻み)。連続変数を含むモデルはGLOP・HiGHS・SCIPで解くのが安全です。AllDifferentや区間変数などCP-SAT固有の制約を使うなら、ortools.sat.python.cp_modelを直接使います。

MathOptとMathOptInterfaceは同じものですか?

別物です。MathOptInterfaceはJulia言語のモデリング環境JuMPが使うライブラリで、OR-ToolsのMathOptとは開発元もAPIも異なります。

MathOptは無料・商用利用できますか?

OR-ToolsはApache License 2.0で配布されており、MathOptと同梱のSCIP(Apache 2.0)・HiGHS(MIT)・GLOP・CP-SAT・PDLPは追加費用なしで商用利用できます。Gurobiを使う場合はGurobi側のライセンスが別に必要です。

関連記事

お気に入りに入れた記事の一覧

一創の公式 SNS

X では毎朝の新着記事と IT ニュースの速報を、YouTube・TikTok・Instagram・LinkedIn・Facebook では記事の解説動画を配信しています。

この記事は以下の記事からリンクされています

資料請求

RELATED POSTS 関連記事

目次