Computing with PuLP#

PuLP is a Python package for computing solutions of linear programming problems. Let’s learn how to setup a problem with variables and constraints and how to call the solvers to find optimial solutions.

from pulp import *

Example#

Consider the following linear programming problem in standard form:

\[\begin{split} \begin{array}{rc} \text{maximize:} & 5x_1 + 4x_2 + 3x_3 \\ \text{subject to:} & 2x_1 + 3x_2 + x_3 \leq 5 \\ & 4x_1 + x_2 + 2x_3 \leq 11 \\ & 3x_1 +4x_2 +2x_3 \leq 8 \\ & x_1, x_2, x_3 \geq 0 \end{array} \end{split}\]

The function \(f(x_1,x_2,x_3) = 5x_1 + 4x_2 + 3x_3\) is called the objective function. The inequalities are constraints. Our goal is to find values \(x_1\), \(x_2\), \(x_3\) which maxmize the objective function and which satisfies all the contraints.

Define the Problem#

We begin by defining a problem object using the function LpProblem:

problem = LpProblem("Example",LpMaximize)

where:

  • "Example" is the name of the problem

  • LpMaximize sets the objective to a maximization problem (as ooposed to LpMinimize)

  • LpProblem creates a pulp problem object and we save it to a variable called problem

Note that LpMaximize is literally the integer value -1:

print(LpMaximize)
-1
type(LpMaximize)
int

And LpMinimize is the integer value 1:

print(LpMinimize)
1
type(LpMinimize)
int

Therefore we could write equivalently:

problem = LpProblem("Example",-1)

But the variable LpMaximize makes the pulp code more explicit.

Define Variables#

Define variables using the function LpVariable:

x = LpVariable("varName",lowBound,upBound,cat)

where:

  • x is the Python variable we use to reference the pulp variable

  • varName is the name we choose for the variable

  • lowBound is a lower bound (default is None)

  • upBound is an upper bound (default is None)

  • cat is the category of variable: Continuous, Integer or Binary (default is Continuous)

For example, we could create a continuous variable with no bounds:

x = LpVariable("xVariable")
print(x.name)
xVariable
print(x.lowBound)
None
print(x.upBound)
None
print(x.cat)
Continuous

Let’s create the variables for our example problem and include the lower bounds:

x1 = LpVariable("x1",0)
x2 = LpVariable("x2",0)
x3 = LpVariable("x3",0)

Add Objective Function#

We treat a pulp problem object as a list-type object and append the objective and constraints using the += operator. For example, use the variables we defined above for our example problem:

problem += 5*x1 + 4*x2 + 3*x3

Print the problem object to see its current formulation:

print(problem)
Example:
MAXIMIZE
5*x1 + 4*x2 + 3*x3 + 0
VARIABLES
x1 Continuous
x2 Continuous
x3 Continuous

Add Constraints#

Use the operator += to append constraints to the problem. Constraints are expressions involving the variables which represent the linear inequalities:

problem += 2*x1 + 3*x2 + x3 <= 5
problem += 4*x1 + x2 + 2*x3 <= 11
problem += 3*x1 + 4*x2 + 2*x3 <= 8

print(problem)
Example:
MAXIMIZE
5*x1 + 4*x2 + 3*x3 + 0
SUBJECT TO
_C1: 2 x1 + 3 x2 + x3 <= 5

_C2: 4 x1 + x2 + 2 x3 <= 11

_C3: 3 x1 + 4 x2 + 2 x3 <= 8

VARIABLES
x1 Continuous
x2 Continuous
x3 Continuous

Solve#

Call the method .solve() on the problem to compute the optimal solution (if it exists):

problem.solve()
Welcome to the CBC MILP Solver 
Version: 2.10.3 
Build Date: Dec 15 2019 

command line - /opt/miniconda3/lib/python3.13/site-packages/pulp/apis/../solverdir/cbc/osx/i64/cbc /var/folders/2h/25vcwmr52mvfqs1h92028rph0000gn/T/885f0bcef26c42f798617b3b4983caee-pulp.mps -max -timeMode elapsed -branch -printingOptions all -solution /var/folders/2h/25vcwmr52mvfqs1h92028rph0000gn/T/885f0bcef26c42f798617b3b4983caee-pulp.sol (default strategy 1)
At line 2 NAME          MODEL
At line 3 ROWS
At line 8 COLUMNS
At line 21 RHS
At line 25 BOUNDS
At line 26 ENDATA
Problem MODEL has 3 rows, 3 columns and 9 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Presolve 3 (0) rows, 3 (0) columns and 9 (0) elements
0  Obj -0 Dual inf 15 (3)
0  Obj -0 Dual inf 15 (3)
2  Obj 13
Optimal - objective value 13
Optimal objective 13 - 2 iterations time 0.002
Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.00   (Wallclock seconds):       0.02
1

Inspect the status of the problem. The dictionary object LpStatus translates the value:

problem.status
1
LpStatus
{0: 'Not Solved',
 1: 'Optimal',
 -1: 'Infeasible',
 -2: 'Unbounded',
 -3: 'Undefined'}
LpStatus[problem.status]
'Optimal'

Therefore the solution computed by the solver is optimal. Let’s inspect the value of each of the variables and the objective function:

x1.value()
2.0
x2.value()
0.0
x3.value()
1.0
problem.objective.value()
13.0

Put It All Together#

Let’s combine all the code into a single cell to see how it all works:

# Initialize the problem
problem = LpProblem("Example",LpMaximize)

# Define variables (each with lower bound 0)
x1 = LpVariable("x1",0)
x2 = LpVariable("x2",0)
x3 = LpVariable("x3",0)

# Define objective function
problem += 5*x1 + 4*x2 + 3*x3

# Define constraints
problem += 2*x1 + 3*x2 + x3 <= 5
problem += 4*x1 + x2 + 2*x3 <= 11
problem += 3*x1 + 4*x2 + 2*x3 <= 8

# Solve and inspect status
problem.solve()
print("Status :", LpStatus[problem.status])

# Inspect optimal values of variables and objective function
print(x1.name,":",x1.value())
print(x2.name,":",x2.value())
print(x3.name,":",x3.value())
print("Objective :",problem.objective.value())
Welcome to the CBC MILP Solver 
Version: 2.10.3 
Build Date: Dec 15 2019 

command line - /opt/miniconda3/lib/python3.13/site-packages/pulp/apis/../solverdir/cbc/osx/i64/cbc /var/folders/2h/25vcwmr52mvfqs1h92028rph0000gn/T/f75df7992713434182f9fe544a004781-pulp.mps -max -timeMode elapsed -branch -printingOptions all -solution /var/folders/2h/25vcwmr52mvfqs1h92028rph0000gn/T/f75df7992713434182f9fe544a004781-pulp.sol (default strategy 1)
At line 2 NAME          MODEL
At line 3 ROWS
At line 8 COLUMNS
At line 21 RHS
At line 25 BOUNDS
At line 26 ENDATA
Problem MODEL has 3 rows, 3 columns and 9 elements
Coin0008I MODEL read with 0 errors
Option for timeMode changed from cpu to elapsed
Presolve 3 (0) rows, 3 (0) columns and 9 (0) elements
0  Obj -0 Dual inf 15 (3)
0  Obj -0 Dual inf 15 (3)
2  Obj 13
Optimal - objective value 13
Optimal objective 13 - 2 iterations time 0.002
Option for printingOptions changed from normal to all
Total time (CPU seconds):       0.00   (Wallclock seconds):       0.00

Status : Optimal
x1 : 2.0
x2 : 0.0
x3 : 1.0
Objective : 13.0