Skip to content

Commit a1c3e0e

Browse files
committed
Add reneging example animation
1 parent 6e8ff3e commit a1c3e0e

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

animation_examples/single_resource_priority.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ def show_priority_icon(row):
116116
override_y_max=250,
117117
plotly_height=600,
118118
plotly_width=1100,
119+
custom_resource_icon="☐",
120+
resource_icon_size=80,
119121
).update_layout(
120122
plot_bgcolor='white',
121123
)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
from vidigi.logging import EventLogger
2+
from vidigi.utils import EventPosition, create_event_position_df
3+
from vidigi.animation import animate_activity_log, generate_animation
4+
from vidigi.prep import generate_animation_df, reshape_for_animations
5+
from vidigi.resources import VidigiStore
6+
import simpy
7+
import numpy as np
8+
9+
class g:
10+
n_cubicles = 1
11+
leaving_reasons = [
12+
"I've had<br>enough",
13+
"I can't<br>wait any<br>longer",
14+
"I'm going<br>to miss<br>my bus",
15+
"This queue<br>is<br>ridiculous",
16+
"I'm<br>bored",
17+
"What a<br>useless<br>service!",
18+
"My car<br>parking has<br>run out"
19+
]
20+
21+
class Patient:
22+
def __init__(self, p_id, patience):
23+
self.id = p_id
24+
self.patience = patience
25+
26+
class SimpleActivityModel:
27+
def __init__(self, master_seed=42):
28+
self.env = simpy.Environment()
29+
self.patient_counter = 0
30+
self.patient_inter = 3.5
31+
self.logger = EventLogger(env=self.env)
32+
33+
# Seed setup using numpy's SeedSequence
34+
self.master_seed = master_seed
35+
self.seed_seq = np.random.SeedSequence(master_seed)
36+
self.rng = np.random.default_rng(self.seed_seq)
37+
38+
self.cubicles = VidigiStore(self.env, num_resources=g.n_cubicles)
39+
40+
def generate_arrivals(self):
41+
while True:
42+
self.patient_counter += 1
43+
p = Patient(self.patient_counter, patience=self.rng.uniform(low=5, high=20))
44+
self.logger.log_arrival(entity_id=p.id)
45+
self.env.process(self.patient_journey(p))
46+
sampled_inter = self.rng.exponential(scale=self.patient_inter)
47+
yield self.env.timeout(sampled_inter)
48+
49+
def patient_journey(self, patient):
50+
self.logger.log_queue(entity_id=patient.id, event="wait_here")
51+
52+
with self.cubicles.request() as req:
53+
results = yield req | self.env.timeout(patient.patience)
54+
# print(req)
55+
# print(results)
56+
57+
if req in results:
58+
59+
cubicle = results[req]
60+
61+
self.logger.log_resource_use_start(
62+
entity_id=patient.id,
63+
event="start_treatment",
64+
resource_id=cubicle.id_attribute
65+
)
66+
67+
yield self.env.timeout(self.rng.uniform(low=1, high=10))
68+
69+
self.logger.log_resource_use_end(
70+
entity_id=patient.id,
71+
event="end_treatment",
72+
resource_id=cubicle.id_attribute
73+
)
74+
else:
75+
# They decide to leave
76+
self.logger.log_queue(
77+
entity_id=patient.id,
78+
event="reneging",
79+
leaving_reason=self.rng.choice(g.leaving_reasons, size=1)
80+
)
81+
# We'll keep them on screen for a little while to make it clear what's happening
82+
yield self.env.timeout(5)
83+
# Make sure to cancel their request (otherwise they'll leave and it will)
84+
# never be fulfilled, but they'll block it for everyone else too
85+
req.cancel()
86+
87+
self.logger.log_departure(entity_id=patient.id)
88+
89+
def run(self):
90+
self.env.process(self.generate_arrivals())
91+
self.env.run(until=120)
92+
93+
model = SimpleActivityModel()
94+
model.run()
95+
event_log = model.logger.to_dataframe()
96+
# event_log.to_csv("test_log.csv")
97+
98+
event_position_df = create_event_position_df([
99+
EventPosition(event="wait_here", x=150 , y=100 , label="Wait Here!"),
100+
EventPosition(event="start_treatment", x=150 , y=25 , resource="n_cubicles", label="Be Treated"),
101+
EventPosition(event="reneging", x=250 , y=225 , label=" "),
102+
EventPosition(event="depart", x=250, y=50, label="Exit")
103+
])
104+
105+
# animate_activity_log(
106+
# event_log = event_log,
107+
# event_position_df = event_position_df,
108+
# scenario=g(),
109+
# every_x_time_units=1,
110+
# limit_duration=180,
111+
# override_x_max=300,
112+
# override_y_max=250,
113+
# plotly_height=600,
114+
# plotly_width=1100,
115+
# display_stage_labels=True,
116+
# gap_between_entities=20,
117+
# entity_icon_size=40,
118+
# wrap_queues_at=5,
119+
# gap_between_resources=30,
120+
# gap_between_queue_rows=30,
121+
# resource_icon_size=80,
122+
# simulation_time_unit="minutes",
123+
# custom_resource_icon="☐",
124+
# resource_opacity=0.7,
125+
# debug_write_intermediate_objects=True
126+
# ).update_layout(
127+
# plot_bgcolor='white',
128+
# )
129+
130+
131+
animation_df = generate_animation_df(
132+
full_entity_df=reshape_for_animations(
133+
event_log=event_log,
134+
every_x_time_units=1,
135+
limit_duration=120,
136+
step_snapshot_max=100
137+
),
138+
event_position_df=event_position_df,
139+
gap_between_entities=30,
140+
wrap_queues_at=5,
141+
gap_between_queue_rows=30
142+
)
143+
144+
def show_priority_icon(row):
145+
if "more" not in row["icon"]:
146+
if row["event"] == "reneging":
147+
return f'{row["icon"]}<br>{row["leaving_reason"][0]}'
148+
else:
149+
return row["icon"]
150+
else:
151+
return row["icon"]
152+
153+
animation_df = animation_df.assign(
154+
icon=animation_df.apply(show_priority_icon, axis=1)
155+
)
156+
157+
generate_animation(
158+
animation_df,
159+
event_position_df,
160+
scenario=g(),
161+
entity_icon_size=20,
162+
override_x_max=300,
163+
override_y_max=300,
164+
plotly_height=600,
165+
plotly_width=1100,
166+
custom_resource_icon="☐",
167+
resource_icon_size=80,
168+
).update_layout(
169+
plot_bgcolor='white',
170+
)

lambda_des_presentation_part_2.qmd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,12 @@ But it's also possible to have priority-based queueing.
171171

172172
## Reneging
173173

174+
If people run out of patience, they may leave the queue (renege).
175+
176+
```{python}
177+
#| echo: false
178+
{{< include animation_examples/single_resource_renege.py >}}
179+
```
174180

175181
## Balking
176182

0 commit comments

Comments
 (0)