import pandas as pd
import numpy as np
import ciwCode 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
N_OPERATORS = 13
# number of nurses available
N_NURSES = 9
# default lambda for arrival distribution
MEAN_IAT = 100.0 / 60.0
## default service time parameters (triangular)
CALL_LOW = 5.0
CALL_MODE = 7.0
CALL_HIGH = 10.0
# nurse distribution parameters
NURSE_CALL_LOW = 10.0
NURSE_CALL_HIGH = 20.0
CHANCE_CALLBACK = 0.4
# run variables
RESULTS_COLLECTION_PERIOD = 1000The 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=MEAN_IAT, call_low=CALL_LOW,
call_mode=CALL_MODE, call_high=CALL_HIGH,
chance_callback=CHANCE_CALLBACK,
nurse_call_low=NURSE_CALL_LOW,
nurse_call_high=NURSE_CALL_HIGH,
random_seed=None):
'''
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.0ciw 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
'''
model = ciw.create_network(arrival_distributions=[args.arrival_dist,
ciw.dists.NoArrivals()],
service_distributions=[args.call_dist,
args.nurse_dist],
routing=[[0.0, args.chance_callback],
[0.0, 0.0]],
number_of_servers=[args.n_operators,
args.n_nurses])
return modelWrapper 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,
rc_period=RESULTS_COLLECTION_PERIOD,
random_seed=None):
'''
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
model = get_model(experiment)
# simulation engine
sim_engine = ciw.Simulation(model)
# run the model
sim_engine.simulate_until_max_time(rc_period)
# return processed results for run.
# get all results
recs = sim_engine.get_all_records()
# operator service times
op_servicetimes = [r.service_time for r in recs if r.node==1]
# nurse service times
nurse_servicetimes = [r.service_time for r in recs if r.node==2]
# operator and nurse waiting times
op_waits = [r.waiting_time for r in recs if r.node==1]
nurse_waits = [r.waiting_time for r in recs if r.node==2]
# mean measures
run_results['01_mean_waiting_time'] = np.mean(op_waits)
# end of run results: calculate mean operator utilisation
run_results['02_operator_util'] = \
(sum(op_servicetimes) / (rc_period * experiment.n_operators)) * 100.0
# end of run results: nurse waiting time
run_results['03_mean_nurse_waiting_time'] = np.mean(nurse_waits)
# end of run results: calculate mean nurse utilisation
run_results['04_nurse_util'] = \
(sum(nurse_servicetimes) / (rc_period * experiment.n_nurses)) * 100.0
# return the results from the run of the model
return run_resultsdef multiple_replications(experiment,
rc_period=RESULTS_COLLECTION_PERIOD,
n_reps=5):
'''
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.
results = [single_run(experiment, rc_period) for rep in range(n_reps)]
# format and return results in a dataframe
df_results = pd.DataFrame(results)
df_results.index = np.arange(1, len(df_results)+1)
df_results.index.name = 'rep'
return df_results