What is PuLP

PuLP is a popular Python library for linear programming and mixed-integer linear programming problems. It provides a high-level interface for the mathematical programming language that allows users to create and solve optimization problems using Python's natural, expressive syntax.

With PuLP, you can create decision variables, construct objective functions, and formulate constraints for optimization problems. After defining the problem, you can use PuLP to call various solvers, such as GLPK, COIN CLP/CBC, CPLEX, and GUROBI, to find an optimal solution.

In the realm of optimization, PuLP is often compared to libraries like SciPy and CVXPY, but PuLP differentiates itself by focusing specifically on linear programming and mixed integer linear programming problems. Additionally, its ability to interact with various third-party solvers, both free and commercial, adds to its flexibility and wide usage.

Key Features of PuLP

The PuLP library has the following features:

  • High-Level Interface
    PuLP provides a Pythonic, high-level interface for constructing optimization problems. This makes it an intuitive choice for Python developers and simplifies the process of problem formulation. It abstracts the underlying mathematical representation of the problem and allows the user to focus on its logical formulation.

  • Solver Flexibility
    PuLP can interface with various solvers, including open-source solvers like COIN-OR's CBC and GLPK, and commercial solvers such as GUROBI and CPLEX. This gives the user flexibility to choose the solver that best fits their specific needs and computational resources.

  • Modeling Power
    With PuLP, you can define complex objective functions and constraints, allowing for robust modeling of real-world optimization scenarios. You can add, modify, and remove constraints, making the model highly flexible and adaptable.

Types of Optimization Problems PuLP Can Solve

PuLP is a potent tool for tackling several classes of optimization problems. This includes:

  • Linear Programming (LP)
  • Integer Programming (IP)
  • Mixed-Integer Linear Programming (MILP)
  • Binary Integer Programming

Case Study - Supply Chain Optimization with PuLP

Consider a company that needs to distribute its products from several factories to various stores. Each factory has a maximum production capacity, and each store has a particular demand. There's also a cost associated with shipping from each factory to each store. The objective is to minimize the total distribution cost while meeting the demands of all stores.

This scenario is a classic transportation problem, a type of linear programming problem, and can be solved using PuLP.

Problem Formulation

Let's denote:

  • Factories as set F = {f_1, f_2, \ldots, f_n}
  • Stores as set S = {s_1, s_2, \ldots, s_m}
  • c_{ij} as the cost of shipping one unit of product from factory i to store j
  • x_{ij} as the number of units to be shipped from factory i to store j
  • C_i as the capacity of factory i
  • D_j as the demand of store j

The problem can then be formulated as the following linear program:

Minimize:

Z = \sum_{i \in F} \sum_{j \in S} c_{ij}x_{ij} \\

Subject to:

\textrm{For each } i \in F: \sum_{j \in S} x_{ij} \leq C_i (\textrm{Capacity constraint}) \\ \textrm{For each } j \in S: \sum_{i \in F} x_{ij} \geq D_j (\textrm{Demand constraint}) \\ \textrm{For all } i,j: x_{ij} \geq 0 (\textrm{Non-negativity constraint})

Data Representation

For example, consider we have the following factories and stores:

python
capacity = {
    'Factory1': 1000,
    'Factory2': 4000
}

costs = {
    'Factory1': {'Store1': 2, 'Store2': 3},
    'Factory2': {'Store1': 4, 'Store2': 1}
}

demand = {
    'Store1': 500,
    'Store2': 4500
}

PuLP Code

Here's the PuLP code that formulates and solves the problem:

python
from pulp import *

# Create the 'prob' variable to contain the problem data
prob = LpProblem("Supply_Chain_Optimization", LpMinimize)

# Decision variables
x = LpVariable.dicts("shipments", [(i,j) for i in capacity.keys() for j in demand.keys()], lowBound=0, cat='Continuous')

# Objective function
prob += lpSum([costs[i][j]*x[(i, j)] for i in capacity.keys() for j in demand.keys()])

# Constraints
for j in demand.keys():
    prob += lpSum([x[(i, j)] for i in capacity.keys()]) >= demand[j]

for i in capacity.keys():
    prob += lpSum([x[(i, j)] for j in demand.keys()]) <= capacity[i]

# Solve the problem
prob.solve()

Results

After the problem is solved, the optimal number of units to be shipped from each factory to each store can be retrieved with:

python
for v in prob.variables():
    print(v.name, "=", v.varValue)
shipments_('Factory1',_'Store1') = 500.0
shipments_('Factory1',_'Store2') = 500.0
shipments_('Factory2',_'Store1') = 0.0
shipments_('Factory2',_'Store2') = 4000.0

This code will print the optimal solution, i.e., the number of units to be shipped from each factory to each store, and the optimal total cost can be retrieved with prob.objective.value().

python
print("Total Cost of Transportation =", value(prob.objective))
Total Cost of Transportation = 6500.0

The above line of code will print the minimal possible total cost of shipping to meet all the demands without exceeding any factory's capacity.

References

https://coin-or.github.io/pulp/

Ryusei Kakujo

researchgatelinkedingithub

Focusing on data science for mobility

Bench Press 100kg!