Feature Breakdown: Comparing gauge, hybrid and gaugeless animations for simulations with high entity volumes

import simpy
from vidigi.logging import EventLogger
from vidigi.animation import animate_activity_log
from examples.feat_gauge_only_animations.simple_triage_assess_treat_model import Model
from vidigi.utils import EventPosition, create_event_position_df

import plotly.io as pio
pio.renderers.default = "notebook"
model = Model(run_number=1)
logs = model.run()
logs.summary()
{'total_events': 12928,
 'event_types': {'queue': 9055, 'arrival_departure': 3873},
 'time_range': (0.0, 363.99308604394935),
 'unique_entities': 3756}
logs_df = logs.to_dataframe()
logs_df
entity_id event_type event time run_number
0 1 arrival_departure arrival 0.000000 1
1 1 queue queue_initial_review 0.000000 1
2 1 queue start_initial_review 0.000000 1
3 2 arrival_departure arrival 0.190817 1
4 2 queue queue_initial_review 0.190817 1
... ... ... ... ... ...
12923 3754 queue queue_initial_review 363.946793 1
12924 3755 arrival_departure arrival 363.949331 1
12925 3755 queue queue_initial_review 363.949331 1
12926 3756 arrival_departure arrival 363.993086 1
12927 3756 queue queue_initial_review 363.993086 1

12928 rows × 5 columns

logs_df.event.unique()
array(['arrival', 'queue_initial_review', 'start_initial_review',
       'end_initial_review', 'queue_assessment', 'start_assessment',
       'end_assessment', 'queue_treatment', 'start_treatment',
       'end_treatment', 'depart'], dtype=object)
event_position_df = create_event_position_df(
    [EventPosition(event='queue_initial_review', x=200, y=600, label="Waiting for <br> Initial Review"),
    EventPosition(event='queue_assessment', x=350, y=400, label="Waiting for <br> Assessment"),
    EventPosition(event='queue_treatment', x=500, y=200, label="Waiting for <br> Treatment")]
)

event_position_df
event x y label resource
0 queue_initial_review 200 600 Waiting for <br> Initial Review None
1 queue_assessment 350 400 Waiting for <br> Assessment None
2 queue_treatment 500 200 Waiting for <br> Treatment None
animate_activity_log(
    event_log=logs_df,
    event_position_df=event_position_df,
    simulation_time_unit="days",
    every_x_time_units=7,
    step_snapshot_limit_gauges=True,
    step_snapshot_max=0,
    limit_duration=365,
    override_x_max=600,
    override_y_max=700
)

For comparison, let’s explore including some icons for individuals to try to demonstrate flow between steps.

However, as many of the individuals moving between the steps are not shown in the queue in the frame before they move, we observe a confusing ‘flying in’ effect from the top left of the animation, which is a plotly limitation/default which has not yet been overcome in the package.

animate_activity_log(
    event_log=logs_df,
    event_position_df=event_position_df,
    simulation_time_unit="days",
    every_x_time_units=7,
    step_snapshot_limit_gauges=True,
    step_snapshot_max=10,
    limit_duration=365,
    override_x_max=600,
    override_y_max=700,
    wrap_queues_at=10,
    custom_entity_icon_list=["⚫"],
    frame_duration=1000
)

Changing to a daily level still doesn’t really represent the rate of flow between steps.

animate_activity_log(
    event_log=logs_df,
    event_position_df=event_position_df,
    simulation_time_unit="days",
    every_x_time_units=1,
    step_snapshot_limit_gauges=True,
    step_snapshot_max=10,
    limit_duration=365,
    override_x_max=600,
    override_y_max=700,
    wrap_queues_at=10,
    custom_entity_icon_list=["⚫"],
    frame_duration=400,
    frame_transition_duration=600
)

Finally, let’s try including all of our individuals in the plot - we’ll just make them very small and adjust our event position dataframe slightly to provide more space.

However - you may notice that the animation starts to struggle significantly, with slowdowns occurring, despite it generating very quickly. This is due to the sheer amount of data per frame that needs to be stored.

However, this does give us more of a sense of flow between steps, which can complement the gauge animation. The gauge version of the animation can be utilised to better track the scale of queues, while the non-gauge animation may better serve as a demonstration of how the underlying model works in terms of potential paths for people to move between steps.

event_position_df = create_event_position_df(
    [EventPosition(event='queue_initial_review', x=200, y=900, label="Waiting for <br> Initial Review"),
    EventPosition(event='queue_assessment', x=350, y=500, label="Waiting for <br> Assessment"),
    EventPosition(event='queue_treatment', x=500, y=200, label="Waiting for <br> Treatment")]
)

event_position_df
event x y label resource
0 queue_initial_review 200 900 Waiting for <br> Initial Review None
1 queue_assessment 350 500 Waiting for <br> Assessment None
2 queue_treatment 500 200 Waiting for <br> Treatment None
fig = animate_activity_log(
    event_log=logs_df,
    event_position_df=event_position_df,
    simulation_time_unit="days",
    every_x_time_units=7,
    step_snapshot_limit_gauges=True,
    step_snapshot_max=9999,
    debug_mode=True,
    limit_duration=365,
    override_x_max=600,
    override_y_max=1800,
    wrap_queues_at=75,
    custom_entity_icon_list=["⚫"],
    entity_icon_size=6,
    gap_between_entities=2,
    gap_between_queue_rows=15,
    frame_duration=1000,
    frame_transition_duration=2000,
)

fig
Animation function called at 18:25:44
Iteration through time-unit-by-time-unit logs complete 18:25:44
Snapshot df concatenation complete at 18:25:44
Reshaped animation dataframe finished construction at 18:25:44
C:\simviz\vidigi\vidigi\animation.py:1191: UserWarning:

`step_snapshot_max` is not a multiple of `wrap_queues_at`.The animation will display better if this is resolved.
Placement dataframe finished construction at 18:25:45
Output animation generation complete at 18:25:46
Total Time Elapsed: 1.82 seconds