Animation of a SimPy Model with Branching and Multiple Steps

from ex_2_model_classes import Trial, g

from vidigi.animation import animate_activity_log
from vidigi.utils import EventPosition, create_event_position_df
import random

import numpy as np
import pandas as pd
import simpy
from sim_tools.distributions import Bernoulli, Exponential, Lognormal, Normal, Uniform

from vidigi.resources import VidigiStore
from vidigi.logging import EventLogger, TrialLogger

TRACE = False


def trace(msg, show=TRACE):
    """
    Utility function for printing a trace as the
    simulation model executes.
    Set the TRACE constant to False, to turn tracing off.

    Params:
    -------
    msg: str
        string to print to screen.
    """
    if show:
        print(msg)


# 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 = []

        # Store the passed in run number
        self.run_number = run_number

        self.event_log = EventLogger(env=self.env, run_number=self.run_number)

        # Create our resources
        self.init_resources()

        # 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 = VidigiStore(
            self.env, num_resources=g.n_triage, label="triage", logger=self.event_log
        )

        self.registration_cubicles = VidigiStore(
            self.env, num_resources=g.n_reg, label="registration", logger=self.event_log
        )

        # Non-trauma
        self.exam_cubicles = VidigiStore(self.env, num_resources=g.n_exam, label="exam", logger=self.event_log)

        self.non_trauma_treatment_cubicles = VidigiStore(
            self.env,
            num_resources=g.n_cubicles_non_trauma_treat,
            label="non_trauma_treatment", logger=self.event_log
        )

        # Trauma
        self.trauma_stabilisation_bays = VidigiStore(
            self.env, num_resources=g.n_trauma, label="trauma_stabilisation", logger=self.event_log
        )

        self.trauma_treatment_cubicles = VidigiStore(
            self.env, num_resources=g.n_cubicles_trauma_treat, label="trauma_treatment", logger=self.event_log
        )

    # 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}")

            # 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.log_arrival(entity_id=patient.identifier, pathway="Non-Trauma")
        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Non-Trauma",
            event="triage_wait_begins"
            )

        ###################################################
        # request sign-in/triage
        with self.triage_cubicles.request(entity_id=patient.identifier, pathway="Non-Trauma") as req:
            yield req

            # record the waiting time for triage
            patient.wait_triage = self.env.now - patient.arrival
            trace(f"patient {patient.identifier} triaged to minors {self.env.now:.3f}")

            # 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}"
            )

        #########################################################

        # record the time that entered the registration queue
        start_wait = self.env.now
        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Non-Trauma",
            event="MINORS_registration_wait_begins"
            )

        #########################################################
        # request registration clerk
        with self.registration_cubicles.request(entity_id=patient.identifier, pathway="Non-Trauma") as req:
            yield req

            # record the waiting time for registration
            patient.wait_reg = self.env.now - start_wait
            trace(f"registration of patient {patient.identifier} at {self.env.now:.3f}")

            # 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}"
            )

            ########################################################

        # record the time that entered the evaluation queue
        start_wait = self.env.now

        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Non-Trauma",
            event="MINORS_examination_wait_begins"
        )

        #########################################################
        # request examination resource
        with self.exam_cubicles.request(entity_id=patient.identifier, pathway="Non-Trauma") as req:
            yield req

            # record the waiting time for examination to begin
            patient.wait_exam = self.env.now - start_wait
            trace(f"examination of patient {patient.identifier} begins {self.env.now:.3f}")

            # 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}"
            )
            ############################################################################

        # 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.log_custom_event(
                entity_id=patient.identifier,
                event_type="attribute_assigned",
                event="requires_treatment",
                pathway="Non-Trauma"
                )

            # record the time that entered the treatment queue
            start_wait = self.env.now
            self.event_log.log_queue(
                        entity_id=patient.identifier,
                        pathway="Non-Trauma",
                        event="MINORS_treatment_wait_begins"
                    )

            ###################################################
            # request treatment cubicle

            with self.non_trauma_treatment_cubicles.request(entity_id=patient.identifier, pathway="Non-Trauma") as req:
                yield req
                # record the waiting time for treatment
                patient.wait_treat = self.env.now - start_wait
                trace(
                    f"treatment of patient {patient.identifier} begins {self.env.now:.3f}"
                )

                # 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}"
                )

        ##########################################################################

        # Return to what happens to all patients, regardless of whether they were sampled as needing treatment
        self.event_log.log_departure(entity_id=patient.identifier, pathway="Trauma")

        # 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.log_arrival(entity_id=patient.identifier, pathway="Trauma")
        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Trauma",
            event="triage_wait_begins"
            )

        ###################################################
        # request sign-in/triage
        with self.triage_cubicles.request(entity_id=patient.identifier, pathway="Trauma") as req:
            yield req

            # record the waiting time for triage
            patient.wait_triage = self.env.now - patient.arrival

            trace(f"patient {patient.identifier} triaged to trauma {self.env.now:.3f}")

            # 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}"
            )

            ###################################################

        # record the time that entered the trauma queue
        start_wait = self.env.now
        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Trauma",
            event="TRAUMA_stabilisation_wait_begins"
            )

        ###################################################
        # request trauma room
        with self.trauma_stabilisation_bays.request(entity_id=patient.identifier, pathway="Trauma") as req:
            yield req

            # 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} begins at {self.env.now}")

            #######################################################

        # record the time that patient entered the treatment queue
        start_wait = self.env.now
        self.event_log.log_queue(
            entity_id=patient.identifier,
            pathway="Trauma",
            event="TRAUMA_treatment_wait_begins"
            )

        ########################################################
        # request treatment cubicle
        with self.trauma_treatment_cubicles.request(entity_id=patient.identifier, pathway="Trauma") as req:
            yield req

            # record the waiting time for trauma
            patient.wait_treat = self.env.now - start_wait
            trace(f"treatment of patient {patient.identifier} at {self.env.now:.3f}")

            # 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.log_departure(entity_id=patient.identifier, pathway="Trauma")

        # 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

        return self.results_df


