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])

Getting Started with Discrete Event Simulation

This IPython notebook demonstrates elementary use of the SimPy package for discrete event simulation.

Initializations

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

SimPy Installation

In [2]:
!pip install simpy
import simpy as simpy
#simpy.test()
Requirement already satisfied (use --upgrade to upgrade): simpy in /Users/jeff/anaconda/lib/python2.7/site-packages
Cleaning up...

Introduction to Modeling with SimPy

A Minimal SimPy Model

A typical simpy model consists of an environment, processes that create events for the environment to process, and resources. We'll start by setting up an environment and running a simulation. This won't do anything, but it is valid (if useless) simulation.

In [13]:
import simpy

# create the simulation environment
env = simpy.Environment()

# run the simulation
env.run()

Adding a Process

An example of a process is a clock that ticks at regular intervals, and at each tick prints a message showing the current time.

The clock is a regular python function that executes until it encounters the yield env.timeout(tick) statement. At that point a new event is scheduled for tick time units in the future after which execution will continue.

The env.process(clock(env, 2.0)) statement adds a clock to the simulation environment. The env.run(until=10) statement processes the environment 10 simulated time units.

In [14]:
import simpy

# define a clock process
def clock(env,tick):
    while True:
        print "Time = {:8.6f} minutes".format(env.now)
        yield env.timeout(tick)

# create the simulation environment
env = simpy.Environment()

# add the clock process to the environment. Set the tick interval.
env.process(clock(env, 2.0))

# run the simulation for a fixed period of time
env.run(until=10)
Time = 0.000000 minutes
Time = 2.000000 minutes
Time = 4.000000 minutes
Time = 6.000000 minutes
Time = 8.000000 minutes

Mutliple Instances of a Process

In [15]:
import simpy

# define a clock process
def clock(env,name,tick):
    while True:
        print "Clock {:s} ticks. Time = {:8.6f} minutes".format(name, env.now)
        yield env.timeout(tick)

# create the simulation environment
env = simpy.Environment()

# add the clock process to the environment. Set the tick interval.
env.process(clock(env, "A", 2.0))
env.process(clock(env, "B", 1.3))

# run the simulation for a fixed period of time
env.run(until=10)
Clock A ticks. Time = 0.000000 minutes
Clock B ticks. Time = 0.000000 minutes
Clock B ticks. Time = 1.300000 minutes
Clock A ticks. Time = 2.000000 minutes
Clock B ticks. Time = 2.600000 minutes
Clock B ticks. Time = 3.900000 minutes
Clock A ticks. Time = 4.000000 minutes
Clock B ticks. Time = 5.200000 minutes
Clock A ticks. Time = 6.000000 minutes
Clock B ticks. Time = 6.500000 minutes
Clock B ticks. Time = 7.800000 minutes
Clock A ticks. Time = 8.000000 minutes
Clock B ticks. Time = 9.100000 minutes

Processes Manage their own State

In [16]:
import simpy

# define a clock process
def clock(env,name,tick):
    nTicks = 0
    while True:
        nTicks += 1
        print "Clock {:s}, tick number {:d}. Time = {:8.6f} minutes".format(name, nTicks, env.now)
        yield env.timeout(tick)

# create the simulation environment
env = simpy.Environment()

# add the clock process to the environment. Set the tick interval.
env.process(clock(env, "A", 2.0))
env.process(clock(env, "B", 1.3))

# run the simulation for a fixed period of time
env.run(until=10)
Clock A, tick number 1. Time = 0.000000 minutes
Clock B, tick number 1. Time = 0.000000 minutes
Clock B, tick number 2. Time = 1.300000 minutes
Clock A, tick number 2. Time = 2.000000 minutes
Clock B, tick number 3. Time = 2.600000 minutes
Clock B, tick number 4. Time = 3.900000 minutes
Clock A, tick number 3. Time = 4.000000 minutes
Clock B, tick number 5. Time = 5.200000 minutes
Clock A, tick number 4. Time = 6.000000 minutes
Clock B, tick number 6. Time = 6.500000 minutes
Clock B, tick number 7. Time = 7.800000 minutes
Clock A, tick number 5. Time = 8.000000 minutes
Clock B, tick number 8. Time = 9.100000 minutes

Application Examples

Geometric Brownian Motion

In [17]:
import simpy
import random        

# geometric brownian motion
def gbm(env,name,tick,P,mu,sigma):
    t = 0;
    while True:
        Plog.append(P)
        tlog.append(t)
        yield env.timeout(tick)
        P += P*(mu*tick + sigma*random.normalvariate(0,1)*sqrt(tick))
        t += tick
    
# create the simulation environment
env = simpy.Environment()

# add the clock process to the environment. Set the tick interval.
env.process(gbm_old(env, "A", sqrt(1.0/252), 80.0, 0, .3))

# run the simulation for a fixed period of time
    
Plog = []
tlog = []
env.run(until=10)

plot(tlog,Plog)
xlabel('Date')
ylabel('Price')
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-17-bd3b196fecc8> in <module>()
     16 
     17 # add the clock process to the environment. Set the tick interval.
---> 18 env.process(gbm_old(env, "A", sqrt(1.0/252), 80.0, 0, .3))
     19 
     20 # run the simulation for a fixed period of time

NameError: name 'gbm_old' is not defined
In [18]:
import simpy
import random

class gbm(object):
    def __init__(self,env,name,tick,val,mu,sigma):
        self.env = env
        self.name = name
        self.tick = tick
        self.val = val
        self.mu = mu
        self.sigma = sigma
        self.t = 0
        
    def process(self):
        while True:
            yield self.env.timeout(self.tick)
            self.t += self.tick
            self.val += self.val*(self.mu*self.tick + self.sigma*random.normalvariate(0,1)*sqrt(self.tick))

def reporter(env,tick,gbm):
    t = 0
    while True:
        yield env.timeout(tick)
        t += tick
        print t, gbm.val

env = simpy.Environment()
a = gbm(env,'A',1.0/sqrt(12.0),80.0,0,0.30)
env.process(a.process())
env.process(reporter(env,1.0,a))
env.run(until=5)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-18-d079aa585dfd> in <module>()
     26 
     27 env = simpy.Environment()
---> 28 a = gbm(env,'A',1.0/sqrt(12.0),80.0,0,0.30)
     29 env.process(a.process())
     30 env.process(reporter(env,1.0,a))

NameError: name 'sqrt' is not defined

Application

Setting up a class provides a means of modeling more complex behaviors. Here we'll consider a Roomba cleaning robot that can be either in a running mode or a charging mode.

In [64]:
class Roomba(object):
    def __init__(self,env,name,charge_duration,clean_duration):
        self.env = env
        self.name = name
        self.charge_duration = charge_duration
        self.clean_duration = clean_duration
        self.proc = env.process(self.run())

    def run(self):
        while True:
            yield env.process(self.charge())
            yield env.process(self.clean())
    
    def clean(self):
        print "{:<3s} start charging at {:4.1f}".format(self.name,env.now)
        yield env.timeout(self.clean_duration)
    
    def charge(self):
        print "{:<3s} start cleaning at {:4.1f}".format(self.name,env.now)
        yield env.timeout(self.charge_duration)

import simpy
env = simpy.Environment()

A = Roomba(env,'A',1.1,2.3)
B = Roomba(env,'B',0.9,3.1)

# start processes
env.run(until=6)
A   start cleaning at  0.0
B   start cleaning at  0.0
B   start charging at  0.9
A   start charging at  1.1
A   start cleaning at  3.4
B   start cleaning at  4.0
A   start charging at  4.5
B   start charging at  4.9