iPython Notebooks And PuLP

Lately, we’ve begun working on various constrained optimization problems, and this was a good opportunity to use Python, and more specifically, iPython Notebooks which I’ve been learning about in recent days.

Assuming you’ve got iPython already working, try typing:

[code language=“python”]ipython notebook[/code]

If it works, great. If not, you may have some dependent python libraries to install e.g.

[code language=“python”] pip install pyzmq pip install tornado [/code]

Once you’ve got the dependent libraries installed, typing ipython notebook should reveal a similar screenshot in your browser;

ipython notebook screenshot

Ok, with that out of the way, I’ve been using a library called PuLP to test out various optimization problems, and so far so good. There’s also a good introduction to PuLP with examples that you can follow. I recreated those examples in notebook and to show them in this blog post, I had to first convert the notebook to HTML format using nbconvert (you may need to pip install pygments to get it working):

[code language=“python”] ipython nbconvert tutorial.ipynb [/code]

I then had the option of hosting the HTML page somewhere (Wordpress doesn’t seem to like the HTML page) or, the other option is simply using the nbviewer, which is what I used. In that case, you just pass in the URL of your .ipynb file and it creates a viewing friendly version of it.

Alternatively, you can just use Wordpress’ code block functionality and paste in your code:

[code language=“python”] #Whiskas optimization problem import pulp #initialise the model whiskas_model = pulp.LpProblem(‘The Whiskas Problem’, pulp.LpMinimize)

make a list of ingredients

ingredients = [‘chicken’, ‘beef’, ‘mutton’, ‘rice’, ‘wheat’, ‘gel’]

create a dictionary of pulp variables with keys from ingredients

the default lower bound is -inf

x = pulp.LpVariable.dict(‘x_%s’, ingredients, lowBound =0)

cost data

cost = dict(zip(ingredients, [0.013, 0.008, 0.010, 0.002, 0.005, 0.001]))

create the objective

whiskas_model += sum( [cost[i] * x[i] for i in ingredients])

ingredient parameters

protein = dict(zip(ingredients, [0.100, 0.200, 0.150, 0.000, 0.040, 0.000])) fat = dict(zip(ingredients, [0.080, 0.100, 0.110, 0.010, 0.010, 0.000])) fibre = dict(zip(ingredients, [0.001, 0.005, 0.003, 0.100, 0.150, 0.000])) salt = dict(zip(ingredients, [0.002, 0.005, 0.007, 0.002, 0.008, 0.000])) #note these are constraints and not an objective as there is a equality/inequality whiskas_model += sum([protein[i]*x[i] for i in ingredients]) >= 8.0 whiskas_model += sum([fat[i]*x[i] for i in ingredients]) >= 6.0 whiskas_model += sum([fibre[i]*x[i] for i in ingredients]) <= 2.0 whiskas_model += sum([salt[i]*x[i] for i in ingredients]) <= 0.4

#problem is then solved with the default solver whiskas_model.solve() #print the result for ingredient in ingredients: print ‘The mass of %s is %s grams per can’%(ingredient, x[ingredient].value()) [/code]

The next one is called the beer distribution problem. And no, drinking them all is not the answer…

[code language=“python”] #The Beer Distribution Problem for the PuLP Modeller

Import PuLP modeler functions

import pulp

Creates a list of all the supply nodes

warehouses = [“A”, “B”]

Creates a dictionary for the number of units of supply for each supply node

supply = {“A”: 1000, “B”: 4000}

Creates a list of all demand nodes

bars = [“1”, “2”, “3”, “4”, “5”]

Creates a dictionary for the number of units of demand for each demand node

demand = {“1”:500, “2”:900, “3”:1800, “4”:200, “5”:700,}

Creates a list of costs of each transportation path

costs = [ #Bars #1 2 3 4 5 [2,4,5,2,1],#A Warehouses [3,1,3,2,3] #B ]

The cost data is made into a dictionary

costs = pulp.makeDict([warehouses, bars], costs,0)

Creates the ‘prob’ variable to contain the problem data

prob = pulp.LpProblem(“Beer Distribution Problem”, pulp.LpMinimize)

Creates a list of tuples containing all the possible routes for transport

routes = [(w,b) for w in warehouses for b in bars]

A dictionary called x is created to contain quantity shipped on the routes

x = pulp.LpVariable.dicts(“route”, (warehouses, bars), lowBound = 0, cat = pulp.LpInteger)

The objective function is added to ‘prob’ first

prob += sum([x[w][b]*costs[w][b] for (w,b) in routes]),
“Sum_of_Transporting_Costs”

Supply maximum constraints are added to prob for each supply node (warehouse)

for w in warehouses: prob += sum([x[w][b] for b in bars]) <= supply[w],
“Sum_of_Products_out_ofWarehouse%s”%w

Demand minimum constraints are added to prob for each demand node (bar)

for b in bars: prob += sum([x[w][b] for w in warehouses]) >= demand[b],
“Sum_of_Products_into_Bar%s”%b

The problem data is written to an .lp file

prob.writeLP(“BeerDistributionProblem.lp”)

The problem is solved using PuLP’s choice of Solver

prob.solve()

The status of the solution is printed to the screen

print “Status:“, pulp.LpStatus[prob.status]

Each of the variables is printed with it’s resolved optimum value

for v in prob.variables(): print v.name, “=”, v.varValue

The optimised objective function value is printed to the screen

print “Total Cost of Transportation = “, prob.objective.value() [/code] [code language=“css”] Status: Optimal route_A_1 = 300.0 route_A_2 = 0.0 route_A_3 = 0.0 route_A_4 = 0.0 route_A_5 = 700.0 route_B_1 = 200.0 route_B_2 = 900.0 route_B_3 = 1800.0 route_B_4 = 200.0 route_B_5 = 0.0 Total Cost of Transportation = 8600.0 [/code]

comments powered by Disqus