simpy treatment centre model#

In this exercise you will learn how to set up and use an existing simulation model. The modelling framework has been setup with a Scenario class for parameterisation and a multiple_replications() wrapper function for running the model and returning results.

1. Problem description#

FirstTreatment: A health clinic based in the US. This example is based on exercise 13 from Nelson (2013) page 170.

Nelson. B.L. (2013). Foundations and methods of stochastic simulation. Springer.

Patients arrive to the health clinic between 6am and 12am following a non-stationary poisson process. After 12am arriving patients are diverted elsewhere and remaining WIP is completed. On arrival, all patients quickly sign-in and are triaged.

The health clinic expects two types of patient arrivals:

Trauma arrivals:

  • patients with severe illness and trauma that must first be stabilised in a trauma room.

  • these patients then undergo treatment in a cubicle before being discharged.

Non-trauma arrivals

  • patients with minor illness and no trauma go through registration and examination activities

  • a proportion of non-trauma patients require treatment in a cubicle before being discharged.

In this model treatment of trauma and non-trauma patients is modelled separately.

image

2. Imports#

The model has been setup as a python package and deployed to pypi: https://pypi.org/project/treat-sim/. It has been pip installed as part of the provided sim conda virtual environment.

The Scenario class and multiple_replications functions are contained in the treat_sim.model module.

An description of all model code is provided in an online Jupyter book: https://bit.ly/treat_sim

Let’s check the version of treat_sim we have installed

import treat_sim
treat_sim.__version__
'1.0.0'
from treat_sim.model import Scenario, multiple_replications

# we will also import pandas
import pandas as pd

3. Example: creating a default scenario#

default_scenario = Scenario()

# let's have a look at some of the default parameters
default_scenario.n_triage
default_scenario.n_exam
3

4. Example creating an alternative scenario#

There are many variables available to update in this model. Some examples are:

  • n_triage = number of triage bays

  • n_exam = number of examination rooms

  • n_cubicles_1 = number of non trauma treatment rooms

  • exam_mean = mean duration of an examination

From version 1.0.0 of treat_sim these are updated via the constructor as follows:

# by default there is 1 triage room
# set parameters through the consutuctor methods as follows
extra_triage = Scenario(n_triage=2)
extra_triage.n_triage
2

4. Running a scenario using the wrapper method#

by default the replications function runs the model 5 times. You can alter this using the n_reps parameter

results = multiple_replications(default_scenario, n_reps=10)

The variable results is a pandas.DataFrame containing key performance indicators (columns) and replications (rows)

results
00_arrivals 01a_triage_wait 01b_triage_util 02a_registration_wait 02b_registration_util 03a_examination_wait 03b_examination_util 04a_treatment_wait(non_trauma) 04b_treatment_util(non_trauma) 05_total_time(non-trauma) 06a_trauma_wait 06b_trauma_util 07a_treatment_wait(trauma) 07b_treatment_util(trauma) 08_total_time(trauma) 09_throughput
rep
1 218.0 28.009290 0.542454 128.490438 0.854615 28.478642 0.847430 145.414321 0.853189 247.872229 8.595360 0.368908 214.198331 0.976808 399.200311 154.0
2 188.0 3.240469 0.475390 63.307540 0.749027 16.328179 0.795921 134.202786 0.846795 178.799154 65.146932 0.838521 231.603567 0.778634 392.064964 139.0
3 233.0 13.040936 0.528681 149.636381 0.839994 18.400280 0.847310 169.503295 0.827088 260.804457 78.130137 0.816547 251.835736 1.188287 369.276920 157.0
4 236.0 29.246780 0.623225 91.137899 0.876762 35.424440 0.881140 125.016255 0.858299 206.945928 225.471749 0.876984 95.601572 1.193115 385.752794 159.0
5 246.0 65.407658 0.647235 130.091608 0.827194 36.254401 0.824874 150.890149 0.840913 288.796547 168.014088 0.979189 13.244608 0.650671 339.817753 148.0
6 215.0 67.536960 0.617639 87.101852 0.807856 24.149025 0.810646 95.628440 0.841574 226.294148 82.263601 0.842665 207.090105 0.771464 418.710896 151.0
7 199.0 16.388866 0.479730 90.818931 0.764699 30.028974 0.812242 119.295530 0.785988 211.711285 70.318603 0.810452 295.851506 0.880920 462.818608 149.0
8 240.0 36.702969 0.676470 126.444397 0.860978 31.189935 0.866354 169.368888 0.858340 268.655456 152.750616 0.801632 171.917036 0.602400 433.502223 154.0
9 238.0 38.201228 0.595762 96.503846 0.878944 29.642379 0.887559 169.737929 0.898665 247.619831 68.075461 0.698513 307.067085 0.952312 477.145587 145.0
10 226.0 54.951247 0.652863 106.388692 0.845639 25.374935 0.846628 136.911866 0.900255 264.451897 85.030588 0.834798 210.969768 0.797586 503.115035 159.0

we can summarise results using the dataframe’s describe() method. The results are transposed to improve readability.

results.describe().T
count mean std min 25% 50% 75% max
00_arrivals 10.0 223.900000 18.864723 188.000000 215.750000 229.500000 237.500000 246.000000
01a_triage_wait 10.0 35.272640 21.891014 3.240469 19.293972 32.974874 50.763742 67.536960
01b_triage_util 10.0 0.583945 0.072690 0.475390 0.532124 0.606701 0.641233 0.676470
02a_registration_wait 10.0 106.992158 26.076703 63.307540 90.898673 101.446269 127.978928 149.636381
02b_registration_util 10.0 0.830571 0.044482 0.749027 0.812690 0.842816 0.859387 0.878944
03a_examination_wait 10.0 27.527119 6.568842 16.328179 24.455503 29.060511 30.899695 36.254401
03b_examination_util 10.0 0.842010 0.030849 0.795921 0.815400 0.846969 0.861623 0.887559
04a_treatment_wait(non_trauma) 10.0 141.596946 24.505998 95.628440 127.312888 141.163093 164.749203 169.737929
04b_treatment_util(non_trauma) 10.0 0.851111 0.033026 0.785988 0.841078 0.849992 0.858330 0.900255
05_total_time(non-trauma) 10.0 240.195093 33.630061 178.799154 215.357001 247.746030 263.540037 288.796547
06a_trauma_wait 10.0 100.379713 62.925813 8.595360 68.636246 80.196869 135.820609 225.471749
06b_trauma_util 10.0 0.786821 0.162391 0.368908 0.803837 0.825673 0.841629 0.979189
07a_treatment_wait(trauma) 10.0 199.937931 88.990650 13.244608 180.710303 212.584049 246.777694 307.067085
07b_treatment_util(trauma) 10.0 0.879220 0.201709 0.602400 0.773256 0.839253 0.970684 1.193115
08_total_time(trauma) 10.0 418.140509 51.147329 339.817753 387.330837 408.955603 455.489512 503.115035
09_throughput 10.0 151.500000 6.433420 139.000000 148.250000 152.500000 156.250000 159.000000

5. Exercise#

Try running the following scenarios

  • extra triage capacity - increase triage bays to 2

  • extra examination capacity = increase examination rooms to 2

  • swap over 1 exam room for extra triage bay

Extra challenge:

  • Create a function called get_scenarios() that creates all of the function in one go and returns them to the calling code. Can you then loop through the scenarios and execute them in one after another?

  • One option is to setup the function so that it creates a python dictionary that has the name of the scenario as a key and the Scenario object as a value.

  • Can you combine the results into a single table?

# example answer that includes extra challenges.

def get_scenarios():
    '''
    Creates a dictionary object containing
    objects of type `Scenario` to run.
    
    Returns:
    --------
    dict
        Contains the scenarios for the model
    '''
    scenarios = {}
    scenarios['base'] = Scenario()
    
    # extra triage capacity
    scenarios['triage+1'] = Scenario(scenarios['base'].n_triage+1)
        
    # extra examination capacity
    scenarios['exam+1'] = Scenario(scenarios['base'].n_exam+1)
        
    # swap over 1 exam room for extra triage cubicle
    scenarios['swap_exam_triage'] = Scenario(scenarios['base'].n_triage+1,
                                             scenarios['base'].n_exam-1)
    return scenarios
def run_scenario_analysis(scenarios, n_reps):
    '''
    Run each of the scenarios for a specified results
    collection period and replications.
    
    Params:
    ------
    scenarios: dict
        dictionary of Scenario objects
        
    n_rep: int
        Number of replications
    
    '''
    print('Scenario Analysis')
    print(f'No. Scenario: {len(scenarios)}')
    print(f'Replications: {n_reps}')

    scenario_results = {}
    for sc_name, scenario in scenarios.items():
        
        print(f'Running {sc_name}', end=' => ')
        replications = multiple_replications(scenario, n_reps=n_reps)
        print('done.\n')
        
        #save the results
        scenario_results[sc_name] = replications
    
    print('Scenario analysis complete.')
    return scenario_results
def scenario_summary_frame(scenario_results):
    '''
    Mean results for each performance measure by scenario
    
    Parameters:
    ----------
    scenario_results: dict
        dictionary of replications.  
        Key identifies the performance measure
        
    Returns:
    -------
    pd.DataFrame
    '''
    columns = []
    summary = pd.DataFrame()
    for sc_name, replications in scenario_results.items():
        summary = pd.concat([summary, replications.mean()], axis=1)
        columns.append(sc_name)

    summary.columns = columns
    return summary
# run the analysis!

scenario_results = run_scenario_analysis(get_scenarios(), 10)
scenario_summary_frame(scenario_results)
Scenario Analysis
No. Scenario: 4
Replications: 10
Running base => 
done.

Running triage+1 => 
done.

Running exam+1 => 
done.

Running swap_exam_triage => 
done.

Scenario analysis complete.
base triage+1 exam+1 swap_exam_triage
00_arrivals 223.900000 223.900000 223.900000 223.900000
01a_triage_wait 35.272640 35.272640 35.272640 1.591082
01b_triage_util 0.583945 0.583945 0.583945 0.291972
02a_registration_wait 106.992158 106.992158 106.992158 136.547677
02b_registration_util 0.830571 0.830571 0.830571 0.837294
03a_examination_wait 27.527119 27.527119 27.527119 28.238646
03b_examination_util 0.842010 0.842010 0.842010 0.844921
04a_treatment_wait(non_trauma) 141.596946 141.596946 141.596946 145.386882
04b_treatment_util(non_trauma) 0.851111 0.851111 0.851111 0.852151
05_total_time(non-trauma) 240.195093 240.195093 240.195093 237.010351
06a_trauma_wait 100.379713 100.379713 100.379713 123.372108
06b_trauma_util 0.786821 0.786821 0.786821 0.786666
07a_treatment_wait(trauma) 199.937931 199.937931 199.937931 233.482451
07b_treatment_util(trauma) 0.879220 0.879220 0.879220 0.899172
08_total_time(trauma) 418.140509 418.140509 418.140509 430.158680
09_throughput 151.500000 151.500000 151.500000 151.400000