diff --git a/content/14_initial_conditions.ipynb b/content/14_initial_conditions.ipynb index 4ae93ad..45e9167 100644 --- a/content/14_initial_conditions.ipynb +++ b/content/14_initial_conditions.ipynb @@ -5,10 +5,29 @@ "id": "0be7dabf-cb34-4faf-abb1-e2c8e735beda", "metadata": {}, "source": [ - "# Setting initial conditions\n", + "# Setting Initial Conditions\n", "\n", + "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", "\n", - "**IMPORTANT: There is still an initialisation bias problem:**\n", + "๐ŸŽ“ **Why use Initial-Conditions over a Warm-Up?**\n", + "\n", + "* 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", + "\n", + "* Setting initial-conditions also allows you to test specific starting scenarios, such as \"What happens if the day begins with a full ward?\"\n", + "\n", + "**In general, adding initial conditions is more complex than including a warm-up period.**\n", + "\n", + "Initial conditions could be \n", + "\n", + "1. Fixed i.e. the same number of patients for each replication.\n", + "2. Random i.e. sampled from a discrete distribution specified by a user.\n", + "\n", + "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", + "\n", + "\"stroke\n", + "\n", + "\n", + "## IMPORTANT: There is still an initialisation bias problem\n", "\n", "* We load patients into the model before we begin the run. \n", "* 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", @@ -16,9 +35,9 @@ "* This is the same problem faced when starting the model from time zero with no patients.\n", "\n", "**Some Options:**\n", - "* Include a setting in a process to switch results collection on and off.\n", - "* Code a seperate process for initial conditions that does not include results collection.\n", - "* Mixed initial conditions and (a shorter) warm-up period.\n", + "1. Include a setting in a process to switch results collection on and off.\n", + "2. Code a seperate process for initial conditions that does not include results collection.\n", + "3. Mixed initial conditions and (a shorter) warm-up period.\n", " * You will need to do some analysis to ensure this is working acceptably.\n", " * The warm-up pushes patients into service and also resets results collection. (deletes an initial transient)." ] @@ -83,7 +102,7 @@ "\n", "# Acute LoS (Lognormal)\n", "ACUTE_LOS_MEAN = 7.0\n", - "ACUTE_LOC_STD = 1.0\n", + "ACUTE_LOS_STD = 1.0\n", "\n", "# initial conditions for acute queue + service \n", "# if there are 9 beds then 10 = 1 queuing\n", @@ -93,11 +112,12 @@ "\n", "INIT_COND_PARAMS = {\n", " \"mode\": \"fixed\",\n", - " \"fixed\": 8,\n", + " \"fixed\": 10,\n", " \"rnd\": {\n", " \"values\":[8, 9, 10, 11, 12, 13],\n", - " \"freq\":[25, 25, 2, 1, 1, 1]\n", - " }\n", + " \"freq\":[15, 15, 25, 20, 5, 5]\n", + " },\n", + " \"collect_results\": False\n", "}\n", " \n", "# sampling settings\n", @@ -129,7 +149,7 @@ "source": [ "def trace(msg):\n", " \"\"\"\n", - " Turing printing of events on and off.\n", + " Turning printing of events on and off.\n", "\n", " Params:\n", " -------\n", @@ -159,6 +179,9 @@ " \"\"\"\n", " Encapsulates the concept of an experiment ๐Ÿงช for the stroke pathway\n", " bed blocking simulator. Manages parameters, PRNG streams and results.\n", + "\n", + " There is a new parameter `init_cond_params` that stores the initial\n", + " conditions to use in an experiment.\n", " \"\"\"\n", " def __init__(\n", " self,\n", @@ -166,7 +189,7 @@ " n_streams=N_STREAMS,\n", " iat_strokes=IAT_STROKES,\n", " acute_los_mean=ACUTE_LOS_MEAN,\n", - " acute_los_std=ACUTE_LOC_STD,\n", + " acute_los_std=ACUTE_LOS_STD,\n", " n_acute_beds=N_ACUTE_BEDS,\n", " init_cond_params=INIT_COND_PARAMS, \n", " ):\n", @@ -182,7 +205,7 @@ " self.acute_los_mean = acute_los_mean\n", " self.acute_los_std = acute_los_std\n", "\n", - " # stored initial conditions\n", + " # ---- stored initial conditions -------\n", " self.init_cond_params = init_cond_params\n", "\n", " # place holder for resources\n", @@ -198,8 +221,9 @@ " def set_random_no_set(self, random_number_set):\n", " \"\"\"\n", " Controls the random sampling\n", + " \n", " Parameters:\n", - " ----------\n", + " -----------\n", " random_number_set: int\n", " Used to control the set of pseudo random numbers used by\n", " the distributions in the simulation.\n", @@ -231,12 +255,14 @@ " self.init_conds = FixedDistribution(\n", " self.init_cond_params[\"fixed\"]\n", " )\n", - " else:\n", + " elif self.init_cond_params[\"mode\"] == \"rnd\":\n", " self.init_conds = DiscreteEmpirical(\n", " values = self.init_cond_params[\"rnd\"][\"values\"],\n", " freq = self.init_cond_params[\"rnd\"][\"freq\"],\n", " random_seed=self.seeds[2]\n", " )\n", + " else:\n", + " raise ValueError(\"Initial conditions mode must be 'fixed' or 'rnd'\")\n", " \n", "\n", " def init_results_variables(self):\n", @@ -273,7 +299,7 @@ " Parameters:\n", " ----------\n", " warm_up_period: float\n", - " Duration of warm-up period in simultion time units\n", + " Duration of warm-up period in simulation time units\n", "\n", " env: simpy.Environment\n", " The simpy environment\n", @@ -296,7 +322,7 @@ "\n", "The key things to recognise are \n", "\n", - "* We include a optional parameter called `collection_results` that defaults to `True`. We may set this `False` in our functions that setup initial conditions" + "* We include a optional parameter called `collect_results` that defaults to `True`. We may set this `False` in our functions that setup initial conditions" ] }, { @@ -307,8 +333,7 @@ "outputs": [], "source": [ "def acute_stroke_pathway(patient_id, env, args, collect_results=True):\n", - " \"\"\"Process a patient through the acute ward\n", - " Simpy generator function.\n", + " \"\"\"Process a patient through the acute ward. Simpy generator function.\n", " \n", " Parameters:\n", " -----------\n", @@ -329,6 +354,7 @@ " acute_admit_time = env.now\n", " wait_for_acute = acute_admit_time - arrival_time\n", "\n", + " # used to avoid collecting stats from initial conditions...\n", " if collect_results:\n", " args.results['waiting_acute'].append(wait_for_acute)\n", " \n", @@ -396,27 +422,25 @@ "source": [ "def setup_initial_conditions(\n", " env: simpy.Environment, \n", - " args: Experiment,\n", - " collect_results: bool = False,\n", + " args: Experiment\n", "):\n", - " \"\"\"Set up initial conditions with patients already in the acute\n", - " stroke queue\n", + " \"\"\"Set up initial conditions with patients already in the acute stroke queue\n", "\n", - " Parmaters:\n", - " ---------\n", + " Parameters:\n", + " -----------\n", " env: simpy.Environment\n", " The simpy environment for the simulation\n", "\n", " args: Experiment\n", - " The settings and input parameters for the simulation.\n", - "\n", - " collect_results: bool, optional (default = False)#\n", - " Should results be collected for initial conditions patients?\n", - " \n", + " The settings and input parameters for the simulation. \n", " \"\"\"\n", " # sample the no. patients to load into queue\n", " patients_to_load = args.init_conds.sample()\n", + " trace(f\"Patient to load: {patients_to_load}\")\n", "\n", + " # collect results or not?\n", + " collect_results = args.init_cond_params[\"collect_results\"]\n", + " \n", " for initial_id in range(1, patients_to_load+1):\n", " # Create patients with negative IDs to distinguish them as init cond.\n", " # we may or may not want collect results for initial conditions\n", @@ -477,15 +501,19 @@ " # simpy resources\n", " experiment.acute_ward = simpy.Resource(env, experiment.n_acute_beds)\n", "\n", - " # load the acute stroke queue\n", - " setup_initial_conditions(env, experiment)\n", + " trace(\"--- Pre-Simulation Actions ---\")\n", "\n", " # schedule a warm_up period\n", " env.process(warmup_complete(wu_period, env, experiment))\n", " \n", + " # load the acute stroke queue\n", + " setup_initial_conditions(env, experiment)\n", + " \n", " # we pass all arrival generators to simpy \n", " env.process(stroke_arrivals_generator(env, experiment))\n", "\n", + " trace(\"--- Start Simulation ---\")\n", + " \n", " # run model\n", " env.run(until=wu_period+rc_period)\n", "\n", @@ -501,123 +529,47 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "caf52390-5455-4fa1-bb22-60b5b91ad8d0", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.00: Patient -1 loaded into queue\n", - "0.00: Patient -2 loaded into queue\n", - "0.00: Patient -3 loaded into queue\n", - "0.00: Patient -4 loaded into queue\n", - "0.00: Patient -5 loaded into queue\n", - "0.00: Patient -6 loaded into queue\n", - "0.00: Patient -7 loaded into queue\n", - "0.00: Patient -8 loaded into queue\n", - "0.00: Patient -9 loaded into queue\n", - "0.00: Patient -10 loaded into queue\n", - "0.00: Patient -1 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -2 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -3 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -4 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -5 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -6 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -7 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -8 admitted to acute ward.(waited 0.00 days)\n", - "0.00: Patient -9 admitted to acute ward.(waited 0.00 days)\n", - "0.00: ๐Ÿฅต Warm up complete.\n", - "2.74: Patient 1. Stroke arrival.\n", - "2.78: Patient 2. Stroke arrival.\n", - "3.36: Patient 3. Stroke arrival.\n", - "4.42: Patient 4. Stroke arrival.\n", - "4.68: Patient 5. Stroke arrival.\n", - "5.80: Patient -3 discharged.\n", - "5.80: Patient -10 admitted to acute ward.(waited 5.80 days)\n", - "5.80: Patient -8 discharged.\n", - "5.80: Patient 1 admitted to acute ward.(waited 3.06 days)\n", - "5.97: Patient 6. Stroke arrival.\n", - "6.08: Patient -6 discharged.\n", - "6.08: Patient 2 admitted to acute ward.(waited 3.30 days)\n", - "6.22: Patient -7 discharged.\n", - "6.22: Patient 3 admitted to acute ward.(waited 2.86 days)\n", - "6.33: Patient 7. Stroke arrival.\n", - "7.28: Patient 8. Stroke arrival.\n", - "7.41: Patient -4 discharged.\n", - "7.41: Patient 4 admitted to acute ward.(waited 2.99 days)\n", - "7.43: Patient 9. Stroke arrival.\n", - "7.55: Patient 10. Stroke arrival.\n", - "7.94: Patient -5 discharged.\n", - "7.94: Patient 5 admitted to acute ward.(waited 3.26 days)\n", - "8.11: Patient -2 discharged.\n", - "8.11: Patient 6 admitted to acute ward.(waited 2.14 days)\n", - "8.26: Patient 11. Stroke arrival.\n", - "8.42: Patient 12. Stroke arrival.\n", - "8.68: Patient 13. Stroke arrival.\n", - "8.72: Patient 14. Stroke arrival.\n", - "8.82: Patient -9 discharged.\n", - "8.82: Patient 7 admitted to acute ward.(waited 2.50 days)\n", - "8.95: Patient 15. Stroke arrival.\n", - "9.18: Patient 16. Stroke arrival.\n", - "9.87: Patient -1 discharged.\n", - "9.87: Patient 8 admitted to acute ward.(waited 2.58 days)\n", - "9.92: Patient 17. Stroke arrival.\n" - ] - }, - { - "data": { - "text/plain": [ - "{'mean_acute_wait': 2.8362718457918676}" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "TRACE = True\n", + "\n", + "# settings dictionary\n", "init_cond_params = INIT_COND_PARAMS.copy()\n", "init_cond_params[\"mode\"] = \"fixed\"\n", "\n", - "# uncomment to vary the fixed amount 10 = 1 in queue.\n", + "# vary the fixed amount. Interpretation 10 = 1 in queue.\n", "init_cond_params[\"fixed\"] = 10\n", "\n", + "# vary if we collect results from initial condition stroke patients\n", + "init_cond_params[\"collect_results\"] = False\n", + "\n", + "# create experiment and pass in initial conditions\n", "experiment = Experiment(init_cond_params=init_cond_params)\n", + "\n", "results = single_run(experiment, rep=1, wu_period=0.0, rc_period=10.0)\n", "results" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "id": "0aaef408-09ca-49e0-8d39-f4a088a4ef1b", "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "{'n_arrivals': 17,\n", - " 'waiting_acute': [3.0586175577655674,\n", - " 3.303449502025928,\n", - " 2.857579305401165,\n", - " 2.9908615153438225,\n", - " 3.2622390398417513,\n", - " 2.136685286636955,\n", - " 2.496306611009193,\n", - " 2.58443594831056]}" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "experiment.results" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9d5266e1-d8f3-4755-838b-89b9b416faef", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {