import pandas as pd
import numpy as np
import ciw
Code walkthrough
The model is implemented in ciw
. The ciw_model.py
module contains several other functions and class definitions that have been coded to improve the usability of the urgent care call centre. Here we list the all of the code in the module and explain its usage.
Imports
The simulation model is simple and relies on three packages: ciw
for the simulation and numpy
and pandas
for simple post-processing of results.
Variables with module level scope
We define a small number of variables with module level scope, i.e. they can be used in any function or class in the module. We use these to define default values for an experiment. The variables are only used in a single place - to initialise an Experiment
. One of the variables RESULTS_COLLECTION_PERIOD
is used as a default value for functions that run the simulation model.
# default resources
= 13
N_OPERATORS
# number of nurses available
= 9
N_NURSES
# default lambda for arrival distribution
= 100.0 / 60.0
MEAN_IAT
## default service time parameters (triangular)
= 5.0
CALL_LOW = 7.0
CALL_MODE = 10.0
CALL_HIGH
# nurse distribution parameters
= 10.0
NURSE_CALL_LOW = 20.0
NURSE_CALL_HIGH
= 0.4
CHANCE_CALLBACK
# run variables
= 1000 RESULTS_COLLECTION_PERIOD
The Experiment
class
The Experiment
allows model configuration. In essense it is a model parameter class. The class is setup so that it uses the default variables we defined above i.e. as default the model reflects the as-is process. To run a new experiment we simply override the default values in the model constructor.
class Experiment:
'''
Parameter class for urgent care simulation model
'''
def __init__(self, n_operators=N_OPERATORS, n_nurses=N_NURSES,
=MEAN_IAT, call_low=CALL_LOW,
mean_iat=CALL_MODE, call_high=CALL_HIGH,
call_mode=CHANCE_CALLBACK,
chance_callback=NURSE_CALL_LOW,
nurse_call_low=NURSE_CALL_HIGH,
nurse_call_high=None):
random_seed'''
The init method sets up our defaults.
'''
self.n_operators = n_operators
# store the number of nurses in the experiment
self.n_nurses = n_nurses
# arrival distribution
self.arrival_dist = ciw.dists.Exponential(mean_iat)
# call duration
self.call_dist = ciw.dists.Triangular(call_low,
call_mode, call_high)
# duration of call with nurse
self.nurse_dist = ciw.dists.Uniform(nurse_call_low,
nurse_call_high)
# prob of call back
self.chance_callback = chance_callback
# initialise results to zero
self.init_results_variables()
def init_results_variables(self):
'''
Initialise all of the experiment variables used in results
collection. This method is called at the start of each run
of the model
'''
# variable used to store results of experiment
self.results = {}
self.results['waiting_times'] = []
# total operator usage time for utilisation calculation.
self.results['total_call_duration'] = 0.0
# nurse sub process results collection
self.results['nurse_waiting_times'] = []
self.results['total_nurse_call_duration'] = 0.0
ciw
model code
In ciw
we define a network model (arrival/service distributions, routing percentages, servers) using the ciw.create_network
function. In this module we have encapsulated all of the model building logic into get_model
def get_model(args):
'''
Build a CiW model using the arguments provided.
Params:
-----
args: Experiment
container class for Experiment. Contains the model inputs/params
Returns:
--------
ciw.network.Network
'''
= ciw.create_network(arrival_distributions=[args.arrival_dist,
model
ciw.dists.NoArrivals()],=[args.call_dist,
service_distributions
args.nurse_dist],=[[0.0, args.chance_callback],
routing0.0, 0.0]],
[=[args.n_operators,
number_of_servers
args.n_nurses])return model
Wrapper code to run the model
There are two wrapper functions: single_run
and multiple_replications
. The first function performs a single run of the simulation model and the processes end of run results. The function is called by the multiple replications function. This latter function also combines all results into a single pandas.DataFrame
.
def single_run(experiment,
=RESULTS_COLLECTION_PERIOD,
rc_period=None):
random_seed'''
Conduct a single run of the simulation model.
Params:
------
experiment: Experiment
The experiment/paramaters to use with model
random_seed: int
Random seed to control simulation run.
'''
# results dictionary. Each KPI is a new entry.
= {}
run_results
# random seed
ciw.seed(random_seed)
# parameterise model
= get_model(experiment)
model
# simulation engine
= ciw.Simulation(model)
sim_engine
# run the model
sim_engine.simulate_until_max_time(rc_period)
# return processed results for run.
# get all results
= sim_engine.get_all_records()
recs
# operator service times
= [r.service_time for r in recs if r.node==1]
op_servicetimes # nurse service times
= [r.service_time for r in recs if r.node==2]
nurse_servicetimes
# operator and nurse waiting times
= [r.waiting_time for r in recs if r.node==1]
op_waits = [r.waiting_time for r in recs if r.node==2]
nurse_waits
# mean measures
'01_mean_waiting_time'] = np.mean(op_waits)
run_results[
# end of run results: calculate mean operator utilisation
'02_operator_util'] = \
run_results[sum(op_servicetimes) / (rc_period * experiment.n_operators)) * 100.0
(
# end of run results: nurse waiting time
'03_mean_nurse_waiting_time'] = np.mean(nurse_waits)
run_results[
# end of run results: calculate mean nurse utilisation
'04_nurse_util'] = \
run_results[sum(nurse_servicetimes) / (rc_period * experiment.n_nurses)) * 100.0
(
# return the results from the run of the model
return run_results
def multiple_replications(experiment,
=RESULTS_COLLECTION_PERIOD,
rc_period=5):
n_reps'''
Perform multiple replications of the model.
Params:
------
experiment: Experiment
The experiment/paramaters to use with model
rc_period: float, optional (default=DEFAULT_RESULTS_COLLECTION_PERIOD)
results collection period.
the number of minutes to run the model to collect results
n_reps: int, optional (default=5)
Number of independent replications to run.
Returns:
--------
pandas.DataFrame
'''
# loop over single run to generate results dicts in a python list.
= [single_run(experiment, rc_period) for rep in range(n_reps)]
results
# format and return results in a dataframe
= pd.DataFrame(results)
df_results = np.arange(1, len(df_results)+1)
df_results.index = 'rep'
df_results.index.name return df_results