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:
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.
Modify the
service
function to updatetotal_call_duration
after each call completes.After the run has completed calculate the average operator utilisation.
Print the results to the screen.
Hints:
The
service
function could update the total_call_duration as follows (wherecall_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.
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
results['total_call_duration'] = 0.0
################################################################################
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
results['total_call_duration'] += call_duration
########################################################################
# 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
mean_util = results['total_call_duration'] / (RUN_LENGTH * N_OPERATORS) * 100.0
# ##############################################################################
print(f'Mean waiting time was {mean_wt:.2f}')
# ##############################################################################
# MODIFICATION: print out results
print(f'Mean operator utilisation {mean_util:.2f}%')
# ##############################################################################
end of run. simulation clock time = 1000
Mean waiting time was 1.88
Mean operator utilisation 92.71%