---
title: "OR-Tools MathOptとは？pywraplpとの違い・対応ソルバーとPythonでの使い方【v9.15】"
url: "https://www.issoh.co.jp/tech/details/10298/"
published: 2025-12-16
updated: 2026-10-24
categories: ["Python"]
publisher: "株式会社一創"
---

# 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](https://pypi.org/project/ortools/)最新版）をPython 3.14に入れ、MathOptのコードを実行して確かめた結果をもとに、pip版でそのまま動くソルバー、ソルバーごとに使える機能、移行のときにつまずく点を整理します。OR-Tools全体の入門とインストールは[Google OR-Toolsとは？Pythonでの使い方とインストール、CP-SAT・配送ルートの実行例【v9.15】](/tech/details/3477/)で扱っています。

## まとめ：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](https://github.com/google/or-tools/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個までです。変数・制約・目的関数の考え方は[決定変数・目的関数・制約条件とは？違いと定式化の手順を例題で解説](/tech/details/8321/)にまとめています。

```
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の機能一覧](https://developers.google.com/optimization/math%5Fopt)でも、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トップページ](https://developers.google.com/optimization/math%5Fopt)（最終更新日：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」](https://github.com/google/or-tools/commit/90b50425804060296fa255cd8044e9858398ff4f)という変更が入っているので、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】](/tech/details/2756/)を参照してください。モデルのコードは`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】](/tech/details/3477/)に載せています。

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

整数変数は`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】](/tech/details/3037/)にまとめています。

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

### 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サービスの概要](https://developers.google.com/optimization/service/math%5Fopt/overview)では、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で書く価値があります。最適化モデルを業務システムに組み込む開発や移行後の検証を外部に任せる場合は、[フルスクラッチ開発](/service/system/fullscratch/)の相談窓口から依頼できます。

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

## よくある質問

### 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側のライセンスが別に必要です。

## 関連記事

- [Google OR-Toolsとは？Pythonでの使い方とインストール、CP-SAT・配送ルートの実行例【v9.15】](/tech/details/3477/)
- [ORツールとは？OR-Tools・Gurobiなど主要6ツールの比較と選び方【無償版の上限を実測】](/tech/details/3034/)
- [SCIPソルバーとは？PySCIPOptでPythonから使う手順とCBC・HiGHSとの比較【SCIP 10.1】](/tech/details/3037/)
- [Gurobiとは？Pythonでの使い方・インストール・ライセンスと価格【gurobipy 13.0】](/tech/details/2756/)
- [決定変数・目的関数・制約条件とは？違いと定式化の手順を例題で解説](/tech/details/8321/)

---

出典: [OR-Tools MathOptとは？pywraplpとの違い・対応ソルバーとPythonでの使い方【v9.15】](<https://www.issoh.co.jp/tech/details/10298/>)（株式会社一創）
