This IPython notebook demonstrates the use of the Python package PuLP for the modeling and solution of linear programming problems.
J.C. Kantor (Kantor.1@nd.edu)
The latest version of this IPython notebook is available at http://github.com/jckantor/CBE20255 for noncommercial use under terms of the Creative Commons Attribution Noncommericial ShareAlike License.
PuLP is an external package that may need to be installed into your python environment. The following cell issues a system command to install PuLP if needed. PuLP includes the COIN-OR CBC solver.
!pip install PuLP
Requirement already satisfied (use --upgrade to upgrade): PuLP in /Applications/anaconda/lib/python2.7/site-packages Requirement already satisfied (use --upgrade to upgrade): pyparsing<=1.9.9 in /Applications/anaconda/lib/python2.7/site-packages (from PuLP) Cleaning up...
import pulp
# Create problem instance
giapetto = pulp.LpProblem("Giapetto's Workshop", pulp.LpMaximize)
# Define Variables with lower bounds of zero
x1 = pulp.LpVariable("Soldiers",0)
x2 = pulp.LpVariable("Trains",0)
# Add Objective
giapetto += 3*x1 + 2*x2, "Profit"
# Add Constraints
giapetto += 2*x1 + x2 <= 100,"Finishing Labor"
giapetto += x1 + x2 <= 80, "Carpentry Labor"
giapetto += x1 <= 40, "Soldier Demand"
giapetto += x1 + x2 == 20, "Minimum Production"
# Display Problem
print giapetto
Giapetto's Workshop: MAXIMIZE 3*Soldiers + 2*Trains + 0 SUBJECT TO Finishing_Labor: 2 Soldiers + Trains <= 100 Carpentry_Labor: Soldiers + Trains <= 80 Soldier_Demand: Soldiers <= 40 Minimum_Production: Soldiers + Trains = 20 VARIABLES Soldiers Continuous Trains Continuous
giapetto.solve()
print pulp.LpStatus[giapetto.status]
print pulp.LpSenses[giapetto.sense], giapetto.objective.name, "=", pulp.value(giapetto.objective)
Optimal Maximize OBJ = 60.0
Values for the decision variables can be accessed with the pulp.value()
method.
print x1.name, "=" , pulp.value(x1)
print x2.name, "=" , pulp.value(x2)
Soldiers = 20.0 Trains = 0.0
Alternatively, the decision variables can be accessed by interating through the list created by the variables()
method.
for x in giapetto.variables():
print "Name: ", x.name
print "Value: ", x.varValue
print "Category: ", x.cat
print "Lower Bound:", x.lowBound
print "Upper Bound:", x.upBound
print
Name: Soldiers Value: 20.0 Category: Continuous Lower Bound: 0 Upper Bound: None Name: Trains Value: 0.0 Category: Continuous Lower Bound: 0 Upper Bound: None
Pandas is a python package for managing and analysing data. The following cell shows how to use a list comprehension and panda DataFrame to create and display a table from the solution data.
The constraints are stored as an ordered dictionary in constraints
attribute of the problem.
for (name,constraint) in giapetto.constraints.items():
print "Name: ", name
print "Constraint: ", constraint
print "Lower Bound: ", constraint.getLb()
print "Upper Bound: ", constraint.getUb()
print "Value: ", pulp.value(constraint)
print "Sense: ", pulp.LpConstraintSenses[constraint.sense]
print "Constant: ", constraint.constant
print "Slack: ", constraint.slack
print "Slack (Feas):", constraint.slack if constraint.sense < 0 else -constraint.slack
print
Name: Finishing_Labor Constraint: 2*Soldiers + Trains <= 100 Lower Bound: None Upper Bound: 100 Value: -60.0 Sense: <= Constant: -100 Slack: 60.0 Slack (Feas): 60.0 Name: Carpentry_Labor Constraint: Soldiers + Trains <= 80 Lower Bound: None Upper Bound: 80 Value: -60.0 Sense: <= Constant: -80 Slack: 60.0 Slack (Feas): 60.0 Name: Soldier_Demand Constraint: Soldiers <= 40 Lower Bound: None Upper Bound: 40 Value: -20.0 Sense: <= Constant: -40 Slack: 20.0 Slack (Feas): 20.0 Name: Minimum_Production Constraint: Soldiers + Trains Lower Bound: 20 Upper Bound: 20 Value: 0.0 Sense: = Constant: -20 Slack: -0.0 Slack (Feas): 0.0
import pandas
def solvelp(lp):
lp.solve();
data = [[lp.objective.name, pulp.LpSenses[lp.sense], pulp.LpStatus[lp.status], pulp.value(lp.objective)]]
objDF = pandas.DataFrame(data,columns=["Objective", "Sense", "Status", "Value"])
data = [[x.name, x.cat, x.lowBound, x.upBound, x.varValue]
for x in lp.variables()]
varDF = pandas.DataFrame(data,columns=["Name","Category","Lower Bound","Upper Bound","Value"])
data = [[c.name, c.getLb(), c.getUb(), c.slack, pulp.value(c) - c.constant]
for (k,c) in lp.constraints.items()]
conDF = pandas.DataFrame(data,columns=["Name", "Lower Bound", "Upper Bound", "Slack", "Value"])
return (objDF,varDF,conDF)
(odf,vdf,cdf) = solvelp(giapetto);
from IPython.display import display,HTML
display(HTML('<h3>Model</h3>'))
display(giapetto)
display(HTML('<h3>Objective</h3>'))
display(odf)
display(HTML('<h3>Variables</h3>'))
display(vdf)
display(HTML('<h3>Constraints</h3>'))
display(cdf)
Giapetto's Workshop: MAXIMIZE 3*Soldiers + 2*Trains + 0 SUBJECT TO Finishing_Labor: 2 Soldiers + Trains <= 100 Carpentry_Labor: Soldiers + Trains <= 80 Soldier_Demand: Soldiers <= 40 Minimum_Production: Soldiers + Trains = 20 VARIABLES Soldiers Continuous Trains Continuous
Objective | Sense | Status | Value | |
---|---|---|---|---|
0 | OBJ | Maximize | Optimal | 60 |
Name | Category | Lower Bound | Upper Bound | Value | |
---|---|---|---|---|---|
0 | Soldiers | Continuous | 0 | None | 20 |
1 | Trains | Continuous | 0 | None | 0 |
Name | Lower Bound | Upper Bound | Slack | Value | |
---|---|---|---|---|---|
0 | Finishing_Labor | NaN | 100 | 60 | 40 |
1 | Carpentry_Labor | NaN | 80 | 60 | 20 |
2 | Soldier_Demand | NaN | 40 | 20 | 20 |
3 | Minimum_Production | 20 | 20 | -0 | 20 |
cdf.plot(title="Value",kind='bar')
<matplotlib.axes.AxesSubplot at 0x107818790>