# 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.trial_logger = TrialLogger()

    # 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)
            patient_level_results = my_model.run()

            self.df_trial_results.loc[run] = [
                len(patient_level_results),
                my_model.mean_q_time_cubicle,
            ]

            self.trial_logger.add_log(event_log=my_model.event_log)
my_trial = Trial()

my_trial.run_trial()

Let’s take a look at some sample logs from a single run.

my_trial.trial_logger.get_log_by_run(run=1, as_df=True).head(20)
entity_id event_type event time pathway run_number resource_id unique_resource_id
0 1 arrival_departure arrival 37.593555 Non-Trauma 1 NaN NaN
1 1 queue triage_wait_begins 37.593555 Non-Trauma 1 NaN NaN
2 1 resource_use triage_start 37.593555 Non-Trauma 1 1.0 triage_1
3 2 arrival_departure arrival 51.835879 Non-Trauma 1 NaN NaN
4 2 queue triage_wait_begins 51.835879 Non-Trauma 1 NaN NaN
5 2 resource_use triage_start 51.835879 Non-Trauma 1 2.0 triage_2
6 1 resource_use_end triage_end 52.018807 Non-Trauma 1 1.0 triage_1
7 1 queue MINORS_registration_wait_begins 52.018807 Non-Trauma 1 NaN NaN
8 1 resource_use registration_start 52.018807 Non-Trauma 1 1.0 registration_1
9 1 resource_use_end registration_end 60.329215 Non-Trauma 1 1.0 registration_1
10 1 queue MINORS_examination_wait_begins 60.329215 Non-Trauma 1 NaN NaN
11 1 resource_use exam_start 60.329215 Non-Trauma 1 1.0 exam_1
12 2 resource_use_end triage_end 65.853017 Non-Trauma 1 2.0 triage_2
13 2 queue MINORS_registration_wait_begins 65.853017 Non-Trauma 1 NaN NaN
14 2 resource_use registration_start 65.853017 Non-Trauma 1 2.0 registration_2
15 2 resource_use_end registration_end 72.417168 Non-Trauma 1 2.0 registration_2
16 2 queue MINORS_examination_wait_begins 72.417168 Non-Trauma 1 NaN NaN
17 2 resource_use exam_start 72.417168 Non-Trauma 1 2.0 exam_2
18 1 resource_use_end exam_end 76.857001 Non-Trauma 1 1.0 exam_1
19 1 arrival_departure depart 76.857001 Trauma 1 NaN NaN
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_start',
            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="registration_start",
            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='exam_start',
            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='non_trauma_treatment_start',
            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_start',
            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_start',
            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.trial_logger,
    run_number=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=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 13:53:19
Iteration through time-unit-by-time-unit logs complete 13:53:19
Snapshot df concatenation complete at 13:53:20
Reshaped animation dataframe finished construction at 13:53:20
Placement dataframe started construction at 13:53:20
Placement dataframe finished construction at 13:53:20
Output animation generation complete at 13:53:21
Total Time Elapsed: 2.30 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.trial_logger,
    run_number=1,
    event_position_df=event_position_df,
    scenario=g(),
    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 13:53:21
Iteration through time-unit-by-time-unit logs complete 13:53:22
Snapshot df concatenation complete at 13:53:22
Reshaped animation dataframe finished construction at 13:53:22
Placement dataframe started construction at 13:53:22
Placement dataframe finished construction at 13:53:22
Output animation generation complete at 13:53:23
Total Time Elapsed: 2.25 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.trial_logger,
    event_position_df=event_position_df,
    run_number=1,
    scenario=g(),
    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 13:53:28
Iteration through time-unit-by-time-unit logs complete 13:53:28
Snapshot df concatenation complete at 13:53:28
Reshaped animation dataframe finished construction at 13:53:29
Placement dataframe started construction at 13:53:29
Placement dataframe finished construction at 13:53:29
Output animation generation complete at 13:53:30
Total Time Elapsed: 2.42 seconds
Back to top