Example 2: Branching + Multi-step Case

from examples.example_2_branching_multistep.ex_2_model_classes import Trial, g
from vidigi.animation import animate_activity_log
import pandas as pd
import plotly.io as pio
pio.renderers.default = "notebook"
import os
import random
import numpy as np
import pandas as pd
import simpy
from sim_tools.distributions import Exponential, Lognormal, Uniform, Normal, Bernoulli
from vidigi.utils 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_ends',
                    '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_ends 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 = pd.DataFrame([
                # {'event': 'arrival', 'x':  10, 'y': 250, 'label': "Arrival" },

                # Triage - minor and trauma
                {'event': 'triage_wait_begins',
                 'x':  160, 'y': 375, 'label': "Waiting for<br>Triage"  },
                {'event': 'triage_begins',
                 'x':  160, 'y': 315, 'resource':'n_triage', 'label': "Being Triaged" },

                # Minors (non-trauma) pathway
                {'event': 'MINORS_registration_wait_begins',
                 'x':  300, 'y': 145, 'label': "Waiting for<br>Registration"  },
                {'event': 'MINORS_registration_begins',
                 'x':  300, 'y': 85, 'resource':'n_reg', 'label':'Being<br>Registered'  },

                {'event': 'MINORS_examination_wait_begins',
                 'x':  465, 'y': 145, 'label': "Waiting for<br>Examination"  },
                {'event': 'MINORS_examination_begins',
                 'x':  465, 'y': 85, 'resource':'n_exam', 'label': "Being<br>Examined" },

                {'event': 'MINORS_treatment_wait_begins',
                 'x':  630, 'y': 145, 'label': "Waiting for<br>Treatment"  },
                {'event': 'MINORS_treatment_begins',
                 'x':  630, 'y': 85, 'resource':'n_cubicles_non_trauma_treat', 'label': "Being<br>Treated" },

                # Trauma pathway
                {'event': 'TRAUMA_stabilisation_wait_begins',
                 'x': 300, 'y': 560, 'label': "Waiting for<br>Stabilisation" },
                {'event': 'TRAUMA_stabilisation_begins',
                 'x': 300, 'y': 490, 'resource':'n_trauma', 'label': "Being<br>Stabilised" },

                {'event': 'TRAUMA_treatment_wait_begins',
                 'x': 630, 'y': 560, 'label': "Waiting for<br>Treatment" },
                {'event': 'TRAUMA_treatment_begins',
                 'x': 630, 'y': 490, 'resource':'n_cubicles_trauma_treat', 'label': "Being<br>Treated" },

                 {'event': 'exit',
                 'x':  670, 'y': 330, 'label': "Exit"}
            ])
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(),
        debug_mode=True,
        setup_mode=True,
        every_x_time_units=5,
        include_play_button=True,
        gap_between_entities=10,
        gap_between_rows=20,
        plotly_height=900,
        plotly_width=1600,
        override_x_max=700,
        override_y_max=675,
        icon_and_text_size=20,
        wrap_queues_at=10,
        step_snapshot_max=50,
        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 00:26:00
Iteration through minute-by-minute logs complete 00:26:00
Snapshot df concatenation complete at 00:26:00
Reshaped animation dataframe finished construction at 00:26:01
Placement dataframe finished construction at 00:26:01
Output animation generation complete at 00:26:02
Total Time Elapsed: 2.07 seconds