from IPython.display import HTML
HTML('''<script>
code_show=true;
function code_toggle() {
if (code_show){
$('div.input').hide();
} else {
$('div.input').show();
}
code_show = !code_show
}
$( document ).ready(code_toggle);
</script>
<form action="javascript:code_toggle()"><input type="submit" value="Click here to toggle on/off the raw code."></form>''')
from gurobipy import *
m = Model("Westvaco")
from IPython.core.display import Image
Image(filename='logo.png', width=500)
Analyst: Jiao Jiang
Date: 2019/11/10
Company Background: Westvaco is the second largest American packaging company. It sells more than $2 billion worht of manufactured paper, paperboard, and speciality chemcials annually.
Distribution Problem: how to assign truckloads to carriers and transport the products at supply location to destinations where they are demanded at a minimum cost.
2.1 Data:
Trips: the number of truckloads required for each destinations.
Stops: the number of stops required for each destinations.
Miles: the total number of miles for the trip to final destination.
Carriers: there are 6 carriers ABCT,IRST,LAST,MRST,NEST,PSST offers delivery services. The rates are different to various destinations. * indicates carrier do not travel to this destination. rates in dollars/mile.
minCharge: minimum truckload charged by each carrier.
stopoffCharge: $ charged each time the truck stoped.
#from gurobipy import *
m = Model("Westvaco")
import pandas as pd
destination = ['Atlanta','Everett','Ephrata','Riverview','Carson','Chamblee','Roseville','Hanover','Sparks','Parsippany','Effingham','Kearny']
states = ['GA','MA','PA','MI','CA','GA','MN','PA','NV','NJ','IL','NJ']
trips = [4,1,3,5,1,1,1,1,2,1,5,7]
stops = [0,3,0,0,2,0,3,0,0,1,0,0]
miles = [612, 612, 190, 383, 3063, 429, 600, 136, 2439, 355, 570, 324]
minCharge = [350,400,350,300,350,300]
stopoffCharge = [50,75,50,35,50,50]
ABCT = ['*','*','*',0.79,'*','*',1.24,'*','*','*',0.87,'*']
IRST = [0.88,1.18,3.42,1.01,0.8,1.23,1.13,4.78,1.45,1.62,0.87,2.01]
LAST = [1.15,1.27,1.73,1.25,0.87,1.61,1.89,2.23,'*',1.36,1.25,1.54]
MRST = [0.87,1.39,1.71,0.96,'*',1.22,1.32,2.39,1.2,1.39,0.87,1.53]
NEST = [0.95,1.35,1.82,0.95,1.00,1.33,1.41,2.26,'*',1.03,0.9,1.28]
PSST = [1.05,1.28,2,1.11,'*',1.47,1.41,2.57,'*',1.76,1.31,1.95]
given=[ABCT,IRST,LAST,MRST,NEST,PSST]
dataframe1 = pd.DataFrame.from_dict({
'Destination':destination,
'State':states,
'Trips':trips,
'Stops':stops,
'Miles':miles,
'ABCT':ABCT,
'IRST':IRST,
'LAST':LAST,
'MRST':MRST,
'NEST':NEST,
'PSST':PSST
})
dataframe1
print("Minimum charge per truckload for each carrier is", minCharge)
print("Stop-off charge for each carrier is", stopoffCharge)
from IPython.core.display import Image
Image(filename='map.png', width=500)
2.2 Cost Table:
First, calculate the cost for each carrier using fomular: cost = rate miles + stops stopoffCharge.
If the cost is less than minCharge, we should pay at least the minCharge.
If rate is not specified, assign a very large cost. Then create a cost table:
cost = []
for j in range(len(minCharge)):
for i in range(len(trips)):
if given[j][i] =='*':
cost.append("-")
if given[j][i] != '*':
if given[j][i]*miles[i]+stops[i]*stopoffCharge[j] <= minCharge[j]:
cost.append(minCharge[j])
else:
cost.append(given[j][i]*miles[i]+stops[i]*stopoffCharge[j])
ABCTcost = cost[:12]
IRSTcost = cost[12:24]
LASTcost = cost[24:36]
MRSTcost = cost[36:48]
NESTcost = cost[48:60]
PSSTcost = cost[60:]
cost = [ABCTcost,IRSTcost,LASTcost,MRSTcost,NESTcost,PSSTcost]
dataframe2 = pd.DataFrame.from_dict({
'Destination':destination,
'State':states,
'ABCTcost':ABCTcost,
'IRSTcost':IRSTcost,
'LASTcost':LASTcost,
'MRSTcost':MRSTcost,
'NESTcost':NESTcost,
'PSSTcost':PSSTcost
})
dataframe2
2.3 Decision Variables: there are 72 decision variables in total: 12 cities and 6 carriers. Westvaco is deciding the allocation of truckloads.
N=12
list1 = [[m.addVar(vtype=GRB.INTEGER, name="ABCT"+str(i+1)) for i in range(N)],
[m.addVar(vtype=GRB.INTEGER, name="IRST"+str(i+1)) for i in range(N)],
[m.addVar(vtype=GRB.INTEGER, name="LAST"+str(i+1)) for i in range(N)],
[m.addVar(vtype=GRB.INTEGER, name="MRST"+str(i+1)) for i in range(N)],
[m.addVar(vtype=GRB.INTEGER, name="NEST"+str(i+1)) for i in range(N)],
[m.addVar(vtype=GRB.INTEGER, name="PSST"+str(i+1)) for i in range(N)]]
m.update()
dataframe3 = pd.DataFrame.from_dict({
'Destination':['Atlanta','Everett','Ephrata','Riverview','Carson','Chamblee','Roseville','Hanover','Sparks','Parsippany','Effingham','Kearny','Constrain2','Constrain3'],
'State':['GA','MA','PA','MI','CA','GA','MN','PA','NV','NJ','IL','NJ','Available pulls','Commitment'],
'ABCTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',4,1],
'IRSTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',8,7],
'LASTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',7,6],
'MRSTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',7,0],
'NESTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',3,0],
'PSSTvariable':['-','-','-','-','-','-','-','-','-','-','-','-',4,4],
'Constrain1':[4,1,3,5,1,1,1,1,2,1,5,7,'','']
})
dataframe3
2.4 Objectives: the objective function is minimize the total cost. Total cost is the sum of multiplication of decision variables and cost table for each carrier: sum(cost for each carrier*decison variables)
sum1=[]
for i in range(len(list1)):
for j in range(len(list1[i])):
sum1.append(list1[i][j]*cost[i][j])
m.setObjective(sum(sum1), GRB.MINIMIZE)
m.update()
2.5 Constrains:
Constrain1: there are required number of trips to each city,
Constrain2: available pulls, upper limit for each carrier,
Constrain3: Westvaco has commitment to some of the carries, lower limit.
constr1 = ['Trip1','Trip2','Trip3','Trip4','Trip5','Trip6','Trip7','Trip8','Trip9','Trip10','Trip11','Trip12']
for j in range(N):
m.addConstr(list1[0][j]+list1[1][j]+list1[2][j]+list1[3][j]+list1[4][j]+list1[5][j], GRB.EQUAL, trips[j], constr1[j])
m.update()
ub = [4,8,7,7,3,4]
lb = [1,7,6,0,0,4]
constr2 = ['ub1','ub2','ub3','ub4','ub5','ub6']
constr3 = ['lb1','lb2','lb3','lb4','lb5','lb6']
for i in range(len(ub)):
m.addConstr(sum(list1[i]), GRB.LESS_EQUAL, ub[i], constr2[i])
m.addConstr(sum(list1[i]), GRB.GREATER_EQUAL, lb[i], constr3[i])
m.update()
2.6 Optimization and results: use m.optimize() function will return the lowest cost feasible. In this case is $22394; the result table shows the least cost assignmnent of truckloads to carriers that meets the necessary requirements
m.optimize()
a = []
for v in m.getVars():
if '%g' % (v.x) == -0:
a.append(0)
else:
a.append(int('%g' % (v.x)))
results = pd.DataFrame.from_dict({
'Destination': destination,
'ABCT': a[0:12],
'IRST': a[12:24],
'LAST': a[24:36],
'MRST': a[36:48],
'NEST': a[48:60],
'PSST': a[60:],
'TRIPS':trips
})
results
The result table above shows how to distribute truckload for each carrier:
ABCT, Westvaco should assign 4 truckloads in total and all towards Riverview.
IRST, 1 truckload to Carson, 1 truckload to Chamblee, 1 truckload to Roseville, and 5 truckload to Effingham.
LAST, 1 truckload to Ephrata, 5 truckload to Kearny.
MRST, 4 truckloads to Atlanta, 1 truckload to Riverview and 2 truckloads to Sparks.
NEST, 1 truckload to Parsippany, and 2 truckloads to Kearny.
PSST, 1 truckload to Evertt, 2 truckloads to Ephrata and 1 truckload to Hanover.
The cost of distribution plan is $22394. From the article we know that the annual trucking costs is about 15 million dollar per year, which is 41095 dollar per day. By implementing LP solution, it only cost 22394 dollar per day. This model can save Westvaco 18702 per day, 6826230 per year. The model can successfully reduce the cost by 45.5%
#15000000/365
#(22394-41096)/41096
#18702*365
from IPython.core.display import Image
Image(filename='costdecrease.png', width=350)