PuLPは、線形計画問題と混合整数計画問題をPythonのコードとして書き下すためのモデリングライブラリです。解を計算するのはPuLP自身ではなく、外部のソルバーです。2026年8月時点の安定版は3.3.2で、Web上に残る入門記事の書き方をそのまま実行すると DeprecationWarning が出るところまで仕様が動いています。この記事では、最小化問題を解くまでの手順と、3.3系で非推奨になったAPIの置き換え方を一次情報にあたって整理します。
まとめ
PuLPは問題を記述する側(モデラー)で、計算する側(ソルバー)はCBCなどの外部プログラムです。この分担を理解すると、インストールもエラーも読み解けます。pip install pulp で入る安定版は3.3.2(2026年5月25日公開)で、Python 3.10以上が必要です。Python 3.9では3.3.1が入ります。
モデリングは LpProblem で問題を作り、変数・目的関数・制約を += で足し、solve() を呼ぶ4手順で完結します。ただし非推奨化が2段階で進んでいます。LpVariable("x", ...) の直接生成は3.3.1で、PULP_CBC_CMD は3.3.2で、それぞれ非推奨になりました。どちらもPuLP 4.0で削除されるため、新規に書くコードは prob.add_variable() と COIN_CMD を使ってください。
ライセンスはPuLP本体がMIT、同梱されるCBCはEclipse Public License 2.0で、別物です。以下、手順とバージョン境界を順に見ていきます。
PuLPが担う役割とソルバーとの分担
PuLPが行うのは、Pythonで書かれた数式をLP形式やMPS形式のファイルへ変換し、ソルバーのプロセスを起動して、返ってきた解ファイルを読み戻すことです。solve() を実行するとコンソールに Welcome to the CBC MILP Solver と表示され、一時ディレクトリの .mps ファイルを引数にCBCが呼ばれます。ここがPuLPと、自前で解法を実装するライブラリとの決定的な違いです。
この構造の利点は、ソルバーの差し替えやすさにあります。モデルの記述を変えずにソルバーだけ交換できるため、CBCで解けない規模になったらGurobiやCPLEXへ乗り換える移行が、インポート文とソルバー指定の変更だけで済みます。裏を返せば、ソルバーが見つからなければPuLP側の文法が正しくても実行は失敗します。
扱えるのは目的関数と制約がすべて一次式で表せる問題に限られます。変数どうしの掛け算や指数が入った時点でPuLPの対象外です。前提となる用語の整理は数理最適化の3要素とアルゴリズムの解説、変数・目的関数・制約の切り分け方は決定変数と目的関数・制約条件の違いにまとめています。
pip installで入るバージョンとPython要件
インストールはpipひとつで完了します。CBCのバイナリが同梱されるため、別途ソルバーを用意しなくてもすぐ動きます。
pip install pulp
注意したいのは、入るバージョンがPythonのバージョンで変わる点です。PyPIのメタデータ上、3.3.2の requires_python は >=3.10 に引き上げられています。Python 3.9の環境で同じコマンドを打つと、pipは条件を満たす3.3.1を選びます。
| バージョン | PyPI公開日 | 必要なPython |
|---|---|---|
| 2.9.0 | 2024-07-12 | 3.7以上 |
| 3.0.0 | 2025-02-19 | 3.7以上 |
| 3.2.0 | 2025-05-29 | 3.9以上 |
| 3.3.0 | 2025-09-18 | 3.9以上 |
| 3.3.1 | 2026-05-05 | 3.9以上 |
| 3.3.2 | 2026-05-25 | 3.10以上 |
公開日はいずれもPyPIのアップロード日です。手元のPuLPが何であるかは import pulp; print(pulp.__version__) で確認できます。3.3.1と3.3.2では後述の非推奨まわりの挙動が変わるため、エラーや警告の話をする前にここを揃えてください。なお4.0系はPyPI上に2026年6月19日の 4.0.0a12 まで並んでいますが、すべてアルファ版です。pipが既定で入れる安定版は3.3.2になります。
最小化問題のモデリング手順
LpProblem・変数・目的関数・制約の記述順
最小化問題を書く手順は4つです。LpProblem に名前と最小化・最大化の別を渡し、変数を定義し、目的関数と制約を += で足し込み、solve() を呼びます。目的関数と制約はどちらも += ですが、比較演算子を含まない式が目的関数、含む式が制約として解釈されます。
import pulp
prob = pulp.LpProblem("mincost", pulp.LpMinimize)
x = pulp.LpVariable("x", lowBound=0)
y = pulp.LpVariable("y", lowBound=0)
prob += 3 * x + 5 * y, "cost"
prob += x + y >= 10, "demand"
prob += x <= 6, "cap_x"
status = prob.solve(pulp.PULP_CBC_CMD(msg=False))
print(pulp.LpStatus[status], pulp.value(prob.objective), x.varValue, y.varValue)
# Optimal 38.0 6.0 4.0
単価3のxを上限いっぱいの6まで使い、残り4を単価5のyで埋めた結果、総コストは38になります。msg=False を付けないとCBCのログが数十行流れるので、実行結果だけ見たい場合は付けてください。第2引数の文字列は制約の名前で、省略すると _C1 のような連番が振られます。
lpSumによる多変数モデルの集約
変数が増えると、目的関数を 3*x + 5*y + 4*z + ... と手で並べるのは現実的ではありません。辞書で変数を作り、lpSum() で合計します。
import pulp
cost = {"A": 3, "B": 5, "C": 4}
lower = {"A": 0, "B": 0, "C": 2}
prob = pulp.LpProblem("blend", pulp.LpMinimize)
v = {k: pulp.LpVariable("x_" + k, lowBound=lower[k]) for k in cost}
prob += pulp.lpSum(cost[k] * v[k] for k in cost)
prob += pulp.lpSum(v.values()) >= 10
prob += v["A"] <= 6
prob.solve(pulp.PULP_CBC_CMD(msg=False))
print(pulp.LpStatus[prob.status], pulp.value(prob.objective))
# Optimal 34.0 (A=6.0, B=0.0, C=4.0)
Cに下限2が付いているため、最安のAを上限6まで使ったうえでCに4を割り当てる解が選ばれます。ここで組み込みの sum() ではなく lpSum() を使うのは、計算量が違うためです。sum() は加算のたびに新しい式オブジェクトを作り直します。PuLP 3.3.2とPython 3.14で変数2,000個の合計式を1回組み立てた計測では、lpSum() が sum() の約300倍速く完了しました。モデルが大きいほど差は開きます。
求解ステータスと解の取り出し方
solve() の戻り値は整数のステータスコードで、pulp.LpStatus[status] で文字列に変換します。返る値は Optimal、Infeasible(制約を同時に満たす解が存在しない)、Unbounded(目的関数がいくらでも良くなる)、Not Solved、Undefined の5種類です。整数計画法(integer programming)で解が見つからない場合も、ここに現れます。
解そのものは変数の varValue 属性から取り出します。目的関数の値は pulp.value(prob.objective) です。ここで押さえておきたいのは、Optimal 以外のときも varValue が None ではなく古い値を返す場合があることです。ステータスを確認せずに varValue を業務ロジックへ流すと、実行不能なモデルの結果を正しい解として扱ってしまいます。判定は必ずステータス側で行ってください。
整数変数を使う混合整数計画(MILP)への拡張
「トラックを何台手配するか」のように答えが整数でなければ意味をなさない問題では、変数の cat に pulp.LpInteger を指定します。0か1かを選ぶ問題なら pulp.LpBinary です。指定を変えるだけで、PuLPはモデルを混合整数計画としてソルバーへ渡し、CBC側が分枝限定法で解きます。
import pulp
for cat in (pulp.LpContinuous, pulp.LpInteger):
prob = pulp.LpProblem("trucks", pulp.LpMinimize)
n = pulp.LpVariable("n", lowBound=0, cat=cat)
prob += n
prob += 8 * n >= 23
prob.solve(pulp.PULP_CBC_CMD(msg=False))
print(cat, pulp.value(prob.objective))
# Continuous 2.875
# Integer 3.0
23トンの荷物を8トン積みのトラックで運ぶ問題です。pulp.LpContinuous の実体は文字列 "Continuous" なので、出力にはその文字列がそのまま並びます。連続変数のままだと2.875台という解が返り、これを切り上げて3台と読み替えたくなります。単純な例では結果が一致しますが、制約が複数絡むモデルでは連続解の切り上げが最適解にならないどころか制約違反になることがあります。整数であるべき変数には最初から LpInteger を指定するのが正解です。
代償として計算時間は跳ね上がります。所要時間はモデルの構造次第で、整数変数の本数だけでは決まりません。CBCで待たされるようになったら、まず PULP_CBC_CMD(timeLimit=60) のように制限時間を与えて打ち切り、その時点の解の質を見てから商用ソルバーを検討するのが現実的な順序です。
PuLP 3.3系で非推奨になったAPIと4.0への移行
PULP_CBC_CMDの非推奨化とCOIN_CMDへの切り替え
3.3.2の変更履歴には PULP_CBC_CMD deprecated (removed in PuLP 4.0); use pip install pulp[cbc] and COIN_CMD instead と明記されています。実際に PULP_CBC_CMD を指定すると、PuLP 4.0で削除される旨の DeprecationWarning が出ます。4.0系ではアルファ4(2026年4月14日)の時点で同梱CBCバイナリごと削除済みです。
この変更には実務上の利点があります。同梱されているCBCは Version: 2.10.3、Build Date: Dec 15 2019 と2019年のビルドです。一方、pip install "pulp[cbc]" で入る cbcbox 2.929 のCBCは Build Date: Mar 23 2026 で、6年分の改善が入っています。ただしこちらはバージョン表記が Devel (unstable) の開発版ビルドです。安定性を最優先する本番環境では、この点を織り込んだうえで採否を決めてください。
pip install "pulp[cbc]"
導入時の落とし穴もあります。cbcboxは仮想環境の bin 配下に cbc 実行ファイルを置く形で入るため、仮想環境を有効化せずにPythonを直接叩くとPATHに cbc が無く、PulpSolverError: Pulp: cannot execute cbc で止まります。PATHが通っていれば pulp.listSolvers(onlyAvailable=True) が ['PULP_CBC_CMD', 'COIN_CMD'] を返し、3.3.2は引数なしの solve() でもCOIN_CMDを優先するため、警告そのものが出なくなります。
LpVariable直接生成からadd_variable系への置き換え
3.3.1で入ったもう一段の変更が、変数の作り方です。LpVariable("x", lowBound=0) という長年の書き方が非推奨になり、警告文は in PuLP 4.0 use prob.add_variable(name, lowBound, upBound, cat=...) と続きます。4.0では変数が生成時点でモデルに紐づく設計へ変わるためです。LpVariable.dicts も同様に非推奨で、prob.add_variable_dicts(...) が後継です。
import pulp
prob = pulp.LpProblem("mincost", pulp.LpMinimize)
x = prob.add_variable("x", 0, None)
y = prob.add_variable("y", 0, None, cat=pulp.LpInteger)
prob += 3 * x + 5 * y
prob += x + y >= 10
prob += x <= 6
prob.solve(pulp.COIN_CMD(msg=False))
print(pulp.LpStatus[prob.status], pulp.value(prob.objective))
# Optimal 38.0 (警告は1件も出ません)
あわせて addVariable、addVariables、LpConstraintVar、辞書として prob.constraints を参照する書き方も非推奨になりました。制約はリストを返す prob.constraints() と、名前で引く prob.get_constraint_by_name("demand") に置き換わります。
移行警告の抑制と作業の進め方
既存コードが大量にある場合、警告を止める手段も用意されています。pulp.set_v4_migration_warnings(False) を呼べば移行警告は出なくなります。
ただし、これを恒久対応にすべきではありません。警告が消えても4.0で削除される事実は変わらず、削除時に一括で壊れます。3.3系は削除前の猶予期間として、警告を手がかりに置き換え箇所を洗い出すために使うのが本来の位置づけです。抑制するなら、移行の期限を決めたうえで一時的に、が前提になります。
ソルバーの追加とライセンスの境界
本体MITと同梱CBCのEPL-2.0
PuLP本体のライセンスはMITです。リポジトリのLICENSEファイルもMITの条文そのもので、PyPIのメタデータも license_expression: MIT を返します。著作権表記はJean-Sebastien Roy(2002-2005)とStuart Anthony Mitchell(2007年以降)です。
混同されやすいのが同梱ソルバーです。CBCのライセンスはEclipse Public License 2.0で、MITとは条件が異なります。EPL-2.0の第3条1項(a)は、Programを頒布する場合にProgram自体をソースコードとして入手可能にすることを求めており、義務の範囲は改変部分ではなくProgram全体に及びます。CBCのバイナリを自社製品へ同梱して再配布する形態を取るなら、法務確認が要ります。自社サーバー内で解くだけの使い方であれば通常は問題になりません。MITで揃えたい場合はHiGHSという選択肢があり、こちらはリポジトリのライセンス表記がMITです。
extras指定によるHiGHS・商用ソルバーの追加
3.3.0で、ソルバーごとのインストールがpipのextrasとして整理されました。pip install "pulp[highs]" のように角括弧で指定します。CBCを個別に指定する cbc が加わったのは3.3.2からで、3.3.0と3.3.1には存在しません。
| extras名 | 入るパッケージ | ライセンス |
|---|---|---|
| cbc | cbcbox | EPL-2.0 |
| highs | highspy 1.13 | MIT |
| scip | PySCIPOpt | Apache-2.0(バインディングはMIT) |
| gurobi | gurobipy | 商用 |
| cplex | cplex | 商用 |
| xpress | xpress | 商用 |
SCIPは8.0.0からApache-2.0で公開されており、Pythonバインディングの PySCIPOpt はMITです。かつてのアカデミック限定ライセンスの記述が残る解説を見かけますが、現行版には当てはまりません。このほか3.3.2には copt、mosek と、無償系をまとめた open_py(highspy 1.13 と PySCIPOpt)、商用系をまとめた public_py(gurobipy・coptpy・xpress・cplex)が定義されています。商用ソルバーはpipで入るのがPythonバインディングだけで、ライセンスキーは別途必要です。ソルバーごとの得意分野と費用感は主要6ツールのライセンス・費用比較で、Gurobiの価格体系はGurobiのPythonでの使い方と価格で扱っています。
PuLPを選ばない方がよい条件
PuLPが適さない場面は、はっきりしています。目的関数や制約に二次以上の項が入る非線形問題は対象外です。ここで無理にPuLPへ寄せず、SciPyの最適化関数の scipy.optimize を使ってください。
シフト表や配車のように「同時に同じ人を割り当てない」といった論理制約が主役になる問題も、PuLPでは向いていません。整数変数と大きな定数を使った定式化で表現はできますが、記述は冗長になり求解も遅くなります。制約プログラミング専用のCP-SATを持つGoogle OR-Toolsのほうが、記述量でも速度でも有利です。
もうひとつ、モデルの記述をPython以外の担当者と共有する必要がある場合も再考の余地があります。PuLPのモデルはPythonコードそのもので、数式としての可読性は専用のモデリング言語に劣ります。この観点ではAMPLのような言語が選ばれてきました。逆に、線形または混合整数で、Pythonのデータ処理と地続きに書きたいなら、PuLPは最短の選択肢です。
よくある質問
lpSum() と Python の sum() はどう使い分けますか?
変数式の合計には lpSum() を使ってください。sum() は加算のたびに新しい式オブジェクトを生成するため、項が増えるほど遅くなります。どちらも返る型は LpAffineExpression で同じなので、モデルの内容は変わらず構築時間だけが変わります。変数が数千規模になるモデルでは、この差が体感できるレベルになります。
LpProblem() の引数には何を渡しますか?
第1引数がモデル名、第2引数が最適化の向きです。向きには pulp.LpMinimize(最小化)か pulp.LpMaximize(最大化)を指定します。第2引数を省略すると最小化になります。モデル名に空白を含めるとPuLPが Spaces are not permitted in the name という警告を出してアンダースコアへ置換するため、"mincost" のように空白なしで付けるのが無難です。
prob += 目的関数 と setObjective() は同じですか?
比較演算子を含まない式を += で足した場合は setObjective() と同じ結果になります。実際、prob += 2 * v と prob.setObjective(2 * v) は同一の目的関数を生成します。+= は式の内容を見て目的関数か制約かを振り分ける仕組みで、明示的に目的関数だと示したいときは setObjective() を使うと意図が伝わります。
ソルバーを指定せずに solve() を呼ぶと何が使われますか?
PuLP 3.3.2では、cbc がPATH上にあればCOIN_CMDが優先され、無ければ同梱CBCを使う PULP_CBC_CMD にフォールバックします。後者では非推奨の警告が出る点に注意してください。pulp.listSolvers(onlyAvailable=True) を実行すると、その環境で実際に使えるソルバー名の一覧が返るので、意図したソルバーが認識されているかはここで確認できます。
PuLPで非線形の最適化問題は解けますか?
解けません。PuLPが扱えるのは目的関数と制約がすべて一次式で表せる線形計画と、そこに整数条件を加えた混合整数計画までです。変数どうしの積、二乗、対数などが入るとモデルを構築する段階で扱えません。非線形問題には scipy.optimize や専用ソルバーを使ってください。