|
| 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 | + ) |
0 commit comments