In [1]:
import pandas as pd
import numpy as np
In [204]:
order = pd.read_excel("data/orders-master.xlsx",sheet_name="Sheet2")
In [205]:
order.sort_values(by='SO Date', inplace=True)
order.reset_index(inplace=True)
In [24]:
dweek = 1
order['dweek']=0
for dd in order.index:
    while order.loc[dd,'dweek']==0:
        if order.loc[dd,'Delivery date'] - pd.Timestamp('2018-03-01') <= pd.Timedelta(dweek*7,'D'):
            order.loc[dd,'dweek'] = dweek
        else:
            dweek = dweek + 1
sweek = 1
order['sweek']=0
for dd in order.index:
    while order.loc[dd,'sweek']==0:
        if order.loc[dd,'SO Date'] - pd.Timestamp('2018-03-01') <= pd.Timedelta(sweek*7,'D'):
            order.loc[dd,'sweek'] = sweek
        else:
            sweek = sweek + 1
In [92]:
ww = pd.DataFrame(data=[range(1,40)])
ww.set_index(0,inplace=True)
In [26]:
do = order['dweek']
ao = order['sweek']
so = order['Shift']

Optimization Model

INDICES

$o$ customer order
$w$ week number $

INPUT PARAMETERS

$a_o$ arrival period of order o
$d_o$ delivery date of order o
$s_o$ number of shift required for order o

DECISION VARIABLES

$u_o = 1$ if order g is completed after due date; otherwise $u_g=0$ (unit penalty for tardy orders)
$x_{ow} = 1$ if order o is selected for assignment in period w otherwise $x_{ow} = 0$ (scheduling variable)

NTO Number of Tardy Orders

Objective Function

Minimize, $$ \; NTO = \sum_{o \in G} u_o $$

Constraints Order Allocation Constraint

$$ \begin{align} & \sum_{w \in W} x_{ow} s_o <= 108 \\ & \sum_{w} x_{ow} = 1 \\ & \sum_{w:do to D} u_o <= x_{ow} \\ & \sum_{w:1\; to\; ao} x_{ow} = 0 \\ \end{align} $$
In [27]:
from pulp import *
In [158]:
prob = LpProblem("Scheduling",LpMinimize)
u = pulp.LpVariable.dicts("delay", [(o) for o in order.index], lowBound=0, cat='Binary')
x = pulp.LpVariable.dicts("selection", [(o,w) for o in order.index for w in ww], lowBound=0, cat='Binary')
prob += lpSum(u[o] for (o) in order.index)
In [159]:
for w in ww:
        prob += lpSum(x[(o,w)]*so[o] for (o) in order.index) <= 108
for o in order.index:
    prob += lpSum(x[(o,w)] for w in ww) == 1
for o in order.index:
    prob += lpSum(u[o] - x[(o,w)] for w in ww.loc[:,do[o]:]) == 0
for o in order.index:
    prob += lpSum(x[(o,w)] for w in ww.loc[:,:ao[o]+4]) == 0
In [160]:
prob.solve()
Out[160]:
1
In [171]:
print("Status is", LpStatus[prob.status], "   Number of Tardy Orders are", prob.objective.value())
Status is Optimal    Number of Tardy Orders are 0.0
In [162]:
for v in prob.variables():
    if v.varValue != 0:
        print(v.name, "=", v.varValue)
selection_(0,_8) = 1.0
selection_(1,_8) = 1.0
selection_(10,_15) = 1.0
selection_(11,_17) = 1.0
selection_(12,_13) = 1.0
selection_(13,_11) = 1.0
selection_(14,_30) = 1.0
selection_(15,_10) = 1.0
selection_(16,_16) = 1.0
selection_(17,_20) = 1.0
selection_(18,_15) = 1.0
selection_(19,_23) = 1.0
selection_(2,_8) = 1.0
selection_(20,_9) = 1.0
selection_(21,_23) = 1.0
selection_(22,_12) = 1.0
selection_(23,_29) = 1.0
selection_(24,_13) = 1.0
selection_(25,_18) = 1.0
selection_(26,_10) = 1.0
selection_(27,_18) = 1.0
selection_(28,_24) = 1.0
selection_(29,_23) = 1.0
selection_(3,_8) = 1.0
selection_(30,_30) = 1.0
selection_(31,_13) = 1.0
selection_(32,_27) = 1.0
selection_(33,_16) = 1.0
selection_(34,_29) = 1.0
selection_(35,_13) = 1.0
selection_(36,_31) = 1.0
selection_(37,_10) = 1.0
selection_(38,_11) = 1.0
selection_(39,_19) = 1.0
selection_(4,_7) = 1.0
selection_(40,_13) = 1.0
selection_(41,_11) = 1.0
selection_(42,_22) = 1.0
selection_(5,_8) = 1.0
selection_(6,_8) = 1.0
selection_(7,_8) = 1.0
selection_(8,_8) = 1.0
selection_(9,_15) = 1.0
In [181]:
pp = pd.DataFrame()
for o,week in x:
    d = {'order' : [o], 'week' : [week], 'schedule' : [x[(o,week)].varValue]}
    d = pd.DataFrame(data=d)
    pp= pd.concat([pp,d])
In [200]:
pp = pp.loc[pp['schedule']==1,:]
pp.set_index('order',inplace=True)
In [209]:
order = order.join(pp)
In [210]:
order
Out[210]:
index SO Date Delivery date FG Code Order Qty Shift week schedule
0 0 2018-03-01 2018-05-26 FR21020000UC 61 1.525000 8 1.0
1 1 2018-03-14 2018-10-07 FC250C20N 3000 5.625000 8 1.0
2 2 2018-03-15 2018-05-25 FB23005001MN 600 5.400000 8 1.0
3 3 2018-03-15 2018-05-25 FB23010001MN 1552 6.984000 8 1.0
4 4 2018-03-15 2018-05-25 FC25010000MN 312 1.497600 7 1.0
5 5 2018-03-15 2018-05-25 FC25015000MN 218 0.697600 8 1.0
6 6 2018-03-15 2018-05-25 FC25020000MN 888 2.841600 8 1.0
7 7 2018-03-15 2018-05-25 FB23020000EX 394 1.576000 8 1.0
8 8 2018-03-21 2018-06-13 FB23010000FR 350 2.800000 8 1.0
9 12 2018-03-24 2018-05-28 FB28002400EX 480 10.800000 15 1.0
10 11 2018-03-24 2018-05-28 FB28013000EX 270 1.200000 15 1.0
11 9 2018-03-24 2018-05-28 FB43213000EX 240 0.384000 17 1.0
12 10 2018-03-24 2018-05-28 FB55001700EX 175 3.181818 13 1.0
13 13 2018-03-26 2018-05-26 FB20018001UC 425 2.125000 11 1.0
14 14 2018-03-26 2018-05-26 FB21017001UC 425 2.125000 30 1.0
15 15 2018-03-26 2018-05-26 FB21017001UC 800 4.000000 10 1.0
16 16 2018-03-26 2018-05-26 FB20018001UC 800 4.000000 16 1.0
17 22 2018-03-28 2018-05-28 FB29900401EX 350 17.500000 20 1.0
18 20 2018-03-28 2018-05-28 FB39000800EX 200 10.000000 15 1.0
19 21 2018-03-28 2018-05-28 FB29901701EX 216 6.480000 23 1.0
20 18 2018-03-28 2018-05-28 FB29907800EX 255 1.836000 9 1.0
21 19 2018-03-28 2018-05-28 FB29901700EX 280 6.666667 23 1.0
22 17 2018-03-28 2018-05-28 FB29904300EX 324 1.944000 12 1.0
23 23 2018-03-29 2018-05-21 FB57304300EX 1950 7.800000 29 1.0
24 24 2018-03-29 2018-05-21 FB57307800EX 375 3.000000 13 1.0
25 25 2018-04-03 2018-06-06 FB30004300EX 504 2.016000 18 1.0
26 26 2018-04-03 2018-06-06 FB30018100EX 240 5.106383 10 1.0
27 27 2018-04-03 2018-06-06 FB55102400EX 180 3.272727 18 1.0
28 28 2018-04-03 2018-06-06 FB43204300EX 224 7.000000 24 1.0
29 29 2018-04-03 2018-06-06 FB28007800EX 210 3.818182 23 1.0
30 31 2018-04-04 2018-06-12 FB47418101EX 2950 11.800000 30 1.0
31 30 2018-04-04 2018-06-12 FB47218101EX 1160 4.640000 13 1.0
32 32 2018-04-05 2018-05-28 FB50512500EX 348 2.088000 27 1.0
33 33 2018-04-05 2018-06-11 FB29904300EX 648 1.440000 16 1.0
34 34 2018-04-05 2018-06-11 FB29907800EX 288 1.152000 29 1.0
35 35 2018-04-05 2018-06-11 FB29913000EX 200 1.000000 13 1.0
36 36 2018-04-05 2018-06-11 FB29901700EX 280 4.912281 31 1.0
37 37 2018-04-05 2018-06-11 FB43213000EX 480 0.768000 10 1.0
38 38 2018-04-05 2018-06-11 FB39000800EX 197 3.581818 11 1.0
39 41 2018-04-12 2018-06-18 FB263100H0EX 348 5.194030 19 1.0
40 39 2018-04-12 2018-06-07 FB292115COEX 1001 3.006006 13 1.0
41 40 2018-04-12 2018-06-18 FB160150GCZ 280 5.090909 11 1.0
42 42 2018-04-12 2018-06-18 FB262100W0EX 223 3.328358 22 1.0