Library / SDK
coin-or/pulp avatar
coin-or/pulp

PuLP 4.0: a Python modelling layer that no longer ships a solver

A python Linear Programming API

2,486 stars437 forksPythonNOASSERTION

At a glance

What is it?
PuLP builds LP and MILP models in Python and hands them to an external solver. Version 4.0 changes the variable API and stops bundling CBC, so the install step now decides whether solve() works at all.
Who is it for?
Adopt PuLP if you want to describe a linear or mixed integer model in plain Python and let an external solver do the work, and you are willing to pin the solver install as part of your environment. Do not adopt it if you need a solver bundled in the package, or if you cannot migrate the variable and status APIs that 4.0 changed.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 11 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 6, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What PuLP actually solves, and who it is for

PuLP is a modeler, not a solver. It gives you Python objects for variables, linear expressions and constraint rows, then writes the model out as MPS or LP and calls a solver binary or library to do the arithmetic. The README puts the target audience plainly: it is for people who want to "create MILP optimisation problems and solve them with the latest open-source (or proprietary) solvers." That split matters more than any single feature, because it means the package you install and the program that computes your answer are two different things.

The people this suits are engineers and researchers who already think in terms of an objective and a set of linear constraints: production planning, blending, scheduling, assignment, cutting stock. The repository's examples directory reflects that range, with files such as WhiskasModel1.py, BeerDistributionProblem.py, AmericanSteelProblem.py and five SpongeRollProblem variants. If your problem is linear or mixed integer and you can write it as sums of coefficient times variable, PuLP is a thin, readable notation for it. If your problem is not linear, PuLP is not the tool, and no amount of solver swapping will fix that.

The model, the file, the solver: how a PuLP run is wired

The data flow has four stages. You construct an LpProblem, attach LpVariable instances, build LpAffineExpression objects with operators or helpers, and wrap them into LpConstraint rows of the form a1x1 + a2x2 + ... + anxn (<=, =, >=) b. PuLP serialises that into MPS or LP text. Then it invokes a solver, either through a command-line executable or through a Python binding, and parses the solver's log back into a result object.

The parsing step is not incidental. The pyproject.toml lists a single runtime dependency, orloge>=1.0.0, annotated as parsing "solver logs into the LpSolveStats returned by solve(stats=True)". That is the whole reason solve() in 4.0 returns an LpSolveStats object instead of a status code: the status, the objective value and the solver's own report now arrive as one structured result. The build system is also worth noting. The backend is maturin, and the module map declares bindings = "pyo3" with module-name = "pulp._rustcore", so part of the package is compiled Rust rather than pure Python. That is a real constraint on how you deploy it: a wheel must exist for your platform and Python version, and requires-python is >=3.12.

Installing PuLP 4.0 and running a first model

The README labels CBC support the recommended install. The extra pulls in the cbcbox wheel, which provides a CBC executable that PuLP finds automatically.

bash
python -m pip install pulp[cbc]

Plain python -m pip install pulp installs only the modeler. In that case you must put your own cbc (or cbc.exe on Windows) on PATH, or install another solver, otherwise the README states the default solve path raises PulpError: No solver available. With CBC present, COIN_CMD is the usual open-source choice and is selected ahead of GLPK.

Here is the quickstart model, following the README's example: a continuous variable x bounded between 0 and 3, a binary y, one constraint, and an objective.

python
from pulp import *
prob = LpProblem("myProblem", LpMinimize)
x = prob.add_variable("x", 0, 3)
y = prob.add_variable("y", cat="Binary")
prob += x + y <= 2
prob += -4*x + y
stats = prob.solve()

The line prob += -4*x + y has no right-hand side sense, so it becomes the objective rather than a constraint. After solving, stats.status_str should read 'Optimal', and value(x) returns the variable's value. If you would rather not use CBC, the README shows swapping in GLPK's command-line tool, which requires glpsol on PATH.

python
stats = prob.solve(GLPK(msg = 0))

For a Python binding instead of a subprocess, install the extra and call PYGLPK. For CP-SAT, install pulp[ortools] and pass CPSAT(msg=False); the README notes that every variable must then have finite lower and upper bounds and that continuous variables are solved on their integer-rounded domain. That last point is a modelling trap, not a footnote.

Where PuLP 4.0 breaks your existing code

The README devotes a note to the 3.x to 4.0 transition, and the changes are not cosmetic. Variables are now created with prob.add_variable(...) rather than a bare LpVariable constructor. prob.constraints() returns a list. prob.solve() returns an LpSolveStats object instead of a status code. Any code that does if prob.solve() == 1, or that iterates constraints expecting a dictionary interface, needs rewriting. The project points to a migration guide at doc/source/guides/how_to_migrate_to_v4.rst, and that guide is the first file to read before upgrading a working model.

The second break is the removal of the bundled CBC binary. Older releases shipped a CBC executable and exposed it as PULP_CBC_CMD. Both the API and the bundled solver are gone. Code that constructed PULP_CBC_CMD(...) to pass solver options will not run, and the replacement is COIN_CMD plus an externally installed CBC. This is a deliberate trade: the package got smaller and the licence surface around redistribution got simpler, at the cost of an install step that can fail silently in a container that has no cbc on PATH. The failure surfaces as PulpError: No solver available, which is at least explicit, but it appears at solve time rather than at import time.

When a modelling layer is the wrong layer

PuLP assumes your problem is linear. Every mechanism in it, LpAffineExpression, lpSum, lpDot, constraint rows, exists to express sums of coefficient times variable. There is no quadratic objective, no general nonlinear constraint, and no way to express a product of two decision variables. If your formulation needs those, you are looking at a different class of tool, and the CP-SAT backend does not change that: it is reached through a CPSAT API for constraint programming models, not as a nonlinear solver.

