|
5 | 5 | "id": "0be7dabf-cb34-4faf-abb1-e2c8e735beda", |
6 | 6 | "metadata": {}, |
7 | 7 | "source": [ |
8 | | - "# Setting initial conditions\n", |
| 8 | + "# Setting Initial Conditions\n", |
9 | 9 | "\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", |
10 | 11 | "\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", |
12 | 31 | "\n", |
13 | 32 | "* We load patients into the model before we begin the run. \n", |
14 | 33 | "* 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", |
15 | 34 | "* 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", |
16 | 35 | "* This is the same problem faced when starting the model from time zero with no patients.\n", |
17 | 36 | "\n", |
18 | 37 | "**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", |
22 | 41 | " * You will need to do some analysis to ensure this is working acceptably.\n", |
23 | 42 | " * The warm-up pushes patients into service and also resets results collection. (deletes an initial transient)." |
24 | 43 | ] |
|
83 | 102 | "\n", |
84 | 103 | "# Acute LoS (Lognormal)\n", |
85 | 104 | "ACUTE_LOS_MEAN = 7.0\n", |
86 | | - "ACUTE_LOC_STD = 1.0\n", |
| 105 | + "ACUTE_LOS_STD = 1.0\n", |
87 | 106 | "\n", |
88 | 107 | "# initial conditions for acute queue + service \n", |
89 | 108 | "# if there are 9 beds then 10 = 1 queuing\n", |
|
93 | 112 | "\n", |
94 | 113 | "INIT_COND_PARAMS = {\n", |
95 | 114 | " \"mode\": \"fixed\",\n", |
96 | | - " \"fixed\": 8,\n", |
| 115 | + " \"fixed\": 10,\n", |
97 | 116 | " \"rnd\": {\n", |
98 | 117 | " \"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", |
101 | 121 | "}\n", |
102 | 122 | " \n", |
103 | 123 | "# sampling settings\n", |
|
129 | 149 | "source": [ |
130 | 150 | "def trace(msg):\n", |
131 | 151 | " \"\"\"\n", |
132 | | - " Turing printing of events on and off.\n", |
| 152 | + " Turning printing of events on and off.\n", |
133 | 153 | "\n", |
134 | 154 | " Params:\n", |
135 | 155 | " -------\n", |
|
159 | 179 | " \"\"\"\n", |
160 | 180 | " Encapsulates the concept of an experiment 🧪 for the stroke pathway\n", |
161 | 181 | " 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", |
162 | 185 | " \"\"\"\n", |
163 | 186 | " def __init__(\n", |
164 | 187 | " self,\n", |
165 | 188 | " random_number_set=DEFAULT_RND_SET,\n", |
166 | 189 | " n_streams=N_STREAMS,\n", |
167 | 190 | " iat_strokes=IAT_STROKES,\n", |
168 | 191 | " acute_los_mean=ACUTE_LOS_MEAN,\n", |
169 | | - " acute_los_std=ACUTE_LOC_STD,\n", |
| 192 | + " acute_los_std=ACUTE_LOS_STD,\n", |
170 | 193 | " n_acute_beds=N_ACUTE_BEDS,\n", |
171 | 194 | " init_cond_params=INIT_COND_PARAMS, \n", |
172 | 195 | " ):\n", |
|
182 | 205 | " self.acute_los_mean = acute_los_mean\n", |
183 | 206 | " self.acute_los_std = acute_los_std\n", |
184 | 207 | "\n", |
185 | | - " # stored initial conditions\n", |
| 208 | + " # ---- stored initial conditions -------\n", |
186 | 209 | " self.init_cond_params = init_cond_params\n", |
187 | 210 | "\n", |
188 | 211 | " # place holder for resources\n", |
|
198 | 221 | " def set_random_no_set(self, random_number_set):\n", |
199 | 222 | " \"\"\"\n", |
200 | 223 | " Controls the random sampling\n", |
| 224 | + " \n", |
201 | 225 | " Parameters:\n", |
202 | | - " ----------\n", |
| 226 | + " -----------\n", |
203 | 227 | " random_number_set: int\n", |
204 | 228 | " Used to control the set of pseudo random numbers used by\n", |
205 | 229 | " the distributions in the simulation.\n", |
|
231 | 255 | " self.init_conds = FixedDistribution(\n", |
232 | 256 | " self.init_cond_params[\"fixed\"]\n", |
233 | 257 | " )\n", |
234 | | - " else:\n", |
| 258 | + " elif self.init_cond_params[\"mode\"] == \"rnd\":\n", |
235 | 259 | " self.init_conds = DiscreteEmpirical(\n", |
236 | 260 | " values = self.init_cond_params[\"rnd\"][\"values\"],\n", |
237 | 261 | " freq = self.init_cond_params[\"rnd\"][\"freq\"],\n", |
238 | 262 | " random_seed=self.seeds[2]\n", |
239 | 263 | " )\n", |
| 264 | + " else:\n", |
| 265 | + " raise ValueError(\"Initial conditions mode must be 'fixed' or 'rnd'\")\n", |
240 | 266 | " \n", |
241 | 267 | "\n", |
242 | 268 | " def init_results_variables(self):\n", |
|
273 | 299 | " Parameters:\n", |
274 | 300 | " ----------\n", |
275 | 301 | " 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", |
277 | 303 | "\n", |
278 | 304 | " env: simpy.Environment\n", |
279 | 305 | " The simpy environment\n", |
|
296 | 322 | "\n", |
297 | 323 | "The key things to recognise are \n", |
298 | 324 | "\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" |
300 | 326 | ] |
301 | 327 | }, |
302 | 328 | { |
|
307 | 333 | "outputs": [], |
308 | 334 | "source": [ |
309 | 335 | "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", |
312 | 337 | " \n", |
313 | 338 | " Parameters:\n", |
314 | 339 | " -----------\n", |
|
329 | 354 | " acute_admit_time = env.now\n", |
330 | 355 | " wait_for_acute = acute_admit_time - arrival_time\n", |
331 | 356 | "\n", |
| 357 | + " # used to avoid collecting stats from initial conditions...\n", |
332 | 358 | " if collect_results:\n", |
333 | 359 | " args.results['waiting_acute'].append(wait_for_acute)\n", |
334 | 360 | " \n", |
|
396 | 422 | "source": [ |
397 | 423 | "def setup_initial_conditions(\n", |
398 | 424 | " env: simpy.Environment, \n", |
399 | | - " args: Experiment,\n", |
400 | | - " collect_results: bool = False,\n", |
| 425 | + " args: Experiment\n", |
401 | 426 | "):\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", |
404 | 428 | "\n", |
405 | | - " Parmaters:\n", |
406 | | - " ---------\n", |
| 429 | + " Parameters:\n", |
| 430 | + " -----------\n", |
407 | 431 | " env: simpy.Environment\n", |
408 | 432 | " The simpy environment for the simulation\n", |
409 | 433 | "\n", |
410 | 434 | " 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", |
416 | 436 | " \"\"\"\n", |
417 | 437 | " # sample the no. patients to load into queue\n", |
418 | 438 | " patients_to_load = args.init_conds.sample()\n", |
| 439 | + " trace(f\"Patient to load: {patients_to_load}\")\n", |
419 | 440 | "\n", |
| 441 | + " # collect results or not?\n", |
| 442 | + " collect_results = args.init_cond_params[\"collect_results\"]\n", |
| 443 | + " \n", |
420 | 444 | " for initial_id in range(1, patients_to_load+1):\n", |
421 | 445 | " # Create patients with negative IDs to distinguish them as init cond.\n", |
422 | 446 | " # we may or may not want collect results for initial conditions\n", |
|
477 | 501 | " # simpy resources\n", |
478 | 502 | " experiment.acute_ward = simpy.Resource(env, experiment.n_acute_beds)\n", |
479 | 503 | "\n", |
480 | | - " # load the acute stroke queue\n", |
481 | | - " setup_initial_conditions(env, experiment)\n", |
| 504 | + " trace(\"--- Pre-Simulation Actions ---\")\n", |
482 | 505 | "\n", |
483 | 506 | " # schedule a warm_up period\n", |
484 | 507 | " env.process(warmup_complete(wu_period, env, experiment))\n", |
485 | 508 | " \n", |
| 509 | + " # load the acute stroke queue\n", |
| 510 | + " setup_initial_conditions(env, experiment)\n", |
| 511 | + " \n", |
486 | 512 | " # we pass all arrival generators to simpy \n", |
487 | 513 | " env.process(stroke_arrivals_generator(env, experiment))\n", |
488 | 514 | "\n", |
| 515 | + " trace(\"--- Start Simulation ---\")\n", |
| 516 | + " \n", |
489 | 517 | " # run model\n", |
490 | 518 | " env.run(until=wu_period+rc_period)\n", |
491 | 519 | "\n", |
|
501 | 529 | }, |
502 | 530 | { |
503 | 531 | "cell_type": "code", |
504 | | - "execution_count": 11, |
| 532 | + "execution_count": null, |
505 | 533 | "id": "caf52390-5455-4fa1-bb22-60b5b91ad8d0", |
506 | 534 | "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": [], |
580 | 536 | "source": [ |
581 | 537 | "TRACE = True\n", |
| 538 | + "\n", |
| 539 | + "# settings dictionary\n", |
582 | 540 | "init_cond_params = INIT_COND_PARAMS.copy()\n", |
583 | 541 | "init_cond_params[\"mode\"] = \"fixed\"\n", |
584 | 542 | "\n", |
585 | | - "# uncomment to vary the fixed amount 10 = 1 in queue.\n", |
| 543 | + "# vary the fixed amount. Interpretation 10 = 1 in queue.\n", |
586 | 544 | "init_cond_params[\"fixed\"] = 10\n", |
587 | 545 | "\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", |
588 | 550 | "experiment = Experiment(init_cond_params=init_cond_params)\n", |
| 551 | + "\n", |
589 | 552 | "results = single_run(experiment, rep=1, wu_period=0.0, rc_period=10.0)\n", |
590 | 553 | "results" |
591 | 554 | ] |
592 | 555 | }, |
593 | 556 | { |
594 | 557 | "cell_type": "code", |
595 | | - "execution_count": 12, |
| 558 | + "execution_count": null, |
596 | 559 | "id": "0aaef408-09ca-49e0-8d39-f4a088a4ef1b", |
597 | 560 | "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": [], |
618 | 562 | "source": [ |
619 | 563 | "experiment.results" |
620 | 564 | ] |
| 565 | + }, |
| 566 | + { |
| 567 | + "cell_type": "code", |
| 568 | + "execution_count": null, |
| 569 | + "id": "9d5266e1-d8f3-4755-838b-89b9b416faef", |
| 570 | + "metadata": {}, |
| 571 | + "outputs": [], |
| 572 | + "source": [] |
621 | 573 | } |
622 | 574 | ], |
623 | 575 | "metadata": { |
|
0 commit comments