Generator exercise#

Imports#

import simpy
import numpy as np

Example code#

The code below is taken from the simple pharmacy example. In this code arrivals occur with an IAT of exactly 5 minutes.

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}')
# 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: modelling a poisson arrival process for prescriptions#

Task:

  • Update prescription_arrival_generator() so that inter-arrival times follow an exponential distribution with a mean of 5.0 minutes between arrivals.

  • Use a run length of 25 minutes.

Bonus: try this initially without setting a random seed. Then update the method choosing an approach to control random sampling.

Example answer 1#

# example answer
def prescription_arrival_generator(env, random_seed=None):
    '''
    Prescriptions arrive with a fixed duration of
    5 minutes.
    
    Parameters:
    ------
    env: simpy.Environment
    
    random_state: int, optional (default=None)
        if set then used as random seed to control sampling.
    '''
    rs_arrivals = np.random.default_rng(random_seed)
    
    while True:
        inter_arrival_time = rs_arrivals.exponential(5.0)
        yield env.timeout(inter_arrival_time)
        print(f'Prescription arrives at: {env.now}')
# 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: 3.2655915821435655
Prescription arrives at: 5.482932196434953
Prescription arrives at: 7.9052204042803
Prescription arrives at: 10.953323224279941
Prescription arrives at: 14.488054172321617
Prescription arrives at: 15.213857121219597
Prescription arrives at: 22.1222648209537
end of run. simulation clock time = 25

Example answer 2#

In this solution we first define a class called Exponential and pass that as an argument to the generator.

class Exponential:
    '''
    Convenience class for the exponential distribution.
    packages up distribution parameters, seed and random generator.
    '''
    def __init__(self, mean, random_seed=None):
        '''
        Constructor

        Params:
        ------
        mean: float
            The mean of the exponential distribution

        random_seed: int, optional (default=None)
            A random seed to reproduce samples.  If set to none then a unique
            sample is created.
        '''
        self.rand = np.random.default_rng(seed=random_seed)
        self.mean = mean

    def sample(self, size=None):
        '''
        Generate a sample from the exponential distribution

        Params:
        -------
        size: int, optional (default=None)
            the number of samples to return.  If size=None then a single
            sample is returned.
        '''
        return self.rand.exponential(self.mean, size=size)
# example answer
def prescription_arrival_generator(env, iat_dist):
    '''
    Prescriptions arrive with a fixed duration of
    5 minutes.
    
    Parameters:
    ------
    env: simpy.Environment
    
    iat_dist: object
        A python class that implements a .sample() method
        and generates the IATs
    
    random_state: int, optional (default=None)
        if set then used as random seed to control sampling.
    '''
        
    while True:
        inter_arrival_time = iat_dist.sample()
        yield env.timeout(inter_arrival_time)
        print(f'Prescription arrives at: {env.now}')
# model parameters
RUN_LENGTH = 25

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

iat = Exponential(mean=5.0, random_seed=42)

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

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

Why would we use solution 2?#

Solution 2 is a useful approach as it is now easy to define new experiments. For example, we could experiment with the mean of the exponential or use an entirely different distribution (as long as it implements .sample()) without changing our generator function. For example.

# model parameters
RUN_LENGTH = 25

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

# *** MODIFICATION: reduce IAT.
iat = Exponential(mean=2.5, random_seed=42)

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

# run the simulation model
env.run(until=RUN_LENGTH)
print(f'end of run. simulation clock time = {env.now}')
Prescription arrives at: 6.0105215099149865
Prescription arrives at: 11.85099564947612
Prescription arrives at: 17.812898149161757
Prescription arrives at: 18.512383873790714
Prescription arrives at: 18.728477373036657
Prescription arrives at: 22.360128662302035
end of run. simulation clock time = 25