Results collection exercise#

Exercise: calculating the utilisation of the call operators.#

The 111 caller model is replicated below. At the moment this only collects results for the mean waiting time experienced by callers. Your task is to update the code so that the average utilsation of the call operators is calculated.

Task:

  1. Create a new entry to the results dictionary with the key total_call_duration

    • You will use results['total_call_duration'] to store the total time operators are in use during the model run.

  2. Modify the service function to update total_call_duration after each call completes.

  3. After the run has completed calculate the average operator utilisation.

  4. Print the results to the screen.

Hints:

  • The service function could update the total_call_duration as follows (where call_duration is the length of time a caller has just spent with an operator).

results['total_call_duration'] += call_duration
  • Average operator utilisation is calculated by:

mean_util = results['total_call_duration'] / (RUN_LENGTH * N_OPERATORS) * 100.0

note we don’t quite capture all operator utilisation using this method. When the simulation terminates there will be a number of callers in service. This can be fixed using some additional logic, and its up to you to decide if this is an issue with results reporting. For this exercise we will ignore it.

To see the solutions please see the Results collection exercise solutions notebook

1. Imports#

import simpy
import numpy as np
import itertools

2. Model and Logic#

2.1 Notebook level variables for results collection.#

The list has notebook level scope. This means that any functions or class in the notebook can access and/or append to the list.

results = {}
results['waiting_times'] = []

# ##############################################################################
# MODIFICATION: added in 2nd results variable
# your code here ...
################################################################################

2.2 A helper function#

We will create a helper function called trace that wraps print. We can set a variable called TRACE that switches printing patient level results on and off.

def trace(msg):
    '''
    Turing printing of events on and off.
    
    Params:
    -------
    msg: str
        string to print to screen.
    '''
    if TRACE:
        print(msg)

2.3 Service and arrival functions#

You need to modify the service function. Add a line of code to update total_call_duration following a completed call.

def service(identifier, operators, env):
    '''
    simulates the service process for a call operator

    1. request and wait for a call operator
    2. phone triage (triangular)
    3. exit system
    
    Params:
    ------
    
    identifier: int 
        A unique identifer for this caller
        
    operators: simpy.Resource
        The pool of call operators that answer calls
        These are shared across resources.
        
    env: simpy.Environment
        The current environent the simulation is running in
        We use this to pause and restart the process after a delay.
    
    '''
    # record the time that call entered the queue
    start_wait = env.now

    # request an operator
    with operators.request() as req:
        yield req

        # record the waiting time for call to be answered
        waiting_time = env.now - start_wait
        
        # store the waiting time.
        results['waiting_times'].append(waiting_time)

        trace(f'operator answered call {identifier} at ' \
              + f'{env.now:.3f}')

        # sample call duration.
        call_duration = np.random.triangular(left=5.0, mode=7.0,
                                             right=10.0)
        
        # schedule process to begin again after call_duration
        yield env.timeout(call_duration)
        
        # ######################################################################
        # MODIFICATION: update total call duration in results
        # your code here ...
        ########################################################################

        # print out information for patient.
        trace(f'call {identifier} ended {env.now:.3f}; ' \
              + f'waiting time was {waiting_time:.3f}')
def arrivals_generator(env, operators):
    '''
    IAT is exponentially distributed

    Parameters:
    ------
    env: simpy.Environment
        The simpy environment for the simulation

    operators: simpy.Resource
        the pool of call operators.
    '''

    # use itertools as it provides an infinite loop 
    # with a counter variable that we can use for unique Ids
    for caller_count in itertools.count(start=1):

        # 100 calls per hour (units = hours). 
        # Time between calls is 1/100
        inter_arrival_time = np.random.exponential(60/100)
        yield env.timeout(inter_arrival_time)

        trace(f'call arrives at: {env.now:.3f}')

        # create a new simpy process for this caller.
        # we pass in the caller id, the operator resources, and env.
        env.process(service(caller_count, operators, env))
# model parameters
RUN_LENGTH = 1000
N_OPERATORS = 13

# MODIFICATION - turn off caller level results.
TRACE = False

# create simpy environment and operator resources
env = simpy.Environment()
operators = simpy.Resource(env, capacity=N_OPERATORS)

env.process(arrivals_generator(env, operators))
env.run(until=RUN_LENGTH)
print(f'end of run. simulation clock time = {env.now}')

# calculate results on notebook level variables.
mean_wt = np.mean(results['waiting_times'])

# ##############################################################################
# MODIFICATION:  calculate mean operator utilisation
# your code here ...
# ##############################################################################

print(f'Mean waiting time was {mean_wt:.2f}')

# ##############################################################################
# MODIFICATION: print out results
# your code here ...
# ##############################################################################
end of run. simulation clock time = 1000
Mean waiting time was 1.59