🎓 レベル:基礎 | 重要度:B(標準)
📎 前提:実装:cvxpyで凸問題を解く | 関連:定式化の技法
要点(BLUF)
- 最適化ツールは 問題クラスで使い分ける:LP/小規模=scipy、LP/MILP=PuLP、凸(QP/SOCP/SDP)=cvxpy、組合せ/ルーティング=OR-Tools。
- 「モデリング言語(人間が数式で書く)」と「ソルバー(実際に解く)」は分離している。
- 同じ LP はどのツールでも同じ最適に至る。違いは 書きやすさ・対応する問題クラス・規模。
ツールの守備範囲
| ツール | 得意な問題 | 性格 |
|---|---|---|
| scipy.optimize | LP(linprog)・無制約/制約付き非線形(minimize) | 低レベル、行列で渡す、依存が軽い |
| PuLP | LP・混合整数計画(MILP) | モデリング言語、CBC など MILP ソルバーを呼ぶ |
| cvxpy | 凸最適化(LP/QP/SOCP/SDP) | DCP で凸性を保証、錐ソルバーへ自動変換 |
| OR-Tools | 組合せ・ルーティング(TSP/VRP)・制約プログラミング・MILP | 専用アルゴリズム、大規模ルーティングに強い |
graph TD
A["どんな問題?"] --> B{"線形か凸か離散か"}
B -->|"LP 小規模/低レベル"| C["scipy.linprog"]
B -->|"LP/MILP モデリング"| D["PuLP"]
B -->|"凸 QP/SOCP/SDP"| E["cvxpy"]
B -->|"組合せ/ルーティング"| F["OR-Tools"]
Pythonで同じLPを3ツールで解く
生産計画 LP(線形計画の標準形と幾何)を scipy・PuLP・cvxpy で解き、結果が一致することを確かめる。
import numpy as np
# scipy.optimize.linprog (低レベル: 行列で渡す。最小化なので符号反転)
from scipy.optimize import linprog
r = linprog([-40, -30], A_ub=[[2, 1], [1, 1]], b_ub=[120, 100],
bounds=[(0, None), (0, None)], method="highs")
print(f"scipy : xA={r.x[0]:.0f}, xB={r.x[1]:.0f}, 利益={-r.fun:.0f}")
# PuLP (LP/MILPモデリング言語)
import pulp
p = pulp.LpProblem("p", pulp.LpMaximize)
xa = pulp.LpVariable("xa", lowBound=0); xb = pulp.LpVariable("xb", lowBound=0)
p += 40*xa + 30*xb
p += 2*xa + xb <= 120; p += xa + xb <= 100
p.solve(pulp.PULP_CBC_CMD(msg=0))
print(f"PuLP : xA={xa.value():.0f}, xB={xb.value():.0f}, 利益={pulp.value(p.objective):.0f}")
# cvxpy (凸最適化モデリング言語)
import cvxpy as cp
x = cp.Variable(2, nonneg=True)
prob = cp.Problem(cp.Maximize(40*x[0] + 30*x[1]),
[2*x[0] + x[1] <= 120, x[0] + x[1] <= 100])
prob.solve()
print(f"cvxpy : xA={x.value[0]:.0f}, xB={x.value[1]:.0f}, 利益={prob.value:.0f}")
実行結果:
scipy : xA=20, xB=80, 利益=3200
PuLP : xA=20, xB=80, 利益=3200
cvxpy : xA=20, xB=80, 利益=3200
3ツールとも同じ最適 、利益 3200。LP のような共通問題はどれでも解けるが、書き味が違う:scipy は行列を組む低レベル、PuLP/cvxpy は数式に近い。問題クラスが広がると選択が決まる ── 整数変数なら PuLP/OR-Tools、二次/錐なら cvxpy、ルーティングなら OR-Tools。
選び方の指針
- LP だけ・依存を増やしたくない → scipy(
linprog)。 - 整数・混合整数(MILP) → PuLP(CBC)、本番は商用(Gurobi/CPLEX)。
- 凸(QP・SOCP・SDP)・数式で書きたい → cvxpy(DCP で凸性を保証、実装:cvxpyで凸問題を解く)。
- 配送・スケジューリング・制約プログラミング → OR-Tools(専用アルゴリズム、スケジューリング・配送のモデル化)。
- 非線形(非凸含む)の局所最適 → scipy(
minimize、無制約最適化)、大規模は専用(IPOPT 等)。
数式の直観的意味
ツールの分かれ目は「問題の数理構造」そのもの。LP・凸(凸最適化 章目次)は多項式時間で大域最適が解けるので、モデリング言語(cvxpy/PuLP)が内部で適切なソルバー(シンプレックス・内点法、シンプレックス法・内点法入門)へ渡せば済む。整数・組合せ(整数計画と組合せ最適化 章目次)は NP 困難なので、分枝カット(PuLP/CBC)や専用探索(OR-Tools)が要る。cvxpy が凸専用なのは、DCP(実装:cvxpyで凸問題を解く)で凸性を保証することで「解けば大域最適」を担保するため ── 非凸を弾くのは安全機構。つまりツール選択とは「自分の問題がどの問題クラスか」を見極める作業で、第1章の分類(最適化問題の分類)がそのまま実装の意思決定になる。良いモデラーは、解く前に「これは LP か、凸か、整数か、非凸か」を判定し、最も構造を活かせるツールを選ぶ。
⚠️ よくある誤解・落とし穴
- 「1つのツールで全部」は非効率:凸問題に汎用非線形ソルバーを使うと遅い・不安定。構造に合わせる。
- cvxpy は非凸を弾く:DCP エラーは「非凸ですよ」の警告。凸な等価表現に直すか別ツールへ。
- 無料ソルバー(CBC)は大規模 MILP で限界:本番は商用ソルバーが桁違いに速いことも。
- モデリングとソルバーは別:同じモデルでもソルバー(と前処理)で性能が大きく変わる。
- 数値スケーリング・許容誤差の設定は、どのツールでも結果の信頼性に効く。
関連ノート
- 前提:実装:cvxpyで凸問題を解く
- 問題分類が選択を決める:最適化問題の分類
- 定式化の技法:定式化の技法
- ルーティングの実装:スケジューリング・配送のモデル化
- 章のハブ:応用とモデリング 章目次