Skip to content
Merged

Init #14

Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
206 changes: 79 additions & 127 deletions content/14_initial_conditions.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,39 @@
"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",
"<img src=\"img/acute_stroke_pathway.png\" alt=\"stroke pathway\" width=\"400\"/>\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",
"* 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",
"* 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)."
]
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -159,14 +179,17 @@
" \"\"\"\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",
" random_number_set=DEFAULT_RND_SET,\n",
" 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",
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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"
]
},
{
Expand All @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
Loading