Introduction

In [14]:
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>''')
Out[14]:
                                                                 Analyst:Luoben Zhang
                                                                 Date: 2019/11/10

The Westvaco Company is facing distribution problem. They want to minimize their cost for truckloads to carriers that meet the necessary requirements. There are 6 carriers who are willing to provide their services: ABCT, IRST, LAST, MRST, NEST, PSST. Each of them have a different cost per mile, the stops to the same destination are the same, but the Stop-off charge is also part of the company cost. For each destination, the company should choose number carriers that equal to the total trips required (same carrier can be chosen several times). On the other hand, each carrier also has a minimum charge per truckload. If the total cost for one trip belows this minimum charge amount, the carrier would still charge the company the minimum charge they asked. This case is to help Westvoca Company to find a most optimize way to lower their transportation cost.

Model

In [15]:
from gurobipy import*
m = Model("Minimum Cost")
In [16]:
Destination = ['Atlanta','Everett','Ephrata','Riverview','Carson','Chamblee','Roseville','Hanover','Sparks','Parsippany','Effingham','Kearny','MinCharge','StopOffCharge']
State = ['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,'*',350,50]
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,400,75]
LAST = [1.15,1.27,1.73,1.25,0.87,1.61,1.89,2.23,'*',1.36,1.25,1.54,350,50]
MRST = [0.87,1.39,1.71,0.96,'*',1.22,1.32,2.39,1.2,1.39,0.87,1.53,300,35]
NEST = [0.95,1.35,1.82,0.95,1.00,1.33,1.41,2.26,'*',1.03,0.9,1.28,350,50]
PSST = [1.05,1.28,2,1.11,'*',1.47,1.41,2.57,'*',1.76,1.31,1.95,300,50]

Current Distribution Data for Westvaco Case Study

In [17]:
import pandas as pd
ak = pd.DataFrame.from_dict({
        'Destination': Destination,
        'State': State,
        'Trips': trips,
        'Stops': stops,
        'Miles': miles,
        'ABCT': ABCT,
        'IRST': IRST,
        'LAST': LAST,
        'MRST': MRST,
        'NEST': NEST,
        'PSST': PSST
     })
ak
Out[17]:
Destination State Trips Stops Miles ABCT IRST LAST MRST NEST PSST
0 Atlanta GA 4 0 612 * 0.88 1.15 0.87 0.95 1.05
1 Everett MA 1 3 612 * 1.18 1.27 1.39 1.35 1.28
2 Ephrata PA 3 0 190 * 3.42 1.73 1.71 1.82 2
3 Riverview MI 5 0 383 0.79 1.01 1.25 0.96 0.95 1.11
4 Carson CA 1 2 3063 * 0.80 0.87 * 1 *
5 Chamblee GA 1 0 429 * 1.23 1.61 1.22 1.33 1.47
6 Roseville MN 1 3 600 1.24 1.13 1.89 1.32 1.41 1.41
7 Hanover PA 1 0 136 * 4.78 2.23 2.39 2.26 2.57
8 Sparks NV 2 0 2439 * 1.45 * 1.2 * *
9 Parsippany NJ 1 1 355 * 1.62 1.36 1.39 1.03 1.76
10 Effingham IL 5 0 570 0.87 0.87 1.25 0.87 0.9 1.31
11 Kearny NJ 7 0 324 * 2.01 1.54 1.53 1.28 1.95
12 MinCharge 350 400.00 350 300 350 300
13 StopOffCharge 50 75.00 50 35 50 50
In [18]:
zlb = [ABCT[0:12],IRST[0:12],LAST[0:12],MRST[0:12],NEST[0:12],PSST[0:12]]
In [19]:
ub = 9999999
cost1 = []
for i in range(len(zlb)):
    cost=[]
    for j in range(len(zlb[i])):
        if zlb[i][j] == "*":
            cost.append(ub)
        elif zlb[i][j]*miles[j]+stops[j]*stopoffCharge[i] > minCharge[i]:
            cost.append(zlb[i][j]*miles[j]+stops[j]*stopoffCharge[i])
        else:    
            cost.append(minCharge[i])
    cost1.append(cost)

Cost for each Carrier to different destinations

In [20]:
ap = pd.DataFrame.from_dict({
        'Destination': Destination[0:12],
        'ABCT': cost1[0],
        'IRST': cost1[1],
        'LAST': cost1[2],
        'MRST': cost1[3],
        'NEST': cost1[4],
        'PSST': cost1[5]
     })
ap
Out[20]:
Destination ABCT IRST LAST MRST NEST PSST
0 Atlanta 9999999.0 538.56 703.80 532.44 581.40 642.60
1 Everett 9999999.0 947.16 927.24 955.68 976.20 933.36
2 Ephrata 9999999.0 649.80 350.00 324.90 350.00 380.00
3 Riverview 350.0 400.00 478.75 367.68 363.85 425.13
4 Carson 9999999.0 2600.40 2764.81 9999999.00 3163.00 9999999.00
5 Chamblee 9999999.0 527.67 690.69 523.38 570.57 630.63
6 Roseville 894.0 903.00 1284.00 897.00 996.00 996.00
7 Hanover 9999999.0 650.08 350.00 325.04 350.00 349.52
8 Sparks 9999999.0 3536.55 9999999.00 2926.80 9999999.00 9999999.00
9 Parsippany 9999999.0 650.10 532.80 528.45 415.65 674.80
10 Effingham 495.9 495.90 712.50 495.90 513.00 746.70
11 Kearny 9999999.0 651.24 498.96 495.72 414.72 631.80
In [21]:
m.remove(m.getVars())
m.remove(m.getConstrs())

Decision Variables

There are 72 decision variables in total: 12 cities and 6 carriers.

In [22]:
#Decision Variables

var = [[m.addVar(vtype=GRB.INTEGER, name="Carrier"+str(i+1)+"_"+"Dest"+str(j+1)) for j in range(len(zlb[i]))] for i in range(len(zlb))]
m.update()

Constraints

The sum of truckloads for each carrier should less than the available pulls for each carrier, but greater than the commitment. The total truckloads for each destination should equal to the total trips needed for each destination.

In [23]:
#Constraints

Available_Pulls = [4,8,7,7,3,4]
Commitment = [1,7,6,0,0,4]
for i in range(len(var)):
    m.addConstr(sum(var[i]),GRB.LESS_EQUAL,Available_Pulls[i])
    m.addConstr(sum(var[i]),GRB.GREATER_EQUAL,Commitment[i])
trips1 = [4,1,3,5,1,1,1,1,2,1,5,7]
for j in range(12):
    m.addConstr(var[0][j]+var[1][j]+var[2][j]+var[3][j]+var[4][j]+var[5][j],GRB.EQUAL,trips1[j])
m.update()

Objective

The objective function is minimize of the total cost. Total cost is the sumproduct of decision variables and Cost for each Carrier to different destinationstable for each carrier.

In [24]:
#Objective

k = []
for i in range(len(var)):
    for j in range(len(var[i])):
        k.append(cost1[i][j]*var[i][j])
m.setObjective(sum(k),GRB.MINIMIZE)
m.update()

Optimization

In [25]:
# Optimization

m.optimize()
Optimize a model with 24 rows, 72 columns and 216 nonzeros
Variable types: 0 continuous, 72 integer (0 binary)
Coefficient statistics:
  Matrix range     [1e+00, 1e+00]
  Objective range  [3e+02, 1e+07]
  Bounds range     [0e+00, 0e+00]
  RHS range        [1e+00, 8e+00]
Found heuristic solution: objective 5.001613e+07
Presolve removed 3 rows and 0 columns
Presolve time: 0.00s
Presolved: 21 rows, 72 columns, 180 nonzeros
Variable types: 0 continuous, 72 integer (36 binary)

Root relaxation: objective 2.239438e+04, 30 iterations, 0.00 seconds

    Nodes    |    Current Node    |     Objective Bounds      |     Work
 Expl Unexpl |  Obj  Depth IntInf | Incumbent    BestBd   Gap | It/Node Time

*    0     0               0    22394.380000 22394.3800  0.00%     -    0s

Explored 0 nodes (30 simplex iterations) in 0.02 seconds
Thread count was 12 (of 12 available processors)

Solution count 2: 22394.4 5.00161e+07 

Optimal solution found (tolerance 1.00e-04)
Best objective 2.239438000000e+04, best bound 2.239438000000e+04, gap 0.0000%

Result

In [26]:
a = []
for v in m.getVars():
    if '%g' % (v.x) == -0:
        a.append(0)
    else:
        a.append(int('%g' % (v.x)))
        
b = [('$%g' % m.objVal),'','','','','','','','','','','']
        
ad = pd.DataFrame.from_dict({
        'Destination': Destination[0:12],
        'ABCT': a[0:12],
        'IRST': a[12:24],
        'LAST': a[24:36],
        'MRST': a[36:48],
        'NEST': a[48:60],
        'PSST': a[60:72],
        'MinCost': b
     })
ad
Out[26]:
Destination ABCT IRST LAST MRST NEST PSST MinCost
0 Atlanta 0 0 0 4 0 0 $22394.4
1 Everett 0 0 0 0 0 1
2 Ephrata 0 0 1 0 0 2
3 Riverview 4 0 0 1 0 0
4 Carson 0 1 0 0 0 0
5 Chamblee 0 1 0 0 0 0
6 Roseville 0 1 0 0 0 0
7 Hanover 0 0 0 0 0 1
8 Sparks 0 0 0 2 0 0
9 Parsippany 0 0 0 0 1 0
10 Effingham 0 5 0 0 0 0
11 Kearny 0 0 5 0 2 0
In [27]:
print('The optimal result in this case will be:','$%g' % m.objVal)
The optimal result in this case will be: $22394.4

Recommendation

In order to get my objective, I used the cost for each carrier to different destinations multiply by the number of times we choose each carrier for a certain destination (my decision variables). Add these costs together, we could get a total cost for the whole transportation progress. I used both Gurobi and Excel to get an optimized solution for this case, and the results are the same: The least-cost of truckloads to carriers that meets the necessary requirements will be $22394.4. I will describe the plan for each carrier in details:

For ABCT, the company should assign 4 truckloads and all towards Riverview.
For IRST, the company should assign 1 truckload towards Carson, 1 truckload towards Chamblee, 1 truckload towards Roseville, and 5 truckloads towards Effingham.
For LAST, the company should assign 1 truckload to Ephrata, and 5 truckloads towards Kearny.
For MRST, the company should assign 4 truckloads towards Atlanta, 1 truckload towards Riverview, and 2 truckloads towards Sparks.
For NEST, the company should assign 1 truckloads towards Parsippany, and 2 truckloads towards Kearny.
For PSST, the company should assign 1 truckload towards Everett, 2 truckloads towards Ephrata, and 1 truckload towards Hanover.

Approach and Challenges

There are a lot of challenges while doing this assignment. The first challenge is tranfer my mind from r to python. I watch the videos the professor posted online, and they are really helpful. They remind me how to use Gurobi for our last assignments. The most difficult part to build the model for this Westvaco case is to determine the cost for each carrier towards certain destination. In excel, I don't know how to deal with the cost per mile at the beginning. However, after reading the book, I learned we can assign a big value to it so they will never be chosen. Thus, I write an if statement in excel, if the cost per mile = , then I assign 999999 to that cell. If not, we choose the maximum value between the actual cost and the minimum charge for that carrier. Opensolver is another challenge since I'm more familiar with using solver, but after watching the videos, I can finally use it to solve this case.

The gurobi the hardest part for this assignment. But since I build an Excel Model first, I got a clear logic towards solving this problem. I have concerns about how to use for loops to set up constraints and decision variables instead of hardcoding. After carefully understanding the logic relationships between those variables, I successfully simplify my codes to fewer lines. I learned how to build a dataframe using panda through videos, and I think it is really helpful. For the output part, I don't know how to just print out the decision variables result, so I googled and change the code to a for loop in order to get the result I want.

Overall, this assignment makes me get more familiar with Gurobi and the logic behind it. I enjoy doing this assignment.

In [ ]: