Skip to content

Commit b9802f1

Browse files
authored
Merge pull request #14 from pythonhealthdatascience/init
Init
2 parents ce59693 + 5f4e1bf commit b9802f1

File tree

1 file changed

+79
-127
lines changed

1 file changed

+79
-127
lines changed

content/14_initial_conditions.ipynb

Lines changed: 79 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,39 @@
55
"id": "0be7dabf-cb34-4faf-abb1-e2c8e735beda",
66
"metadata": {},
77
"source": [
8-
"# Setting initial conditions\n",
8+
"# Setting Initial Conditions\n",
99
"\n",
10+
"An alternative to including a warm-up period in a model is to set initial conditions. In our stroke example this would mean adding patients to beds and if required the admission queue. \n",
1011
"\n",
11-
"**IMPORTANT: There is still an initialisation bias problem:**\n",
12+
"🎓 **Why use Initial-Conditions over a Warm-Up?**\n",
13+
"\n",
14+
"* While warm-up periods are simpler to code, they can also increase how long it takes to run your model. Setting initial conditions is in theory computationally more efficient (no wasted run-time).\n",
15+
"\n",
16+
"* Setting initial-conditions also allows you to test specific starting scenarios, such as \"What happens if the day begins with a full ward?\"\n",
17+
"\n",
18+
"**In general, adding initial conditions is more complex than including a warm-up period.**\n",
19+
"\n",
20+
"Initial conditions could be \n",
21+
"\n",
22+
"1. Fixed i.e. the same number of patients for each replication.\n",
23+
"2. Random i.e. sampled from a discrete distribution specified by a user.\n",
24+
"\n",
25+
"In this notebook we will learn how to add initial conditions to a SimPy model. We will again use the acute stroke pathway model from the warm-up example notebook. Our initial conditions approach will load stroke patients to the queue before the simulation begins.\n",
26+
"\n",
27+
"<img src=\"img/acute_stroke_pathway.png\" alt=\"stroke pathway\" width=\"400\"/>\n",
28+
"\n",
29+
"\n",
30+
"## IMPORTANT: There is still an initialisation bias problem\n",
1231
"\n",
1332
"* We load patients into the model before we begin the run. \n",
1433
"* If there are $n$ acute stroke beds then the first $n$ patients loaded into the queue will begin service immediately and have a zero queuing time.\n",
1534
"* This applies if we have one queue in the model or if we have multiple queues and activities: the time in system metrics will be biased for initial condition patients.\n",
1635
"* This is the same problem faced when starting the model from time zero with no patients.\n",
1736
"\n",
1837
"**Some Options:**\n",
19-
"* Include a setting in a process to switch results collection on and off.\n",
20-
"* Code a seperate process for initial conditions that does not include results collection.\n",
21-
"* Mixed initial conditions and (a shorter) warm-up period.\n",
38+
"1. Include a setting in a process to switch results collection on and off.\n",
39+
"2. Code a seperate process for initial conditions that does not include results collection.\n",
40+
"3. Mixed initial conditions and (a shorter) warm-up period.\n",
2241
" * You will need to do some analysis to ensure this is working acceptably.\n",
2342
" * The warm-up pushes patients into service and also resets results collection. (deletes an initial transient)."
2443
]
@@ -83,7 +102,7 @@
83102
"\n",
84103
"# Acute LoS (Lognormal)\n",
85104
"ACUTE_LOS_MEAN = 7.0\n",
86-
"ACUTE_LOC_STD = 1.0\n",
105+
"ACUTE_LOS_STD = 1.0\n",
87106
"\n",
88107
"# initial conditions for acute queue + service \n",
89108
"# if there are 9 beds then 10 = 1 queuing\n",
@@ -93,11 +112,12 @@
93112
"\n",
94113
"INIT_COND_PARAMS = {\n",
95114
" \"mode\": \"fixed\",\n",
96-
" \"fixed\": 8,\n",
115+
" \"fixed\": 10,\n",
97116
" \"rnd\": {\n",
98117
" \"values\":[8, 9, 10, 11, 12, 13],\n",
99-
" \"freq\":[25, 25, 2, 1, 1, 1]\n",
100-
" }\n",
118+
" \"freq\":[15, 15, 25, 20, 5, 5]\n",
119+
" },\n",
120+
" \"collect_results\": False\n",
101121
"}\n",
102122
" \n",
103123
"# sampling settings\n",
@@ -129,7 +149,7 @@
129149
"source": [
130150
"def trace(msg):\n",
131151
" \"\"\"\n",
132-
" Turing printing of events on and off.\n",
152+
" Turning printing of events on and off.\n",
133153
"\n",
134154
" Params:\n",
135155
" -------\n",
@@ -159,14 +179,17 @@
159179
" \"\"\"\n",
160180
" Encapsulates the concept of an experiment 🧪 for the stroke pathway\n",
161181
" bed blocking simulator. Manages parameters, PRNG streams and results.\n",
182+
"\n",
183+
" There is a new parameter `init_cond_params` that stores the initial\n",
184+
" conditions to use in an experiment.\n",
162185
" \"\"\"\n",
163186
" def __init__(\n",
164187
" self,\n",
165188
" random_number_set=DEFAULT_RND_SET,\n",
166189
" n_streams=N_STREAMS,\n",
167190
" iat_strokes=IAT_STROKES,\n",
168191
" acute_los_mean=ACUTE_LOS_MEAN,\n",
169-
" acute_los_std=ACUTE_LOC_STD,\n",
192+
" acute_los_std=ACUTE_LOS_STD,\n",
170193
" n_acute_beds=N_ACUTE_BEDS,\n",
171194
" init_cond_params=INIT_COND_PARAMS, \n",
172195
" ):\n",
@@ -182,7 +205,7 @@
182205
" self.acute_los_mean = acute_los_mean\n",
183206
" self.acute_los_std = acute_los_std\n",
184207
"\n",
185-
" # stored initial conditions\n",
208+
" # ---- stored initial conditions -------\n",
186209
" self.init_cond_params = init_cond_params\n",
187210
"\n",
188211
" # place holder for resources\n",
@@ -198,8 +221,9 @@
198221
" def set_random_no_set(self, random_number_set):\n",
199222
" \"\"\"\n",
200223
" Controls the random sampling\n",
224+
" \n",
201225
" Parameters:\n",
202-
" ----------\n",
226+
" -----------\n",
203227
" random_number_set: int\n",
204228
" Used to control the set of pseudo random numbers used by\n",
205229
" the distributions in the simulation.\n",
@@ -231,12 +255,14 @@
231255
" self.init_conds = FixedDistribution(\n",
232256
" self.init_cond_params[\"fixed\"]\n",
233257
" )\n",
234-
" else:\n",
258+
" elif self.init_cond_params[\"mode\"] == \"rnd\":\n",
235259
" self.init_conds = DiscreteEmpirical(\n",
236260
" values = self.init_cond_params[\"rnd\"][\"values\"],\n",
237261
" freq = self.init_cond_params[\"rnd\"][\"freq\"],\n",
238262
" random_seed=self.seeds[2]\n",
239263
" )\n",
264+
" else:\n",
265+
" raise ValueError(\"Initial conditions mode must be 'fixed' or 'rnd'\")\n",
240266
" \n",
241267
"\n",
242268
" def init_results_variables(self):\n",
@@ -273,7 +299,7 @@
273299
" Parameters:\n",
274300
" ----------\n",
275301
" warm_up_period: float\n",
276-
" Duration of warm-up period in simultion time units\n",
302+
" Duration of warm-up period in simulation time units\n",
277303
"\n",
278304
" env: simpy.Environment\n",
279305
" The simpy environment\n",
@@ -296,7 +322,7 @@
296322
"\n",
297323
"The key things to recognise are \n",
298324
"\n",
299-
"* We include a optional parameter called `collection_results` that defaults to `True`. We may set this `False` in our functions that setup initial conditions"
325+
"* We include a optional parameter called `collect_results` that defaults to `True`. We may set this `False` in our functions that setup initial conditions"
300326
]
301327
},
302328
{
@@ -307,8 +333,7 @@
307333
"outputs": [],
308334
"source": [
309335
"def acute_stroke_pathway(patient_id, env, args, collect_results=True):\n",
310-
" \"\"\"Process a patient through the acute ward\n",
311-
" Simpy generator function.\n",
336+
" \"\"\"Process a patient through the acute ward. Simpy generator function.\n",
312337
" \n",
313338
" Parameters:\n",
314339
" -----------\n",
@@ -329,6 +354,7 @@
329354
" acute_admit_time = env.now\n",
330355
" wait_for_acute = acute_admit_time - arrival_time\n",
331356
"\n",
357+
" # used to avoid collecting stats from initial conditions...\n",
332358
" if collect_results:\n",
333359
" args.results['waiting_acute'].append(wait_for_acute)\n",
334360
" \n",
@@ -396,27 +422,25 @@
396422
"source": [
397423
"def setup_initial_conditions(\n",
398424
" env: simpy.Environment, \n",
399-
" args: Experiment,\n",
400-
" collect_results: bool = False,\n",
425+
" args: Experiment\n",
401426
"):\n",
402-
" \"\"\"Set up initial conditions with patients already in the acute\n",
403-
" stroke queue\n",
427+
" \"\"\"Set up initial conditions with patients already in the acute stroke queue\n",
404428
"\n",
405-
" Parmaters:\n",
406-
" ---------\n",
429+
" Parameters:\n",
430+
" -----------\n",
407431
" env: simpy.Environment\n",
408432
" The simpy environment for the simulation\n",
409433
"\n",
410434
" args: Experiment\n",
411-
" The settings and input parameters for the simulation.\n",
412-
"\n",
413-
" collect_results: bool, optional (default = False)#\n",
414-
" Should results be collected for initial conditions patients?\n",
415-
" \n",
435+
" The settings and input parameters for the simulation. \n",
416436
" \"\"\"\n",
417437
" # sample the no. patients to load into queue\n",
418438
" patients_to_load = args.init_conds.sample()\n",
439+
" trace(f\"Patient to load: {patients_to_load}\")\n",
419440
"\n",
441+
" # collect results or not?\n",
442+
" collect_results = args.init_cond_params[\"collect_results\"]\n",
443+
" \n",
420444
" for initial_id in range(1, patients_to_load+1):\n",
421445
" # Create patients with negative IDs to distinguish them as init cond.\n",
422446
" # we may or may not want collect results for initial conditions\n",
@@ -477,15 +501,19 @@
477501
" # simpy resources\n",
478502
" experiment.acute_ward = simpy.Resource(env, experiment.n_acute_beds)\n",
479503
"\n",
480-
" # load the acute stroke queue\n",
481-
" setup_initial_conditions(env, experiment)\n",
504+
" trace(\"--- Pre-Simulation Actions ---\")\n",
482505
"\n",
483506
" # schedule a warm_up period\n",
484507
" env.process(warmup_complete(wu_period, env, experiment))\n",
485508
" \n",
509+
" # load the acute stroke queue\n",
510+
" setup_initial_conditions(env, experiment)\n",
511+
" \n",
486512
" # we pass all arrival generators to simpy \n",
487513
" env.process(stroke_arrivals_generator(env, experiment))\n",
488514
"\n",
515+
" trace(\"--- Start Simulation ---\")\n",
516+
" \n",
489517
" # run model\n",
490518
" env.run(until=wu_period+rc_period)\n",
491519
"\n",
@@ -501,123 +529,47 @@
501529
},
502530
{
503531
"cell_type": "code",
504-
"execution_count": 11,
532+
"execution_count": null,
505533
"id": "caf52390-5455-4fa1-bb22-60b5b91ad8d0",
506534
"metadata": {},
507-
"outputs": [
508-
{
509-
"name": "stdout",
510-
"output_type": "stream",
511-
"text": [
512-
"0.00: Patient -1 loaded into queue\n",
513-
"0.00: Patient -2 loaded into queue\n",
514-
"0.00: Patient -3 loaded into queue\n",
515-
"0.00: Patient -4 loaded into queue\n",
516-
"0.00: Patient -5 loaded into queue\n",
517-
"0.00: Patient -6 loaded into queue\n",
518-
"0.00: Patient -7 loaded into queue\n",
519-
"0.00: Patient -8 loaded into queue\n",
520-
"0.00: Patient -9 loaded into queue\n",
521-
"0.00: Patient -10 loaded into queue\n",
522-
"0.00: Patient -1 admitted to acute ward.(waited 0.00 days)\n",
523-
"0.00: Patient -2 admitted to acute ward.(waited 0.00 days)\n",
524-
"0.00: Patient -3 admitted to acute ward.(waited 0.00 days)\n",
525-
"0.00: Patient -4 admitted to acute ward.(waited 0.00 days)\n",
526-
"0.00: Patient -5 admitted to acute ward.(waited 0.00 days)\n",
527-
"0.00: Patient -6 admitted to acute ward.(waited 0.00 days)\n",
528-
"0.00: Patient -7 admitted to acute ward.(waited 0.00 days)\n",
529-
"0.00: Patient -8 admitted to acute ward.(waited 0.00 days)\n",
530-
"0.00: Patient -9 admitted to acute ward.(waited 0.00 days)\n",
531-
"0.00: 🥵 Warm up complete.\n",
532-
"2.74: Patient 1. Stroke arrival.\n",
533-
"2.78: Patient 2. Stroke arrival.\n",
534-
"3.36: Patient 3. Stroke arrival.\n",
535-
"4.42: Patient 4. Stroke arrival.\n",
536-
"4.68: Patient 5. Stroke arrival.\n",
537-
"5.80: Patient -3 discharged.\n",
538-
"5.80: Patient -10 admitted to acute ward.(waited 5.80 days)\n",
539-
"5.80: Patient -8 discharged.\n",
540-
"5.80: Patient 1 admitted to acute ward.(waited 3.06 days)\n",
541-
"5.97: Patient 6. Stroke arrival.\n",
542-
"6.08: Patient -6 discharged.\n",
543-
"6.08: Patient 2 admitted to acute ward.(waited 3.30 days)\n",
544-
"6.22: Patient -7 discharged.\n",
545-
"6.22: Patient 3 admitted to acute ward.(waited 2.86 days)\n",
546-
"6.33: Patient 7. Stroke arrival.\n",
547-
"7.28: Patient 8. Stroke arrival.\n",
548-
"7.41: Patient -4 discharged.\n",
549-
"7.41: Patient 4 admitted to acute ward.(waited 2.99 days)\n",
550-
"7.43: Patient 9. Stroke arrival.\n",
551-
"7.55: Patient 10. Stroke arrival.\n",
552-
"7.94: Patient -5 discharged.\n",
553-
"7.94: Patient 5 admitted to acute ward.(waited 3.26 days)\n",
554-
"8.11: Patient -2 discharged.\n",
555-
"8.11: Patient 6 admitted to acute ward.(waited 2.14 days)\n",
556-
"8.26: Patient 11. Stroke arrival.\n",
557-
"8.42: Patient 12. Stroke arrival.\n",
558-
"8.68: Patient 13. Stroke arrival.\n",
559-
"8.72: Patient 14. Stroke arrival.\n",
560-
"8.82: Patient -9 discharged.\n",
561-
"8.82: Patient 7 admitted to acute ward.(waited 2.50 days)\n",
562-
"8.95: Patient 15. Stroke arrival.\n",
563-
"9.18: Patient 16. Stroke arrival.\n",
564-
"9.87: Patient -1 discharged.\n",
565-
"9.87: Patient 8 admitted to acute ward.(waited 2.58 days)\n",
566-
"9.92: Patient 17. Stroke arrival.\n"
567-
]
568-
},
569-
{
570-
"data": {
571-
"text/plain": [
572-
"{'mean_acute_wait': 2.8362718457918676}"
573-
]
574-
},
575-
"execution_count": 11,
576-
"metadata": {},
577-
"output_type": "execute_result"
578-
}
579-
],
535+
"outputs": [],
580536
"source": [
581537
"TRACE = True\n",
538+
"\n",
539+
"# settings dictionary\n",
582540
"init_cond_params = INIT_COND_PARAMS.copy()\n",
583541
"init_cond_params[\"mode\"] = \"fixed\"\n",
584542
"\n",
585-
"# uncomment to vary the fixed amount 10 = 1 in queue.\n",
543+
"# vary the fixed amount. Interpretation 10 = 1 in queue.\n",
586544
"init_cond_params[\"fixed\"] = 10\n",
587545
"\n",
546+
"# vary if we collect results from initial condition stroke patients\n",
547+
"init_cond_params[\"collect_results\"] = False\n",
548+
"\n",
549+
"# create experiment and pass in initial conditions\n",
588550
"experiment = Experiment(init_cond_params=init_cond_params)\n",
551+
"\n",
589552
"results = single_run(experiment, rep=1, wu_period=0.0, rc_period=10.0)\n",
590553
"results"
591554
]
592555
},
593556
{
594557
"cell_type": "code",
595-
"execution_count": 12,
558+
"execution_count": null,
596559
"id": "0aaef408-09ca-49e0-8d39-f4a088a4ef1b",
597560
"metadata": {},
598-
"outputs": [
599-
{
600-
"data": {
601-
"text/plain": [
602-
"{'n_arrivals': 17,\n",
603-
" 'waiting_acute': [3.0586175577655674,\n",
604-
" 3.303449502025928,\n",
605-
" 2.857579305401165,\n",
606-
" 2.9908615153438225,\n",
607-
" 3.2622390398417513,\n",
608-
" 2.136685286636955,\n",
609-
" 2.496306611009193,\n",
610-
" 2.58443594831056]}"
611-
]
612-
},
613-
"execution_count": 12,
614-
"metadata": {},
615-
"output_type": "execute_result"
616-
}
617-
],
561+
"outputs": [],
618562
"source": [
619563
"experiment.results"
620564
]
565+
},
566+
{
567+
"cell_type": "code",
568+
"execution_count": null,
569+
"id": "9d5266e1-d8f3-4755-838b-89b9b416faef",
570+
"metadata": {},
571+
"outputs": [],
572+
"source": []
621573
}
622574
],
623575
"metadata": {

0 commit comments

Comments
 (0)