A first look at simpy#

In this tutorial we will make use of free and open source software for discrete-event simulation called simpy.

Why simpy?

An advantage of simpy over commercial simulation packages is its simplicity and flexibility. As it is part of python, it is often straightforward to use simpy to model complex logic and make use of the SciPy stack! Initially, you will need to write a lot of code. But don’t worry. As you use simpy you will build your own library of reusable code that you can draw on (and build on) for future simulation projects. As simpy is free and open it has a big advantage over commercial software for both research: it is more transparent, can be readily shared with others, and can easily link to other data science tools such as those from machine learning.

1. Imports#

The first library we will import is simpy. The typical style is to import the whole package as follows:

import simpy

We will also need a few other packages in our simulation model.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import itertools
import math

2. A first example: a hospital pharmacy#

In this first example, let’s assume (unrealistically) that prescriptions arrive exactly 5 minutes apart. To build this model we need the following components:

A simpy environment#

simpy has process based worldview. These processes take place in an environment. You can create a environment with the following line of code:

env = simpy.Environment()

simpy timeouts#

We can introduce delays or activities into a process. For example these might be the duration of a stay on a ward, or the duration of a operation. In this case we are going to introduce a delay between arrivals (inter-arrival time). In simpy you control this with the following method:

activity_duration = 20
env.timeout(activity_duration)

generators#

The event process mechanism in simpy is implemented using python generators. A basic generator function that yields a new arrival every 5 minutes looks like this:

def prescription_arrival_generator(env):
    while True:
        yield env.timeout(5.0)

Notice that the generator takes the environment as a parameter. It then internally calls the env.timeout() method in an infinite loop.

running a simpy model#

Once we have coded the model logic and created an environment instance, there are two remaining instructions we need to code.

  1. set the generator up as a simpy process

env.process(prescription_arrival_generator(env))
  1. run the environment for a user specified run length

env.run(until=25)

The run method handle the infinite loop we set up in prescription_arrival_generator. The simulation model has an internal concept of time. It will end execution when its internal clock reaches 25 time units.

Now that we have covered the basic building blocks, let’s code the actual model. It makes sense to create our model logic first. The code below will generate arrivals every 5 minutes. Note that the function takes an environment object as a parameter.

def prescription_arrival_generator(env):
    '''
    Prescriptions arrive with a fixed duration of
    5 minutes.

    Parameters:
    ------
    env: simpy.Environment
    '''
    
    # don't worry about the infinite while loop, simpy will
    # exit at the correct time.
    while True:
        
        # sample an inter-arrival time.
        inter_arrival_time = 5.0
        
        # we use the yield keyword instead of return
        yield env.timeout(inter_arrival_time)
        
        # print out the time of the arrival
        print(f'Prescription arrives at: {env.now}')

Now that we have our generator function we can setup the environment, process and call run. We will create a RUN_LENGTH parameter that you can change to run the model for different time lengths. What would happen if this was set to 50?

# model parameters
RUN_LENGTH = 25

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

# tell simpy that the `prescription_arrival_generator` is a process
env.process(prescription_arrival_generator(env))

# run the simulation model
env.run(until=RUN_LENGTH)
print(f'end of run. simulation clock time = {env.now}')
Prescription arrives at: 5.0
Prescription arrives at: 10.0
Prescription arrives at: 15.0
Prescription arrives at: 20.0
end of run. simulation clock time = 25

Exercise

Before going any further have a go at the generators exercise. In the exercise you will need to modify the prescription_arrival_generator so that it has random arrivals. This exercise test that you have understood the basics of simpy and random sampling in numpy