---
title: "PuLPとは？Pythonで最小化問題を解く手順と3.3系で非推奨になったAPI"
url: "https://www.issoh.co.jp/tech/details/3036/"
published: 2024-07-11
updated: 2026-08-04
categories: ["Python"]
publisher: "株式会社一創"
---

# PuLPとは？Pythonで最小化問題を解く手順と3.3系で非推奨になったAPI

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要素とアルゴリズムの解説](/tech/details/2976/)、変数・目的関数・制約の切り分け方は[決定変数と目的関数・制約条件の違い](/tech/details/8321/)にまとめています。

## 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ツールのライセンス・費用比較](/tech/details/3034/)で、Gurobiの価格体系は[GurobiのPythonでの使い方と価格](/tech/details/2756/)で扱っています。

## PuLPを選ばない方がよい条件

PuLPが適さない場面は、はっきりしています。目的関数や制約に二次以上の項が入る非線形問題は対象外です。ここで無理にPuLPへ寄せず、[SciPyの最適化関数](/tech/details/2049/)の `scipy.optimize` を使ってください。

シフト表や配車のように「同時に同じ人を割り当てない」といった論理制約が主役になる問題も、PuLPでは向いていません。整数変数と大きな定数を使った定式化で表現はできますが、記述は冗長になり求解も遅くなります。制約プログラミング専用のCP-SATを持つ[Google OR-Tools](/tech/details/3477/)のほうが、記述量でも速度でも有利です。

もうひとつ、モデルの記述を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` や専用ソルバーを使ってください。

## 関連記事

- [数理最適化とは？3要素・アルゴリズム・機械学習との違いをPythonで解説](/tech/details/2976/)
- [決定変数とは？目的関数・制約条件との違いと決め方【数理最適化の基本】](/tech/details/8321/)
- [ORツールとは？主要6ツールの比較と選び方【ライセンス・費用・得意問題】](/tech/details/3034/)
- [Google OR-Toolsとは？できること・インストール・実装例をわかりやすく解説](/tech/details/3477/)
- [Gurobiとは？Pythonでの使い方・インストール・価格を解説](/tech/details/2756/)

---

出典: [PuLPとは？Pythonで最小化問題を解く手順と3.3系で非推奨になったAPI](<https://www.issoh.co.jp/tech/details/3036/>)（株式会社一創）
