The latest version of this IPython notebook is available at http://github.com/jckantor/ESTM60203 for noncommercial use under terms of the Creative Commons Attribution Noncommericial ShareAlike License (CC BY-NC-SA 4.0).

J.C. Kantor ([email protected])

Warehouse Fulfullment Operations

This IPython notebook demonstrates use of the SimPy to simulate the order fulfillment operations of a hypothetical warehouse.

Initializations

In [1]:
from IPython.core.display import HTML
HTML(open("styles/custom.css", "r").read())
Out[1]:

Order Fulfillment Model

Order Processing

The order fulfillment operation is a sequence of eight events

  1. Order sent to the fulfillment center.
  2. Picker requested for the order.
  3. Order picked from the warehouse.
  4. Picker released.
  5. Inspector requested to review the order prior to shipping.
  6. Order reviewed.
  7. Reviewer released.
  8. Order shipped.

Order Generator

orderGenerator creates a sequence of orders following a Poisson distribution at an average rate 1/T_ORDER. Each order is tagged with a unique name using itertools.count().

Event Logging

Events are logged and stored in a dictionary attached to an instance of the fulfillment class. The keys are an (orderId,event) tuple with the time stamp stored as the value. The raw data trace can be accessed as the ._data field, or as a pandas Dataframe in the .log field.

Fulfillment Class

In [2]:
# Global variables
T_ORDER = 10                      # average time between orders (Poisson)
T_PICK = 15                       # mean time to pick order from warehouse (beta)
T_REVIEW = 2                      # mean time to review order prior to shipment (beta)
T_SHIP_MIN = 8; T_SHIP_MAX = 12   # mean time to ship order (uniform)
ALPHA = 2.0; BETA = 5.0           # beta distribution parameters

# Global defaults
T_SIM = 1440               # simulation period
N_PICKER = 2               # number of product pickers
N_REVIEWER = 1             # number of product reviewers

import simpy
import itertools
import random
import pandas as pd

class fulfillment(object):
    def __init__(self, n_picker=N_PICKER, n_reviewer=N_REVIEWER):
        self.env = simpy.Environment()
        self._data = dict()
        self.picker = simpy.Resource(self.env,n_picker)
        self.reviewer = simpy.Resource(self.env,n_reviewer)
        self.env.process(self.orderGenerator())
        
    def writeLog(self,orderId,event):
        self._data[orderId,event] = self.env.now
        
    @property
    def log(self):
        df = pd.DataFrame([[a,b,self._data[a,b]] for (a,b) in self._data.keys()])      
        df = df.pivot(index=0,columns=1,values=2).reset_index()
        return df[list(df.columns)[1:]] 
    
    @property
    def stats(self):
        tdelta = pd.DataFrame()
        for c in self.log.columns:
            tdelta[c] = self.log[c] - self.log['Ordered']
        return pd.DataFrame([tdelta.mean(),tdelta.std()],index=['mean','std'])
        
    def order(self,orderId):
        self.writeLog(orderId,'Ordered')
        with self.picker.request() as preq:
            yield preq
            self.writeLog(orderId,'Pick Assigned')
            yield self.env.timeout(T_PICK*((ALPHA+BETA)/ALPHA)*random.betavariate(ALPHA,BETA))
            self.writeLog(orderId,'Picked')
            self.picker.release(preq)
        with self.reviewer.request() as rreq:
            yield rreq
            self.writeLog(orderId,'Review Assigned')
            yield self.env.timeout(T_REVIEW*((ALPHA+BETA)/ALPHA)*random.betavariate(ALPHA,BETA))
            self.writeLog(orderId,'Reviewed')
            self.reviewer.release(rreq)
                
        yield self.env.timeout(random.uniform(T_SHIP_MIN,T_SHIP_MAX))
        self.writeLog(orderId,'Shipped')
                
    def orderGenerator(self):
        for orderId in itertools.count():
            yield self.env.timeout(random.expovariate(1.0/T_ORDER))
            self.env.process(self.order(orderId))
             
    def run(self,t_sim = T_SIM):
        self.env.run(until=t_sim)

Simulation

In [3]:
# create an order fulfillment instance
w = fulfillment(n_picker = 2, n_reviewer = 1)

# run a simulation for a specified period
w.run(10000)

print "Elapsed Time from Order Placement"
display(w.stats)

# plot log results
w.log.plot()
xlabel('Order Number')
ylabel('Time [minutes]')
Elapsed Time from Order Placement
Ordered Pick Assigned Picked Review Assigned Reviewed Shipped
mean 0 10.651194 25.624065 25.814860 27.888061 37.850109
std 0 13.739119 16.246825 16.263375 16.347871 16.356324

2 rows × 6 columns

Out[3]:
<matplotlib.text.Text at 0x106c96550>

Analyzing Results

Total Processing Time

In [4]:
torder = w.log['Shipped'] - w.log['Ordered']
subplot(3,1,1)
plot(torder)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time from Order to Shipment')

subplot(2,1,2)
torder.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[4]:
<matplotlib.text.Text at 0x109790ad0>

Time to Assign Picker

In [5]:
twait = w.log['Pick Assigned'] - w.log['Ordered']
subplot(3,1,1)
plot(twait)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time to Assign Picker')

subplot(2,1,2)
twait.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[5]:
<matplotlib.text.Text at 0x109d0aad0>

Time to Pick Order

In [6]:
tpick = w.log['Picked'] - w.log['Pick Assigned']
subplot(3,1,1)
plot(tpick)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time to Pick Order')

subplot(2,1,2)
tpick.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[6]:
<matplotlib.text.Text at 0x109d5ead0>

Time to Assign Reviewer

In [7]:
twait = w.log['Review Assigned'] - w.log['Picked']
subplot(3,1,1)
plot(twait)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time to Assign Inspector')

subplot(2,1,2)
twait.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[7]:
<matplotlib.text.Text at 0x10aa71410>

Time to Review Order

In [8]:
treview = w.log['Reviewed'] - w.log['Review Assigned']
subplot(3,1,1)
plot(treview)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time to Review Order')

subplot(2,1,2)
treview.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[8]:
<matplotlib.text.Text at 0x10b076350>

Time to Ship

In [9]:
tship = w.log['Shipped'] - w.log['Reviewed']
subplot(3,1,1)
plot(tship)
xlabel('Order Number')
ylabel('Time [minutes]')
title('Time to Ship')

subplot(2,1,2)
tship.hist(bins=30)
xlabel('Time [minutes]')
ylabel('Count')
Out[9]:
<matplotlib.text.Text at 0x10b0cc0d0>
In [10]:
display(w.log)
1 Ordered Pick Assigned Picked Review Assigned Reviewed Shipped
0 3.366705 3.366705 37.402323 37.402323 39.949771 48.042636
1 13.430272 13.430272 21.922379 21.922379 22.382470 30.712128
2 19.418406 21.922379 49.876027 49.876027 51.697383 61.445490
3 21.600034 37.402323 55.225490 55.225490 56.326083 67.619233
4 44.690380 49.876027 64.700524 64.700524 65.987656 76.276382
5 54.288239 55.225490 81.024971 81.024971 84.335838 94.602018
6 115.896088 115.896088 121.008058 121.008058 122.036705 130.296162
7 138.233840 138.233840 147.719116 147.719116 150.813193 161.399557
8 141.529573 141.529573 158.381395 159.397793 160.363795 172.213511
9 153.438150 153.438150 157.620364 157.620364 159.397793 169.442468
10 159.131296 159.131296 175.103136 175.103136 176.382204 185.881145
11 163.297610 163.297610 184.078637 184.078637 186.888640 195.598683
12 178.897147 178.897147 195.458748 197.502702 197.979072 209.569898
13 185.773526 185.773526 194.232028 194.232028 197.502702 205.966477
14 190.770863 194.232028 198.518328 198.518328 200.241701 210.248278
15 191.436358 195.458748 207.199782 207.199782 209.417259 218.042570
16 192.996673 198.518328 225.358624 225.358624 228.112898 239.672279
17 235.721763 235.721763 259.271193 261.177311 262.964280 272.140727
18 236.982136 236.982136 248.707889 248.707889 249.005599 257.179921
19 249.739558 249.739558 257.191378 257.191378 261.177311 270.570713
20 251.211860 257.191378 292.433111 292.433111 294.317196 306.109379
21 269.896839 269.896839 290.482557 290.482557 291.492054 301.762160
22 277.618827 290.482557 326.783904 326.783904 330.541525 340.451760
23 316.297180 316.297180 345.825218 345.825218 348.454351 356.890010
24 327.438778 327.438778 348.613301 348.613301 351.616536 362.402863
25 329.058777 345.825218 360.018647 360.293374 361.870379 370.748167
26 331.270089 348.613301 358.402893 358.402893 360.293374 370.413160
27 342.801178 358.402893 360.985239 361.870379 365.254626 375.016625
28 344.822530 360.018647 366.005058 366.005058 366.567726 375.151551
29 356.046606 360.985239 385.387547 385.387547 387.873472 396.610833
30 364.586061 366.005058 390.795758 390.795758 392.832533 404.251737
31 365.424787 385.387547 420.092838 420.092838 424.591573 433.471879
32 391.448971 391.448971 402.942336 402.942336 403.217641 413.900558
33 405.971702 405.971702 414.498846 414.498846 417.528996 427.306887
34 420.121007 420.121007 426.304452 426.304452 427.300927 436.224023
35 436.134513 436.134513 459.086424 459.086424 460.308443 471.132942
36 436.209495 436.209495 457.539077 457.539077 458.243014 467.955627
37 438.721645 457.539077 471.973973 473.986796 475.914645 485.816522
38 441.427207 459.086424 471.666111 471.666111 473.986796 483.931980
39 471.557560 471.666111 495.620128 495.620128 496.530393 505.060393
40 480.587433 480.587433 502.549153 502.549153 502.995927 513.842043
41 500.355643 500.355643 515.954981 515.954981 518.426460 528.518062
42 511.943491 511.943491 535.724551 535.724551 538.380528 548.860482
43 520.640852 520.640852 550.562285 550.562285 554.395111 562.597845
44 522.541796 535.724551 554.860670 554.860670 557.044421 568.202196
45 527.026733 550.562285 568.436347 568.436347 570.647454 580.206084
46 527.895047 554.860670 564.437151 564.437151 565.958044 575.275032
47 538.061558 564.437151 573.728371 573.728371 575.261145 584.398957
48 542.604827 568.436347 586.843015 586.843015 588.093484 597.997861
49 549.846024 573.728371 587.726074 588.093484 588.638568 600.166950
50 593.573719 593.573719 622.320771 622.320771 623.469427 631.540442
51 600.933472 600.933472 614.026085 614.026085 616.538472 625.870210
52 611.151563 614.026085 638.060880 638.060880 639.591900 650.086266
53 614.890535 622.320771 626.535703 626.535703 628.924357 639.583351
54 624.824109 626.535703 634.225679 634.225679 635.431060 645.758394
55 653.657424 653.657424 657.297616 657.297616 658.924891 669.187379
56 665.674698 665.674698 675.278395 675.278395 675.537426 685.107765
57 668.401265 668.401265 683.858594 683.858594 685.067501 696.779362
58 694.595630 694.595630 712.863166 712.863166 712.972219 722.550661
59 698.948035 698.948035 708.010551 708.010551 709.042020 718.971709
... ... ... ... ... ...

984 rows × 6 columns