Spaces:
Sleeping
Sleeping
# -*- coding: utf-8 -*- | |
""" | |
Created on Mon Apr 22 10:55:25 2024 | |
@author: varad | |
""" | |
#OOP model for a real ED with 3 processes and 3 resources | |
#import libraries | |
import simpy | |
import matplotlib | |
import matplotlib.pyplot as plt | |
import math | |
import numpy as np | |
import pandas as pd | |
import random | |
import csv | |
import gradio as gr | |
import plotly.graph_objects as go | |
class g (object): | |
''' | |
A class that holds all the global variables that will be passed into the different processes. | |
It's easier to change the global variables here rather than change them in the process code as it could lead to errors | |
''' | |
#service times (these are more or less constant and hence won't be changed through user input, although it is possible to change them too, | |
#but for the sake of this program, they won't be changed) | |
ed_inter_arrival = 6 #mins | |
mean_registeration = 2 #mins | |
mean_triage = 6 #mins | |
mean_acu_ass = 60 #mins | |
mean_ed_ass = 30 #mins | |
#resources (this will need to me moved from a static variable to a variable within the ED class as it will then be fed into the gradio app | |
#as variables to be modified through user input) | |
receptionist = 1 | |
nurse = 2 | |
ed_doc = 2 | |
acu_doc = 1 | |
#simulation variables | |
number_of_runs = 100 | |
warmup_time = 1440 #24 hours and 60 minutes per hour | |
run_time = 100 # mins | |
class ed_patient (object): | |
''' | |
Defines the patient characteristics. Could be interesting if we have different types of patients | |
But for this exercise we have only one type of patient that is a regular patient that has a 20% chance | |
of going to the ACU. Also important to store patient level variables to then summarize and plot | |
''' | |
def __init__(self, uhid): | |
self.id = uhid | |
#declaring these variables as they will be recorded and manipulated with later | |
self.time_entered_in_system = 0 | |
self.q_reception = 0 | |
self.service_reception = 0 | |
self.q_nurse = 0 | |
self.service_nurse = 0 | |
self.q_edd_ass = 0 | |
self.ed_ass_time = 0 | |
self.acu_ass_time = 0 | |
self.q_acu_ass = 0 | |
self.time_exit_system = 0 | |
self.tot_system_time = 0 | |
class ED_sim (object): | |
''' | |
This is the actual clinic where everything is simulated. | |
''' | |
def __init__(self, run_number,receptionist = 3, nurse = 2, ed_doc = 2, acu_doc = 1 ): | |
#declaring the environment | |
self.env = simpy.Environment() | |
#declaring resource capacity as variables to later be changed through gradio inputs | |
self.rec_no = receptionist | |
self.nurse_no = nurse | |
self.ed_doc_no = ed_doc | |
self.acu_doc_no = acu_doc | |
self.patient_counter = 0 | |
#declaring resources | |
self.receptionist = simpy.Resource(self.env, capacity = self.rec_no) | |
self.nurse = simpy.Resource(self.env, capacity = self.nurse_no) | |
self.ed_doc = simpy.Resource(self.env, capacity = self.ed_doc_no) | |
self.acu_doc = simpy.Resource(self.env, capacity = self.acu_doc_no) | |
self.run_number = run_number + 1 | |
#initiating a dataframe with required columns | |
self.individual_level_results = pd.DataFrame({ | |
"UHID" :[], | |
"Time_entered_in_system" : [], | |
"Q_time_receptionist":[], | |
"Q_time_nurse":[], | |
"Q_time_acu_doc":[], | |
"Q_time_ed_doc":[], | |
"Service_time_receptionist":[], | |
"Service_time_nurse":[], | |
"Service_time_acu_doc":[], | |
"Service_time_ed_doc":[], | |
"Time_exit_system":[], | |
"Total time in System":[] | |
}) | |
self.individual_level_results.set_index('UHID', inplace= True) #sets the index to UHID which is just 1,2,3,4 etc | |
#declaring mean variables for or KPIs to be calculated at the end of one run | |
self.Mean_Q_Rec_time = 0 | |
self.Mean_Q_Nurse_time = 0 | |
self.Mean_Q_ED_time = 0 | |
self.Mean_Q_ACU_time = 0 | |
self.Rec_utilize = 0 | |
self.Nurse_utilize = 0 | |
self.ED_doc_utilize = 0 | |
self.ACU_doc_utilize = 0 | |
def generate_ed_arrivals(self): | |
while True: | |
self.patient_counter += 1 #this is also the UHID of the patient | |
ed_pt = ed_patient(self.patient_counter) | |
ed_pt.time_entered_in_system = self.env.now #Used to calculate total time spent in the system | |
#Patient goes to registeration | |
self.env.process(self.registration(ed_pt)) | |
#print(self.individual_level_results) | |
#draws a random value from an exponential distribution with lambda = interarrival time | |
ed_arrival_time = random.expovariate(1/g.ed_inter_arrival) | |
yield self.env.timeout(ed_arrival_time) | |
def registration(self, patient): | |
start_q_rec = self.env.now | |
with self.receptionist.request() as req: | |
yield req | |
end_q_rec = self.env.now | |
#storing patient level values in patient level variables | |
patient.q_reception = end_q_rec - start_q_rec | |
#patient goes to triage | |
self.env.process(self.triage(patient)) | |
#register_time = random.triangular(0,g.mean_registeration, 2*g.mean_registeration) | |
register_time = random.expovariate(1/g.mean_registeration) | |
patient.service_reception = register_time | |
yield self.env.timeout(register_time) | |
def triage (self, patient): | |
start_q_nurse = self.env.now | |
with self.nurse.request() as req: | |
yield req | |
end_q_nurse = self.env.now | |
patient.q_nurse = end_q_nurse - start_q_nurse | |
#patient goes either to ACU or to ED based on probability | |
if random.random() > 0.2: #80% chance that the patient goes to ED | |
self.env.process(self.ed_ass(patient)) | |
else: | |
self.env.process(self.acu_ass(patient)) | |
triage_time = random.triangular(g.mean_triage/2, g.mean_triage, g.mean_triage*2 ) | |
patient.service_nurse = triage_time | |
yield self.env.timeout(triage_time) | |
def ed_ass (self, patient): | |
start_ed_q = self.env.now | |
with self.ed_doc.request() as req: | |
yield req | |
end_ed_q = self.env.now | |
patient.q_edd_ass = end_ed_q - start_ed_q | |
ed_ass_time = random.triangular(g.mean_ed_ass/2, g.mean_ed_ass, g.mean_ed_ass*2) | |
patient.ed_ass_time = ed_ass_time | |
yield self.env.timeout(ed_ass_time) | |
patient.time_exit_system = self.env.now | |
patient.tot_system_time = patient.time_exit_system - patient.time_entered_in_system | |
ED_sim.add_to_df(self, patient) | |
def acu_ass (self, patient): | |
start_acu_q = self.env.now | |
with self.acu_doc.request() as req: | |
yield req | |
end_acu_q = self.env.now | |
patient.q_acu_ass = end_acu_q - start_acu_q | |
acu_ass_time = random.triangular(g.mean_acu_ass/2, g.mean_acu_ass, g.mean_acu_ass*2) | |
patient.acu_ass_time = acu_ass_time | |
yield self.env.timeout(acu_ass_time) | |
patient.time_exit_system = self.env.now | |
patient.tot_system_time = patient.time_exit_system - patient.time_entered_in_system | |
ED_sim.add_to_df(self, patient) | |
def add_to_df(self, patient): | |
''' | |
Basically takes all the variables and adds them to the dataframe without having to enter them manually with | |
12 line codes in every function | |
''' | |
df_to_add = pd.DataFrame({ | |
"UHID" :[patient.id], | |
"Time_entered_in_system" : [patient.time_entered_in_system], | |
"Q_time_receptionist":[patient.q_reception], | |
#using zeros as placeholders | |
"Q_time_nurse":[patient.q_nurse], | |
"Q_time_acu_doc":[patient.q_acu_ass], | |
"Q_time_ed_doc":[patient.q_edd_ass], | |
"Service_time_receptionist":[patient.service_reception], | |
"Service_time_nurse":[patient.service_nurse], | |
"Service_time_acu_doc":[patient.acu_ass_time], | |
"Service_time_ed_doc":[patient.ed_ass_time], | |
"Time_exit_system" :[patient.time_exit_system], | |
"Total time in System":[patient.tot_system_time] | |
}) | |
df_to_add.set_index('UHID', inplace=True) | |
self.individual_level_results = self.individual_level_results._append(df_to_add) | |
#print(self.individual_level_results) | |
def mean_calculator (self): | |
''' | |
calculates the average statistic for each individual run for all the different KPIs and maybe stores it in a global database | |
''' | |
#mean queuing times | |
self.Mean_Q_Rec_time = self.individual_level_results['Q_time_receptionist'].mean() | |
self.Mean_Q_Nurse_time = self.individual_level_results['Q_time_nurse'].mean() | |
self.Mean_Q_ED_time = self.individual_level_results['Q_time_ed_doc'].mean() | |
self.Mean_Q_ACU_time = self.individual_level_results['Q_time_acu_doc'].mean() | |
#%resource utilisation | |
self.Rec_utilize = self.individual_level_results[ | |
'Service_time_receptionist'].sum()/(g.run_time*self.rec_no) | |
self.Nurse_utilize = self.individual_level_results['Service_time_nurse'].sum()/(g.run_time*self.nurse_no) | |
self.ED_doc_utilize = self.individual_level_results['Service_time_ed_doc'].sum()/(g.run_time*self.ed_doc_no) | |
self.ACU_doc_utilize = self.individual_level_results['Service_time_acu_doc'].sum()/(g.run_time*self.acu_doc_no) | |
def export_row_to_csv(self): | |
''' | |
Writes the results of an individual run as a row in a csv file in the desired folder | |
''' | |
with open (r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\trial_results.csv",'a') as f: | |
writer = csv.writer(f, delimiter = ',') | |
row_to_add = [self.run_number, | |
self.Mean_Q_Rec_time, | |
self.Mean_Q_Nurse_time, | |
self.Mean_Q_ED_time, | |
self.Mean_Q_ACU_time, | |
self.Rec_utilize, | |
self.Nurse_utilize, | |
self.ED_doc_utilize, | |
self.ACU_doc_utilize] | |
writer.writerow(row_to_add) | |
def run (self): | |
''' | |
suns the simulation program | |
''' | |
self.env.process(self.generate_ed_arrivals()) | |
self.env.run(until = g.run_time) | |
#print(self.individual_level_results) | |
self.individual_level_results.to_csv(r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\individual_results.csv") | |
self.mean_calculator() | |
self.export_row_to_csv() | |
class summary_statistics(object): | |
''' | |
This object calculates the mean, median or other summary statistics from a dataframe that has means of individual runs | |
So this object calculates the means of means | |
''' | |
def __init__(self): | |
self.total_mean_df = pd.DataFrame({ | |
"Median_q_rec_time":[], | |
"25_q_rec_time":[], | |
"75_q_rec_time":[], | |
"Median_q_nurse_time":[], | |
"25_q_nurse_time":[], | |
"75_q_nurse_time":[], | |
"Median_q_ed_doc_time":[], | |
"25_q_ed_doc_time":[], | |
"75_q_ed_doc_time":[], | |
"Median_q_acu_doc_time":[], | |
"25_q_acu_doc_time":[], | |
"75_q_acu_doc_time":[], | |
"Median_%_utilize_rec":[], | |
"Median_%_utilize_nurse":[], | |
"Median_%_utilize_ed_doc":[], | |
"Median_%_utilize_acu_doc":[], | |
}) | |
filepath = r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\trial_results.csv" | |
self.dataframe = pd.read_csv(filepath) | |
def mean_of_means(self): | |
median_q_rec = self.dataframe["Mean_Q_Rec_time"].median() | |
twofive_q_rec = self.dataframe["Mean_Q_Rec_time"].quantile(0.25) | |
sevfive_q_rec = self.dataframe["Mean_Q_Rec_time"].quantile(0.75) | |
median_q_nurse = self.dataframe["Mean_Q_Nurse_time"].median() | |
twofive_q_nurse = self.dataframe["Mean_Q_Nurse_time"].quantile(0.25) | |
sevfive_q_nurse = self.dataframe["Mean_Q_Nurse_time"].quantile(0.75) | |
median_q_ed_doc = self.dataframe["Mean_Q_ED_time"].median() | |
twofive_q_ed_doc = self.dataframe["Mean_Q_ED_time"].quantile(0.25) | |
sevfive_q_ed_doc = self.dataframe["Mean_Q_ED_time"].quantile(0.75) | |
median_q_acu_doc = self.dataframe["Mean_Q_ACU_time"].median() | |
twofive_q_acu_doc = self.dataframe["Mean_Q_ACU_time"].quantile(0.25) | |
sevfive_q_acu_doc = self.dataframe["Mean_Q_ACU_time"].quantile(0.75) | |
median_rec_uti = self.dataframe["Rec_%_utilize"].median() | |
median_nurse_uti = self.dataframe["Nurse_%_utilize"].median() | |
median_ed_doc_uti = self.dataframe["ED_doc_%_utilize"].median() | |
median_acu_doc_uti = self.dataframe["ACU_doc_%_utilize"].median() | |
print("Results of " + str(g.number_of_runs) + " runs") | |
print("-------------") | |
self.total_mean_df = pd.DataFrame({ | |
"Median_q_rec_time":[median_q_rec], | |
"25_q_rec_time":[twofive_q_rec], | |
"75_q_rec_time":[sevfive_q_rec], | |
"Median_q_nurse_time":[median_q_nurse], | |
"25_q_nurse_time":[twofive_q_nurse], | |
"75_q_nurse_time":[sevfive_q_nurse], | |
"Median_q_ed_doc_time":[median_q_ed_doc], | |
"25_q_ed_doc_time":[twofive_q_ed_doc], | |
"75_q_ed_doc_time":[sevfive_q_ed_doc], | |
"Median_q_acu_doc_time":[median_q_acu_doc], | |
"25_q_acu_doc_time":[twofive_q_acu_doc], | |
"75_q_acu_doc_time":[sevfive_q_acu_doc], | |
"Median_%_utilize_rec":[median_rec_uti], | |
"Median_%_utilize_nurse":[median_nurse_uti], | |
"Median_%_utilize_ed_doc":[median_ed_doc_uti], | |
"Median_%_utilize_acu_doc":[median_acu_doc_uti], | |
}) | |
print(self.total_mean_df) | |
with open (r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\mean_per_lambda.csv",'a') as f: | |
writer = csv.writer(f, delimiter = ',') | |
row_to_add = [g.ed_inter_arrival, | |
median_q_rec, | |
twofive_q_rec, | |
sevfive_q_rec, | |
median_q_nurse, | |
twofive_q_nurse, | |
sevfive_q_nurse, | |
median_q_ed_doc, | |
twofive_q_ed_doc, | |
sevfive_q_ed_doc, | |
median_q_acu_doc, | |
twofive_q_acu_doc, | |
sevfive_q_acu_doc, | |
median_rec_uti, | |
median_nurse_uti, | |
median_ed_doc_uti, | |
median_acu_doc_uti | |
] | |
writer.writerow(row_to_add) | |
def file_opener(): | |
''' | |
Adds one row to the filename that is passed to it | |
''' | |
#This is not the most compulsory function here as the above function will also create a file if it does not exist already | |
with open (r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\trial_results.csv",'w') as f: | |
writer = csv.writer(f, delimiter = ',') | |
columns_headers = ["Run_Number", | |
"Mean_Q_Rec_time", | |
"Mean_Q_Nurse_time", | |
"Mean_Q_ED_time", | |
"Mean_Q_ACU_time", | |
"Rec_%_utilize", | |
"Nurse_%_utilize", | |
"ED_doc_%_utilize", | |
"ACU_doc_%_utilize"] | |
writer.writerow(columns_headers) | |
with open (r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\mean_per_lambda.csv",'w') as f: | |
writer = csv.writer(f, delimiter = ',') | |
columns_headers = ["Pt Interarrival Time (lambda)", | |
"Median_Q_Rec_time", | |
"25_Q_rec_time", | |
"75_Q_rec_time", | |
"Median_Q_Nurse_time", | |
"25_Q_Nurse_time", | |
"75_Q_Nurse_time", | |
"Median_Q_ED_time", | |
"25_Q_ED_time", | |
"75_Q_ED_time", | |
"Median_Q_ACU_time", | |
"25_Q_ACU_time", | |
"75_Q_ACU_time", | |
"Median_Rec_%_utilize", | |
"Median_Nurse_%_utilize", | |
"Median_ED_doc_%_utilize", | |
"Median_ACU_doc_%_utilize"] | |
writer.writerow(columns_headers) | |
def Plotter(): | |
filepath = r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\mean_per_lambda.csv" | |
df_to_plot = pd.read_csv(filepath) | |
figure = plt.subplots() | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Q_Rec_time'], color = 'green', linestyle = '-', label = 'Queue for reception') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Q_Nurse_time'], color = 'blue', linestyle = ':', label = 'Queue for nurses') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Q_ED_time'], color = 'red', linestyle = '--', label = 'Queue for ED_doc') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Q_ACU_time'], color = 'black', linestyle = '-.', label = 'Queue for ACU_doc') | |
plt.xlabel("Pt interarrival time (min)") | |
plt.ylabel("Time in minutes") | |
plt.title("Queuing times for the Emergency room") | |
plt.text(3,10,"Rec = 1, Nur = 2, ED_doc = 2, ACU_doc = 1") | |
plt.legend() | |
figure = plt.subplots() | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Rec_%_utilize'], color = 'green', linestyle = '-', label = '% utilise of reception') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_Nurse_%_utilize'], color = 'blue', linestyle = ':', label = '% utilise of nurses') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_ED_doc_%_utilize'], color = 'red', linestyle = '--', label = '%utilise of ED_doc') | |
plt.plot(df_to_plot["Pt Interarrival Time (lambda)"], df_to_plot['Median_ACU_doc_%_utilize'], color = 'black', linestyle = '-.', label = '%utilise of ACU_doc') | |
plt.xlabel("Pt interarrival time (min)") | |
plt.ylabel("Time in minutes") | |
plt.title("Percentage utlisation for the Emergency room different HR") | |
plt.text(3,10,"Rec = 1, Nur = 2, ED_doc = 2, ACU_doc = 1") | |
plt.legend() | |
return figure | |
def plotly_plotter(): | |
''' | |
Uses the Plotly library to plot the graphs as the plotly library is web application friendly | |
''' | |
filepath = r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\mean_per_lambda.csv" | |
df_to_plot = pd.read_csv(filepath) | |
fig = go.Figure() | |
fig.add_trace(go.Scatter(x = df_to_plot["Pt Interarrival Time (lambda)"], y = df_to_plot['Median_Rec_%_utilize'], name='Receptionist utilization%')) | |
fig.add_trace(go.Scatter(x = df_to_plot["Pt Interarrival Time (lambda)"], y = df_to_plot['Median_Nurse_%_utilize'], name='Nurse utilization%')) | |
fig.add_trace(go.Scatter(x = df_to_plot["Pt Interarrival Time (lambda)"], y = df_to_plot['Median_ED_doc_%_utilize'], name='ED Doc utilization%')) | |
fig.add_trace(go.Scatter(x = df_to_plot["Pt Interarrival Time (lambda)"], y = df_to_plot['Median_ACU_doc_%_utilize'], name='ACU Doc utilization%')) | |
fig.update_layout(title = "% utilization of different HR") | |
#fig.show() | |
return fig | |
def main(receptionist, nurse, ed_doc, acu_doc): | |
''' | |
this is going to to be the main function that gradio will operate on | |
It will take input parameters as model parameters which will be modified by the users | |
It will output a plot or a set of plots which will then be plotted in a given block in gradio | |
''' | |
file_opener() | |
for l in range(1,11): | |
print("Pt interarrival time = ", l) | |
for run in range (g.number_of_runs): | |
print(f"Run {run + 1} of {g.number_of_runs}") | |
my_ED_model = ED_sim(run, receptionist, nurse, ed_doc, acu_doc) | |
my_ED_model.run() | |
g.ed_inter_arrival = l | |
my_sum_stats = summary_statistics() | |
my_sum_stats.mean_of_means() | |
return plotly_plotter() | |
def get_data_gradio(): | |
filepath = r"C:\Users\varad\Desktop\Education Material\Mathematical Modelling\HSMA\HSMA_modelling_ED\mean_per_lambda.csv" | |
return pd.read_csv(filepath) | |
#main() | |
with gr.Blocks() as demo: | |
gr.Markdown(r"A Discrete Event Simulation run of an imaginary Emergency Room") | |
gr.Image("Ed_process_map.png") | |
with gr.Row(): | |
gr.HighlightedText(label = "Modify these parameters (number of different human resources) using the sliders below") | |
with gr.Row(): | |
receptionist = gr.Slider(minimum=1, maximum=10,label = "No of Receptionists") | |
nurse = gr.Slider(minimum=1, maximum=10, label = "No of Nurses") | |
with gr.Row(): | |
ed_doc = gr.Slider(minimum=1, maximum=10, label = "No of ED doctors") | |
acu_doc = gr.Slider(minimum=1, maximum=10,label = "No of ACU Doctors") | |
with gr.Row(): | |
btn = gr.Button(value = "Run the Simulation") | |
with gr.Row(): | |
output = gr.Plot(label = "Simulation Run") | |
btn.click(main,[receptionist,nurse,ed_doc,acu_doc], output) | |
demo.launch(share = True) | |