The solver dependency is the second limit. Because CBC is no longer shipped, a minimal install is a modeler with nothing to run. In an air-gapped build, a slim container, or a CI image where pip cannot reach the cbcbox wheel, you must vendor a CBC executable yourself and place it on PATH. The README does not document a fallback for that case beyond installing another solver such as GLPK. And the Rust extension means you cannot simply copy the pulp directory onto a machine with a different architecture; the compiled module has to match.

Finally, the Python floor is 3.12. Projects pinned to 3.10 or 3.11 cannot take 4.0 at all, and the classifiers list 3.12 through 3.14, so there is no older-interpreter path.

PuLP versus OR-Tools CP-SAT

The comparison people reach for is OR-Tools, and the difference is architectural rather than a matter of which is faster. PuLP is a notation layer over external solvers: it writes MPS or LP and shells out or binds in, which is why one model can be solved by CBC, GLPK, HiGHS, SCIP, CPLEX, GUROBI, MOSEK or XPRESS with a one-line change. OR-Tools CP-SAT is a solver with its own modelling objects, and its native domain is integer and Boolean variables with a rich constraint vocabulary.

PuLP does reach CP-SAT, but through an adapter: from pulp import CPSAT, installed via pulp[ortools]. The README is explicit that under this backend every variable must have finite lower and upper bounds, and continuous variables are solved on their integer-rounded domain. So you get CP-SAT's search behind PuLP's linear syntax, with a rounding caveat that a native CP-SAT model would not impose. The practical rule: if your model is genuinely linear or mixed integer and you care about swapping solvers or emitting LP/MPS files for someone else, PuLP's indirection is the point. If your model is dominated by logical, scheduling or disjunctive constraints, the adapter is a detour and the native CP-SAT API is the shorter path.

Licence, maintenance and the cost of the 4.0 upgrade

The repository is not archived, and the last push was on 2026-09-25, the same day release 4.0.0 was tagged, following 4.0.0a13 earlier that day and 4.0.0a12 in June 2026. The project is moving. The pyproject.toml declares license = "MIT" with license-files = ["LICENSE"], while the repository metadata reports NOASSERTION; read the LICENSE file itself rather than relying on either label. Note that the licence covers PuLP, not the solvers you attach to it. The README warns that some solver extras "require a commercial license for running or for large models", which applies to CPLEX, GUROBI, MOSEK and XPRESS. Installing an extra is not the same as being licensed to run it in production, and that is a question for your own legal review, not something the package resolves.

The upgrade cost is concentrated in two places. First, the API migration: add_variable, the list returned by constraints(), and the LpSolveStats return value. Second, the solver plumbing: replacing PULP_CBC_CMD with COIN_CMD and making CBC available through the pulp[cbc] extra or PATH. Budget for the second one in your deployment scripts, because it is the step that fails on a machine where the first one passes. Pin the version, since 4.0.0a12 to 4.0.0 spans a short window with several changes.

Editorial conclusion

Adopt PuLP if you want to describe a linear or mixed integer model in plain Python and let an external solver do the work, and you are willing to pin the solver install as part of your environment. Do not adopt it if you need a solver bundled in the package, or if you cannot migrate the variable and status APIs that 4.0 changed. Before writing code, confirm two things: that python -m pip install pulp[cbc] resolves on your platform, and that prob.solve() returns an LpSolveStats object rather than a status code in the version you pinned.

Frequently asked questions

How do I install PuLP?

The README recommends python -m pip install pulp[cbc], which installs the modeler plus the cbcbox wheel that provides a CBC executable PuLP can find automatically. A plain python -m pip install pulp installs only the modeler, and you must then supply your own CBC on PATH or install another solver. PuLP requires Python 3.12 or newer.

Can PuLP provide a MILP solver in Python?

PuLP is a modeler rather than a solver: it builds LP and MIP models and calls solvers such as CBC, GLPK, HiGHS, SCIP, CPLEX, GUROBI, MOSEK, XPRESS, MIPCL, CHOCO and OR-Tools CP-SAT. CBC is no longer shipped inside the package, so you install it through the pulp[cbc] extra or place a cbc executable on PATH. Without an available solver, the default solve path raises PulpError: No solver available.

Does PuLP 4.0 still bundle CBC?

No. The README states that older releases bundled a CBC binary exposed as PULP_CBC_CMD, and that both that API and the bundled solver are removed in 4.0. Use the COIN_CMD solver with CBC installed via python -m pip install pulp[cbc] or with a cbc executable on PATH.

What changed in the PuLP 4.0 API compared with 3.x?

Variables are created with prob.add_variable(...), prob.constraints() returns a list, and prob.solve() returns an LpSolveStats object instead of a status code. The README links a migration guide at doc/source/guides/how_to_migrate_to_v4.rst. Code that checks the old numeric status or expects a dictionary of constraints needs updating.

Which solvers can PuLP call?

The README lists GLPK, COIN-OR CLP and CBC, CPLEX, GUROBI, MOSEK, XPRESS, CHOCO, MIPCL, HiGHS, SCIP and FSCIP, and OR-Tools CP-SAT through the CPSAT API. Some are installed through optional PyPI extras such as pulp[gurobi] or pulp[highs], and the README notes that some extras require a commercial license for running or for large models.

Official sources

  1. coin-or/pulp on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/coin-or-pulp.svg)](https://hysenlabs.com/projects/coin-or-pulp)