from examples.example_2_branching_multistep.ex_2_model_classes import Trial, g
from vidigi.animation import animate_activity_log
from vidigi.utils import create_event_position_df, EventPosition
import pandas as pd
import plotly.io as pio
pio.renderers.default = "notebook"
import osAnimation of a SimPy Model with Branching and Multiple Steps
View Imported Code, which has had logging steps added at the appropriate points in the ‘model’ class
import random
import numpy as np
import pandas as pd
import simpy
from sim_tools.distributions import Exponential, Lognormal, Uniform, Normal, Bernoulli
from vidigi.resources import populate_store
from examples.simulation_utility_functions import trace
# Class to store global parameter values. We don't create an instance of this
# class - we just refer to the class blueprint itself to access the numbers
# inside.
class g:
'''
Create a scenario to parameterise the simulation model
Parameters:
-----------
random_number_set: int, optional (default=DEFAULT_RNG_SET)
Set to control the initial seeds of each stream of pseudo
random numbers used in the model.
n_triage: int
The number of triage cubicles
n_reg: int
The number of registration clerks
n_exam: int
The number of examination rooms
n_trauma: int
The number of trauma bays for stablisation
n_cubicles_non_trauma_treat: int
The number of non-trauma treatment cubicles
n_cubicles_trauma_treat: int
The number of trauma treatment cubicles
triage_mean: float
Mean duration of the triage distribution (Exponential)
reg_mean: float
Mean duration of the registration distribution (Lognormal)
reg_var: float
Variance of the registration distribution (Lognormal)
exam_mean: float
Mean of the examination distribution (Normal)
exam_var: float
Variance of the examination distribution (Normal)
trauma_mean: float
Mean of the trauma stabilisation distribution (Exponential)
trauma_treat_mean: float
Mean of the trauma cubicle treatment distribution (Lognormal)
trauma_treat_var: float
Variance of the trauma cubicle treatment distribution (Lognormal)
non_trauma_treat_mean: float
Mean of the non trauma treatment distribution
non_trauma_treat_var: float
Variance of the non trauma treatment distribution
non_trauma_treat_p: float
Probability non trauma patient requires treatment
prob_trauma: float
probability that a new arrival is a trauma patient.
'''
random_number_set = 42
n_triage=2
n_reg=2
n_exam=3
n_trauma=4
n_cubicles_non_trauma_treat=4
n_cubicles_trauma_treat=5
triage_mean=6
reg_mean=8
reg_var=2
exam_mean=16
exam_var=3
trauma_mean=90
trauma_treat_mean=30
trauma_treat_var=4
non_trauma_treat_mean=13.3
non_trauma_treat_var=2
non_trauma_treat_p=0.6
prob_trauma=0.12
arrival_df="ed_arrivals.csv"
sim_duration = 600
number_of_runs = 100
# Class representing patients coming in to the clinic.
class Patient:
'''
Class defining details for a patient entity
'''
def __init__(self, p_id):
'''
Constructor method
Params:
-----
identifier: int
a numeric identifier for the patient.
'''
self.identifier = p_id
# Time of arrival in model/at centre
self.arrival = -np.inf
# Total time in pathway
self.total_time = -np.inf
# Shared waits
self.wait_triage = -np.inf
self.wait_reg = -np.inf
self.wait_treat = -np.inf
# Non-trauma pathway - examination wait
self.wait_exam = -np.inf
# Trauma pathway - stabilisation wait
self.wait_trauma = -np.inf
# Shared durations
self.triage_duration = -np.inf
self.reg_duration = -np.inf
self.treat_duration = -np.inf
# Non-trauma pathway - examination duration
self.exam_duration = -np.inf
# Trauma pathway - stabilisation duration
self.trauma_duration = -np.inf
# Class representing our model of the clinic.
class Model:
'''
Simulates the simplest minor treatment process for a patient
1. Arrive
2. Examined/treated by nurse when one available
3. Discharged
'''
# Constructor to set up the model for a run. We pass in a run number when
# we create a new model.
def __init__(self, run_number):
# Create a SimPy environment in which everything will live
self.env = simpy.Environment()
self.event_log = []
# Create a patient counter (which we'll use as a patient ID)
self.patient_counter = 0
self.trauma_patients = []
self.non_trauma_patients = []
# Create our resources
self.init_resources()
# Store the passed in run number
self.run_number = run_number
# Create a new Pandas DataFrame that will store some results against
# the patient ID (which we'll use as the index).
self.results_df = pd.DataFrame()
self.results_df["Patient ID"] = [1]
self.results_df["Queue Time Cubicle"] = [0.0]
self.results_df["Time with Nurse"] = [0.0]
self.results_df.set_index("Patient ID", inplace=True)
# Create an attribute to store the mean queuing times across this run of
# the model
self.mean_q_time_cubicle = 0
# create distributions
# Triage duration
self.triage_dist = Exponential(g.triage_mean,
random_seed=self.run_number*g.random_number_set)
# Registration duration (non-trauma only)
self.reg_dist = Lognormal(g.reg_mean,
np.sqrt(g.reg_var),
random_seed=self.run_number*g.random_number_set)
# Evaluation (non-trauma only)
self.exam_dist = Normal(g.exam_mean,
np.sqrt(g.exam_var),
random_seed=self.run_number*g.random_number_set)
# Trauma/stablisation duration (trauma only)
self.trauma_dist = Exponential(g.trauma_mean,
random_seed=self.run_number*g.random_number_set)
# Non-trauma treatment
self.nt_treat_dist = Lognormal(g.non_trauma_treat_mean,
np.sqrt(g.non_trauma_treat_var),
random_seed=self.run_number*g.random_number_set)
# treatment of trauma patients
self.treat_dist = Lognormal(g.trauma_treat_mean,
np.sqrt(g.non_trauma_treat_var),
random_seed=self.run_number*g.random_number_set)
# probability of non-trauma patient requiring treatment
self.nt_p_treat_dist = Bernoulli(g.non_trauma_treat_p,
random_seed=self.run_number*g.random_number_set)
# probability of non-trauma versus trauma patient
self.p_trauma_dist = Bernoulli(g.prob_trauma,
random_seed=self.run_number*g.random_number_set)
# init sampling for non-stationary poisson process
self.init_nspp()
def init_nspp(self):
# read arrival profile
self.arrivals = pd.read_csv(g.arrival_df) # pylint: disable=attribute-defined-outside-init
self.arrivals['mean_iat'] = 60 / self.arrivals['arrival_rate']
# maximum arrival rate (smallest time between arrivals)
self.lambda_max = self.arrivals['arrival_rate'].max() # pylint: disable=attribute-defined-outside-init
# thinning exponential
self.arrival_dist = Exponential(60.0 / self.lambda_max, # pylint: disable=attribute-defined-outside-init
random_seed=self.run_number*g.random_number_set)
# thinning uniform rng
self.thinning_rng = Uniform(low=0.0, high=1.0, # pylint: disable=attribute-defined-outside-init
random_seed=self.run_number*g.random_number_set)
def init_resources(self):
'''
Init the number of resources
and store in the arguments container object
Resource list:
1. Nurses/treatment bays (same thing in this model)
'''
# Shared Resources
self.triage_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_triage,
simpy_store=self.triage_cubicles,
sim_env=self.env)
self.registration_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_reg,
simpy_store=self.registration_cubicles,
sim_env=self.env)
# Non-trauma
self.exam_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_exam,
simpy_store=self.exam_cubicles,
sim_env=self.env)
self.non_trauma_treatment_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_cubicles_non_trauma_treat,
simpy_store=self.non_trauma_treatment_cubicles,
sim_env=self.env)
# Trauma
self.trauma_stabilisation_bays = simpy.Store(self.env)
populate_store(num_resources=g.n_trauma,
simpy_store=self.trauma_stabilisation_bays,
sim_env=self.env)
self.trauma_treatment_cubicles = simpy.Store(self.env)
populate_store(num_resources=g.n_cubicles_trauma_treat,
simpy_store=self.trauma_treatment_cubicles,
sim_env=self.env)
# A generator function that represents the DES generator for patient
# arrivals
def generator_patient_arrivals(self):
# We use an infinite loop here to keep doing this indefinitely whilst
# the simulation runs
while True:
t = int(self.env.now // 60) % self.arrivals.shape[0]
lambda_t = self.arrivals['arrival_rate'].iloc[t]
# set to a large number so that at least 1 sample taken!
u = np.Inf
interarrival_time = 0.0
# reject samples if u >= lambda_t / lambda_max
while u >= (lambda_t / self.lambda_max):
interarrival_time += self.arrival_dist.sample()
u = self.thinning_rng.sample()
# Freeze this instance of this function in place until the
# inter-arrival time we sampled above has elapsed. Note - time in
# SimPy progresses in "Time Units", which can represent anything
# you like (just make sure you're consistent within the model)
yield self.env.timeout(interarrival_time)
# Increment the patient counter by 1 (this means our first patient
# will have an ID of 1)
self.patient_counter += 1
# Create a new patient - an instance of the Patient Class we
# defined above. Remember, we pass in the ID when creating a
# patient - so here we pass the patient counter to use as the ID.
p = Patient(self.patient_counter)
trace(f'patient {self.patient_counter} arrives at: {self.env.now:.3f}')
self.event_log.append(
{'patient': self.patient_counter,
'pathway': 'Shared',
'event': 'arrival',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# sample if the patient is trauma or non-trauma
trauma = self.p_trauma_dist.sample()
# Tell SimPy to start up the attend_clinic generator function with
# this patient (the generator function that will model the
# patient's journey through the system)
# and store patient in list for later easy access
if trauma:
# create and store a trauma patient to update KPIs.
self.trauma_patients.append(p)
self.env.process(self.attend_trauma_pathway(p))
else:
# create and store a non-trauma patient to update KPIs.
self.non_trauma_patients.append(p)
self.env.process(self.attend_non_trauma_pathway(p))
# A generator function that represents the pathway for a patient going
# through the clinic.
# The patient object is passed in to the generator function so we can
# extract information from / record information to it
def attend_non_trauma_pathway(self, patient):
'''
simulates the non-trauma/minor treatment process for a patient
1. request and wait for sign-in/triage
2. patient registration
3. examination
4a. percentage discharged
4b. remaining percentage treatment then discharge
'''
# record the time of arrival and entered the triage queue
patient.arrival = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'queue',
'event': 'triage_wait_begins',
'time': self.env.now}
)
###################################################
# request sign-in/triage
triage_resource = yield self.triage_cubicles.get()
# record the waiting time for triage
patient.wait_triage = self.env.now - patient.arrival
trace(f'patient {patient.identifier} triaged to minors '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use',
'event': 'triage_begins',
'time': self.env.now,
'resource_id': triage_resource.id_attribute
}
)
# sample triage duration.
patient.triage_duration = self.triage_dist.sample()
yield self.env.timeout(patient.triage_duration)
trace(f'triage {patient.identifier} complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_triage:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use_end',
'event': 'triage_complete',
'time': self.env.now,
'resource_id': triage_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.triage_cubicles.put(triage_resource)
#########################################################
# record the time that entered the registration queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'queue',
'event': 'MINORS_registration_wait_begins',
'time': self.env.now}
)
#########################################################
# request registration clerk
registration_resource = yield self.registration_cubicles.get()
# record the waiting time for registration
patient.wait_reg = self.env.now - start_wait
trace(f'registration of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event_type': 'resource_use',
'event': 'MINORS_registration_begins',
'time': self.env.now,
'resource_id': registration_resource.id_attribute
}
)
# sample registration duration.
patient.reg_duration = self.reg_dist.sample()
yield self.env.timeout(patient.reg_duration)
trace(f'patient {patient.identifier} registered at'
f'{self.env.now:.3f}; '
f'waiting time was {patient.wait_reg:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_registration_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': registration_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.registration_cubicles.put(registration_resource)
########################################################
# record the time that entered the evaluation queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_wait_begins',
'event_type': 'queue',
'time': self.env.now}
)
#########################################################
# request examination resource
examination_resource = yield self.exam_cubicles.get()
# record the waiting time for examination to begin
patient.wait_exam = self.env.now - start_wait
trace(f'examination of patient {patient.identifier} begins '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_begins',
'event_type': 'resource_use',
'time': self.env.now,
'resource_id': examination_resource.id_attribute
}
)
# sample examination duration.
patient.exam_duration = self.exam_dist.sample()
yield self.env.timeout(patient.exam_duration)
trace(f'patient {patient.identifier} examination complete '
f'at {self.env.now:.3f};'
f'waiting time was {patient.wait_exam:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_examination_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': examination_resource.id_attribute}
)
# Resource is no longer in use, so put it back in
self.exam_cubicles.put(examination_resource)
############################################################################
# sample if patient requires treatment?
patient.require_treat = self.nt_p_treat_dist.sample() #pylint: disable=attribute-defined-outside-init
if patient.require_treat:
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'requires_treatment',
'event_type': 'attribute_assigned',
'time': self.env.now}
)
# record the time that entered the treatment queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_wait_begins',
'event_type': 'queue',
'time': self.env.now}
)
###################################################
# request treatment cubicle
non_trauma_treatment_resource = yield self.non_trauma_treatment_cubicles.get()
# record the waiting time for treatment
patient.wait_treat = self.env.now - start_wait
trace(f'treatment of patient {patient.identifier} begins '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_begins',
'event_type': 'resource_use',
'time': self.env.now,
'resource_id': non_trauma_treatment_resource.id_attribute
}
)
# sample treatment duration.
patient.treat_duration = self.nt_treat_dist.sample()
yield self.env.timeout(patient.treat_duration)
trace(f'patient {patient.identifier} treatment complete '
f'at {self.env.now:.3f};'
f'waiting time was {patient.wait_treat:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Non-Trauma',
'event': 'MINORS_treatment_complete',
'event_type': 'resource_use_end',
'time': self.env.now,
'resource_id': non_trauma_treatment_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.non_trauma_treatment_cubicles.put(non_trauma_treatment_resource)
##########################################################################
# Return to what happens to all patients, regardless of whether they were sampled as needing treatment
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Shared',
'event': 'depart',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# total time in system
patient.total_time = self.env.now - patient.arrival
def attend_trauma_pathway(self, patient):
'''
simulates the major treatment process for a patient
1. request and wait for sign-in/triage
2. trauma
3. treatment
'''
# record the time of arrival and entered the triage queue
patient.arrival = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'triage_wait_begins',
'time': self.env.now}
)
###################################################
# request sign-in/triage
triage_resource = yield self.triage_cubicles.get()
# record the waiting time for triage
patient.wait_triage = self.env.now - patient.arrival
trace(f'patient {patient.identifier} triaged to trauma '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'triage_begins',
'time': self.env.now,
'resource_id': triage_resource.id_attribute
}
)
# sample triage duration.
patient.triage_duration = self.triage_dist.sample()
yield self.env.timeout(patient.triage_duration)
trace(f'triage {patient.identifier} complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_triage:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'triage_complete',
'time': self.env.now,
'resource_id': triage_resource.id_attribute}
)
# Resource is no longer in use, so put it back in the store
self.triage_cubicles.put(triage_resource)
###################################################
# record the time that entered the trauma queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'TRAUMA_stabilisation_wait_begins',
'time': self.env.now}
)
###################################################
# request trauma room
trauma_resource = yield self.trauma_stabilisation_bays.get()
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'TRAUMA_stabilisation_begins',
'time': self.env.now,
'resource_id': trauma_resource.id_attribute
}
)
# record the waiting time for trauma
patient.wait_trauma = self.env.now - start_wait
# sample stablisation duration.
patient.trauma_duration = self.trauma_dist.sample()
yield self.env.timeout(patient.trauma_duration)
trace(f'stabilisation of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'TRAUMA_stabilisation_complete',
'time': self.env.now,
'resource_id': trauma_resource.id_attribute
}
)
# Resource is no longer in use, so put it back in the store
self.trauma_stabilisation_bays.put(trauma_resource)
#######################################################
# record the time that patient entered the treatment queue
start_wait = self.env.now
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'queue',
'event': 'TRAUMA_treatment_wait_begins',
'time': self.env.now}
)
########################################################
# request treatment cubicle
trauma_treatment_resource = yield self.trauma_treatment_cubicles.get()
# record the waiting time for trauma
patient.wait_treat = self.env.now - start_wait
trace(f'treatment of patient {patient.identifier} at '
f'{self.env.now:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use',
'event': 'TRAUMA_treatment_begins',
'time': self.env.now,
'resource_id': trauma_treatment_resource.id_attribute
}
)
# sample treatment duration.
patient.treat_duration = self.trauma_dist.sample()
yield self.env.timeout(patient.treat_duration)
trace(f'patient {patient.identifier} treatment complete {self.env.now:.3f}; '
f'waiting time was {patient.wait_treat:.3f}')
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Trauma',
'event_type': 'resource_use_end',
'event': 'TRAUMA_treatment_complete',
'time': self.env.now,
'resource_id': trauma_treatment_resource.id_attribute}
)
self.event_log.append(
{'patient': patient.identifier,
'pathway': 'Shared',
'event': 'depart',
'event_type': 'arrival_departure',
'time': self.env.now}
)
# Resource is no longer in use, so put it back in the store
self.trauma_treatment_cubicles.put(trauma_treatment_resource)
#########################################################
# total time in system
patient.total_time = self.env.now - patient.arrival
# This method calculates results over a single run. Here we just calculate
# a mean, but in real world models you'd probably want to calculate more.
def calculate_run_results(self):
# Take the mean of the queuing times across patients in this run of the
# model.
self.mean_q_time_cubicle = self.results_df["Queue Time Cubicle"].mean()
# The run method starts up the DES entity generators, runs the simulation,
# and in turns calls anything we need to generate results for the run
def run(self):
# Start up our DES entity generators that create new patients. We've
# only got one in this model, but we'd need to do this for each one if
# we had multiple generators.
self.env.process(self.generator_patient_arrivals())
# Run the model for the duration specified in g class
self.env.run(until=g.sim_duration)
# Now the simulation run has finished, call the method that calculates
# run results
self.calculate_run_results()
self.event_log = pd.DataFrame(self.event_log)
self.event_log["run"] = self.run_number
return {'results': self.results_df, 'event_log': self.event_log}
# Class representing a Trial for our simulation - a batch of simulation runs.
class Trial:
# The constructor sets up a pandas dataframe that will store the key
# results from each run against run number, with run number as the index.
def __init__(self):
self.df_trial_results = pd.DataFrame()
self.df_trial_results["Run Number"] = [0]
self.df_trial_results["Arrivals"] = [0]
self.df_trial_results["Mean Queue Time Cubicle"] = [0.0]
self.df_trial_results.set_index("Run Number", inplace=True)
self.all_event_logs = []
# Method to run a trial
def run_trial(self):
# Run the simulation for the number of runs specified in g class.
# For each run, we create a new instance of the Model class and call its
# run method, which sets everything else in motion. Once the run has
# completed, we grab out the stored run results (just mean queuing time
# here) and store it against the run number in the trial results
# dataframe.
for run in range(g.number_of_runs):
random.seed(run)
my_model = Model(run)
model_outputs = my_model.run()
patient_level_results = model_outputs["results"]
event_log = model_outputs["event_log"]
self.df_trial_results.loc[run] = [
len(patient_level_results),
my_model.mean_q_time_cubicle,
]
# print(event_log)
self.all_event_logs.append(event_log)
self.all_event_logs = pd.concat(self.all_event_logs)my_trial = Trial()
my_trial.run_trial()my_trial.all_event_logs.head(50)| patient | pathway | event | event_type | time | resource_id | run | |
|---|---|---|---|---|---|---|---|
| 0 | 1 | Shared | arrival | arrival_departure | 3.285355 | NaN | 0 |
| 1 | 1 | Non-Trauma | triage_wait_begins | queue | 3.285355 | NaN | 0 |
| 2 | 1 | Non-Trauma | triage_begins | resource_use | 3.285355 | 1.0 | 0 |
| 3 | 2 | Shared | arrival | arrival_departure | 3.289691 | NaN | 0 |
| 4 | 2 | Non-Trauma | triage_wait_begins | queue | 3.289691 | NaN | 0 |
| 5 | 2 | Non-Trauma | triage_begins | resource_use | 3.289691 | 2.0 | 0 |
| 6 | 1 | Non-Trauma | triage_complete | resource_use_end | 7.364946 | 1.0 | 0 |
| 7 | 1 | Non-Trauma | MINORS_registration_wait_begins | queue | 7.364946 | NaN | 0 |
| 8 | 1 | Non-Trauma | MINORS_registration_begins | resource_use | 7.364946 | 1.0 | 0 |
| 9 | 2 | Non-Trauma | triage_complete | resource_use_end | 9.407274 | 2.0 | 0 |
| 10 | 2 | Non-Trauma | MINORS_registration_wait_begins | queue | 9.407274 | NaN | 0 |
| 11 | 2 | Non-Trauma | MINORS_registration_begins | resource_use | 9.407274 | 2.0 | 0 |
| 12 | 1 | Non-Trauma | MINORS_registration_complete | resource_use_end | 15.418481 | 1.0 | 0 |
| 13 | 1 | Non-Trauma | MINORS_examination_wait_begins | queue | 15.418481 | NaN | 0 |
| 14 | 1 | Non-Trauma | MINORS_examination_begins | resource_use | 15.418481 | 1.0 | 0 |
| 15 | 2 | Non-Trauma | MINORS_registration_complete | resource_use_end | 17.104670 | 2.0 | 0 |
| 16 | 2 | Non-Trauma | MINORS_examination_wait_begins | queue | 17.104670 | NaN | 0 |
| 17 | 2 | Non-Trauma | MINORS_examination_begins | resource_use | 17.104670 | 2.0 | 0 |
| 18 | 1 | Non-Trauma | MINORS_examination_complete | resource_use_end | 31.636252 | 1.0 | 0 |
| 19 | 1 | Shared | depart | arrival_departure | 31.636252 | NaN | 0 |
| 20 | 2 | Non-Trauma | MINORS_examination_complete | resource_use_end | 32.875857 | 2.0 | 0 |
| 21 | 2 | Non-Trauma | requires_treatment | attribute_assigned | 32.875857 | NaN | 0 |
| 22 | 2 | Non-Trauma | MINORS_treatment_wait_begins | queue | 32.875857 | NaN | 0 |
| 23 | 2 | Non-Trauma | MINORS_treatment_begins | resource_use | 32.875857 | 1.0 | 0 |
| 24 | 3 | Shared | arrival | arrival_departure | 33.426168 | NaN | 0 |
| 25 | 3 | Non-Trauma | triage_wait_begins | queue | 33.426168 | NaN | 0 |
| 26 | 3 | Non-Trauma | triage_begins | resource_use | 33.426168 | 1.0 | 0 |
| 27 | 3 | Non-Trauma | triage_complete | resource_use_end | 33.545008 | 1.0 | 0 |
| 28 | 3 | Non-Trauma | MINORS_registration_wait_begins | queue | 33.545008 | NaN | 0 |
| 29 | 3 | Non-Trauma | MINORS_registration_begins | resource_use | 33.545008 | 1.0 | 0 |
| 30 | 4 | Shared | arrival | arrival_departure | 37.900548 | NaN | 0 |
| 31 | 4 | Non-Trauma | triage_wait_begins | queue | 37.900548 | NaN | 0 |
| 32 | 4 | Non-Trauma | triage_begins | resource_use | 37.900548 | 2.0 | 0 |
| 33 | 4 | Non-Trauma | triage_complete | resource_use_end | 37.914164 | 2.0 | 0 |
| 34 | 4 | Non-Trauma | MINORS_registration_wait_begins | queue | 37.914164 | NaN | 0 |
| 35 | 4 | Non-Trauma | MINORS_registration_begins | resource_use | 37.914164 | 2.0 | 0 |
| 36 | 3 | Non-Trauma | MINORS_registration_complete | resource_use_end | 42.359504 | 1.0 | 0 |
| 37 | 3 | Non-Trauma | MINORS_examination_wait_begins | queue | 42.359504 | NaN | 0 |
| 38 | 3 | Non-Trauma | MINORS_examination_begins | resource_use | 42.359504 | 3.0 | 0 |
| 39 | 4 | Non-Trauma | MINORS_registration_complete | resource_use_end | 45.938325 | 2.0 | 0 |
| 40 | 4 | Non-Trauma | MINORS_examination_wait_begins | queue | 45.938325 | NaN | 0 |
| 41 | 4 | Non-Trauma | MINORS_examination_begins | resource_use | 45.938325 | 1.0 | 0 |
| 42 | 2 | Non-Trauma | MINORS_treatment_complete | resource_use_end | 46.278797 | 1.0 | 0 |
| 43 | 2 | Shared | depart | arrival_departure | 46.278797 | NaN | 0 |
| 44 | 5 | Shared | arrival | arrival_departure | 51.770459 | NaN | 0 |
| 45 | 5 | Non-Trauma | triage_wait_begins | queue | 51.770459 | NaN | 0 |
| 46 | 5 | Non-Trauma | triage_begins | resource_use | 51.770459 | 1.0 | 0 |
| 47 | 5 | Non-Trauma | triage_complete | resource_use_end | 55.072516 | 1.0 | 0 |
| 48 | 5 | Non-Trauma | MINORS_registration_wait_begins | queue | 55.072516 | NaN | 0 |
| 49 | 5 | Non-Trauma | MINORS_registration_begins | resource_use | 55.072516 | 1.0 | 0 |
event_position_df = create_event_position_df([
EventPosition(event='arrival', x=10, y=250, label="Arrival"),
# Triage - minor and trauma
EventPosition(event='triage_wait_begins', x=160, y=375, label="Waiting for<br>Triage"),
EventPosition(event='triage_begins', x=160, y=315, resource='n_triage', label="Being Triaged"),
# Minors (non-trauma) pathway
EventPosition(event='MINORS_registration_wait_begins', x=300, y=145, label="Waiting for<br>Registration"),
EventPosition(event='MINORS_registration_begins', x=300, y=85, resource='n_reg', label='Being<br>Registered'),
EventPosition(event='MINORS_examination_wait_begins', x=465, y=145, label="Waiting for<br>Examination"),
EventPosition(event='MINORS_examination_begins', x=465, y=85, resource='n_exam', label="Being<br>Examined"),
EventPosition(event='MINORS_treatment_wait_begins', x=630, y=145, label="Waiting for<br>Treatment"),
EventPosition(event='MINORS_treatment_begins', x=630, y=85, resource='n_cubicles_non_trauma_treat', label="Being<br>Treated"),
# Trauma pathway
EventPosition(event='TRAUMA_stabilisation_wait_begins', x=300, y=560, label="Waiting for<br>Stabilisation"),
EventPosition(event='TRAUMA_stabilisation_begins', x=300, y=490, resource='n_trauma', label="Being<br>Stabilised"),
EventPosition(event='TRAUMA_treatment_wait_begins', x=630, y=560, label="Waiting for<br>Treatment"),
EventPosition(event='TRAUMA_treatment_begins', x=630, y=490, resource='n_cubicles_trauma_treat', label="Being<br>Treated"),
EventPosition(event='depart', x=670, y=330, label="Exit")
])setup_mode = True
setup_mode allows us to see how the coordinates of our plot relate to the positioning of our background image, allowing us to more accurately place our entities.
animate_activity_log(
event_log=my_trial.all_event_logs[my_trial.all_event_logs['run']==1],
event_position_df= event_position_df,
scenario=g(),
entity_col_name="patient",
debug_mode=True,
setup_mode=True,
every_x_time_units=5,
include_play_button=True,
gap_between_entities=11,
gap_between_resources=15,
gap_between_resource_rows=30,
gap_between_queue_rows=30,
plotly_height=600,
plotly_width=1000,
override_x_max=700,
override_y_max=675,
entity_icon_size=10,
resource_icon_size=13,
text_size=15,
wrap_queues_at=10,
step_snapshot_max=20,
limit_duration=g.sim_duration,
time_display_units="dhm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_2_branching_multistep/Full%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
)Animation function called at 12:53:51
Iteration through time-unit-by-time-unit logs complete 12:53:52
Snapshot df concatenation complete at 12:53:52
Reshaped animation dataframe finished construction at 12:53:52
Placement dataframe finished construction at 12:53:52
Output animation generation complete at 12:53:53
Total Time Elapsed: 1.73 seconds
setup_mode = False
We can then rerun our plot with setup_mode=False to remove the grid lines and axis tick marks.
animate_activity_log(
event_log=my_trial.all_event_logs[my_trial.all_event_logs['run']==1],
event_position_df= event_position_df,
scenario=g(),
entity_col_name="patient",
debug_mode=True,
setup_mode=False,
every_x_time_units=5,
include_play_button=True,
gap_between_entities=11,
gap_between_resources=15,
gap_between_resource_rows=30,
gap_between_queue_rows=30,
plotly_height=600,
plotly_width=1000,
override_x_max=700,
override_y_max=675,
entity_icon_size=10,
resource_icon_size=13,
text_size=15,
wrap_queues_at=10,
step_snapshot_max=20,
limit_duration=g.sim_duration,
time_display_units="dhm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_2_branching_multistep/Full%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
)Animation function called at 12:53:53
Iteration through time-unit-by-time-unit logs complete 12:53:54
Snapshot df concatenation complete at 12:53:54
Reshaped animation dataframe finished construction at 12:53:54
Placement dataframe finished construction at 12:53:54
Output animation generation complete at 12:53:54
Total Time Elapsed: 1.33 seconds
Visualising long queues
Finally, let’s rerun this with some build-up of queues by forcing faster arrivals and reducing the number of resources.
g.arrival_df = "ed_arrivals_more_frequent.csv"
g.n_cubicles_trauma_treat = 3
g.n_cubicles_non_trauma_treat = 2
g.n_exam = 2
my_trial = Trial()
my_trial.run_trial()animate_activity_log(
event_log=my_trial.all_event_logs[my_trial.all_event_logs['run']==1],
event_position_df= event_position_df,
scenario=g(),
entity_col_name="patient",
debug_mode=True,
setup_mode=False,
every_x_time_units=5,
include_play_button=True,
gap_between_entities=11,
gap_between_resources=15,
gap_between_resource_rows=30,
gap_between_queue_rows=30,
plotly_height=600,
plotly_width=1000,
override_x_max=700,
override_y_max=675,
entity_icon_size=10,
resource_icon_size=13,
text_size=15,
wrap_queues_at=10,
step_snapshot_max=20,
limit_duration=g.sim_duration,
time_display_units="dhm",
display_stage_labels=False,
add_background_image="https://raw.githubusercontent.com/Bergam0t/vidigi/refs/heads/main/examples/example_2_branching_multistep/Full%20Model%20Background%20Image%20-%20Horizontal%20Layout.drawio.png",
step_snapshot_limit_gauges=True
)Animation function called at 12:53:56
Iteration through time-unit-by-time-unit logs complete 12:53:57
Snapshot df concatenation complete at 12:53:57
Reshaped animation dataframe finished construction at 12:53:57
Placement dataframe finished construction at 12:53:57
Output animation generation complete at 12:53:57
Total Time Elapsed: 1.53 seconds