From 2c9aa6110995a7ee7a050511e185adf35009e5a9 Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 14:12:09 +0100 Subject: [PATCH 01/11] chore(changes): +missing 0.4.0 --- docs/03_CHANGES.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/03_CHANGES.md b/docs/03_CHANGES.md index ef6eec3..d13454a 100644 --- a/docs/03_CHANGES.md +++ b/docs/03_CHANGES.md @@ -1,5 +1,12 @@ # Change log +## v0.4.0 + +### Changes + +* BUILD: Dropped legacy `setuptools` and migrated package build to `hatch` +* BUILD: Removed `setup.py`, `requirements.txt` and `MANIFEST` in favour of `pyproject.toml` + ## v0.3.0 * Distributions classes now have python type hints. From f39adb8aa30976c90f35264e37eb2956cc4223bf Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 14:16:41 +0100 Subject: [PATCH 02/11] feat(trace): +treat-sim notebook --- docs/03_trace/04_model.ipynb | 2417 ++++++++++++++++++++++++++++++++++ 1 file changed, 2417 insertions(+) create mode 100644 docs/03_trace/04_model.ipynb diff --git a/docs/03_trace/04_model.ipynb b/docs/03_trace/04_model.ipynb new file mode 100644 index 0000000..79f86f8 --- /dev/null +++ b/docs/03_trace/04_model.ipynb @@ -0,0 +1,2417 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "64094656", + "metadata": {}, + "source": [ + "# SimPy: Treatment Centre\n", + "\n", + "```{admonition} Running the model\n", + "**This version of the simulation model is in this notebook and can be executed online. You do not need to install anything on your computer.** Move you mourse cursor over the rocket ship icon above and choose one of the two options: Binder (launches an external remote instance of Jupyter-Lab) or Thebe (code cells are excutable within the book).\n", + "```\n", + "> See [detailed instructions](./03_instr.md) for more help.\n", + "---" + ] + }, + { + "cell_type": "markdown", + "id": "1164a9e9", + "metadata": {}, + "source": [ + "## 1. Imports\n", + "\n", + "The model is provided with a conda virtual environment `stars_treat_sim`. Details are available in the [Github repo](https://github.com/pythonhealthdatascience/stars-treat-sim)." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "24b9f806", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'4.1.1'" + ] + }, + "execution_count": 1, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import simpy\n", + "simpy.__version__" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "e056caed", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "import itertools\n", + "import math\n", + "import matplotlib.pyplot as plt\n", + "import fileinput\n", + "\n", + "from rich.console import Console\n", + "from rich.progress import track\n", + "#console = Console()" + ] + }, + { + "cell_type": "markdown", + "id": "bd14d296", + "metadata": {}, + "source": [ + "## 2. Constants and defaults for modelling **as-is**" + ] + }, + { + "cell_type": "markdown", + "id": "3884e7ae", + "metadata": {}, + "source": [ + "### 2.1 Distribution parameters" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3b512678", + "metadata": {}, + "outputs": [], + "source": [ + "# sign-in/triage parameters\n", + "DEFAULT_TRIAGE_MEAN = 3.0\n", + "\n", + "# registration parameters\n", + "DEFAULT_REG_MEAN = 5.0\n", + "DEFAULT_REG_VAR= 2.0\n", + "\n", + "# examination parameters\n", + "DEFAULT_EXAM_MEAN = 16.0\n", + "DEFAULT_EXAM_VAR = 3.0\n", + "DEFAULT_EXAM_MIN = 0.5\n", + "\n", + "# trauma/stabilisation\n", + "DEFAULT_TRAUMA_MEAN = 90.0\n", + "\n", + "# Trauma treatment\n", + "DEFAULT_TRAUMA_TREAT_MEAN = 30.0\n", + "DEFAULT_TRAUMA_TREAT_VAR = 4.0\n", + "\n", + "# Non trauma treatment\n", + "DEFAULT_NON_TRAUMA_TREAT_MEAN = 13.3\n", + "DEFAULT_NON_TRAUMA_TREAT_VAR = 2.0\n", + "\n", + "# prob patient requires treatment given trauma\n", + "DEFAULT_NON_TRAUMA_TREAT_P = 0.60\n", + "\n", + "# proportion of patients triaged as trauma\n", + "DEFAULT_PROB_TRAUMA = 0.12" + ] + }, + { + "cell_type": "markdown", + "id": "0789198a", + "metadata": {}, + "source": [ + "### 2.2 Time dependent arrival rates data\n", + "\n", + "The data for arrival rates varies between clinic opening at 6am and closure at 12am." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "c2127af1", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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wKUo2gEAAAAAcCmKdgAAAAAAXIqiHQAAAAAAl6JoBwAAAADApSjaAQAAAABwKYp2AAAAAABciqIdAAAAAACXomgHAAAAAMClKNoBAAAAAHApinYAAAAAAFyKoh0AAAAAAJeiaAcAAAAAwKUo2gEAAAAAcKk0F+1//PGHrV692nn82WefWfv27W3kyJF25syZqxocAAAAAADXszQX7Q8++KD9+eefZma2detWu+uuuyw8PNzmzJljw4YNS9PfmjBhglWvXt1y5MhhkZGR1r59e9u4caPfPpJszJgxVrBgQcuaNas1bNjQ1q5dm9awAQAAAAAIOmku2v/880+rVKmSmZnNmTPH6tevb++99569+eab9vHHH6fpby1cuND69etnS5YssXnz5llSUpLFxsbaiRMnnH0mT55s06ZNsxkzZtjSpUstKirKmjVrZsePH09r6AAAAAAABJUb0voLkiwlJcXMzL777jtr06aNmZnFxMTYoUOH0vS3vv76a7/Hb7zxhkVGRtrvv/9u9evXN0k2ffp0e/zxx61jx45mZvbWW29ZgQIF7L333rMHH3wwreEDAAAAABA00nylvVq1avb000/b22+/bQsXLrTWrVubmdm2bdusQIEC/yiYY8eOmZlZnjx5nL+5f/9+i42NdfYJCwuzBg0a2OLFiy/4NxITEy0+Pt7vBwAAAACAYJTmon369On2xx9/WP/+/e3xxx+3kiVLmpnZRx99ZHXq1LniQCTZoEGDrG7dula+fHkzM9u/f7+Z2XmdAQUKFHCeO9eECRMsV65czk9MTMwVxwQAAAAAQCCleXh8xYoV/WaP95oyZYplzJjxigPp37+/rVq1yn766afznvN4PH6PJZ23zWvEiBE2aNAg53F8fDyFOwAAAAAgKKW5aL+YLFmyXPHvPvzwwzZ37lxbtGiRFS5c2NkeFRVlZqlX3KOjo53tBw8evOhQ/LCwMAsLC7viWAAAAAAAcIvLKtojIiIuemX7XIcPH77sF5dkDz/8sH3yySe2YMECK168uN/zxYsXt6ioKJs3b55VrlzZzMzOnDljCxcutEmTJl326wAAAAAAEIwuq2ifPn16urx4v3797L333rPPPvvMcuTI4dynnitXLsuaNat5PB4bMGCAjR8/3kqVKmWlSpWy8ePHW3h4uN1zzz3pEhMAAAAAAG5xWUV79+7d0+XFX3rpJTMza9iwod/2N954w3r06GFmZsOGDbNTp05Z37597ciRI1azZk379ttvLUeOHOkSEwAAAAAAbvGP7mk/deqUnT171m9bzpw5L/v3Jf3tPh6Px8aMGWNjxoxJa3gAAAAAAAS1NC/5duLECevfv79FRkZa9uzZLSIiwu8HAAAAAABcHWku2ocNG2bz58+3F1980cLCwuy1116zsWPHWsGCBW3WrFnpESMAAAAAANelNA+P//zzz23WrFnWsGFD69mzp9WrV89KlixpRYsWtXfffde6dOmSHnECAAAAAHDdSfOV9sOHDztLs+XMmdNZ4q1u3bq2aNGiqxsdAAAAAADXsTQX7SVKlLDt27ebmVnZsmXtww8/NLPUK/C5c+e+mrEBAAAAAHBdS3PRft9999nKlSvNzGzEiBHOve0DBw60oUOHXvUAAQAAAAC4XqX5nvaBAwc6/27UqJFt2LDBli1bZjfeeKPdcsstVzU4AAAAAACuZ2ku2rdv327FihVzHhcpUsSKFClyNWMCAAAAAAB2hfe0161b12bOnOlMQgcAAAAAAK6+NBfty5Yts9q1a9vTTz9tBQsWtHbt2tmcOXMsMTExPeIDAAAAAOC6leaivUqVKjZlyhTbuXOnffXVVxYZGWkPPvigRUZGWs+ePdMjRgAAAAAArktpLtq9PB6PNWrUyF599VX77rvvrESJEvbWW29dzdgAAAAAALiuXXHRvmvXLps8ebJVqlTJqlevbtmyZbMZM2ZczdgAAAAAALiupXn2+FdeecXeffdd+/nnn61MmTLWpUsX+/TTT/1mlAcAAAAAAP9cmov2p556yu666y57/vnnrVKlSukQEgAAAAAAMLuCon3nzp3m8XjSIxYAAAAAAODjsor2VatWWfny5S1Dhgy2evXqS+5bsWLFqxIYAACXUmz4F+n+Gtsntk731wBw5a7F94AZ3wUAAuuyivZKlSrZ/v37LTIy0ipVqmQej8ckOc97H3s8HktOTk63YAEAAAAAuJ5cVtG+bds2y58/v/NvAAAAAACQ/i6raC9atKiZmZ09e9bGjBljTzzxhJUoUSJdAwMAAAAA4HqXpnXaM2XKZJ988kl6xQIAAAAAAHykqWg3M+vQoYN9+umn6RAKAAAAAADwleYl30qWLGlPPfWULV682KpWrWrZsmXze/6RRx65asEBAAAAAHA9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k7M77rjD2Z6SkhLAqK7Mvn37nLZ88803koKvHXv27DmvDcFk5syZTvwPPvig/vrrr0CH9I999NFHatKkiUqXLq3ChQvr559/DnRIV4TcuFuo5YfcuFso5cfXm2++6eSqZ8+eSk5ODnRIV8XJkyedc9CGDRs625OSkgIY1ZVLz/M1rrS7gCSrXr269e/f35588kmLi4uzu+66K2iHYufLl8/atm1r0dHRNnToUJs2bZqdPHky0GFdsVDLj5ckS0lJsUqVKtlNN91k77zzjk2aNMlGjx4d6NCuiooVK1rNmjVt9OjRtnTpUuvQoYOZBecV96SkJKtSpYqVLFnSWrRoYZ9++mnQtSN79ux2yy23WGRkpLVv394+++yzQIeUJh07drTu3btb//797Z133rGBAwdaXFxcoMP6R0qUKGEZMmSw//znP1a/fn27/fbbbfHixYEOK83IjbuFWn7IjbuFUn581atXz9q2bWsjR460L774wrp06RJU5wAXkyVLFmvXrp01atTI9u/fb7fccouZBe8V9/Q8X6NodwGPx2NZsmSxfPny2ffff28//PCDeTwe69KlS9AVht4PWPPmza1jx442c+ZMGzt2rE2aNCloC/dQyo8vj8djGTJksP79+9uyZcssPj7e3nzzTRs/fryNHTs20OFdMe97sFKlSlamTBn74YcfbMaMGbZ48WJn+FWwFbz58uWzUqVKWfny5W3MmDHWsWNH++yzz4KmHZIsLCzM6tSpYx07drShQ4dap06dgqpwj4iIMEmWlJRkS5YssU8//dQGDRoUVCe4Z8+e9XtcuXJlK1WqlI0YMcJeffVVq1atmt15551Bd4JLbtwt2PNDbtwtlPNj9n/3RZcoUcKyZctmq1atss8//9y+++4769atW1CcA1yKx+Ox6tWr28qVK+3JJ5+08PBwq1y5spkFZ+GerudrV+V6PdJsz5492rRp03lDx2vUqKHXX39dcXFxKlOmjKpXrx6UQ7G3bdumPHny6JNPPtEnn3wij8ej0aNH68SJE4EO7bKEan7Wrl2rf/3rX1q3bp3f9qlTp+qhhx5SUlKSXnnlFWXMmFFjxowJUJRXZv/+/edtW7VqlVq3bq1FixZp7ty5ypUrlzp16uQ8H0zDy1avXq1ixYrp3Xff1eDBg+XxePTZZ59JCp6h8r/88oty5cqlb7/9ViNHjlTmzJmdNrjNX3/9pUOHDvm9R3bt2qWiRYtq0aJFWrJkibJmzapu3boFxZDSlStXql69epo0aZIWL17sbN+zZ4+aNWum3377TZLUpEkTFSpUyNVDSsmNe3MjhVZ+yI27hVp+fB08eFBJSUl+w8S3bdummjVr6rvvvtP333+vnDlzqmvXrkFzDnApPXr0UN++fbVw4UKVLl1aVapUcZ4LtqHy6XW+RtEeAH/++ac8Ho9uvvlmtWvXTj///LOOHDkiSXrqqad09913S0r9ci1durRq166ttWvXBjDiS1u/fr1mzJihJUuW+G2fMWOGcy/xa6+9Jo/HozFjxri+cA+1/HjFxcU599m0adNGt912m9auXauEhAStXr1aefLkcQ5wr776qrJkyaKhQ4cGOOrL481Zo0aNNG3aNK1evVqSdPz4ccXGxmrYsGGSpLlz5yoiIkJ33XVXIMP9Wxs3btT//vc/HT9+3NkWHx+v3r17a9q0aUpISFCfPn38DgRu64DYuHGjvv32W+3du9dv+yOPPKLRo0fr1KlT6tu3r8LCwlxXuK9du1Yej0fNmjVT//79FRcXp5MnT0qSHnroIY0YMUKS9MMPPyhr1qzq2bOnDh48GMiQLyk5OVm1atVy7hnMmTOnRowYoU8++USS1KlTJ/Xs2dPZv3Xr1sqSJct53+luQG7cmxsptPJDbtybGyn08uNrw4YN8ng8io2N1YgRI7Rr1y5J0unTp3XnnXdq1KhRkqRvv/1WOXPmVI8ePVx3DnAxq1evVrt27bRw4UJt2bLF2f6f//xHrVq1UmJiopYtW6ZSpUoFReF+Lc/XKNoDYOXKlfJ4PCpSpIjuuecexcTEqHv37nrllVe0Y8cORUREaO7cuZJSC60CBQqoUaNGOnPmTIAjP9+hQ4ecQrBGjRpq27atli5dqri4OK1fv1433XSTli9fLim1cM+cObOGDBni6sI9lPLjKyUlRcOGDZPH49GQIUN0zz33qEqVKmrfvr1+/PFH9e7dW127dtWpU6ckSS+88ILy5cvn+t72lJQUzZo1Sx6PR/ny5dPAgQOVM2dOjR8/XsuWLdOGDRt08803a/Xq1UpJSdH//vc/eTwede/ePdChX5DvJCYPPvighg4dqsTEREnShx9+qLx58+rAgQNKSEhQv379lDlzZs2ZMyfAUfvbu3ev04YOHTpo2LBhOnr0qFJSUvTRRx+pWLFiOnr0qCSpX79+yp49u6va8N5778nj8ahSpUqqW7euypUrp0GDBumXX37RggULlC9fPv3555+SpB9//FEej0d9+vRx9UnT3r17VbJkSdWvX18zZ87Uvffeq7p166p9+/aaMmWKcufOrZ9++snZ//bbb3fa6Cbkxr25kUIvP+TGvbmRQis/vt5//315PB7deOON6ty5s/Lnz6+nnnpKv/zyi1auXKnIyEjn4sT333/vnC+4XUJCgqpUqSKPx6NWrVqpUaNGzoTVycnJqlSpkjPKc8mSJSpXrpyKFy8e4Kgv7lqfr1G0X2PeL7/ffvvNKWA/+ugjvfTSSypcuLBuv/125ciRQz169FBCQoIk6fDhw9q8eXMgw76khx9+WB6PR0888YRatWqlli1bql69elq6dKnatGmjtm3b6vTp05Kkl19+WXny5HFtIRiK+TmXt6hdtGiRFi1apMmTJysqKkoFChRQkSJF/HLjLazcLj4+XjNnzlSmTJk0depUff/997rnnntUtmxZNWrUSMWKFdOrr74qSUpMTNRXX33l2tsatmzZojZt2sjj8WjkyJFq2bKlKlSooJEjR2rDhg3q2rWrpk6dKkk6cOCA7rvvPuXJk0fHjx93zRC5I0eOqFmzZs7omsqVK6tx48bq27evDhw4oKZNmzorFZw5c0Y9e/ZUgQIF/HqqA23GjBnyeDx66aWX9OKLL2rkyJEKDw/Xo48+qsyZM2vs2LHOwfmXX37R+vXrAxzx+RISEnTixAnt2bNHUuow2Hz58qlTp0769ddfdejQIfXs2VNNmjSRx+MJiitQErlxu2DPD7lxb26k0M6Pr5dfflkej0evvPKK3n77bT3yyCPKly+f7rnnHhUoUEDTp093jvkLFy507TmNr1OnTumDDz5Q0aJFVadOHX322WcqVaqUWrVqpYEDB2ratGlq3769jh07puTkZC1atEjVq1fX1q1bAx36BW3dulVt27a9ZudrFO3XkHdoh/e/ixYtUqZMmdS7d28dPnxYCQkJevHFF9W6dWu98847ktw35NWX71CVBx98UPnz59d///tfLV68WBMmTFCZMmV04403KioqSrt373b2dWshGGr58XVunA899JCyZ8+uTz/9VFJqoThz5kx9/PHHgQjvivm268SJE5o2bZo8Ho/eeOMNSdLmzZvVpUsXFStWTLNnzw5QlGm3c+dONW/eXMWKFdPevXv1wQcfqGfPnsqdO7fCw8PVoEEDZ2THwYMHL3g/f6DFx8erQYMGqlSpktauXasPP/xQ3bp1U3R0tCIjI1W3bl2nSE9MTAx4Gw4cOKBffvlF//vf/5yD6fjx45UpUyZNmzZNSUlJWrFihYYPH66bbrpJH374oST3ziewbt06tWnTRuXLl1fp0qWd3v2dO3cqMjJS9evX186dOyWlXq36448/AhnuJZEb9+ZGCq38kBv35kYKvfz4OnjwoDPqwXtRaNKkScqQIYNmzpyps2fPav369XrggQdUunRpJ1fBwNsRdPbsWUmpy/JlzZpVo0eP1tGjR/W///1PsbGxypIlizwejzMaIiUlxRn96VZbtmxR69atr8n5GkX7NXDuVeXk5GTnjbto0SJlzpxZd999t+Li4pzng4XvkPCePXsqR44cevfddyVJmzZt0vvvv++qYa8XEqr52bZtm7788ku/bd52SanDkrNkyaKPPvroWof2jyUkJDgnFb4nF6dOndKzzz4rj8ejZ599VlJqvvbt2xeQOC/X9u3bNWPGDI0fP97pSNm/f7/q1KmjG2+80bmfbcGCBerXr5/zGXOT7du367nnntPYsWP1/vvvS0rtSKlWrZrKlSunNWvWSJK+++47PfbYY65qw6pVq1SpUiXFxMQoU6ZMqlatmtOhMHHiRHk8Ho0fP955r7lpRMCFLF++XDly5NBDDz2kxx57TC1btvS7n2737t2KiopSvXr1/K7OuPFEndykcmNupNDKD7lxb26k0MuPr1WrVqly5coqXLiwsmfPrtq1azvnnFOmTFGGDBk0ZcoUSann3d7ngsGGDRvUq1cvNWjQQPfff78zd9IHH3ygsLAwPfroo86+n3/+uX744QdJ7s/buRME1q9fP93P1yja09mff/6pTJkyqVGjRvrwww/Pm7VbSr13KHPmzLr33nv9rki70ebNm/Xtt9/6bfMOfZekBx54QFmyZNG7777r2kkjfIVafrwOHTqk7Nmzy+Px6N5779Urr7zi9Nz66t+/v7JkyeIUisFg/fr1qly5sjp37qytW7fq8OHDfs/7XnGfPHmys92tB4CVK1eqcOHCqlWrlrJly6aCBQvqiSeekJQ67K9BgwYqVKiQMzzMO3GQm6xYsUIxMTGqVauW8ubNq2zZsunxxx+XlHoieOutt6p48eLO58tN3w0rVqxQeHi4hgwZop9++kmvv/66ChcurIYNGzr7TJ06VR6PRxMnTtSxY8cCGO3fW7dunTJnzqyJEyc627Zs2aLy5curTZs2zvtnz549ioqKUtOmTZ17I92G3Lg3N1Jo5YfcuDc3Uujlx9eKFSuULVs2DRo0SIsXL9Zzzz2nEiVKqEWLFs5V5unTp/tdjAgWK1asUEREhDp37qw2bdqoVKlSKleunFatWiVJmjNnjsLDw9WrV68AR3p59u3b58R+rm3btqlhw4bper5G0Z7Oli1bpooVK6pdu3bq0aOHChYsqGnTpp13j83ChQuVLVs2tW/f/rzZlt3i0KFDuuGGG+TxeNSvXz9NmjTpglede/furaxZs2r27NmuH9YSSvnxlZCQoN69e+vll1/WqFGj1LJlS8XExOg///mPli5d6rdvv3795PF49L///S9A0abNyy+/rCpVqqht27aqUaOGOnfurDlz5vgVgidOnNDUqVOVOXNmPf300wGM9tJWrVqlrFmzatSoUTp27Jg2btyo7t27KyYmxnkPbt26VY0bN1ahQoW0fft2Se4a7bFy5UqFh4dr5MiRSkhI0Pr16/Xoo48qZ86cmj9/vqTUW2Lq16+v4sWLu2qlhS1btihz5szO/fVS6v/b4cOHKzo62hlmKcl5P40ZM+a8pSDd4sSJE7rrrruUJUsWZ1Ze7+iaO+64Q507d5b0fyOk9u7dq8yZM+u2225zhi+6Bblxb26k0MoPuXFvbqTQy4+vzZs3Kzw83Fnhxuvhhx9W0aJF/UaCPv/88woLC3P1OY2vNWvWKGvWrH7xfvrpp8qbN6+eeeYZSak5++ijjxQeHq5+/foFKtTLsmfPHuXKlUt58+bV0KFD/SY49NqyZYsaNWqUbudrFO3pbP/+/erataszBPn1119XkyZN1LBhQ/Xq1UsrV650hrnMnz9fBQoUcCbXcKP+/ftr+PDhmjRpkurVq6fSpUtr2rRpzgzxXr1795bH43H90OtQy4+vwYMHKzY2VlLqlc2pU6fqrrvuUoECBTR+/Hi/L5zhw4e7csKZC1m8eLEqVqyo7du3a/ny5Ro7dqxy586t7t27a+rUqX5X1MeNG6c8efK4cijZzp07lT9/frVr185v+6JFi5Q1a1Z99913zrZt27apWbNmCg8P9zvhCrTdu3crOjparVu39tu+aNEi5ciRw29UzrFjx9S4cWNFRES44r2WkpKiF154QZGRkectbfjGG2+oRIkS2rNnj19n0NNPP62IiAgdOnToWod72ebOnet0aHnv59yyZYvCw8P14osvOvt5T3r37dunjRs3BiTWiwnV3Hz++edBnxsp9SR0xowZIZWfTz/9NCRyw2fH3fk518SJExUdHa3Ro0f73W765ptvqmTJktq9e7ffOc3EiRNde07j68iRIypXrpzKly9/3mjIGjVqaMiQIc7jpKQk/fe//5XH49GgQYOudaiXbd26dWratKneeustdezYUbGxsapbt65+//13v1G4O3bsUOPGjdPlfI2iPZ34fiFOnjxZxYsX14EDBySlTsjgXfLg1ltvVZMmTZz7vt18ZTolJUWjR492ejWl1HttevXqpZw5c2rSpEn6/PPPneeGDh3qipPzvxMq+fHyfsEnJyerWrVqeu6555znYmNjFR0drQoVKqhGjRqqVq1a0Awh89WnTx+1bdvWGdK3e/duFShQQB6PR5UrV9b48eO1YsUKSXLtwW3VqlWqUKGCbr/9dn311VfO9sWLFytnzpzn9eJu2bJFbdu2ddVyNb///rsaNWqk2NhYv7XWly5dqhw5cmjRokV++x89elStW7fWpk2brnWoF3To0CFNmzZN5cuXV9++fSWlznGRO3dujR071tnPt6fcre8nX19++aVatWqlunXr6quvvlLx4sX10EMPOc97vyPcdJvCueLi4kIiN+devfzmm2+CPjdSai6CPT8bN27UW2+95Tz++uuvQyY3U6dODercXEgofK+dKz4+XqNGjVLNmjWdQvbw4cPn5crXuUWwW40ZM8Yp0L3DxTdu3KiwsDBnMmevpKQkzZ071/U1Q6tWrZwRAevWrVP37t3VsGFD3Xrrrfrwww918OBBSalX5du0aXPVz9co2q+y3bt3O4Wd94vj5MmTatWqld58801J0n333aeiRYvqjz/+0Mcff6xOnTo5a/m5lffL/eTJkypevLgmTJjgPNe6dWtFRESoYcOGuvnmm1W1alVnogk38/bIhkJ+znXmzBklJSXpscceU58+fSRJ3bp1U4ECBbRjxw799ddf+vTTT9WkSZOg6I328h6UFy1apEaNGjlL7fXq1UvFihXTihUrNHToUNWoUUOFChW64H38brJkyRLV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0kSJiYmu/KzMnj1bZcuW1VNPPaWbb775vCvuR48eDYrcxMfHq0yZMipVqpSk1O/bO+64QzVr1lTGjBnVpEkTvfDCC87+33zzjetz8/rrr6tZs2aSUtszbtw49ezZU927d3cKJu8VdzfnZ8uWLcqVK5ffe+s///mPihYt6tzS59tJ/O2337o6NydOnFDLli39Rmc999xzzvJZviOJ3J4br6+++kp16tTxWzN+/vz5zhK2vmt6B8Nnx+vMmTPq1KmT7r//fmfbzz//rJ49eypz5szO8dJ7xf3WW29Vv379guJK++bNm505he6//36/Sdl8458wYYIqV66sYcOGnTcq1E3WrFmjBg0aOKMDpk2bpuTkZCUnJyslJUUJCQlq0aKFSpQo4cyddK1QtP9Da9euVcaMGZ1e2+PHj6tz587q0KGDEhIS/Aqk+fPnKzw83NVLax04cEBlypRxDmp79+71u9/ba+7cuSpfvryzLJWbhyY99dRT6tq1q9+2/v37y+PxqESJEtq3b5+k1AO02/Pja8OGDbrpppv8JpI7duyY3n//feXJk8fv4NCyZUvVrVs3KA4AcXFxatGihbp166Z69eqpcOHC+v777y/4Hps1a5ayZ89+0XUz3WDv3r2qXLmy320z+/btU0REhP773/9K+r8Oh19//VWNGzdWvXr1NG/evIDEeyFxcXFq3ry57rzzTjVp0kRVqlTRRx99dMHC/eeff9Ydd9yhSpUq6csvvwxUyI6zZ89q0qRJ6ty5s/766y8n1i1btqhQoUJOx503B0eOHNG4ceNUp04d7d+/P2BxX0pCQoI+++wzvytMzzzzjDwej5o3b65bb71V+fLlc4bwSanHHzetPHCuzZs3q1u3bjp48KDGjh2rsmXL6umnn1bfvn319ttvS0rtVHF7bk6dOqVXXnlF4eHhGj58uNq0aaPY2Fh9+OGH+vDDD9WnTx8VK1ZMs2bNcn7H7bkZO3as7r77bklSzZo1FRsbqzvuuENNmzZVxowZnZE1f/31l6vz8/PPP593LiNJ5cqV0x133OE8Pve8za25OXTokMqVK6d3333X2TZs2DCVLFlS1apV00033eR03rk9N16fffaZoqKi/Iqh33//Xc2bN9fUqVOVJ08evw5JN+fH18mTJ1WlShU99dRTftt37NihXr16KU+ePFq4cKGk1I6/AQMGqGnT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zx7Vr19fLVu2VN++fdW7d29FREQE/MT8Qi4nP75F3sqVK3XvvfcqIiLC1V+Ux44dU4cOHXT33Xerb9++qlKlisaMGXPBwv3333/XzJkz1blzZ40YMcKVV9l87d69W9WrV1eZMmVUrFgxjRw58pKF+6lTp9SnTx+FhYU59726SVrb065dO2XPnt1VV3h37tzpN5pD+r/P0Y4dOzR48GC/UQSnTp3Sgw8+qPDw8IDnZPXq1cqZM6ffjPzek4rt27crOjra73aEQ4cOKW/evIqIiHBlB9Dx48dVsGBBeTweZ+Zur61btyoqKkoPPPCA34nG0qVLNWnSJNeN2Lic3IwePVpScCxJeSW5WbJkicaMGePKUWppyY+U+t2wcOFCrV692nXnN1eSGze/5y4nN2PGjJGU2o6PPvpIjz/+uJ577jlXXjC6kvycOnVKixcvduXkZb7Onj2r4cOHq3379vrss8/UpEkTNW/e3K9w971daf369frwww/1v//975LnCm7wdyvIXGgE1fDhw+XxeFxZM1xOB4T3HOi5555T586dNWnSJIWFhQWsYJco2i/Lrl275PF45PF4nOG43hPZrVu3qnDhwurTp4/f73gTf+4skW6Qll7Or776Sr1791b16tXVvXv3gJ+YX0ha85OSkqJt27Zp9OjRrmyPrxMnTmjcuHH66KOPJKUu53Khwj0Yff7552rVqpX+/PNP/etf/1KhQoUuWeju379fd911l7Mqg9tcbnu897U2bdpUHo/HNZ0r27Ztk8fjUcWKFZ3JcLwHsm3btikqKuq8mVITEhLUvn17Z+nHQElJSVGfPn3k8XiUMWNGzZ4923lu9+7dypw5s/r06XPe/azDhw935ZU1KfU7rE+fPmrUqJGio6PVvHlz57nJkyerS5cuF7wy4Lb5U64kN253pblx461KoZafK82NG6UlN27uePAVSvm5kF9//VVvvfWWpNT1vhs3bnxe4e7G74G/czkryPjmLTExUf/9739deQtjWjsg/v3vf8vj8SgiIiLgF1go2i/Dhg0bVKpUKRUqVEjh4eFO0k6dOqWyZcuqa9euQfMlc7m9nL6FYGJiopKSklw7odGV5idYDnLx8fF+8Q8dOlRVqlTR6NGjnfufTp8+HXQHgn379jnLuknS9OnTnULX9x483wn43PoelC6/Pd7P1v79+111X+tnn32m7Nmzq0yZMqpatapzT+GxY8dUpEgR3XfffZe8fy3QPvroI9WtW1cPPPCAPB6P0/F47NgxjR071u/zHiyf/ZkzZ6pw4cL68MMPVbJkSWcGWym4TvzSkptgESq5kUIvP+TGPROBXkgo5edc5/5/X7ly5QWvuHvXZA8Wl7uCjG+nsVvfg5fTAeFrzpw58ng8rrjIR9F+GY4eParWrVurR48e6t+/v99w1p07dwbVAS0tvZwXm5XYbUIpP5fi+2U4ZMgQ54r7vn379Oijj+r22293fa7+zvPPP+8Uut51yp9//nlX9tZejou1x43Dsbdt26abbrpJPXv2VOfOnVW1alVnsqZffvnFde+tc1dTOHLkiG688UYNGTJEEydOlMfjcWZNdlvsadG2bVs99thj+vLLL5U/f36/E1y3XVX3IjfuzY10feSH3LhbsOYnLbz5WbFihVO4f/zxx3rkkUeUO3duHTp0KChz+HcryIwaNSrAEV7a5XZA+Bbw3pGtgUbRfgm+H6bvvvtOMTEx+uSTT9S1a1dlzZrVmSzHLVeaLleo9HKGan4uxTc/Q4cOVfXq1VW+fHlly5bNlRNpXS7fjhXfoeVdunRRjhw5XHeP7t8Jpvb4fj5efPFFVa9eXW+++aaaNGmiatWqOYW72z5HvsvleL3//vu67bbbtHz5cg0ePFgej0fvv/++JLl+COmaNWvUq1cvrVixwrlPOCkpybmfLiUlRd98843y58+v1q1bO7/ntrxI5MbLjbmRQis/5Ma9uZFCLz9/59y5ErztWLlypZo1a6bcuXMrR44cAb0v+mpw+woyl+vvOiBGjhwpyT2j8yjaL2Dz5s369ddf/YbiHj58WF27dtVrr72mgwcPqmPHjgoPD3c+eMH2BRPMvZyhnJ+0rE6QlJSkm266SREREUFxJfrvluHyfd9Nnz5dHo9HuXLlcu1EgcHenl27dp23NN3y5cvVunVr/fTTT/r555916623qnr16tq7d68k93w3rF27Vnnz5tWTTz7pzGotpZ4YVapUyRl6OGjQIHk8Hn3wwQeS3HtlKj4+XjfeeKM8Ho86dOigpk2bavbs2Tp79qzi4+MVExOjl19+WZL09ddfq1ChQqpbt26Ao74wcuPe3EihlR9y497cSKGXnwu5nCUFvfnp3LmzcufO7cqZ1M8VrCvIXIlg6oCgaD/H7t27nUnNHnnkEU2cONF57l//+peKFy+uxMREHThwQJ06dVKuXLkCPgnTpYRaL2eo5ccrrasTJCYm6v7771eWLFlcfwA4fvy48+9zl6vZt2+f/vvf//oNCUxKStLAgQMVERHhmknafIVCe7Zt26ZMmTIpT548mj59ujMzsST17NlTzZo1kyTNnz9fDRo0UO3atS+59vy1dPbsWT366KPOpHl33HGHqlSpooULF+rkyZP697//rZo1ayohIUFHjhxxZrD1TuboRgkJCXrppZeUN29etWrVSm+88YYKFSqk9u3ba+LEiZo0aZK6dOmiU6dO6ezZs/r8889VunRp55YLtyA37s2NFHr5ITfuzY0UWvnxdSVLCj7++OOunUndVzCvIHMxodQBQdF+jjVr1qh27dryeDwaM2aMqlatqrp162ry5Mnas2eP2rZtq9dee01S6hu4RYsWKliwoE6fPu263s5Q7OUMpfx4XcnqBJI0cOBA13dIrFu3ToULF9Ybb7zhbPNdriZ//vx+HS9S6gQtGTNmPG+tZjcIhfakpKTo448/VrFixZQpUyYNGjRIFSpUUMeOHTVv3jytXr1abdu21eLFiyVJ3377rSpVqqTGjRv7LTEYSFu3blW3bt2UNWtWLViwQEOGDFFsbKwqVqyoAQMGqHLlys68FgcPHtTo0aNd02Hiy/s59/4/femll5QhQwa9+uqr2rdvn9555x1Vq1ZNmTNnVpYsWZyT2aSkpAsOo3UDcuPe3EihkR9y497cSKGbH+nKlqyTUs/z3H6BJZhXkLmQUOyAoGg/R0pKilatWqVatWqpWrVqOnTokF588UV17NhRefPmVc6cOdWhQwfnA7lr1y5nlmW3OXnypGbOnBlSvZyhlB+vtM5+74ai6XIkJSXpoYceksfjUaFChZwOIknas2ePsmbNqoceeuiC7fnrr7+uZaiXJZTak5CQoA8++EA333yz2rVrp/379+uBBx5QixYtVLhwYUVEROiJJ55w9p8/f77fDPiBEB8frz179jhD9f/66y/FxsaqSJEi2rFjh/766y+99tpruummm5QhQwZ9++23zu+65X40X3/++acefvhhtWnTRoMHD3bWVPYuLzN58mRn33fffVdff/11oEL9W+TGvbmRQis/5Ma9uZFCLz/nupIl69w6cvVcwb6CjK9Q64Dwomj34fsFuHr1at18882q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c1AWiUlJSXrttdeCrrPrYpYuXSqPx6O1a9cGOpR/bM2aNbrzzjtDoi2haOXKlWrdurXfRYpgdqHbz4JZSkqKqy9GpEVcXJz2798fFJNqSlKTJk00ZswYSXLuvR82bJg6duyoihUr6qWXXpLk/iUFfXlXwbn//vudbTt27NBDDz2kKlWqOO2VUq+4u20FmXNd7HzNt3B36xK26em6Ktolem3dLth6baVLHwAqVaqkF198UceOHVPRokWDZvb7vzsA+LajS5cuqlWrlqvvzdu/f79uvfVWNWnSRPny5dMdd9zhdyDw7UTp2rWra9tzuUujJSUlKS4uTrly5VLGjBld3fH1d4KhgystQuVEXQq+yTOvN8E0YSOQ3lJSUnTixAnVq1dPXbt2dW6L3b17t4oWLarXX39d9957r+rVq+f8TjAsWefVrFkz9ezZU9L/HTf37t2rAQMGqFatWnrzzTedfcePH++6FWS8/u58zbdwd+MStunpuivavei1da9g6bW93ANA/fr1lZSUFFSz30t/fwB46623JEnff/+9YmJinIlc3Oirr75Sly5d9Ntvv+nXX39Vnjx5zjsQeL8Pvv/+exUqVMi17bmcpdHOnj2rQ4cO6euvvw6ZK20AAPxTP/30kzJkyKD69eura9euypYtm3r16iUpddm97NmzB9VxMykpSWfOnNF9992nDh06OCteeM9pduzYoZYtW6pt27ZBsYLM5Zyv+RbublzCNr1ct0U7cLX83QEgW7ZsOnDggOtnv/e63APAbbfdJim1iGzcuLFrJmm7kIMHD/otffbLL784BwLftc2l1NESLVq0cHV7pEsvjTZw4EB16NAhaDqIAAC4Vn777Tfde++96tWrl1544QVn+2effaabb775vPMCNzp3/qoFCxYoY8aMev75551t3vO23377TR6PR8uXL/dbYcaNLvd8zXekl1s7IK42inbgKrjUAeCmm24Kii+UKzkAeO+TduOolYtNyOiNdcmSJX49uGfOnNG//vUv/fbbb649mJ3rYkujZcyYMWTuAwcA4Gq70HF+yJAhatiw4UXnvXGLC60gI6Uu7ZYhQwa9+uqrftvXrVuncuXKaePGjdcyzMt2JedrL774or766itJ7u2AuNpuMAD/WPXq1W3WrFnm8Xj8tv/4448WFRVlN9zg7o/an3/+aZ9//rndc889Fh0d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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# data are held in the Github repo and loaded from there.\n", + "NSPP_PATH = 'https://raw.githubusercontent.com/TomMonks/' \\\n", + " + 'open-science-for-sim/main/src/notebooks/01_foss_sim/data/ed_arrivals.csv'\n", + "\n", + "# visualise\n", + "ax = pd.read_csv(NSPP_PATH).plot(y='arrival_rate', x='period', rot=45,\n", + " kind='bar',figsize=(12,5), legend=False)\n", + "ax.set_xlabel('hour of day')\n", + "ax.set_ylabel('mean arrivals');" + ] + }, + { + "cell_type": "markdown", + "id": "9f21bc9a", + "metadata": {}, + "source": [ + "### 2.3 Resource counts\n", + "\n", + "> Inter count variables representing the number of resources at each activity in the processes." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "60399f79", + "metadata": {}, + "outputs": [], + "source": [ + "DEFAULT_N_TRIAGE = 1\n", + "DEFAULT_N_REG = 1\n", + "DEFAULT_N_EXAM = 3\n", + "DEFAULT_N_TRAUMA = 2\n", + "\n", + "# Non-trauma cubicles\n", + "DEFAULT_N_CUBICLES_1 = 1\n", + "\n", + "# trauma pathway cubicles\n", + "DEFAULT_N_CUBICLES_2 = 1" + ] + }, + { + "cell_type": "markdown", + "id": "f14d0e2d", + "metadata": {}, + "source": [ + "### 2.4 Simulation model run settings" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "id": "145fb875", + "metadata": {}, + "outputs": [], + "source": [ + "# default random number SET\n", + "DEFAULT_RNG_SET = None\n", + "N_STREAMS = 20\n", + "\n", + "# default results collection period\n", + "DEFAULT_RESULTS_COLLECTION_PERIOD = 60 * 19\n", + "\n", + "# number of replications.\n", + "DEFAULT_N_REPS = 5\n", + "\n", + "## set to True to show a trace of the simulation model (best used in single run mode)\n", + "DEBUG = False\n", + "\n", + "# provide a list of integers that represent patient IDs. Only these patients will be tracked\n", + "TRACKED = None" + ] + }, + { + "cell_type": "markdown", + "id": "259f69ea", + "metadata": {}, + "source": [ + "## 3. Trace functionality \n", + "\n", + "Rather than using the `print` built in function to output a simulated trace it is desirable to control the if trace messages are shown or not shown. The `SimulatedTrace` class is partly based on R's `simmer` functionality for output. A user sets a `trace_level` threshold when creating an instance of the class. The class is callable like a function and replaces the print built in e.g\n", + "\n", + "```python\n", + "trace = SimulatedTrace()\n", + "trace(\"hello world\")\n", + "```\n", + "\n", + "It is then trivial to suppress messages to a user\n", + "\n", + "```python\n", + "trace = SimulatedTrace(trace_level=2)\n", + "\n", + "# this will not be printed as log_level is 0 by default\n", + "trace(\"hello world\")\n", + "\n", + "# this will NOT be printed in this experiment as log_level is less than 2\n", + "trace(\"level 1 message\", trace_level=1)\n", + "\n", + "# this will be printed!\n", + "trace(\"level 2 message\", trace_level=2)\n", + "```\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cc1a07e0", + "metadata": {}, + "outputs": [], + "source": [ + "class SimulatedTrace:\n", + " '''\n", + " Utility class for printing a trace as the\n", + " simulation model executes.\n", + " \n", + " Implemented using the Rich library\n", + " to allow for coloured text.\n", + " '''\n", + " def __init__(self, trace_level=0):\n", + " '''Simulated Trace\n", + " Log events as they happen.\n", + "\n", + " Params:\n", + " -------\n", + " \n", + " env: simpy.Environment\n", + " The simpy environment. Used for printing out\n", + " time.\n", + " \n", + " log_level: int, optional (default=0)\n", + " Minimum log level of a print statement\n", + " in order for it to be logged.\n", + " '''\n", + " self.trace_level = trace_level\n", + " \n", + " def __call__(self, msg, trace_level=0):\n", + " '''Override callable.\n", + " This makes objects behave like functions.\n", + " decorates the print function. conditional\n", + " logic to print output or not.\n", + " \n", + " Params:\n", + " ------\n", + " msg: str\n", + " string to print to screen.\n", + " \n", + " trace_level: int, optional (default=0)\n", + " minimum trace level in order for the message\n", + " to display\n", + " \n", + " '''\n", + " if (trace_level >= self.trace_level):\n", + " console.print(msg)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "acb1d6a0", + "metadata": {}, + "outputs": [], + "source": [ + "def traceable(debug=False, \n", + " name=None, \n", + " name_colour=\"bold blue\", \n", + " time_colour='bold blue', \n", + " message_colour='black',\n", + " tracked=None):\n", + " def decorator(cls): \n", + " class TracedProcess(cls):\n", + " def __init__(self, *args, **kwargs):\n", + " super().__init__(*args, **kwargs)\n", + " self.debug = debug\n", + " self.name = name\n", + " self.name_colour = name_colour\n", + " self.time_colour = time_colour\n", + " self.message_colour = message_colour\n", + " self.tracked = tracked\n", + " \n", + " def trace(self, time, msg=None, process_id=None):\n", + " if self.debug:\n", + " \n", + " if tracked is None or process_id in self.tracked:\n", + " \n", + " out = f'[{self.time_colour}][{time:.3f}]:[/{self.time_colour}]'\n", + " if self.name is not None and process_id is not None:\n", + " out += f'[{name_colour}]<{name} {process_id}>: [/{name_colour}]'\n", + "\n", + " out += f'[{message_colour}]{msg}[/{message_colour}]'\n", + "\n", + " console.print(out)\n", + " \n", + " return TracedProcess\n", + " return decorator" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "c3456c55", + "metadata": {}, + "outputs": [], + "source": [ + "from abc import ABC, abstractmethod\n", + "\n", + "class Traceable(ABC):\n", + " '''Provide basic trace functionality to subclass\n", + " \n", + " Abstract base class Traceable\n", + " \n", + " Subclasses must call \n", + " \n", + " super().__init__(debug=True) in their __init__() method to \n", + " initialise trace.\n", + " \n", + " This adds \n", + " '''\n", + " def __init__(self, debug=False):\n", + " self.debug = debug\n", + " self.config = self._default_config()\n", + " self.console = Console()\n", + " \n", + " def _default_config(self):\n", + " config = {\n", + " \"name\":None, \n", + " \"name_colour\":\"bold blue\", \n", + " \"time_colour\":'bold blue', \n", + " \"time_dp\":2,\n", + " \"message_colour\":'black',\n", + " \"tracked\":None\n", + " }\n", + " return config\n", + " \n", + " \n", + " def _trace_config(self):\n", + " config = {\n", + " \"name\":None, \n", + " \"name_colour\":\"bold blue\", \n", + " \"time_colour\":'bold blue', \n", + " \"time_dp\":2,\n", + " \"message_colour\":'black',\n", + " \"tracked\":None\n", + " }\n", + " return config\n", + " \n", + " \n", + " def trace(self, time, msg=None, process_id=None):\n", + " '''\n", + " Display a trace of an event\n", + " '''\n", + " \n", + " if not hasattr(self, 'config'):\n", + " raise AttributeError(\"Your trace has not been initialised. Call super__init__(debug=True) in class initialiser\"\n", + " \"or omit debug for default of no trace.\")\n", + " \n", + " # if in debug mode\n", + " if self.debug:\n", + " \n", + " # check for override to default configs\n", + " process_config = self._trace_config()\n", + " self.config.update(process_config)\n", + " \n", + " # conditional logic to limit tracking to specific processes/entities\n", + " if self.config['tracked'] is None or process_id in self.config['tracked']:\n", + "\n", + " # display and format time stamp\n", + " out = f\"[{self.config['time_colour']}][{time:.{self.config['time_dp']}f}]:[/{self.config['time_colour']}]\"\n", + " \n", + " # if provided display and format a process ID \n", + " if self.config['name'] is not None and process_id is not None:\n", + " out += f\"[{self.config['name_colour']}]<{self.config['name']} {process_id}>: [/{self.config['name_colour']}]\"\n", + "\n", + " # format traced event message\n", + " out += f\"[{self.config['message_colour']}]{msg}[/{self.config['message_colour']}]\"\n", + "\n", + " # print to rich console\n", + " self.console.print(out)\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "3160552e", + "metadata": {}, + "source": [ + "## 4. Distribution classes\n", + "\n", + "To help with controlling sampling `numpy` distributions are packaged up into classes that allow easy control of random numbers.\n", + "\n", + "**Distributions included:**\n", + "* Exponential\n", + "* Log Normal\n", + "* Bernoulli\n", + "* Normal\n", + "* Uniform" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "0508d46d", + "metadata": {}, + "outputs": [], + "source": [ + "class Exponential:\n", + " '''\n", + " Convenience class for the exponential distribution.\n", + " packages up distribution parameters, seed and random generator.\n", + " '''\n", + " def __init__(self, mean, random_seed=None):\n", + " '''\n", + " Constructor\n", + " \n", + " Params:\n", + " ------\n", + " mean: float\n", + " The mean of the exponential distribution\n", + " \n", + " random_seed: int, optional (default=None)\n", + " A random seed to reproduce samples. If set to none then a unique\n", + " sample is created.\n", + " '''\n", + " self.rng = np.random.default_rng(seed=random_seed)\n", + " self.mean = mean\n", + " \n", + " def sample(self, size=None):\n", + " '''\n", + " Generate a sample from the exponential distribution\n", + " \n", + " Params:\n", + " -------\n", + " size: int, optional (default=None)\n", + " the number of samples to return. If size=None then a single\n", + " sample is returned.\n", + " '''\n", + " return self.rng.exponential(self.mean, size=size)\n", + "\n", + " \n", + "class Bernoulli:\n", + " '''\n", + " Convenience class for the Bernoulli distribution.\n", + " packages up distribution parameters, seed and random generator.\n", + " '''\n", + " def __init__(self, p, random_seed=None):\n", + " '''\n", + " Constructor\n", + " \n", + " Params:\n", + " ------\n", + " p: float\n", + " probability of drawing a 1\n", + " \n", + " random_seed: int, optional (default=None)\n", + " A random seed to reproduce samples. If set to none then a unique\n", + " sample is created.\n", + " '''\n", + " self.rng = np.random.default_rng(seed=random_seed)\n", + " self.p = p\n", + " \n", + " def sample(self, size=None):\n", + " '''\n", + " Generate a sample from the exponential distribution\n", + " \n", + " Params:\n", + " -------\n", + " size: int, optional (default=None)\n", + " the number of samples to return. If size=None then a single\n", + " sample is returned.\n", + " '''\n", + " return self.rng.binomial(n=1, p=self.p, size=size)\n", + "\n", + "class Lognormal:\n", + " \"\"\"\n", + " Encapsulates a lognormal distirbution\n", + " \"\"\"\n", + " def __init__(self, mean, stdev, random_seed=None):\n", + " \"\"\"\n", + " Params:\n", + " -------\n", + " mean: float\n", + " mean of the lognormal distribution\n", + " \n", + " stdev: float\n", + " standard dev of the lognormal distribution\n", + " \n", + " random_seed: int, optional (default=None)\n", + " Random seed to control sampling\n", + " \"\"\"\n", + " self.rng = np.random.default_rng(seed=random_seed)\n", + " mu, sigma = self.normal_moments_from_lognormal(mean, stdev**2)\n", + " self.mu = mu\n", + " self.sigma = sigma\n", + " \n", + " def normal_moments_from_lognormal(self, m, v):\n", + " '''\n", + " Returns mu and sigma of normal distribution\n", + " underlying a lognormal with mean m and variance v\n", + " source: https://blogs.sas.com/content/iml/2014/06/04/simulate-lognormal\n", + " -data-with-specified-mean-and-variance.html\n", + "\n", + " Params:\n", + " -------\n", + " m: float\n", + " mean of lognormal distribution\n", + " v: float\n", + " variance of lognormal distribution\n", + " \n", + " Returns:\n", + " -------\n", + " (float, float)\n", + " '''\n", + " phi = math.sqrt(v + m**2)\n", + " mu = math.log(m**2/phi)\n", + " sigma = math.sqrt(math.log(phi**2/m**2))\n", + " return mu, sigma\n", + " \n", + " def sample(self):\n", + " \"\"\"\n", + " Sample from the normal distribution\n", + " \"\"\"\n", + " return self.rng.lognormal(self.mu, self.sigma)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "071144e4", + "metadata": {}, + "outputs": [], + "source": [ + "class Normal:\n", + " '''\n", + " Convenience class for the normal distribution.\n", + " packages up distribution parameters, seed and random generator.\n", + "\n", + " Use the minimum parameter to truncate the distribution\n", + " '''\n", + " def __init__(self, mean, sigma, minimum=None, random_seed=None):\n", + " '''\n", + " Constructor\n", + " \n", + " Params:\n", + " ------\n", + " mean: float\n", + " The mean of the normal distribution\n", + " \n", + " sigma: float\n", + " The stdev of the normal distribution\n", + " \n", + " minimum: float, optional (default=None)\n", + " Used to truncate the distribution (e.g. to 0.0 or 0.5)\n", + " \n", + " random_seed: int, optional (default=None)\n", + " A random seed to reproduce samples. If set to none then a unique\n", + " sample is created.\n", + " '''\n", + " self.rng = np.random.default_rng(seed=random_seed)\n", + " self.mean = mean\n", + " self.sigma = sigma\n", + " self.minimum = minimum\n", + " \n", + " def sample(self, size=None):\n", + " '''\n", + " Generate a sample from the normal distribution\n", + " \n", + " Params:\n", + " -------\n", + " size: int, optional (default=None)\n", + " the number of samples to return. If size=None then a single\n", + " sample is returned.\n", + " '''\n", + " samples = self.rng.normal(self.mean, self.sigma, size=size)\n", + "\n", + " if self.minimum is None:\n", + " return samples\n", + " elif size is None:\n", + " return max(self.minimum, samples)\n", + " else:\n", + " # index of samples with negative value\n", + " neg_idx = np.where(samples < 0)[0]\n", + " samples[neg_idx] = self.minimum\n", + " return samples\n", + "\n", + " \n", + "class Uniform():\n", + " '''\n", + " Convenience class for the Uniform distribution.\n", + " packages up distribution parameters, seed and random generator.\n", + " '''\n", + " def __init__(self, low, high, random_seed=None):\n", + " '''\n", + " Constructor\n", + " \n", + " Params:\n", + " ------\n", + " low: float\n", + " lower range of the uniform\n", + " \n", + " high: float\n", + " upper range of the uniform\n", + " \n", + " random_seed: int, optional (default=None)\n", + " A random seed to reproduce samples. If set to none then a unique\n", + " sample is created.\n", + " '''\n", + " self.rand = np.random.default_rng(seed=random_seed)\n", + " self.low = low\n", + " self.high = high\n", + " \n", + " def sample(self, size=None):\n", + " '''\n", + " Generate a sample from the uniform distribution\n", + " \n", + " Params:\n", + " -------\n", + " size: int, optional (default=None)\n", + " the number of samples to return. If size=None then a single\n", + " sample is returned.\n", + " '''\n", + " return self.rand.uniform(low=self.low, high=self.high, size=size)" + ] + }, + { + "cell_type": "markdown", + "id": "172d99f5", + "metadata": {}, + "source": [ + "## 5. Model parameterisation\n", + "\n", + "For convienience a container class is used to hold the large number of model parameters. The `Scenario` class includes defaults these can easily be changed and at runtime to experiments with different designs." + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "7f554b27", + "metadata": {}, + "outputs": [], + "source": [ + "class Scenario:\n", + " '''\n", + " Container class for scenario parameters/arguments\n", + " \n", + " Passed to a model and its process classes\n", + " '''\n", + " def __init__(self, random_number_set=DEFAULT_RNG_SET,\n", + " n_triage=DEFAULT_N_TRIAGE,\n", + " n_reg=DEFAULT_N_REG,\n", + " n_exam=DEFAULT_N_EXAM,\n", + " n_trauma=DEFAULT_N_TRAUMA,\n", + " n_cubicles_1=DEFAULT_N_CUBICLES_1,\n", + " n_cubicles_2=DEFAULT_N_CUBICLES_2,\n", + " triage_mean=DEFAULT_TRIAGE_MEAN,\n", + " reg_mean=DEFAULT_REG_MEAN,\n", + " reg_var=DEFAULT_REG_VAR,\n", + " exam_mean=DEFAULT_EXAM_MEAN,\n", + " exam_var=DEFAULT_EXAM_VAR,\n", + " exam_min=DEFAULT_EXAM_MIN,\n", + " trauma_mean=DEFAULT_TRAUMA_MEAN,\n", + " trauma_treat_mean=DEFAULT_TRAUMA_TREAT_MEAN,\n", + " trauma_treat_var=DEFAULT_TRAUMA_TREAT_VAR,\n", + " non_trauma_treat_mean=DEFAULT_NON_TRAUMA_TREAT_MEAN,\n", + " non_trauma_treat_var=DEFAULT_NON_TRAUMA_TREAT_VAR,\n", + " non_trauma_treat_p=DEFAULT_NON_TRAUMA_TREAT_P,\n", + " prob_trauma=DEFAULT_PROB_TRAUMA,\n", + " debug=DEBUG,\n", + " tracked=TRACKED):\n", + " '''\n", + " Create a scenario to parameterise the simulation model\n", + " \n", + " Parameters:\n", + " -----------\n", + " random_number_set: int, optional (default=DEFAULT_RNG_SET)\n", + " Set to control the initial seeds of each stream of pseudo\n", + " random numbers used in the model.\n", + " \n", + " n_triage: int\n", + " The number of triage cubicles\n", + " \n", + " n_reg: int\n", + " The number of registration clerks\n", + " \n", + " n_exam: int\n", + " The number of examination rooms\n", + " \n", + " n_trauma: int\n", + " The number of trauma bays for stabilisation\n", + " \n", + " n_cubicles_1: int\n", + " The number of non-trauma treatment cubicles\n", + " \n", + " n_cubicles_2: int\n", + " The number of trauma treatment cubicles\n", + " \n", + " triage_mean: float\n", + " Mean duration of the triage distribution (Exponential)\n", + " \n", + " reg_mean: float\n", + " Mean duration of the registration distribution (Lognormal)\n", + " \n", + " reg_var: float\n", + " Variance of the registration distribution (Lognormal)\n", + " \n", + " exam_mean: float\n", + " Mean of the examination distribution (Normal)\n", + " \n", + " exam_var: float\n", + " Variance of the examination distribution (Normal)\n", + "\n", + " exam_min: float\n", + " The minimum value that an examination can take (Truncated Normal)\n", + " \n", + " trauma_mean: float\n", + " Mean of the trauma stabilisation distribution (Exponential)\n", + " \n", + " trauma_treat_mean: float\n", + " Mean of the trauma cubicle treatment distribution (Lognormal)\n", + " \n", + " trauma_treat_var: float\n", + " Variance of the trauma cubicle treatment distribution (Lognormal)\n", + " \n", + " non_trauma_treat_mean: float\n", + " Mean of the non trauma treatment distribution\n", + " \n", + " non_trauma_treat_var: float\n", + " Variance of the non trauma treatment distribution\n", + " \n", + " non_trauma_treat_p: float\n", + " Probability non trauma patient requires treatment\n", + " \n", + " prob_trauma: float\n", + " probability that a new arrival is a trauma patient.\n", + " \n", + " debug: int\n", + " Set to true to display a trace of simulated events in the model\n", + " Note this is best used in single_run mode.\n", + " \n", + " tracked: list\n", + " List of integers that represent patient IDs.\n", + " Used with debug. If debug is True then only display trace for specific\n", + " patients.\n", + " '''\n", + " # sampling\n", + " self.random_number_set = random_number_set\n", + " \n", + " # store parameters for sampling\n", + " self.triage_mean = triage_mean\n", + " self.reg_mean = reg_mean\n", + " self.reg_var = reg_var\n", + " self.exam_mean= exam_mean\n", + " self.exam_var = exam_var\n", + " self.exam_min = exam_min\n", + " self.trauma_mean = trauma_mean\n", + " self.trauma_treat_mean = trauma_treat_mean\n", + " self.trauma_treat_var = trauma_treat_var\n", + " self.non_trauma_treat_mean = non_trauma_treat_mean\n", + " self.non_trauma_treat_var = non_trauma_treat_var\n", + " self.non_trauma_treat_p = non_trauma_treat_p\n", + " self.prob_trauma = prob_trauma\n", + " \n", + " self.init_sampling()\n", + " \n", + " # count of each type of resource\n", + " self.init_resourse_counts(n_triage, n_reg, n_exam, n_trauma,\n", + " n_cubicles_1, n_cubicles_2)\n", + " \n", + " # debug/trace information\n", + " self.debug = debug\n", + " self.tracked = tracked\n", + " \n", + " def set_random_no_set(self, random_number_set):\n", + " '''\n", + " Controls the random sampling \n", + " Parameters:\n", + " ----------\n", + " random_number_set: int\n", + " Used to control the set of pseudo random numbers\n", + " used by the distributions in the simulation.\n", + " '''\n", + " self.random_number_set = random_number_set\n", + " self.init_sampling()\n", + "\n", + " def init_resourse_counts(self, n_triage, n_reg, n_exam, n_trauma,\n", + " n_cubicles_1, n_cubicles_2):\n", + " '''\n", + " Init the counts of resources to default values...\n", + " '''\n", + " self.n_triage = n_triage\n", + " self.n_reg = n_reg\n", + " self.n_exam = n_exam\n", + " self.n_trauma = n_trauma\n", + " \n", + " # non-trauma (1), trauma (2) treatment cubicles\n", + " self.n_cubicles_1 = n_cubicles_1\n", + " self.n_cubicles_2 = n_cubicles_2\n", + "\n", + " def init_sampling(self):\n", + " '''\n", + " Create the distributions used by the model and initialise \n", + " the random seeds of each.\n", + " ''' \n", + " # MODIFICATION. Better method for producing n non-overlapping streams\n", + " seed_sequence = np.random.SeedSequence(self.random_number_set)\n", + " \n", + " # Generate n high quality child seeds\n", + " self.seeds = seed_sequence.spawn(N_STREAMS)\n", + " \n", + " # create distributions\n", + " \n", + " # Triage duration\n", + " self.triage_dist = Exponential(self.triage_mean, \n", + " random_seed=self.seeds[0])\n", + " \n", + " # Registration duration (non-trauma only)\n", + " self.reg_dist = Lognormal(self.reg_mean, \n", + " np.sqrt(self.reg_var),\n", + " random_seed=self.seeds[1])\n", + " \n", + " # Evaluation (non-trauma only)\n", + " self.exam_dist = Normal(self.exam_mean,\n", + " np.sqrt(self.exam_var),\n", + " minimum=self.exam_min,\n", + " random_seed=self.seeds[2])\n", + " \n", + " # Trauma/stablisation duration (trauma only)\n", + " self.trauma_dist = Exponential(self.trauma_mean, \n", + " random_seed=self.seeds[3])\n", + " \n", + " # Non-trauma treatment\n", + " self.nt_treat_dist = Lognormal(self.non_trauma_treat_mean, \n", + " np.sqrt(self.non_trauma_treat_var),\n", + " random_seed=self.seeds[4])\n", + " \n", + " # treatment of trauma patients\n", + " self.treat_dist = Lognormal(self.trauma_treat_mean, \n", + " np.sqrt(self.trauma_treat_var),\n", + " random_seed=self.seeds[5])\n", + " \n", + " # probability of non-trauma patient requiring treatment\n", + " self.nt_p_treat_dist = Bernoulli(self.non_trauma_treat_p, \n", + " random_seed=self.seeds[6])\n", + " \n", + " \n", + " # probability of non-trauma versus trauma patient\n", + " self.p_trauma_dist = Bernoulli(self.prob_trauma, \n", + " random_seed=self.seeds[7])\n", + " \n", + " # init sampling for non-stationary poisson process\n", + " self.init_nspp()\n", + " \n", + " def init_nspp(self):\n", + " \n", + " # read arrival profile\n", + " self.arrivals = pd.read_csv(NSPP_PATH)\n", + " self.arrivals['mean_iat'] = 60 / self.arrivals['arrival_rate']\n", + " \n", + " # maximum arrival rate (smallest time between arrivals)\n", + " self.lambda_max = self.arrivals['arrival_rate'].max()\n", + " \n", + " # thinning exponential\n", + " self.arrival_dist = Exponential(60.0 / self.lambda_max,\n", + " random_seed=self.seeds[8])\n", + " \n", + " # thinning uniform rng\n", + " self.thinning_rng = Uniform(low=0.0, high=1.0, \n", + " random_seed=self.seeds[9])" + ] + }, + { + "cell_type": "markdown", + "id": "b970c4b5", + "metadata": {}, + "source": [ + "## 6. Patient Pathways Process Logic\n", + "\n", + "`simpy` uses a process based worldview. We can easily create whatever logic - simple or complex for the model. Here the process logic for trauma and non-trauma patients is seperated into two classes `TraumaPathway` and `NonTraumaPathway`. " + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "id": "e0c23537", + "metadata": {}, + "outputs": [], + "source": [ + "class TraumaPathway(Traceable):\n", + " '''\n", + " Encapsulates the process a patient with severe injuries or illness.\n", + " \n", + " These patients are signed into the ED and triaged as having severe injuries\n", + " or illness.\n", + " \n", + " Patients are stabilised in resus (trauma) and then sent to Treatment. \n", + " Following treatment they are discharged.\n", + " '''\n", + " def __init__(self, identifier, env, args):\n", + " '''\n", + " Constructor method\n", + " \n", + " Params:\n", + " -----\n", + " identifier: int\n", + " a numeric identifier for the patient.\n", + " \n", + " env: simpy.Environment\n", + " the simulation environment\n", + " \n", + " args: Scenario\n", + " Container class for the simulation parameters\n", + " \n", + " '''\n", + " # initialise Trace\n", + " super().__init__(debug=True)\n", + " \n", + " self.identifier = identifier\n", + " self.env = env\n", + " self.args = args\n", + " \n", + " # metrics\n", + " self.arrival = -np.inf\n", + " self.wait_triage = -np.inf\n", + " self.wait_trauma = -np.inf\n", + " self.wait_treat = -np.inf\n", + " self.total_time = -np.inf\n", + " \n", + " self.triage_duration = -np.inf\n", + " self.trauma_duration = -np.inf\n", + " self.treat_duration = -np.inf\n", + " \n", + " def execute(self):\n", + " '''\n", + " simulates the major treatment process for a patient\n", + " \n", + " 1. request and wait for sign-in/triage\n", + " 2. trauma\n", + " 3. treatment\n", + " '''\n", + " # record the time of arrival and entered the triage queue\n", + " self.arrival = self.env.now\n", + "\n", + " self.trauma_arrival()\n", + " \n", + " # request sign-in/triage \n", + " with self.args.triage.request() as req:\n", + " yield req\n", + " # record the waiting time for triage\n", + " self.wait_triage = self.env.now - self.arrival \n", + " \n", + " # triage begin event\n", + " self.triage_begin()\n", + " \n", + " # sample triage duration.\n", + " self.triage_duration = self.args.triage_dist.sample()\n", + " yield self.env.timeout(self.triage_duration)\n", + " \n", + " # triage complete event\n", + " self.triage_complete()\n", + " \n", + " # record the time that entered the trauma queue\n", + " start_wait = self.env.now\n", + " \n", + " # request trauma room \n", + " with self.args.trauma.request() as req:\n", + " yield req\n", + " \n", + " # record the waiting time for trauma\n", + " self.wait_trauma = self.env.now - start_wait\n", + " \n", + " # trauma begin event\n", + " self.trauma_begin()\n", + " \n", + " # sample stablisation duration.\n", + " self.trauma_duration = self.args.trauma_dist.sample()\n", + " yield self.env.timeout(self.trauma_duration)\n", + " \n", + " # trauma complete event\n", + " self.trauma_complete()\n", + " \n", + " # record the time that entered the treatment queue\n", + " start_wait = self.env.now\n", + " \n", + " # request treatment cubicle \n", + " with self.args.cubicle_2.request() as req:\n", + " yield req\n", + " \n", + " # record the waiting time for trauma\n", + " self.wait_treat = self.env.now - start_wait\n", + " self.treatment_begin()\n", + " \n", + " # sample treatment duration.\n", + " self.treat_duration = self.args.treat_dist.sample()\n", + " yield self.env.timeout(self.treat_duration)\n", + " \n", + " self.treatment_complete()\n", + " \n", + " # total time in system\n", + " self.total_time = self.env.now - self.arrival \n", + " \n", + " def trauma_arrival(self):\n", + " '''Trauma arrival event'''\n", + " self.trace(self.env.now, 'arrival at centre 🚑', self.identifier)\n", + " \n", + " def triage_begin(self):\n", + " '''Enter triage event\n", + " '''\n", + " self.trace(self.env.now, f'enter triage. Waiting time: {self.wait_triage:.3f}', \n", + " self.identifier)\n", + " \n", + " def trauma_begin(self):\n", + " '''Enter trauma stabilisation\n", + " '''\n", + " self.trace(self.env.now, f'enter stabilisation. Waiting time: {self.wait_trauma:.3f}', \n", + " self.identifier)\n", + " \n", + " def treatment_begin(self):\n", + " '''Enter trauma treatment post stabilisation\n", + " '''\n", + " self.trace(self.env.now, f'enter treatment. Waiting time: {self.wait_treat:.3f}', \n", + " self.identifier)\n", + " \n", + " def triage_complete(self):\n", + " '''\n", + " Triage complete event\n", + " '''\n", + " self.trace(self.env.now, 'triage complete', self.identifier)\n", + " \n", + " def trauma_complete(self):\n", + " '''\n", + " Patient stay in trauma is complete.\n", + " '''\n", + " self.trace(self.env.now, 'stabilisation complete.', self.identifier)\n", + " \n", + " def treatment_complete(self):\n", + " '''\n", + " Treatment complete event\n", + " '''\n", + " self.trace(self.env.now, f'patient {self.identifier} treatment complete; '\n", + " f'waiting time was {self.wait_treat:.3f}', self.identifier)\n", + " \n", + " def _trace_config(self):\n", + " '''Override trace config'''\n", + " config = {\n", + " \"name\":\"Trauma\", \n", + " \"time_dp\":2,\n", + " \"name_colour\":\"bold magenta\", \n", + " \"time_colour\":'bold magenta', \n", + " \"message_colour\":'black',\n", + " \"tracked\":self.args.tracked\n", + " }\n", + " return config" + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "id": "cf40186c", + "metadata": {}, + "outputs": [], + "source": [ + "class NonTraumaPathway(Traceable):\n", + " '''\n", + " Encapsulates the process a patient with minor injuries and illness.\n", + " \n", + " These patients are signed into the ED and triaged as having minor \n", + " complaints and streamed to registration and then examination. \n", + " \n", + " Post examination 40% are discharged while 60% proceed to treatment. \n", + " Following treatment they are discharged.\n", + " '''\n", + " def __init__(self, identifier, env, args):\n", + " '''\n", + " Constructor method\n", + " \n", + " Params:\n", + " -----\n", + " identifier: int\n", + " a numeric identifier for the patient.\n", + " \n", + " env: simpy.Environment\n", + " the simulation environment\n", + " \n", + " args: Scenario\n", + " Container class for the simulation parameters\n", + " \n", + " '''\n", + " super().__init__(debug=True)\n", + " self.identifier = identifier\n", + " self.env = env\n", + " self.args = args\n", + " \n", + " # triage resource\n", + " self.triage = args.triage\n", + " \n", + " # metrics\n", + " self.arrival = -np.inf\n", + " self.wait_triage = -np.inf\n", + " self.wait_reg = -np.inf\n", + " self.wait_exam = -np.inf\n", + " self.wait_treat = -np.inf\n", + " self.total_time = -np.inf\n", + " \n", + " self.triage_duration = -np.inf\n", + " self.reg_duration = -np.inf\n", + " self.exam_duration = -np.inf\n", + " self.treat_duration = -np.inf\n", + " \n", + " \n", + " def execute(self):\n", + " '''\n", + " simulates the non-trauma/minor treatment process for a patient\n", + " \n", + " 1. request and wait for sign-in/triage\n", + " 2. patient registration\n", + " 3. examination\n", + " 4.1 40% discharged\n", + " 4.2 60% treatment then discharge\n", + " '''\n", + " # record the time of arrival and entered the triage queue\n", + " self.arrival = self.env.now\n", + " \n", + " # trace arrival\n", + " self.trace(self.env.now, 'arrival to centre.', self.identifier)\n", + "\n", + " # request sign-in/triage \n", + " with self.triage.request() as req:\n", + " yield req\n", + " \n", + " # record the waiting time for triage\n", + " self.wait_triage = self.env.now - self.arrival\n", + " \n", + " # trace enter triage\n", + " self.trace(self.env.now, 'entering triage. '\n", + " f'Waiting time: {self.wait_triage:.3f} mins', \n", + " self.identifier)\n", + " \n", + " \n", + " # sample triage duration.\n", + " self.triage_duration = self.args.triage_dist.sample()\n", + " yield self.env.timeout(self.triage_duration)\n", + " \n", + " # trace exit triage\n", + " self.trace(self.env.now, 'triage complete', self.identifier)\n", + " \n", + " # record the time that entered the registration queue\n", + " start_wait = self.env.now\n", + " \n", + " # request registration clert \n", + " with self.args.registration.request() as req:\n", + " yield req\n", + " \n", + " # record the waiting time for registration\n", + " self.wait_reg = self.env.now - start_wait\n", + " \n", + " # trace begin registration\n", + " self.trace(self.env.now, 'starting patient registration.', self.identifier)\n", + " \n", + " # sample registration duration.\n", + " self.reg_duration = self.args.reg_dist.sample()\n", + " yield self.env.timeout(self.reg_duration)\n", + " \n", + " # registration complete...\n", + " self.trace(self.env.now, 'patient registered;'\n", + " f'waiting time was {self.wait_triage:.3f}', \n", + " self.identifier)\n", + " \n", + " # record the time that entered the evaluation queue\n", + " start_wait = self.env.now\n", + " \n", + " # request examination resource\n", + " with self.args.exam.request() as req:\n", + " yield req\n", + " \n", + " # record the waiting time for registration\n", + " self.wait_exam = self.env.now - start_wait\n", + " \n", + " # trace begin examination\n", + " self.trace(self.env.now, f'enter examination. Waiting time: {self.wait_exam:.3f}', \n", + " self.identifier)\n", + " \n", + " # sample examination duration.\n", + " self.exam_duration = self.args.exam_dist.sample()\n", + " yield self.env.timeout(self.exam_duration)\n", + " \n", + " # trace exit examination\n", + " self.trace(self.env.now, 'examination complete.', self.identifier)\n", + " \n", + " # sample if patient requires treatment?\n", + " self.require_treat = self.args.nt_p_treat_dist.sample()\n", + " \n", + " if self.require_treat:\n", + " \n", + " # record the time that entered the treatment queue\n", + " start_wait = self.env.now\n", + " \n", + " # request treatment cubicle\n", + " with self.args.cubicle_1.request() as req:\n", + " yield req\n", + "\n", + " # record the waiting time for treatment\n", + " self.wait_treat = self.env.now - start_wait\n", + " \n", + " # trace enter treatment\n", + " self.trace(self.env.now, f'enter treatment. Waiting time:{self.wait_treat:.3f}',\n", + " self.identifier)\n", + " \n", + " # sample treatment duration.\n", + " self.treat_duration = self.args.nt_treat_dist.sample()\n", + " yield self.env.timeout(self.treat_duration)\n", + "\n", + " # trace exit treatment\n", + " self.trace(self.env.now, 'treatment complete ⛔', self.identifier)\n", + " \n", + " # total time in system\n", + " self.total_time = self.env.now - self.arrival \n", + " \n", + " def _trace_config(self):\n", + " '''Override trace config'''\n", + " config = {\n", + " \"name\":\"Non-trauma\", \n", + " \"name_colour\":\"bold green\", \n", + " \"time_colour\":'bold green', \n", + " \"time_dp\":2,\n", + " \"message_colour\":'black',\n", + " \"tracked\":self.args.tracked\n", + " }\n", + " return config\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "68ef8e42", + "metadata": {}, + "source": [ + "## 7. Main model class\n", + "\n", + "The main class that a user interacts with to run the model is `TreatmentCentreModel`. This implements a `.run()` method, contains a simple algorithm for the non-stationary poission process for patients arrivals and inits instances of `TraumaPathway` or `NonTraumaPathway` depending on the arrival type." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0a1a0a5f", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "b1690504", + "metadata": {}, + "outputs": [], + "source": [ + "class TreatmentCentreModel:\n", + " '''\n", + " The treatment centre model\n", + " \n", + " Patients arrive at random to a treatment centre, are triaged\n", + " and then processed in either a trauma or non-trauma pathway.\n", + " '''\n", + " def __init__(self, args):\n", + " self.env = simpy.Environment()\n", + " self.args = args\n", + " self.init_resources()\n", + " \n", + " self.patients = []\n", + " self.trauma_patients = []\n", + " self.non_trauma_patients = []\n", + "\n", + " self.rc_period = None\n", + " self.results = None\n", + " \n", + " def init_resources(self):\n", + " '''\n", + " Init the number of resources\n", + " and store in the arguments container object\n", + " \n", + " Resource list:\n", + " 1. Sign-in/triage bays\n", + " 2. registration clerks\n", + " 3. examination bays\n", + " 4. trauma bays\n", + " 5. non-trauma cubicles (1)\n", + " 6. trauma cubicles (2)\n", + " \n", + " '''\n", + " # sign/in triage\n", + " self.args.triage = simpy.Resource(self.env, \n", + " capacity=self.args.n_triage)\n", + " \n", + " # registration\n", + " self.args.registration = simpy.Resource(self.env, \n", + " capacity=self.args.n_reg)\n", + " \n", + " # examination\n", + " self.args.exam = simpy.Resource(self.env, \n", + " capacity=self.args.n_exam)\n", + " \n", + " # trauma\n", + " self.args.trauma = simpy.Resource(self.env, \n", + " capacity=self.args.n_trauma)\n", + " \n", + " # non-trauma treatment\n", + " self.args.cubicle_1 = simpy.Resource(self.env, \n", + " capacity=self.args.n_cubicles_1)\n", + " \n", + " # trauma treatment\n", + " self.args.cubicle_2 = simpy.Resource(self.env, \n", + " capacity=self.args.n_cubicles_2)\n", + " \n", + " \n", + " \n", + " def run(self, results_collection_period=DEFAULT_RESULTS_COLLECTION_PERIOD):\n", + " '''\n", + " Conduct a single run of the model in its current \n", + " configuration\n", + " \n", + " \n", + " Parameters:\n", + " ----------\n", + " results_collection_period, float, optional\n", + " default = DEFAULT_RESULTS_COLLECTION_PERIOD\n", + " \n", + " warm_up, float, optional (default=0)\n", + " \n", + " length of initial transient period to truncate\n", + " from results.\n", + " \n", + " Returns:\n", + " --------\n", + " None\n", + " '''\n", + " # setup the arrival generator process\n", + " self.env.process(self.arrivals_generator())\n", + " \n", + " # store rc perio\n", + " self.rc_period = results_collection_period\n", + " \n", + " # run\n", + " self.env.run(until=results_collection_period)\n", + " \n", + " \n", + " def arrivals_generator(self): \n", + " ''' \n", + " Simulate the arrival of patients to the model\n", + " \n", + " Patients either follow a TraumaPathway or\n", + " NonTraumaPathway simpy process.\n", + " \n", + " Non stationary arrivals implemented via Thinning acceptance-rejection \n", + " algorithm.\n", + " '''\n", + " for patient_count in itertools.count():\n", + "\n", + " # this give us the index of dataframe to use\n", + " t = int(self.env.now // 60) % self.args.arrivals.shape[0]\n", + " lambda_t = self.args.arrivals['arrival_rate'].iloc[t]\n", + "\n", + " #set to a large number so that at least 1 sample taken!\n", + " u = np.Inf\n", + " \n", + " interarrival_time = 0.0\n", + "\n", + " # reject samples if u >= lambda_t / lambda_max\n", + " while u >= (lambda_t / self.args.lambda_max):\n", + " interarrival_time += self.args.arrival_dist.sample()\n", + " u = self.args.thinning_rng.sample()\n", + "\n", + " # iat\n", + " yield self.env.timeout(interarrival_time)\n", + " \n", + "\n", + " # sample if the patient is trauma or non-trauma\n", + " trauma = self.args.p_trauma_dist.sample()\n", + " if trauma:\n", + " # create and store a trauma patient to update KPIs.\n", + " new_patient = TraumaPathway(patient_count, self.env, self.args)\n", + " self.trauma_patients.append(new_patient)\n", + " else:\n", + " # create and store a non-trauma patient to update KPIs.\n", + " new_patient = NonTraumaPathway(patient_count, self.env, \n", + " self.args)\n", + " self.non_trauma_patients.append(new_patient)\n", + " \n", + " # start the pathway process for the patient\n", + " self.env.process(new_patient.execute())" + ] + }, + { + "cell_type": "markdown", + "id": "115e8fd9", + "metadata": {}, + "source": [ + "### 8. Logic to process end of run results.\n", + "\n", + "the class `SimulationSummary` accepts a `TraumaCentreModel`. At the end of a run it can be used calculate mean queuing times and the percentage of the total run that a resource was in use." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "fc06f50f", + "metadata": {}, + "outputs": [], + "source": [ + "class SimulationSummary:\n", + " '''\n", + " End of run result processing logic of the simulation model\n", + " '''\n", + " def __init__(self, model):\n", + " '''\n", + " Constructor\n", + " \n", + " Params:\n", + " ------\n", + " model: TraumaCentreModel\n", + " The model.\n", + " '''\n", + " self.model = model\n", + " self.args = model.args\n", + " self.results = None\n", + " \n", + " def process_run_results(self):\n", + " '''\n", + " Calculates statistics at end of run.\n", + " '''\n", + " self.results = {}\n", + " # list of all patients \n", + " patients = self.model.non_trauma_patients + self.model.trauma_patients\n", + " \n", + " # mean triage times (both types of patient)\n", + " mean_triage_wait = self.get_mean_metric('wait_triage', patients)\n", + " \n", + " # triage utilisation (both types of patient)\n", + " triage_util = self.get_resource_util('triage_duration', \n", + " self.args.n_triage, \n", + " patients)\n", + " \n", + " # mean waiting time for registration (non_trauma)\n", + " mean_reg_wait = self.get_mean_metric('wait_reg', \n", + " self.model.non_trauma_patients)\n", + " \n", + " # registration utilisation (trauma)\n", + " reg_util = self.get_resource_util('reg_duration', \n", + " self.args.n_reg, \n", + " self.model.non_trauma_patients)\n", + " \n", + " # mean waiting time for examination (non_trauma)\n", + " mean_wait_exam = self.get_mean_metric('wait_exam', \n", + " self.model.non_trauma_patients)\n", + " \n", + " # examination utilisation (non-trauma)\n", + " exam_util = self.get_resource_util('exam_duration', \n", + " self.args.n_exam, \n", + " self.model.non_trauma_patients)\n", + " \n", + " \n", + " # mean waiting time for treatment (non-trauma) \n", + " mean_treat_wait = self.get_mean_metric('wait_treat', \n", + " self.model.non_trauma_patients)\n", + " \n", + " # treatment utilisation (non_trauma)\n", + " treat_util1 = self.get_resource_util('treat_duration', \n", + " self.args.n_cubicles_1, \n", + " self.model.non_trauma_patients)\n", + " \n", + " # mean total time (non_trauma)\n", + " mean_total = self.get_mean_metric('total_time', \n", + " self.model.non_trauma_patients)\n", + " \n", + " # mean waiting time for trauma \n", + " mean_trauma_wait = self.get_mean_metric('wait_trauma', \n", + " self.model.trauma_patients)\n", + " \n", + " # trauma utilisation (trauma)\n", + " trauma_util = self.get_resource_util('trauma_duration', \n", + " self.args.n_trauma, \n", + " self.model.trauma_patients)\n", + " \n", + " # mean waiting time for treatment (rauma) \n", + " mean_treat_wait2 = self.get_mean_metric('wait_treat', \n", + " self.model.trauma_patients)\n", + " \n", + " # treatment utilisation (trauma)\n", + " treat_util2 = self.get_resource_util('treat_duration', \n", + " self.args.n_cubicles_2, \n", + " self.model.trauma_patients)\n", + "\n", + " # mean total time (trauma)\n", + " mean_total2 = self.get_mean_metric('total_time', \n", + " self.model.trauma_patients)\n", + " \n", + " \n", + " self.results = {'00_arrivals':len(patients),\n", + " '01a_triage_wait': mean_triage_wait,\n", + " '01b_triage_util': triage_util,\n", + " '02a_registration_wait':mean_reg_wait,\n", + " '02b_registration_util': reg_util,\n", + " '03a_examination_wait':mean_wait_exam,\n", + " '03b_examination_util': exam_util,\n", + " '04a_treatment_wait(non_trauma)':mean_treat_wait,\n", + " '04b_treatment_util(non_trauma)':treat_util1,\n", + " '05_total_time(non-trauma)':mean_total,\n", + " '06a_trauma_wait':mean_trauma_wait,\n", + " '06b_trauma_util':trauma_util,\n", + " '07a_treatment_wait(trauma)':mean_treat_wait2,\n", + " '07b_treatment_util(trauma)':treat_util2,\n", + " '08_total_time(trauma)':mean_total2,\n", + " '09_throughput': self.get_throughput(patients)}\n", + " \n", + " def get_mean_metric(self, metric, patients):\n", + " '''\n", + " Calculate mean of the performance measure for the\n", + " select cohort of patients,\n", + " \n", + " Only calculates metrics for patients where it has been \n", + " measured.\n", + " \n", + " Params:\n", + " -------\n", + " metric: str\n", + " The name of the metric e.g. 'wait_treat'\n", + " \n", + " patients: list\n", + " A list of patients\n", + " '''\n", + " mean = np.array([getattr(p, metric) for p in patients \n", + " if getattr(p, metric) > -np.inf]).mean()\n", + " return mean\n", + " \n", + " \n", + " def get_resource_util(self, metric, n_resources, patients):\n", + " '''\n", + " Calculate proportion of the results collection period\n", + " where a resource was in use.\n", + " \n", + " Done by tracking the duration by patient.\n", + " \n", + " Only calculates metrics for patients where it has been \n", + " measured.\n", + " \n", + " Params:\n", + " -------\n", + " metric: str\n", + " The name of the metric e.g. 'treatment_duration'\n", + " \n", + " patients: list\n", + " A list of patients\n", + " '''\n", + " total = np.array([getattr(p, metric) for p in patients \n", + " if getattr(p, metric) > -np.inf]).sum() \n", + " \n", + " return total / (self.model.rc_period * n_resources)\n", + " \n", + " def get_throughput(self, patients):\n", + " '''\n", + " Returns the total number of patients that have successfully\n", + " been processed and discharged in the treatment centre\n", + " (they have a total time record)\n", + " \n", + " Params:\n", + " -------\n", + " patients: list\n", + " list of all patient objects simulated.\n", + " \n", + " Returns:\n", + " ------\n", + " float\n", + " '''\n", + " return len([p for p in patients if p.total_time > -np.inf])\n", + " \n", + " def summary_frame(self):\n", + " '''\n", + " Returns run results as a pandas.DataFrame\n", + " \n", + " Returns:\n", + " -------\n", + " pd.DataFrame\n", + " '''\n", + " #append to results df\n", + " if self.results is None:\n", + " self.process_run_results()\n", + "\n", + " df = pd.DataFrame({'1':self.results})\n", + " df = df.T\n", + " df.index.name = 'rep'\n", + " return df\n", + " " + ] + }, + { + "cell_type": "markdown", + "id": "ed7608a7", + "metadata": {}, + "source": [ + "## 9. Model execution\n", + "\n", + "We note that there are **many ways** to setup a `simpy` model and execute it (that is part of its fantastic flexibility). The organisation of code we show below is based on our experience of using the package in practice. The approach also allows for easy parallisation over multiple CPU cores using `joblib`.\n", + "\n", + "We include two functions. `single_run()` and `multiple_replications`. The latter is used to repeatedly call and process the results from `single_run`." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "d751c883", + "metadata": {}, + "outputs": [], + "source": [ + "def single_run(scenario, rc_period=DEFAULT_RESULTS_COLLECTION_PERIOD, \n", + " random_no_set=DEFAULT_RNG_SET):\n", + " '''\n", + " Perform a single run of the model and return the results\n", + " \n", + " Parameters:\n", + " -----------\n", + " \n", + " scenario: Scenario object\n", + " The scenario/paramaters to run\n", + " \n", + " rc_period: int\n", + " The length of the simulation run that collects results\n", + " \n", + " random_no_set: int or None, optional (default=DEFAULT_RNG_SET)\n", + " Controls the set of random seeds used by the stochastic parts of the \n", + " model. Set to different ints to get different results. Set to None\n", + " for a random set of seeds.\n", + " \n", + " Returns:\n", + " --------\n", + " pandas.DataFrame:\n", + " results from single run.\n", + " ''' \n", + " \n", + " # set random number set - this controls sampling for the run.\n", + " scenario.set_random_no_set(random_no_set)\n", + "\n", + " # create an instance of the model\n", + " model = TreatmentCentreModel(scenario)\n", + "\n", + " # run the model\n", + " model.run(results_collection_period=rc_period)\n", + " \n", + " # run results\n", + " summary = SimulationSummary(model)\n", + " summary_df = summary.summary_frame()\n", + " \n", + " return summary_df" + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "512a699c", + "metadata": {}, + "outputs": [], + "source": [ + "def multiple_replications(scenario, rc_period=DEFAULT_RESULTS_COLLECTION_PERIOD, \n", + " n_reps=5):\n", + " '''\n", + " Perform multiple replications of the model.\n", + " \n", + " Params:\n", + " ------\n", + " scenario: Scenario\n", + " Parameters/arguments to configurethe model\n", + " \n", + " rc_period: float, optional (default=DEFAULT_RESULTS_COLLECTION_PERIOD)\n", + " results collection period. \n", + " the number of minutes to run the model to collect results\n", + "\n", + " n_reps: int, optional (default=DEFAULT_N_REPS)\n", + " Number of independent replications to run.\n", + " \n", + " Returns:\n", + " --------\n", + " pandas.DataFrame\n", + " '''\n", + "\n", + " results = [single_run(scenario, rc_period, random_no_set=rep) \n", + " for rep in range(n_reps)]\n", + " \n", + " #format and return results in a dataframe\n", + " df_results = pd.concat(results)\n", + " df_results.index = np.arange(1, len(df_results)+1)\n", + " df_results.index.name = 'rep'\n", + " return df_results" + ] + }, + { + "cell_type": "markdown", + "id": "508bb782", + "metadata": {}, + "source": [ + "### 9.1 Single run of the model\n", + "\n", + "The script below performs a single replication of the simulation model. \n", + "\n", + "**Try:**\n", + "\n", + "* Changing the `random_no_set` of the `single_run` call.\n", + "* Assigning the value `True` to `DEBUG`\n", + "* Tracking patient arrivals 10 and 19 through the model by setting `tracked=[10, 19]`" + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "id": "54eab6a5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running simulation ... => " + ] + }, + { + "data": { + "text/html": [ + "
[169.77]:<Non-trauma 10>: arrival to centre.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m169.77\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30marrival to centre.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[175.62]:<Non-trauma 10>: entering triage. Waiting time: 5.849 mins\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m175.62\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mentering triage. Waiting time: \u001b[0m\u001b[1;30m5.849\u001b[0m\u001b[30m mins\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[175.79]:<Non-trauma 10>: triage complete\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m175.79\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[188.70]:<Non-trauma 10>: starting patient registration.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m188.70\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mstarting patient registration.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[193.51]:<Non-trauma 10>: patient registered;waiting time was 5.849\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mpatient registered;waiting time was \u001b[0m\u001b[1;30m5.849\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[193.51]:<Non-trauma 10>: enter examination. Waiting time: 0.000\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter examination. Waiting time: \u001b[0m\u001b[1;30m0.000\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[204.96]:<Trauma 19>: arrival at centre 🚑\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m204.96\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30marrival at centre 🚑\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[208.94]:<Non-trauma 10>: examination complete.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mexamination complete.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[208.94]:<Non-trauma 10>: enter treatment. Waiting time:0.000\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter treatment. Waiting tim\u001b[0m\u001b[1;30me:0\u001b[0m\u001b[30m.\u001b[0m\u001b[1;30m000\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[214.79]:<Trauma 19>: enter triage. Waiting time: 9.833\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m214.79\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter triage. Waiting time: \u001b[0m\u001b[1;30m9.833\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[221.03]:<Trauma 19>: triage complete\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m221.03\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[221.63]:<Non-trauma 10>: treatment complete ⛔\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m221.63\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtreatment complete ⛔\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[384.82]:<Trauma 19>: enter stabilisation. Waiting time: 163.799\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m384.82\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter stabilisation. Waiting time: \u001b[0m\u001b[1;30m163.799\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[441.65]:<Trauma 19>: stabilisation complete.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m441.65\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mstabilisation complete.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[473.10]:<Trauma 19>: enter treatment. Waiting time: 31.447\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m473.10\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter treatment. Waiting time: \u001b[0m\u001b[1;30m31.447\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "simulation complete.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
rep1
00_arrivals118.000000
01a_triage_wait16.822191
01b_triage_util0.635315
02a_registration_wait33.176241
02b_registration_util0.701956
03a_examination_wait11.976192
03b_examination_util0.688563
04a_treatment_wait(non_trauma)50.027313
04b_treatment_util(non_trauma)0.633359
05_total_time(non-trauma)92.469682
06a_trauma_wait89.715777
06b_trauma_util0.841324
07a_treatment_wait(trauma)14.706091
07b_treatment_util(trauma)0.234110
08_total_time(trauma)271.271242
09_throughput50.000000
\n", + "
" + ], + "text/plain": [ + "rep 1\n", + "00_arrivals 118.000000\n", + "01a_triage_wait 16.822191\n", + "01b_triage_util 0.635315\n", + "02a_registration_wait 33.176241\n", + "02b_registration_util 0.701956\n", + "03a_examination_wait 11.976192\n", + "03b_examination_util 0.688563\n", + "04a_treatment_wait(non_trauma) 50.027313\n", + "04b_treatment_util(non_trauma) 0.633359\n", + "05_total_time(non-trauma) 92.469682\n", + "06a_trauma_wait 89.715777\n", + "06b_trauma_util 0.841324\n", + "07a_treatment_wait(trauma) 14.706091\n", + "07b_treatment_util(trauma) 0.234110\n", + "08_total_time(trauma) 271.271242\n", + "09_throughput 50.000000" + ] + }, + "execution_count": 33, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create the default scenario. \n", + "args = Scenario(debug=True, tracked=[10, 19])\n", + "\n", + "# use the single_run() func\n", + "# try changing `random_no_set` to see different run results\n", + "print('Running simulation ...', end=' => ')\n", + "results = single_run(args, rc_period=500.0, random_no_set=42)\n", + "print('simulation complete.')\n", + "\n", + "# show results (transpose replication for easier view)\n", + "results.T" + ] + }, + { + "cell_type": "markdown", + "id": "7636b290", + "metadata": {}, + "source": [ + "### 9.2 Multiple independent replications\n", + "\n", + "Given the set up it is now easy to perform multiple replications of the model.\n", + "\n", + "> Notes: here we also use the `rich` library to provide a **status spinner**. This is not a full progress bar implementation, but provides a small amount of user feedback that the model is still running.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ac6bd328", + "metadata": {}, + "outputs": [], + "source": [ + "args = Scenario(trace_level=2)\n", + "\n", + "#run multiple replications.\n", + "with console.status(\"[magenta]Running multiple replications...\", spinner=\"dots\") as status:\n", + " results = multiple_replications(args, n_reps=50)\n", + "\n", + "print(\"All replications complete.\")\n", + "\n", + "results.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9bce241b", + "metadata": {}, + "outputs": [], + "source": [ + "# summarise the results (2.dp)\n", + "results.mean().round(2)" + ] + }, + { + "cell_type": "markdown", + "id": "94419f54", + "metadata": {}, + "source": [ + "### 9.3 Visualise replications" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf20feb7", + "metadata": {}, + "outputs": [], + "source": [ + "fig, ax = plt.subplots(2, 1, figsize=(12,4))\n", + "ax[0].hist(results['01a_triage_wait']);\n", + "ax[0].set_ylabel('wait for triage')\n", + "ax[1].hist(results['02a_registration_wait']);\n", + "ax[1].set_ylabel('wait for registration');" + ] + }, + { + "cell_type": "markdown", + "id": "e1f938a8", + "metadata": {}, + "source": [ + "## 10. Scenario Analysis\n", + "\n", + "The structured approach we took to organising our `simpy` model allows us to easily experiment with alternative scenarios. We could employ a formal experimental design if needed. For simplicity here we will limit ourselves by running user chosen competing scenarios and compare their mean performance to the base case.\n", + "\n", + "> Note that we have our `simpy` model includes an implementation of **Common Random Numbers** across scenarios. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98c70169", + "metadata": {}, + "outputs": [], + "source": [ + "def get_scenarios(trace_level=2):\n", + " '''\n", + " Creates a dictionary object containing\n", + " objects of type `Scenario` to run.\n", + " \n", + " Returns:\n", + " --------\n", + " dict\n", + " Contains the scenarios for the model\n", + " '''\n", + " scenarios = {}\n", + " scenarios['base'] = Scenario(trace_level=trace_level)\n", + " \n", + " # extra triage capacity\n", + " scenarios['triage+1'] = Scenario(n_triage=DEFAULT_N_TRIAGE+1,\n", + " trace_level=trace_level)\n", + " \n", + " # extra examination capacity\n", + " scenarios['exam+1'] = Scenario(n_exam=DEFAULT_N_EXAM+1,\n", + " trace_level=trace_level)\n", + " \n", + " # extra non-trauma treatment capacity\n", + " scenarios['treat+1'] = Scenario(n_cubicles_1=DEFAULT_N_CUBICLES_1+1,\n", + " trace_level=trace_level)\n", + " \n", + " scenarios['triage+exam'] = Scenario(n_triage=DEFAULT_N_TRIAGE+1,\n", + " n_exam=DEFAULT_N_EXAM+1,\n", + " trace_level=trace_level)\n", + " \n", + " return scenarios" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "09906659", + "metadata": {}, + "outputs": [], + "source": [ + "def run_scenario_analysis(scenarios, rc_period, n_reps):\n", + " '''\n", + " Run each of the scenarios for a specified results\n", + " collection period and replications.\n", + " \n", + " Params:\n", + " ------\n", + " scenarios: dict\n", + " dictionary of Scenario objects\n", + " \n", + " rc_period: float\n", + " model run length\n", + " \n", + " n_rep: int\n", + " Number of replications\n", + " \n", + " status: rich.Console.status\n", + " \n", + " ''' \n", + " console.print(\"# Scenario analysis\")\n", + " scenario_results = {} \n", + " for sc_name, scenario in track(scenarios.items(), description=\"Running experiments...\"):\n", + " \n", + " #status.update(f'[magenta]Running {n_reps} replications of {sc_name}...')\n", + " replications = multiple_replications(scenario, \n", + " rc_period=rc_period,\n", + " n_reps=n_reps)\n", + " #save the results\n", + " scenario_results[sc_name] = replications\n", + " \n", + " console.rule(\"Scenario analysis complete.\")\n", + " return scenario_results" + ] + }, + { + "cell_type": "markdown", + "id": "42a24778", + "metadata": {}, + "source": [ + "### 10.1 Script to run scenario analysis" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69008d35", + "metadata": {}, + "outputs": [], + "source": [ + "#number of replications\n", + "N_REPS = 20\n", + "\n", + "#get the scenarios\n", + "scenarios = get_scenarios()\n", + "\n", + "\n", + "#run the scenario analysis\n", + "scenario_results = run_scenario_analysis(scenarios, \n", + " DEFAULT_RESULTS_COLLECTION_PERIOD,\n", + " N_REPS)\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5769e14f", + "metadata": {}, + "outputs": [], + "source": [ + "def scenario_summary_frame(scenario_results):\n", + " '''\n", + " Mean results for each performance measure by scenario\n", + " \n", + " Parameters:\n", + " ----------\n", + " scenario_results: dict\n", + " dictionary of replications. \n", + " Key identifies the performance measure\n", + " \n", + " Returns:\n", + " -------\n", + " pd.DataFrame\n", + " '''\n", + " columns = []\n", + " summary = pd.DataFrame()\n", + " for sc_name, replications in scenario_results.items():\n", + " summary = pd.concat([summary, replications.mean()], axis=1)\n", + " columns.append(sc_name)\n", + "\n", + " summary.columns = columns\n", + " return summary" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158f2fae", + "metadata": {}, + "outputs": [], + "source": [ + "# as well as rounding you may want to rename the cols/rows to \n", + "# more readable alternatives.\n", + "summary_frame = scenario_summary_frame(scenario_results)\n", + "summary_frame.round(2)" + ] + }, + { + "cell_type": "markdown", + "id": "b17a87b8", + "metadata": {}, + "source": [ + "## 11. Script to produce formatted LaTeX table for paper" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c4f5d832", + "metadata": {}, + "outputs": [], + "source": [ + "HEADER_URL = 'data/tbl_row_headers.csv'\n", + "WAIT = 'wait'\n", + "\n", + "# filter for waiting times only and round to 2dp\n", + "waiting_times = summary_frame[summary_frame.index.str.contains(WAIT)].round(2)\n", + "\n", + "# load formatted table headers\n", + "row_headers = pd.read_csv(HEADER_URL)\n", + "\n", + "# merge and format headers\n", + "waiting_times = waiting_times.reset_index()\n", + "waiting_times = pd.concat([row_headers, waiting_times], axis=1)\n", + "waiting_times = waiting_times.set_index(row_headers.columns[0])\n", + "waiting_times = waiting_times.drop(['index'], axis=1)\n", + "waiting_times = waiting_times.reset_index()\n", + "waiting_times" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "262c1013", + "metadata": {}, + "outputs": [], + "source": [ + "# output to file as LaTeX\n", + "OUTPUT_FILE = 'output/table_3.txt'\n", + "LABEL = 'tab:table3'\n", + "CAPTION = 'Simulation results that can be verified by our example reproducible pipeline.'\n", + "\n", + "waiting_times.to_latex(OUTPUT_FILE, index=False, label=LABEL, caption=CAPTION)\n", + "\n", + "# modify LaTeX file -> convert \\caption to \\tbl\n", + "with fileinput.FileInput(OUTPUT_FILE, inplace=1) as latex_file:\n", + " for line in latex_file:\n", + " line = line.replace('caption', 'tbl')\n", + " print(line, end='')\n", + "\n", + "# view LaTeX to verify\n", + "with open(OUTPUT_FILE, \"r+\") as file1:\n", + " print(file1.read())\n" + ] + }, + { + "cell_type": "markdown", + "id": "b9100795", + "metadata": {}, + "source": [ + "## End" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 7f810718dc0b3933782793038557c203bdfafe8b Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 14:29:44 +0100 Subject: [PATCH 03/11] feat(dist): modified Normal to include min value instead of resample. --- sim_tools/distributions.py | 56 ++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 30 deletions(-) diff --git a/sim_tools/distributions.py b/sim_tools/distributions.py index 38113f7..7224302 100644 --- a/sim_tools/distributions.py +++ b/sim_tools/distributions.py @@ -169,70 +169,66 @@ def sample(self, size: Optional[int] = None) -> float | np.ndarray: class Normal(Distribution): - """ + ''' Convenience class for the normal distribution. packages up distribution parameters, seed and random generator. - Option to prevent negative samples by resampling - - """ - + Use the minimum parameter to truncate the distribution + ''' def __init__( self, mean: float, sigma: float, - allow_neg: Optional[bool] = True, + minimum: Optional[float] = None, random_seed: Optional[int] = None, ): - """ + ''' Constructor - + Params: ------ mean: float The mean of the normal distribution - + sigma: float The stdev of the normal distribution - allow_neg: bool, optional (default=True) - False = resample on negative values - True = negative samples allowed. - + minimum: float + Truncate the normal distribution to a minimum + value. + random_seed: int, optional (default=None) A random seed to reproduce samples. If set to none then a unique sample is created. - """ - super().__init__(random_seed) + ''' + self.rng = np.random.default_rng(seed=random_seed) self.mean = mean self.sigma = sigma - self.allow_neg = allow_neg - + self.minimum = minimum + def sample(self, size: Optional[int] = None) -> float | np.ndarray: - """ + ''' Generate a sample from the normal distribution - + Params: ------- size: int, optional (default=None) the number of samples to return. If size=None then a single sample is returned. - """ - # initial sample + ''' samples = self.rng.normal(self.mean, self.sigma, size=size) - # no need to check if neg allowed. - if self.allow_neg: + if self.minimum is None: + return samples + elif size is None: + return max(self.minimum, samples) + else: + # index of samples with negative value + neg_idx = np.where(samples < 0)[0] + samples[neg_idx] = self.minimum return samples - # repeatedly resample negative values - negs = np.where(samples < 0)[0] - while len(negs) > 0: - resample = self.rng.normal(self.mean, self.sigma, size=len(negs)) - samples[negs] = resample - negs = np.where(samples < 0)[0] - return samples class Uniform(Distribution): From 2b65e5d5b5c0dfff44bd5bb488e4e95328fcf122 Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 16:52:34 +0100 Subject: [PATCH 04/11] fix(nspp): patched thinning exponential to use min mean_iat --- sim_tools/__init__.py | 2 +- sim_tools/time_dependent.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/sim_tools/__init__.py b/sim_tools/__init__.py index 37e1028..4256ea8 100644 --- a/sim_tools/__init__.py +++ b/sim_tools/__init__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.0' +__version__ = '0.5.0' __author__ = 'Thomas Monks' from . import datasets, distributions, time_dependent, ovs \ No newline at end of file diff --git a/sim_tools/time_dependent.py b/sim_tools/time_dependent.py index a69c8d0..be3d3cc 100644 --- a/sim_tools/time_dependent.py +++ b/sim_tools/time_dependent.py @@ -58,6 +58,7 @@ def __init__( self.arr_rng = np.random.default_rng(random_seed1) self.thinning_rng = np.random.default_rng(random_seed2) self.lambda_max = data["arrival_rate"].max() + self.min_iat = data["mean_iat"].min() # assumes all other intervals are equal in length. self.interval = int(data.iloc[1]["t"] - data.iloc[0]["t"]) self.rejects_last_sample = None @@ -94,7 +95,7 @@ def sample(self, simulation_time: float) -> float: # reject samples if u >= lambda_t / lambda_max while u >= (lambda_t / self.lambda_max): self.rejects_last_sample += 1 - interarrival_time += self.arr_rng.exponential(1 / self.lambda_max) + interarrival_time += self.arr_rng.exponential(self.min_iat) u = self.thinning_rng.uniform(0.0, 1.0) return interarrival_time From 340b82faef9f037a7f2b611ca68b8b9af3f39f82 Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 16:53:04 +0100 Subject: [PATCH 05/11] feat(trace): trace notebook-> sim_tools notebooks --- docs/03_trace/04_model.ipynb | 762 +++++++------------------ docs/03_trace/data/ed_arrivals.csv | 19 + docs/03_trace/data/tbl_row_headers.csv | 7 + docs/03_trace/output/table_3.txt | 16 + 4 files changed, 264 insertions(+), 540 deletions(-) create mode 100644 docs/03_trace/data/ed_arrivals.csv create mode 100644 docs/03_trace/data/tbl_row_headers.csv create mode 100644 docs/03_trace/output/table_3.txt diff --git a/docs/03_trace/04_model.ipynb b/docs/03_trace/04_model.ipynb index 79f86f8..ac00d91 100644 --- a/docs/03_trace/04_model.ipynb +++ b/docs/03_trace/04_model.ipynb @@ -49,6 +49,46 @@ { "cell_type": "code", "execution_count": 2, + "id": "5bf88997", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "'0.5.0'" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "import sim_tools\n", + "sim_tools.__version__" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "0a83b3cd", + "metadata": {}, + "outputs": [], + "source": [ + "from sim_tools.distributions import (\n", + " Exponential, \n", + " Uniform, \n", + " Normal, \n", + " Lognormal, \n", + " Bernoulli\n", + ")\n", + "\n", + "from sim_tools.time_dependent import NSPPThinning" + ] + }, + { + "cell_type": "code", + "execution_count": 4, "id": "e056caed", "metadata": {}, "outputs": [], @@ -83,7 +123,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "id": "3b512678", "metadata": {}, "outputs": [], @@ -130,13 +170,13 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 6, "id": "c2127af1", "metadata": {}, "outputs": [ { "data": { - "image/png": "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wKUo2gEAAAAAcCmKdgAAAAAAXIqiHQAAAAAAl6JoBwAAAADApSjaAQAAAABwKYp2AAAAAABciqIdAAAAAACXomgHAAAAAMClKNoBAAAAAHApinYAAAAAAFyKoh0AAAAAAJeiaAcAAAAAwKUo2gEAAAAAcKk0F+1//PGHrV692nn82WefWfv27W3kyJF25syZqxocAAAAAADXszQX7Q8++KD9+eefZma2detWu+uuuyw8PNzmzJljw4YNS9PfmjBhglWvXt1y5MhhkZGR1r59e9u4caPfPpJszJgxVrBgQcuaNas1bNjQ1q5dm9awAQAAAAAIOmku2v/880+rVKmSmZnNmTPH6tevb++99569+eab9vHHH6fpby1cuND69etnS5YssXnz5llSUpLFxsbaiRMnnH0mT55s06ZNsxkzZtjSpUstKirKmjVrZsePH09r6AAAAAAABJUb0voLkiwlJcXMzL777jtr06aNmZnFxMTYoUOH0vS3vv76a7/Hb7zxhkVGRtrvv/9u9evXN0k2ffp0e/zxx61jx45mZvbWW29ZgQIF7L333rMHH3wwreEDAAAAABA00nylvVq1avb000/b22+/bQsXLrTWrVubmdm2bdusQIEC/yiYY8eOmZlZnjx5nL+5f/9+i42NdfYJCwuzBg0a2OLFiy/4NxITEy0+Pt7vBwAAAACAYJTmon369On2xx9/WP/+/e3xxx+3kiVLmpnZRx99ZHXq1LniQCTZoEGDrG7dula+fHkzM9u/f7+Z2XmdAQUKFHCeO9eECRMsV65czk9MTMwVxwQAAAAAQCCleXh8xYoV/WaP95oyZYplzJjxigPp37+/rVq1yn766afznvN4PH6PJZ23zWvEiBE2aNAg53F8fDyFOwAAAAAgKKW5aL+YLFmyXPHvPvzwwzZ37lxbtGiRFS5c2NkeFRVlZqlX3KOjo53tBw8evOhQ/LCwMAsLC7viWAAAAAAAcIvLKtojIiIuemX7XIcPH77sF5dkDz/8sH3yySe2YMECK168uN/zxYsXt6ioKJs3b55VrlzZzMzOnDljCxcutEmTJl326wAAAAAAEIwuq2ifPn16urx4v3797L333rPPPvvMcuTI4dynnitXLsuaNat5PB4bMGCAjR8/3kqVKmWlSpWy8ePHW3h4uN1zzz3pEhMAAAAAAG5xWUV79+7d0+XFX3rpJTMza9iwod/2N954w3r06GFmZsOGDbNTp05Z37597ciRI1azZk379ttvLUeOHOkSEwAAAAAAbvGP7mk/deqUnT171m9bzpw5L/v3Jf3tPh6Px8aMGWNjxoxJa3gAAAAAAAS1NC/5duLECevfv79FRkZa9uzZLSIiwu8HAAAAAABcHWku2ocNG2bz58+3F1980cLCwuy1116zsWPHWsGCBW3WrFnpESMAAAAAANelNA+P//zzz23WrFnWsGFD69mzp9WrV89KlixpRYsWtXfffde6dOmSHnECAAAAAHDdSfOV9sOHDztLs+XMmdNZ4q1u3bq2aNGiqxsdAAAAAADXsTQX7SVKlLDt27ebmVnZsmXtww8/NLPUK/C5c+e+mrEBAAAAAHBdS3PRft9999nKlSvNzGzEiBHOve0DBw60oUOHXvUAAQAAAAC4XqX5nvaBAwc6/27UqJFt2LDBli1bZjfeeKPdcsstVzU4AAAAAACuZ2ku2rdv327FihVzHhcpUsSKFClyNWMCAAAAAAB2hfe0161b12bOnOlMQgcAAAAAAK6+NBfty5Yts9q1a9vTTz9tBQsWtHbt2tmcOXMsMTExPeIDAAAAAOC6leaivUqVKjZlyhTbuXOnffXVVxYZGWkPPvigRUZGWs+ePdMjRgAAAAAArktpLtq9PB6PNWrUyF599VX77rvvrESJEvbWW29dzdgAAAAAALiuXXHRvmvXLps8ebJVqlTJqlevbtmyZbMZM2ZczdgAAAAAALiupXn2+FdeecXeffdd+/nnn61MmTLWpUsX+/TTT/1mlAcAAAAAAP9cmov2p556yu666y57/vnnrVKlSukQEgAAAAAAMLuCon3nzp3m8XjSIxYAAAAAAODjsor2VatWWfny5S1Dhgy2evXqS+5bsWLFqxIYAACXUmz4F+n+Gtsntk731wBw5a7F94AZ3wUAAuuyivZKlSrZ/v37LTIy0ipVqmQej8ckOc97H3s8HktOTk63YAEAAAAAuJ5cVtG+bds2y58/v/NvAAAAAACQ/i6raC9atKiZmZ09e9bGjBljTzzxhJUoUSJdAwMAAAAA4HqXpnXaM2XKZJ988kl6xQIAAAAAAHykqWg3M+vQoYN9+umn6RAKAAAAAADwleYl30qWLGlPPfWULV682KpWrWrZsmXze/6RRx65asEBAAAAAHA9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k7M77rjD2Z6SkhLAqK7Mvn37nLZ88803koKvHXv27DmvDcFk5syZTvwPPvig/vrrr0CH9I999NFHatKkiUqXLq3ChQvr559/DnRIV4TcuFuo5YfcuFso5cfXm2++6eSqZ8+eSk5ODnRIV8XJkyedc9CGDRs625OSkgIY1ZVLz/M1rrS7gCSrXr269e/f35588kmLi4uzu+66K2iHYufLl8/atm1r0dHRNnToUJs2bZqdPHky0GFdsVDLj5ckS0lJsUqVKtlNN91k77zzjk2aNMlGjx4d6NCuiooVK1rNmjVt9OjRtnTpUuvQoYOZBecV96SkJKtSpYqVLFnSWrRoYZ9++mnQtSN79ux2yy23WGRkpLVv394+++yzQIeUJh07drTu3btb//797Z133rGBAwdaXFxcoMP6R0qUKGEZMmSw//znP1a/fn27/fbbbfHixYEOK83IjbuFWn7IjbuFUn581atXz9q2bWsjR460L774wrp06RJU5wAXkyVLFmvXrp01atTI9u/fb7fccouZBe8V9/Q8X6NodwGPx2NZsmSxfPny2ffff28//PCDeTwe69KlS9AVht4PWPPmza1jx442c+ZMGzt2rE2aNCloC/dQyo8vj8djGTJksP79+9uyZcssPj7e3nzzTRs/fryNHTs20OFdMe97sFKlSlamTBn74YcfbMaMGbZ48WJn+FWwFbz58uWzUqVKWfny5W3MmDHWsWNH++yzz4KmHZIsLCzM6tSpYx07drShQ4dap06dgqpwj4iIMEmWlJRkS5YssU8//dQGDRoUVCe4Z8+e9XtcuXJlK1WqlI0YMcJeffVVq1atmt15551Bd4JLbtwt2PNDbtwtlPNj9n/3RZcoUcKyZctmq1atss8//9y+++4769atW1CcA1yKx+Ox6tWr28qVK+3JJ5+08PBwq1y5spkFZ+GerudrV+V6PdJsz5492rRp03lDx2vUqKHXX39dcXFxKlOmjKpXrx6UQ7G3bdumPHny6JNPPtEnn3wij8ej0aNH68SJE4EO7bKEan7Wrl2rf/3rX1q3bp3f9qlTp+qhhx5SUlKSXnnlFWXMmFFjxowJUJRXZv/+/edtW7VqlVq3bq1FixZp7ty5ypUrlzp16uQ8H0zDy1avXq1ixYrp3Xff1eDBg+XxePTZZ59JCp6h8r/88oty5cqlb7/9ViNHjlTmzJmdNrjNX3/9pUOHDvm9R3bt2qWiRYtq0aJFWrJkibJmzapu3boFxZDSlStXql69epo0aZIWL17sbN+zZ4+aNWum3377TZLUpEkTFSpUyNVDSsmNe3MjhVZ+yI27hVp+fB08eFBJSUl+w8S3bdummjVr6rvvvtP333+vnDlzqmvXrkFzDnApPXr0UN++fbVw4UKVLl1aVapUcZ4LtqHy6XW+RtEeAH/++ac8Ho9uvvlmtWvXTj///LOOHDkiSXrqqad09913S0r9ci1durRq166ttWvXBjDiS1u/fr1mzJihJUuW+G2fMWOGcy/xa6+9Jo/HozFjxri+cA+1/HjFxcU599m0adNGt912m9auXauEhAStXr1aefLkcQ5wr776qrJkyaKhQ4cGOOrL481Zo0aNNG3aNK1evVqSdPz4ccXGxmrYsGGSpLlz5yoiIkJ33XVXIMP9Wxs3btT//vc/HT9+3NkWHx+v3r17a9q0aUpISFCfPn38DgRu64DYuHGjvv32W+3du9dv+yOPPKLRo0fr1KlT6tu3r8LCwlxXuK9du1Yej0fNmjVT//79FRcXp5MnT0qSHnroIY0YMUKS9MMPPyhr1qzq2bOnDh48GMiQLyk5OVm1atVy7hnMmTOnRowYoU8++USS1KlTJ/Xs2dPZv3Xr1sqSJct53+luQG7cmxsptPJDbtybGyn08uNrw4YN8ng8io2N1YgRI7Rr1y5J0unTp3XnnXdq1KhRkqRvv/1WOXPmVI8ePVx3DnAxq1evVrt27bRw4UJt2bLF2f6f//xHrVq1UmJiopYtW6ZSpUoFReF+Lc/XKNoDYOXKlfJ4PCpSpIjuuecexcTEqHv37nrllVe0Y8cORUREaO7cuZJSC60CBQqoUaNGOnPmTIAjP9+hQ4ecQrBGjRpq27atli5dqri4OK1fv1433XSTli9fLim1cM+cObOGDBni6sI9lPLjKyUlRcOGDZPH49GQIUN0zz33qEqVKmrfvr1+/PFH9e7dW127dtWpU6ckSS+88ILy5cvn+t72lJQUzZo1Sx6PR/ny5dPAgQOVM2dOjR8/XsuWLdOGDRt08803a/Xq1UpJSdH//vc/eTwede/ePdChX5DvJCYPPvighg4dqsTEREnShx9+qLx58+rAgQNKSEhQv379lDlzZs2ZMyfAUfvbu3ev04YOHTpo2LBhOnr0qFJSUvTRRx+pWLFiOnr0qCSpX79+yp49u6va8N5778nj8ahSpUqqW7euypUrp0GDBumXX37RggULlC9fPv3555+SpB9//FEej0d9+vRx9UnT3r17VbJkSdWvX18zZ87Uvffeq7p166p9+/aaMmWKcufOrZ9++snZ//bbb3fa6Cbkxr25kUIvP+TGvbmRQis/vt5//315PB7deOON6ty5s/Lnz6+nnnpKv/zyi1auXKnIyEjn4sT333/vnC+4XUJCgqpUqSKPx6NWrVqpUaNGzoTVycnJqlSpkjPKc8mSJSpXrpyKFy8e4Kgv7lqfr1G0X2PeL7/ffvvNKWA/+ugjvfTSSypcuLBuv/125ciRQz169FBCQoIk6fDhw9q8eXMgw76khx9+WB6PR0888YRatWqlli1bql69elq6dKnatGmjtm3b6vTp05Kkl19+WXny5HFtIRiK+TmXt6hdtGiRFi1apMmTJysqKkoFChRQkSJF/HLjLazcLj4+XjNnzlSmTJk0depUff/997rnnntUtmxZNWrUSMWKFdOrr74qSUpMTNRXX33l2tsatmzZojZt2sjj8WjkyJFq2bKlKlSooJEjR2rDhg3q2rWrpk6dKkk6cOCA7rvvPuXJk0fHjx93zRC5I0eOqFmzZs7omsqVK6tx48bq27evDhw4oKZNmzorFZw5c0Y9e/ZUgQIF/HqqA23GjBnyeDx66aWX9OKLL2rkyJEKDw/Xo48+qsyZM2vs2LHOwfmXX37R+vXrAxzx+RISEnTixAnt2bNHUuow2Hz58qlTp0769ddfdejQIfXs2VNNmjSRx+MJiitQErlxu2DPD7lxb26k0M6Pr5dfflkej0evvPKK3n77bT3yyCPKly+f7rnnHhUoUEDTp093jvkLFy507TmNr1OnTumDDz5Q0aJFVadOHX322WcqVaqUWrVqpYEDB2ratGlq3769jh07puTkZC1atEjVq1fX1q1bAx36BW3dulVt27a9ZudrFO3XkHdoh/e/ixYtUqZMmdS7d28dPnxYCQkJevHFF9W6dWu98847ktw35NWX71CVBx98UPnz59d///tfLV68WBMmTFCZMmV04403KioqSrt373b2dWshGGr58XVunA899JCyZ8+uTz/9VFJqoThz5kx9/PHHgQjvivm268SJE5o2bZo8Ho/eeOMNSdLmzZvVpUsXFStWTLNnzw5QlGm3c+dONW/eXMWKFdPevXv1wQcfqGfPnsqdO7fCw8PVoEEDZ2THwYMHL3g/f6DFx8erQYMGqlSpktauXasPP/xQ3bp1U3R0tCIjI1W3bl2nSE9MTAx4Gw4cOKBffvlF//vf/5yD6fjx45UpUyZNmzZNSUlJWrFihYYPH66bbrpJH374oST3ziewbt06tWnTRuXLl1fp0qWd3v2dO3cqMjJS9evX186dOyWlXq36448/AhnuJZEb9+ZGCq38kBv35kYKvfz4OnjwoDPqwXtRaNKkScqQIYNmzpyps2fPav369XrggQdUunRpJ1fBwNsRdPbsWUmpy/JlzZpVo0eP1tGjR/W///1PsbGxypIlizwejzMaIiUlxRn96VZbtmxR69atr8n5GkX7NXDuVeXk5GTnjbto0SJlzpxZd999t+Li4pzng4XvkPCePXsqR44cevfddyVJmzZt0vvvv++qYa8XEqr52bZtm7788ku/bd52SanDkrNkyaKPPvroWof2jyUkJDgnFb4nF6dOndKzzz4rj8ejZ599VlJqvvbt2xeQOC/X9u3bNWPGDI0fP97pSNm/f7/q1KmjG2+80bmfbcGCBerXr5/zGXOT7du367nnntPYsWP1/vvvS0rtSKlWrZrKlSunNWvWSJK+++47PfbYY65qw6pVq1SpUiXFxMQoU6ZMqlatmtOhMHHiRHk8Ho0fP955r7lpRMCFLF++XDly5NBDDz2kxx57TC1btvS7n2737t2KiopSvXr1/K7OuPFEndykcmNupNDKD7lxb26k0MuPr1WrVqly5coqXLiwsmfPrtq1azvnnFOmTFGGDBk0ZcoUSann3d7ngsGGDRvUq1cvNWjQQPfff78zd9IHH3ygsLAwPfroo86+n3/+uX744QdJ7s/buRME1q9fP93P1yja09mff/6pTJkyqVGjRvrwww/Pm7VbSr13KHPmzLr33nv9rki70ebNm/Xtt9/6bfMOfZekBx54QFmyZNG7777r2kkjfIVafrwOHTqk7Nmzy+Px6N5779Urr7zi9Nz66t+/v7JkyeIUisFg/fr1qly5sjp37qytW7fq8OHDfs/7XnGfPHmys92tB4CVK1eqcOHCqlWrlrJly6aCBQvqiSeekJQ67K9BgwYqVKiQMzzMO3GQm6xYsUIxMTGqVauW8ubNq2zZsunxxx+XlHoieOutt6p48eLO58tN3w0rVqxQeHi4hgwZop9++kmvv/66ChcurIYNGzr7TJ06VR6PRxMnTtSxY8cCGO3fW7dunTJnzqyJEyc627Zs2aLy5curTZs2zvtnz549ioqKUtOmTZ17I92G3Lg3N1Jo5YfcuDc3Uujlx9eKFSuULVs2DRo0SIsXL9Zzzz2nEiVKqEWLFs5V5unTp/tdjAgWK1asUEREhDp37qw2bdqoVKlSKleunFatWiVJmjNnjsLDw9WrV68AR3p59u3b58R+rm3btqlhw4bper5G0Z7Oli1bpooVK6pdu3bq0aOHChYsqGnTpp13j83ChQuVLVs2tW/f/rzZlt3i0KFDuuGGG+TxeNSvXz9NmjTpglede/furaxZs2r27NmuH9YSSvnxlZCQoN69e+vll1/WqFGj1LJlS8XExOg///mPli5d6rdvv3795PF49L///S9A0abNyy+/rCpVqqht27aqUaOGOnfurDlz5vgVgidOnNDUqVOVOXNmPf300wGM9tJWrVqlrFmzatSoUTp27Jg2btyo7t27KyYmxnkPbt26VY0bN1ahQoW0fft2Se4a7bFy5UqFh4dr5MiRSkhI0Pr16/Xoo48qZ86cmj9/vqTUW2Lq16+v4sWLu2qlhS1btihz5szO/fVS6v/b4cOHKzo62hlmKcl5P40ZM+a8pSDd4sSJE7rrrruUJUsWZ1Ze7+iaO+64Q507d5b0fyOk9u7dq8yZM+u2225zhi+6Bblxb26k0MoPuXFvbqTQy4+vzZs3Kzw83Fnhxuvhhx9W0aJF/UaCPv/88woLC3P1OY2vNWvWKGvWrH7xfvrpp8qbN6+eeeYZSak5++ijjxQeHq5+/foFKtTLsmfPHuXKlUt58+bV0KFD/SY49NqyZYsaNWqUbudrFO3pbP/+/erataszBPn1119XkyZN1LBhQ/Xq1UsrV650hrnMnz9fBQoUcCbXcKP+/ftr+PDhmjRpkurVq6fSpUtr2rRpzgzxXr1795bH43H90OtQy4+vwYMHKzY2VlLqlc2pU6fqrrvuUoECBTR+/Hi/L5zhw4e7csKZC1m8eLEqVqyo7du3a/ny5Ro7dqxy586t7t27a+rUqX5X1MeNG6c8efK4cijZzp07lT9/frVr185v+6JFi5Q1a1Z99913zrZt27apWbNmCg8P9zvhCrTdu3crOjparVu39tu+aNEi5ciRw29UzrFjx9S4cWNFRES44r2WkpKiF154QZGRkectbfjGG2+oRIkS2rNnj19n0NNPP62IiAgdOnToWod72ebOnet0aHnv59yyZYvCw8P14osvOvt5T3r37dunjRs3BiTWiwnV3Hz++edBnxsp9SR0xowZIZWfTz/9NCRyw2fH3fk518SJExUdHa3Ro0f73W765ptvqmTJktq9e7ffOc3EiRNde07j68iRIypXrpzKly9/3mjIGjVqaMiQIc7jpKQk/fe//5XH49GgQYOudaiXbd26dWratKneeustdezYUbGxsapbt65+//13v1G4O3bsUOPGjdPlfI2iPZ34fiFOnjxZxYsX14EDBySlTsjgXfLg1ltvVZMmTZz7vt18ZTolJUWjR492ejWl1HttevXqpZw5c2rSpEn6/PPPneeGDh3qipPzvxMq+fHyfsEnJyerWrVqeu6555znYmNjFR0drQoVKqhGjRqqVq1a0Awh89WnTx+1bdvWGdK3e/duFShQQB6PR5UrV9b48eO1YsUKSXLtwW3VqlWqUKGCbr/9dn311VfO9sWLFytnzpzn9eJu2bJFbdu2ddVyNb///rsaNWqk2NhYv7XWly5dqhw5cmjRokV++x89elStW7fWpk2brnWoF3To0CFNmzZN5cuXV9++fSWlznGRO3dujR071tnPt6fcre8nX19++aVatWqlunXr6quvvlLx4sX10EMPOc97vyPcdJvCueLi4kIiN+devfzmm2+CPjdSai6CPT8bN27UW2+95Tz++uuvQyY3U6dODercXEgofK+dKz4+XqNGjVLNmjWdQvbw4cPn5crXuUWwW40ZM8Yp0L3DxTdu3KiwsDBnMmevpKQkzZ071/U1Q6tWrZwRAevWrVP37t3VsGFD3Xrrrfrwww918OBBSalX5du0aXPVz9co2q+y3bt3O4Wd94vj5MmTatWqld58801J0n333aeiRYvqjz/+0Mcff6xOnTo5a/m5lffL/eTJkypevLgmTJjgPNe6dWtFRESoYcOGuvnmm1W1alVnogk38/bIhkJ+znXmzBklJSXpscceU58+fSRJ3bp1U4ECBbRjxw799ddf+vTTT9WkSZOg6I328h6UFy1apEaNGjlL7fXq1UvFihXTihUrNHToUNWoUUOFChW64H38brJkyRLV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0kSJiYmu/KzMnj1bZcuW1VNPPaWbb775vCvuR48eDYrcxMfHq0yZMipVqpSk1O/bO+64QzVr1lTGjBnVpEkTvfDCC87+33zzjetz8/rrr6tZs2aSUtszbtw49ezZU927d3cKJu8VdzfnZ8uWLcqVK5ffe+s///mPihYt6tzS59tJ/O2337o6NydOnFDLli39Rmc999xzzvJZviOJ3J4br6+++kp16tTxWzN+/vz5zhK2vmt6B8Nnx+vMmTPq1KmT7r//fmfbzz//rJ49eypz5szO8dJ7xf3WW29Vv379guJK++bNm505he6//36/Sdl8458wYYIqV66sYcOGnTcq1E3WrFmjBg0aOKMDpk2bpuTkZCUnJyslJUUJCQlq0aKFSpQo4cyddK1QtP9Da9euVcaMGZ1e2+PHj6tz587q0KGDEhIS/Aqk+fPnKzw83NVLax04cEBlypRxDmp79+71u9/ba+7cuSpfvryzLJWbhyY99dRT6tq1q9+2/v37y+PxqESJEtq3b5+k1AO02/Pja8OGDbrpppv8JpI7duyY3n//feXJk8fv4NCyZUvVrVs3KA4AcXFxatGihbp166Z69eqpcOHC+v777y/4Hps1a5ayZ89+0XUz3WDv3r2qXLmy320z+/btU0REhP773/9K+r8Oh19//VWNGzdWvXr1NG/evIDEeyFxcXFq3ry57rzzTjVp0kRVqlTRRx99dMHC/eeff9Ydd9yhSpUq6csvvwxUyI6zZ89q0qRJ6ty5s/766y8n1i1btqhQoUJOx503B0eOHNG4ceNUp04d7d+/P2BxX0pCQoI+++wzvytMzzzzjDwej5o3b65bb71V+fLlc4bwSanHHzetPHCuzZs3q1u3bjp48KDGjh2rsmXL6umnn1bfvn319ttvS0rtVHF7bk6dOqVXXnlF4eHhGj58uNq0aaPY2Fh9+OGH+vDDD9WnTx8VK1ZMs2bNcn7H7bkZO3as7r77bklSzZo1FRsbqzvuuENNmzZVxowZnZE1f/31l6vz8/PPP593LiNJ5cqV0x133OE8Pve8za25OXTokMqVK6d3333X2TZs2DCVLFlS1apV00033eR03rk9N16fffaZoqKi/Iqh33//Xc2bN9fUqVOVJ08evw5JN+fH18mTJ1WlShU99dRTftt37NihXr16KU+ePFq4cKGk1I6/AQMGqGnT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zx7Vr19fLVu2VN++fdW7d29FREQE/MT8Qi4nP75F3sqVK3XvvfcqIiLC1V+Ux44dU4cOHXT33Xerb9++qlKlisaMGXPBwv3333/XzJkz1blzZ40YMcKVV9l87d69W9WrV1eZMmVUrFgxjRw58pKF+6lTp9SnTx+FhYU59726SVrb065dO2XPnt1VV3h37tzpN5pD+r/P0Y4dOzR48GC/UQSnTp3Sgw8+qPDw8IDnZPXq1cqZM6ffjPzek4rt27crOjra73aEQ4cOKW/evIqIiHBlB9Dx48dVsGBBeTweZ+Zur61btyoqKkoPPPCA34nG0qVLNWnSJNeN2Lic3IwePVpScCxJeSW5WbJkicaMGePKUWppyY+U+t2wcOFCrV692nXnN1eSGze/5y4nN2PGjJGU2o6PPvpIjz/+uJ577jlXXjC6kvycOnVKixcvduXkZb7Onj2r4cOHq3379vrss8/UpEkTNW/e3K9w971daf369frwww/1v//975LnCm7wdyvIXGgE1fDhw+XxeFxZM1xOB4T3HOi5555T586dNWnSJIWFhQWsYJco2i/Lrl275PF45PF4nOG43hPZrVu3qnDhwurTp4/f73gTf+4skW6Qll7Or776Sr1791b16tXVvXv3gJ+YX0ha85OSkqJt27Zp9OjRrmyPrxMnTmjcuHH66KOPJKUu53Khwj0Yff7552rVqpX+/PNP/etf/1KhQoUuWeju379fd911l7Mqg9tcbnu897U2bdpUHo/HNZ0r27Ztk8fjUcWKFZ3JcLwHsm3btikqKuq8mVITEhLUvn17Z+nHQElJSVGfPn3k8XiUMWNGzZ4923lu9+7dypw5s/r06XPe/azDhw935ZU1KfU7rE+fPmrUqJGio6PVvHlz57nJkyerS5cuF7wy4Lb5U64kN253pblx461KoZafK82NG6UlN27uePAVSvm5kF9//VVvvfWWpNT1vhs3bnxe4e7G74G/czkryPjmLTExUf/9739deQtjWjsg/v3vf8vj8SgiIiLgF1go2i/Dhg0bVKpUKRUqVEjh4eFO0k6dOqWyZcuqa9euQfMlc7m9nL6FYGJiopKSklw7odGV5idYDnLx8fF+8Q8dOlRVqlTR6NGjnfufTp8+HXQHgn379jnLuknS9OnTnULX9x483wn43PoelC6/Pd7P1v79+111X+tnn32m7Nmzq0yZMqpatapzT+GxY8dUpEgR3XfffZe8fy3QPvroI9WtW1cPPPCAPB6P0/F47NgxjR071u/zHiyf/ZkzZ6pw4cL68MMPVbJkSWcGWym4TvzSkptgESq5kUIvP+TGPROBXkgo5edc5/5/X7ly5QWvuHvXZA8Wl7uCjG+nsVvfg5fTAeFrzpw58ng8rrjIR9F+GY4eParWrVurR48e6t+/v99w1p07dwbVAS0tvZwXm5XYbUIpP5fi+2U4ZMgQ54r7vn379Oijj+r22293fa7+zvPPP+8Uut51yp9//nlX9tZejou1x43Dsbdt26abbrpJPXv2VOfOnVW1alVnsqZffvnFde+tc1dTOHLkiG688UYNGTJEEydOlMfjcWZNdlvsadG2bVs99thj+vLLL5U/f36/E1y3XVX3IjfuzY10feSH3LhbsOYnLbz5WbFihVO4f/zxx3rkkUeUO3duHTp0KChz+HcryIwaNSrAEV7a5XZA+Bbw3pGtgUbRfgm+H6bvvvtOMTEx+uSTT9S1a1dlzZrVmSzHLVeaLleo9HKGan4uxTc/Q4cOVfXq1VW+fHlly5bNlRNpXS7fjhXfoeVdunRRjhw5XHeP7t8Jpvb4fj5efPFFVa9eXW+++aaaNGmiatWqOYW72z5HvsvleL3//vu67bbbtHz5cg0ePFgej0fvv/++JLl+COmaNWvUq1cvrVixwrlPOCkpybmfLiUlRd98843y58+v1q1bO7/ntrxI5MbLjbmRQis/5Ma9uZFCLz9/59y5ErztWLlypZo1a6bcuXMrR44cAb0v+mpw+woyl+vvOiBGjhwpyT2j8yjaL2Dz5s369ddf/YbiHj58WF27dtVrr72mgwcPqmPHjgoPD3c+eMH2BRPMvZyhnJ+0rE6QlJSkm266SREREUFxJfrvluHyfd9Nnz5dHo9HuXLlcu1EgcHenl27dp23NN3y5cvVunVr/fTTT/r555916623qnr16tq7d68k93w3rF27Vnnz5tWTTz7pzGotpZ4YVapUyRl6OGjQIHk8Hn3wwQeS3HtlKj4+XjfeeKM8Ho86dOigpk2bavbs2Tp79qzi4+MVExOjl19+WZL09ddfq1ChQqpbt26Ao74wcuPe3EihlR9y497cSKGXnwu5nCUFvfnp3LmzcufO7cqZ1M8VrCvIXIlg6oCgaD/H7t27nUnNHnnkEU2cONF57l//+peKFy+uxMREHThwQJ06dVKuXLkCPgnTpYRaL2eo5ccrrasTJCYm6v7771eWLFlcfwA4fvy48+9zl6vZt2+f/vvf//oNCUxKStLAgQMVERHhmknafIVCe7Zt26ZMmTIpT548mj59ujMzsST17NlTzZo1kyTNnz9fDRo0UO3atS+59vy1dPbsWT366KPOpHl33HGHqlSpooULF+rkyZP697//rZo1ayohIUFHjhxxZrD1TuboRgkJCXrppZeUN29etWrVSm+88YYKFSqk9u3ba+LEiZo0aZK6dOmiU6dO6ezZs/r8889VunRp55YLtyA37s2NFHr5ITfuzY0UWvnxdSVLCj7++OOunUndVzCvIHMxodQBQdF+jjVr1qh27dryeDwaM2aMqlatqrp162ry5Mnas2eP2rZtq9dee01S6hu4RYsWKliwoE6fPu263s5Q7OUMpfx4XcnqBJI0cOBA13dIrFu3ToULF9Ybb7zhbPNdriZ//vx+HS9S6gQtGTNmPG+tZjcIhfakpKTo448/VrFixZQpUyYNGjRIFSpUUMeOHTVv3jytXr1abdu21eLFiyVJ3377rSpVqqTGjRv7LTEYSFu3blW3bt2UNWtWLViwQEOGDFFsbKwqVqyoAQMGqHLlys68FgcPHtTo0aNd02Hiy/s59/4/femll5QhQwa9+uqr2rdvn9555x1Vq1ZNmTNnVpYsWZyT2aSkpAsOo3UDcuPe3EihkR9y497cSKGbH+nKlqyTUs/z3H6BJZhXkLmQUOyAoGg/R0pKilatWqVatWqpWrVqOnTokF588UV17NhRefPmVc6cOdWhQwfnA7lr1y5nlmW3OXnypGbOnBlSvZyhlB+vtM5+74ai6XIkJSXpoYceksfjUaFChZwOIknas2ePsmbNqoceeuiC7fnrr7+uZaiXJZTak5CQoA8++EA333yz2rVrp/379+uBBx5QixYtVLhwYUVEROiJJ55w9p8/f77fDPiBEB8frz179jhD9f/66y/FxsaqSJEi2rFjh/766y+99tpruummm5QhQwZ9++23zu+65X40X3/++acefvhhtWnTRoMHD3bWVPYuLzN58mRn33fffVdff/11oEL9W+TGvbmRQis/5Ma9uZFCLz/nupIl69w6cvVcwb6CjK9Q64Dwomj34fsFuHr1at18882qXbu2s9b6nDlzdM899+jtt98OVIhpdvLkSb366qvKmDFj0PdyhmJ+pNCe/f61115TyZIlNWTIEBUvXtwpdPfv36/JkycHXduCuT379+/XN998o2+++cbpXZ49e7aioqLUt29fSakHrbFjx6pChQrOWrNusGbNGjVt2lTFihVT2bJl9eSTT0qSDh06pGbNmikqKkrr1q2TlNqD7r3VxK0dXCtWrFDevHnVsWNH1alTR0WLFlXNmjW1Y8cOSakTAmbMmNFpp5uRG3cLpfyQG/fmRgq9/FxMqEzmfK5gW0HmUkKpA8LXdV+0Hzx4UFu3br3gc6tXr1b58uVVsWJFpzD0vZ/VzXwnx0hMTNSLL74YlL2coZofKbRnv/fGnJiYqPLly+u+++7Tk08+qUKFCmnmzJnOfsFyEAj29qxatUplypRRqVKl5PF4VK9ePc2fP19SauFesGBBv6F+3p5pN1ixYoVy5MihBx98UC+88II6dOigqKgoTZ06VVLqaJpWrVopX758zgmum61Zs0ZZs2bVU0895bxf3njjDUVERGjGjBmSUkfZvPTSS8qYMaOefvrpQIZ7SeTGvbmRQis/5MbdQi0/fyeYJ3M+V7CuIHMpodQB4eu6Ltp37typPHnyqFChQpo6dep5ayenpKRo9erVqlChgipUqOAUhm69mrZlyxa9+eabOnXq1HnPnT59Ouh6OUMtP16hPPu9Nwe+3njjDfXu3VvLly/XwIEDFR0drVdeecV53s35CoX2rFy5UuHh4Ro6dKjWrl2r999/X6VLl1br1q117NgxJSQk6P3331fhwoV1xx13OL/nhvfc+vXrFR4ertGjRzvbDh06pKpVq/oNS9y9e7datWqlggULuvZeNCk19pIlS6pq1apKTEx0ticnJ+umm27SU0895WxLTEzUzJkz5fF4NGXKlECEe0nkxr25kUIrP+TGvbmRQi8/vkJtMmdfwbyCzMWEYgeEr+u6aF+2bJkaNmyoGTNmqHHjxmrWrJnat2+vzZs3O5MXJCcna82aNbrllltUpEgR117JjY+PV7FixZQjRw7ddNNNevHFF8+b+OrEiRNB1csZSvnxCtXZ76XUE5EiRYpo6NCh+vnnn3XixAlJ0h9//KFChQpp/vz5OnHihAYPHqyCBQs6Ewa6VSi0Z+PGjcqRI8d5ExmOHz9eOXPmdIYtnjx5Uu+//76KFy/ud+IRSGfOnFGHDh0UGRmpefPmSfq/A+2gQYPUpEkTJydS6merbt26KlWqlKuHKPbv31+1atXS2LFjdeDAAUmpExyGhYVpzpw5fvsmJibq9ddfd92VtlDNTd++fYM+NykpKUpMTAy5/PTr1y/ocyOl5qFDhw7Knz9/yORGCp38+ArFyZy9gnkFmQsJxQ6IC7mui/ZTp06pTp06Gj9+vCRpyZIlatGihRo3bqyWLVtqwYIFOnbsmKTU3rY6depoy5YtgQz5oo4dO6auXbvqzTff1Ntvv60uXbooMjJSjz/+uL777jtnv+TkZL300kvnDZV3o1DKj1cozn4vpR6E+/btK4/Ho8jISD3wwAOqUqWKfvrpJyUlJemFF15QixYtdPLkSW3dulWPPfaYsmTJ4qr7pn2FSnvefvtteTwePf30034TTL7//vuKiYnRxo0bnffVyZMn9dZbb6l8+fKumbzxjz/+UGxsrFq0aKGPP/5YUuqkf+Hh4XruuefO23/Pnj2unUjTdwTGoEGDVKVKFU2bNk3Lly9XTEyM+vXr5zzv5s+61++//67Y2Fg1b948qHOzd+9ev+U0Bw4cGNS58RaAy5YtC/rPzokTJ3Ty5Enn8eDBg4M6N7t27dK2bdu0bt26oM/NhQT7Z+dcobpkXSisIOMr1DogLuW6Ldq9J1CLFy9WlSpV9OuvvzrPVahQQblz51bOnDnVqVMnPf7445LcP8HEf/7zHxUoUECHDh3SmTNntGjRIt1+++0qUKCAbr/9di1atEhHjhyRlDqhlpt7Ob0nHqGUHyk0Z7/3+u233/Tggw8qIiJCs2fP1jPPPKPKlSurRYsW6tSpk2699VZnJtlNmzbpiSee0J9//hngqC8uVNrz/PPPq1ChQhoxYoROnTqlv/76S3nz5nU+N75OnTp1wVsCrqVzh62tWLFCjRs3Vrt27fTKK68E3YlgQkKC4uPjnQ5Gr0GDBumWW25Rjhw51KNHD2e7226v8BUXF6d169Zpw4YNklJHozRu3Fht27YNytzs3r1befPmVYcOHfTLL7842wcOHBh0uZFSO1Lq1q3rfIaD+bPjLRwWLVrkN0FuMH5upNQO+8KFC2vAgAGSpKVLlwZtbqTUc5PZs2fro48+cm7jk4I3P75Ceck6r2BcQeZCQq0D4u9cV0X7iRMnzivs9uzZo6ZNmzrrLnfv3l1RUVHas2ePFixYoAEDBqhw4cKu7JXZtWuX/vjjD79td999t98V9Pbt2+vmm29WvXr1VLlyZRUrVsz1k895JScnB3V+zhWqs9/7tmv58uXq1KmTChcurD179mjPnj166623VKRIEXk8Hr/3nluHJoVaeyTpueeeU+HChdW/f38VLFjQb6kTN51Mbdy4Uc8++6wzfM1r+fLlaty4sXLkyOF3z6eb/59L0tq1axUbG6vKlSurYMGCeuedd/w6JR5//HEVL15cY8aMcTpU3ZQPX6tXr1blypVVoUIFZcqUSWPGjJGUOhoiGHMjpZ6I3nDDDWrcuLG6devm1zk8fPhwxcTEBEVupNQCPVu2bBo0aJCk/ys4fv/9dzVu3FjZs2cPmvysWbNGERER6tu37wU7rYcPHx40nxspNTfh4eEqXry4ChQo4Hy/BetnZ9WqVSpatKiqVaumAgUKqG3bts7M9pI0cuTIoMqPr1Besi6YV5C5lFDpgLgc103R7ttr6zsBmCS98MILKl68uG677TZFR0c7y21JqRO4ufE+6TVr1igmJsY5QHtPBJ988kk1aNBAUuqwkAIFCjhfpl999ZX69OmjtWvXBiTmS/HttfUdqigFZ368Qnn2e98TC99/r1y5UrfddpsKFSrk5HLPnj1avXq1JPdeQQiF9mzdulXTpk3ToEGDNHv2bL/nnn/+eWXPnl2VK1fWtm3bAhPgJWzatEl58uSRx+PRiBEjzlvbfs2aNWrcuLFatGihuXPnOtvd9P/f19q1a5U3b14NHDhQ7733ngYNGqRMmTKdN6Hm4MGDVbVqVT311FPOXB1u423LkCFDtHbtWj377LPyeDzO+2j16tVq3LixYmNjgyI3XnFxcWrbtq1mzpypKlWqqEuXLlqxYoXz/NixY1WlShVX50ZK/Y7Kli2bhg4d6rfde9Vv06ZNQZOfhIQExcbG+s3DsX79eq1YscLvWDpgwADXf26k1II9a9asGjlypP766y+VK1dO48aNc44xwfa9tn37dhUqVEjDhw9XQkKCvvzyS0VFRZ03h1Kw5MdXKC9ZF8wryFxIqHZA/J3romi/WK+t90vxr7/+Uu3atVWiRInzTqjcyLfXNioqypn0Q0otNsqWLavIyEhFRUWddyXejb2dF+q19b03PS4uLqjy4xWqs99LqSfxvXr1ciZhOdfKlSvVvn17RUdHO+9BN7crFNqzatUqFS5cWE2bNlWdOnWUIUOG8+atePHFF1WoUCGNGjXKVaNTEhIS1LNnT/Xo0UMzZsyQx+PR0KFDzyvcvcN927Rpow8//DBA0f69uLg4xcbG6pFHHvHb3qhRI2eb76ivYcOGqUSJEpo0aZLr3ld//fWX6tevr0cffdTZlpKSohYtWuinn37SsmXLdPLkSW3fvl2NGzdWq1atXJ0br6SkJB08eFClS5fW7t279d///lfVq1dX7969VaNGDXXv3l2SNGTIENfmRpL27dunqKgo50ptUlKSHn74YTVv3lzFihXTmDFjtGPHDm3dulWNGzdW69atXZ2f06dPq27duvrjjz+UlJSk5s2bq3r16sqRI4dq1arl9x09dOhQV+dm5cqVCgsL08iRIyWlHjM6deqk6tWr++0XLN9rkvTyyy+rYcOGfp0KrVq10syZM/XWW2/p22+/dba7/bPjK5SXrAvmFWQuJNQ6INIi5Iv2S/XaenvPpNT7cG6++WbnsVt7OS/Ua/v0008rJSXFOQkcP368YmJizuv5dKPL7bUNlvz4CsXZ76XUYqNbt24qW7asqlatqvr16+u555674Myd7du3V5EiRfzueXObUGjP9u3bVbJkSQ0bNsw50P7nP/9RVFSUNm3a5HfC9Nxzz6lYsWIaOHCga+ZLOHnypF544QVndMAHH3xw0cJ95cqVqlq1qjp27Ojaz8v+/ftVo0YNLVq0SNL/dfDcf//96tKli7PfuUPlLzYqJ5AOHTqk8ePH+83XMG7cOHk8HlWqVEmFChVSs2bNtGHDBm3YsEFVqlRRp06dXJsbL+8xpEuXLs4Q1y+++EL58uVTjhw59Oqrrzr7ujU3UmrR3qFDB1WrVk2ffvqpWrRooaZNm2rkyJEaMmSIypUrp9tvv11xcXFavXq16/Ozf/9+5c+fX99++60GDhyo5s2ba8WKFfrqq680dOhQRUVF6f3333f2f+KJJ1ybm99++80Zluv9DtiwYYNy5cqlF198UdL/vQ9Xrlzp+txIqfd3lyhRwum8fvrpp+XxeNS0aVNVr15dkZGRfp+dUaNGuTY/XqG8ZF0wryBzIaHWAZFWIV+0/12v7cyZMyWlLu1QpEgRjRs3LsARX9zl9tquXLlSWbNm1XvvvReIMNPk73ptvUuieJfXcHN+zhWKs997Pf/886pZs6aSk5P17LPPOssLTZ06VT/++KOz38aNG1W/fn3ddNNNrp4BP5jbk5ycrIkTJ6pFixY6evSos3316tWKiYlxJg3zLdyfeeYZlS1bVgcPHrzm8V7MuRP4zJ49Wx6PR0OGDHE6uc6cOaMTJ05o+/btrr8nzbfI9XaoPvnkk+ratavfft57Pt3Md3LC999/Xx6PR7Nnz1ZcXJwWLlyoatWqOUNGV65c6frc+OrWrZuGDx8uKbVTJSIiQmXLllXPnj31008/BTi6y7N3715169ZNWbJkUbNmzRQXF+c898knnyh//vxOobtq1SpX5yclJUV33XWX+vfvrzZt2vjdM7xr1y7de++9euihh867zTEYpKSk6OjRo2rfvr3uvPNOJSUlKSkpyfluXr16tatzI6XeglWnTh2VLFlSt99+uzwejz799FOlpKTowIEDeuSRR9SwYcOgu7rZt29f1axZM6SWrJOCfwUZX6HWAXElQr5ov5xeW+/VnU6dOqlNmzZ+a2S6SVp6bUeMGKGKFSv6jSZwo8vptfUugXbHHXe4Oj++vPerhdrs975uvfVW/etf/3LinjNnjjJnzqw8efKoR48e+umnn3TmzJmgWa4mmNuzcOFCp/DwSk5OVvHixfXDDz8423w7GXxP7N3Ed0ZXb4E4dOhQ7dmzRwMGDFC7du38roa4nW9nyeOPP67Y2Fjn8fjx4zV16lTXTzzla/v27eeNNLntttvUpk0bV3RiXS5vrG+++aaefPJJ9enTR9HR0dq6dav++9//6sYbb9RDDz2kU6dOBUW79uzZo5EjRzqfd9/3XdmyZZ37PIPB0qVLlS1bNnk8Hr/7vKXUOSDq168fFDm5mI8//lgej8fpFEpJSQmq9mzbtk1z5szRmDFj1KlTJ7/nJk6cqFtuuUWnTp0KUHSXLzk52e9Y8vjjj6tixYohs2Sd1/Tp04NqBZmLCaUOiCsV8kX75fTaPvjgg5JSi2I3Ltl0MRfqtfUeqGfNmqXy5cu76krahVxOr22DBg2UkpLi+vwkJyefd9/Wzp07Q2b2ey9vG6dMmeJ3wO7Tp49KlCih9957TzVr1lRMTIxatWrl+gNdsLbnYsO9vPGlpKSoRIkSfvcYfvfdd9q3b5/ffm6UkpLi5GX27NnKlCmTypQpoxtuuOG8eTqCgff/9ahRo9SyZUtJqcN6PR6P38RnwSYlJUWnT5/W3XffrWeeeSbQ4VyRhQsXyuPxKCoqSsuWLXO2f/LJJ64f1nuuo0eP+hUhKSkpOnz4sOrVq6fXX389gJGl3aJFi+TxeNSmTRutWbPG2f7II4+oV69eQdfJ7SsxMVGxsbHq0qWL3zr0webVV19V69at/d5zAwcOVLt27Vy/9NnatWvVtWtXNW7cWPfdd58+//xzSdKYMWNUtmzZoF6y7kKeffbZoFhB5u8E2xK2V1vIF+3S3/fa1q1bN6iudJzr3F5br2AZnvR3vbYVK1YMqgNA7969/e65mz59etDOfn8p27ZtU758+fTJJ584V6m8bUtISNDnn3/uLJcSDIKpPRdaGs23CD979qwSEhJUsmRJLVmyRFLq6BuPx6M9e/Zc83ivhO/Vp8aNGytPnjxatWpVgKO6Mt6TotGjR+uBBx7QlClTFBYW5rq5Ea7EE088oSJFiri6Q/VSzpw5o//85z/OyhBu7sy6Ek888YRKlizpyhUj/s7ChQtVsGBB1ahRQ/fff7+6du2qXLlyOSt3BLMJEyYoZ86cTidqMFq7dq1y5cqlyZMna9asWRo2bJhy587t+u/p9evXKyIiQvfff7+mTp2qpk2bqmjRos5otUmTJqlw4cJBuWTdhg0bNHDgQHXu3FkTJkzwmx9q6tSprl5B5lJ8L1JMnz49JDogrsR1UbRL11evbbBOuBCsvbYXOgDceOONznDEpKQkVa9ePehmv5fOPwCcW2RMnDhRmTNnVokSJZyrVG7+0jxw4MAF7yH2xhwM7fm7pdGk1JhPnTqlG2+8UcuWLdO4ceOULVu2oJic0ldSUpIGDhwoj8dz3lKQwch7+0+uXLn8Ou+C0Zw5c9SvXz/lzZs3KEc/+HLbZ/xqeP/99/Xggw8qIiIiqPOzYcMGjRo1Sk2bNlWfPn2CvmD3dgodPnxYVatWDbri6Vzz58/XjTfeqFKlSqlhw4au/54+ffq0unTp4reyx6lTp3TLLbfI4/God+/eklLnH6lUqVJQLVnn7URp06aN7r33XkVFRalevXqaNGmSs8+///1vV64gcyHnnq/51jZuX8I2vVw3RbtEr63bBWOv7d8dALwzRY8ePVplypRx9gmGqzkXOwBMnTrV2Wf+/PmKiIhw5oVw88nvunXrlDlzZnXq1MmZAPBcCxcudHV7LndpNK/KlSurevXqypw5c1AWiUlJSXrttdeCrrPrYpYuXSqPx6O1a9cGOpR/bM2aNbrzzjtDoi2haOXKlWrdurXfRYpgdqHbz4JZSkqKqy9GpEVcXJz2798fFJNqSlKTJk00ZswYSXLuvR82bJg6duyoihUr6qWXXpLk/iUFfXlXwbn//vudbTt27NBDDz2kKlWqOO2VUq+4u20FmXNd7HzNt3B36xK26em6Ktolem3dLth6baVLHwAqVaqkF198UceOHVPRokWDZvb7vzsA+LajS5cuqlWrlqvvzdu/f79uvfVWNWnSRPny5dMdd9zhdyDw7UTp2rWra9tzuUujJSUlKS4uTrly5VLGjBld3fH1d4KhgystQuVEXQq+yTOvN8E0YSOQ3lJSUnTixAnVq1dPXbt2dW6L3b17t4oWLarXX39d9957r+rVq+f8TjAsWefVrFkz9ezZU9L/HTf37t2rAQMGqFatWnrzzTedfcePH++6FWS8/u58zbdwd+MStunpuivavei1da9g6bW93ANA/fr1lZSUFFSz30t/fwB46623JEnff/+9YmJinIlc3Oirr75Sly5d9Ntvv+nXX39Vnjx5zjsQeL8Pvv/+exUqVMi17bmcpdHOnj2rQ4cO6euvvw6ZK20AAPxTP/30kzJkyKD69eura9euypYtm3r16iUpddm97NmzB9VxMykpSWfOnNF9992nDh06OCteeM9pduzYoZYtW6pt27ZBsYLM5Zyv+RbublzCNr1ct0U7cLX83QEgW7ZsOnDggOtnv/e63APAbbfdJim1iGzcuLFrJmm7kIMHD/otffbLL784BwLftc2l1NESLVq0cHV7pEsvjTZw4EB16NAhaDqIAAC4Vn777Tfde++96tWrl1544QVn+2effaabb775vPMCNzp3/qoFCxYoY8aMev75551t3vO23377TR6PR8uXL/dbYcaNLvd8zXekl1s7IK42inbgKrjUAeCmm24Kii+UKzkAeO+TduOolYtNyOiNdcmSJX49uGfOnNG//vUv/fbbb649mJ3rYkujZcyYMWTuAwcA4Gq70HF+yJAhatiw4UXnvXGLC60gI6Uu7ZYhQwa9+uqrftvXrVuncuXKaePGjdcyzMt2JedrL774or766itJ7u2AuNpuMAD/WPXq1W3WrFnm8Xj8tv/4448WFRVlN9zg7o/an3/+aZ9//rndc889Fh0d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", + "image/png": "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wKUo2gEAAAAAcCmKdgAAAAAAXIqiHQAAAAAAl6JoBwAAAADApSjaAQAAAABwKYp2AAAAAABciqIdAAAAAACXomgHAAAAAMClKNoBAAAAAHApinYAAAAAAFyKoh0AAAAAAJeiaAcAAAAAwKUo2gEAAAAAcKk0F+1//PGHrV692nn82WefWfv27W3kyJF25syZqxocAAAAAADXszQX7Q8++KD9+eefZma2detWu+uuuyw8PNzmzJljw4YNS9PfmjBhglWvXt1y5MhhkZGR1r59e9u4caPfPpJszJgxVrBgQcuaNas1bNjQ1q5dm9awAQAAAAAIOmku2v/880+rVKmSmZnNmTPH6tevb++99569+eab9vHHH6fpby1cuND69etnS5YssXnz5llSUpLFxsbaiRMnnH0mT55s06ZNsxkzZtjSpUstKirKmjVrZsePH09r6AAAAAAABJUb0voLkiwlJcXMzL777jtr06aNmZnFxMTYoUOH0vS3vv76a7/Hb7zxhkVGRtrvv/9u9evXN0k2ffp0e/zxx61jx45mZvbWW29ZgQIF7L333rMHH3wwreEDAAAAABA00nylvVq1avb000/b22+/bQsXLrTWrVubmdm2bdusQIEC/yiYY8eOmZlZnjx5nL+5f/9+i42NdfYJCwuzBg0a2OLFiy/4NxITEy0+Pt7vBwAAAACAYJTmon369On2xx9/WP/+/e3xxx+3kiVLmpnZRx99ZHXq1LniQCTZoEGDrG7dula+fHkzM9u/f7+Z2XmdAQUKFHCeO9eECRMsV65czk9MTMwVxwQAAAAAQCCleXh8xYoV/WaP95oyZYplzJjxigPp37+/rVq1yn766afznvN4PH6PJZ23zWvEiBE2aNAg53F8fDyFOwAAAAAgKKW5aL+YLFmyXPHvPvzwwzZ37lxbtGiRFS5c2NkeFRVlZqlX3KOjo53tBw8evOhQ/LCwMAsLC7viWAAAAAAAcIvLKtojIiIuemX7XIcPH77sF5dkDz/8sH3yySe2YMECK168uN/zxYsXt6ioKJs3b55VrlzZzMzOnDljCxcutEmTJl326wAAAAAAEIwuq2ifPn16urx4v3797L333rPPPvvMcuTI4dynnitXLsuaNat5PB4bMGCAjR8/3kqVKmWlSpWy8ePHW3h4uN1zzz3pEhMAAAAAAG5xWUV79+7d0+XFX3rpJTMza9iwod/2N954w3r06GFmZsOGDbNTp05Z37597ciRI1azZk379ttvLUeOHOkSEwAAAAAAbvGP7mk/deqUnT171m9bzpw5L/v3Jf3tPh6Px8aMGWNjxoxJa3gAAAAAAAS1NC/5duLECevfv79FRkZa9uzZLSIiwu8HAAAAAABcHWku2ocNG2bz58+3F1980cLCwuy1116zsWPHWsGCBW3WrFnpESMAAAAAANelNA+P//zzz23WrFnWsGFD69mzp9WrV89KlixpRYsWtXfffde6dOmSHnECAAAAAHDdSfOV9sOHDztLs+XMmdNZ4q1u3bq2aNGiqxsdAAAAAADXsTQX7SVKlLDt27ebmVnZsmXtww8/NLPUK/C5c+e+mrEBAAAAAHBdS3PRft9999nKlSvNzGzEiBHOve0DBw60oUOHXvUAAQAAAAC4XqX5nvaBAwc6/27UqJFt2LDBli1bZjfeeKPdcsstVzU4AAAAAACuZ2ku2rdv327FihVzHhcpUsSKFClyNWMCAAAAAAB2hfe0161b12bOnOlMQgcAAAAAAK6+NBfty5Yts9q1a9vTTz9tBQsWtHbt2tmcOXMsMTExPeIDAAAAAOC6leaivUqVKjZlyhTbuXOnffXVVxYZGWkPPvigRUZGWs+ePdMjRgAAAAAArktpLtq9PB6PNWrUyF599VX77rvvrESJEvbWW29dzdgAAAAAALiuXXHRvmvXLps8ebJVqlTJqlevbtmyZbMZM2ZczdgAAAAAALiupXn2+FdeecXeffdd+/nnn61MmTLWpUsX+/TTT/1mlAcAAAAAAP9cmov2p556yu666y57/vnnrVKlSukQEgAAAAAAMLuCon3nzp3m8XjSIxYAAAAAAODjsor2VatWWfny5S1Dhgy2evXqS+5bsWLFqxIYAACXUmz4F+n+Gtsntk731wBw5a7F94AZ3wUAAuuyivZKlSrZ/v37LTIy0ipVqmQej8ckOc97H3s8HktOTk63YAEAAAAAuJ5cVtG+bds2y58/v/NvAAAAAACQ/i6raC9atKiZmZ09e9bGjBljTzzxhJUoUSJdAwMAAAAA4HqXpnXaM2XKZJ988kl6xQIAAAAAAHykqWg3M+vQoYN9+umn6RAKAAAAAADwleYl30qWLGlPPfWULV682KpWrWrZsmXze/6RRx65asEBAAAAAHA9S3PR/tprr1nu3Lnt999/t99//93vOY/HQ9EOAAAAAMBVkqaiXZL98MMPFhkZaeHh4ekVEwAAAAAAsDTe0y7JSpcubXv27EmveAAAAAAAwP+XpqI9Q4YMVqpUKYuLi0uveAAAAAAAwP+X5tnjJ0+ebEOHDrU1a9akRzwAAAAAAOD/S/NEdPfee6+dPHnSbrnlFsucObNlzZrV7/nDhw9fteAAAAAAALiepblonz59ejqEAQAAAAAAzpXmor179+7pEQcAAAAAADhHmot2X6dOnbKzZ8/6bcuZM+c/CggAAAAAAKRK80R0J06csP79+1tkZKRlz57dIiIi/H4AAAAAAMDVkeaifdiwYTZ//nx78cUXLSwszF577TUbO3asFSxY0GbNmpUeMQIAAAAAcF1K8/D4zz//3GbNmmUNGza0nj17Wr169axkyZJWtGhRe/fdd61Lly7pEScAAAAAANedNBfthw8ftuLFi5tZ6v3r3iXe6tata3369Lm60QEAcB0oNvyLa/I62ye2viavAwAArp40D48vUaKEbd++3czMypYtax9++KGZpV6Bz50799WMDQAAAACA61qai/b77rvPVq5caWZmI0aMcO5tHzhwoA0dOvSqBwgAAAAAwPUqzcPjBw4c6Py7UaNGtmHDBlu2bJndeOONdsstt1zV4AAAAAAAuJ79o3XazcyKFCliRYoUuRqxAAAAAAAAH2keHg8AAAAAAK4NinYAAAAAAFyKoh0AAAAAAJeiaAcAAAAAwKWuaCK6lJQU27x5sx08eNBSUlL8nqtfv/5VCQwAAAAAgOtdmov2JUuW2D333GM7duwwSX7PeTweS05OvmrBAQAAAABwPUtz0f7QQw9ZtWrV7IsvvrDo6GjzeDzpERcAAAAAANe9NN/TvmnTJhs/frzdfPPNljt3bsuVK5ffT1osWrTIbrvtNitYsKB5PB779NNP/Z7v0aOHeTwev59atWqlNWQAAAAAAIJSmov2mjVr2ubNm6/Ki584ccJuueUWmzFjxkX3adGihe3bt8/5+fLLL6/KawMAAAAA4HZpHh7/8MMP2+DBg23//v1WoUIFy5Qpk9/zFStWvOy/1bJlS2vZsuUl9wkLC7OoqKi0hgkAAAAAQNBLc9F+++23m5lZz549nW0ej8ckpctEdAsWLLDIyEjLnTu3NWjQwJ555hmLjIy8qq8BAAAAAIAbpblo37ZtW3rEcUEtW7a0O+64w4oWLWrbtm2zJ554who3bmy///67hYWFXfB3EhMTLTEx0XkcHx9/rcIFAAAAAOCqSnPRXrRo0fSI44I6d+7s/Lt8+fJWrVo1K1q0qH3xxRfWsWPHC/7OhAkTbOzYsdcqRAAAAAAA0k2ai3avdevW2c6dO+3MmTN+29u2bfuPg7qY6OhoK1q0qG3atOmi+4wYMcIGDRrkPI6Pj7eYmJh0iwkAAAAAgPSS5qJ969at1qFDB1u9erVzL7uZOeu1X+172n3FxcXZrl27LDo6+qL7hIWFXXToPID0V2z4F+n+Gtsntk731wAAAADcIM1Lvj366KNWvHhxO3DggIWHh9vatWtt0aJFVq1aNVuwYEGa/lZCQoKtWLHCVqxYYWap98uvWLHCdu7caQkJCTZkyBD75ZdfbPv27bZgwQK77bbbLF++fNahQ4e0hg0AAAAAQNBJ85X2X375xebPn2/58+e3DBkyWIYMGaxu3bo2YcIEe+SRR2z58uWX/beWLVtmjRo1ch57h7V3797dXnrpJVu9erXNmjXLjh49atHR0daoUSP74IMPLEeOHGkNGwAAAACAoJPmoj05OdmyZ89uZmb58uWzvXv3WpkyZaxo0aK2cePGNP2thg0bOsPrL+Sbb75Ja3gAAAAAAISMNBft5cuXt1WrVlmJEiWsZs2aNnnyZMucObO98sorVqJEifSIEQAAAACA61Kai/ZRo0bZiRMnzMzs6aeftjZt2li9evUsb9689sEHH1z1AAEAAAAAuF6luWhv3ry58+8SJUrYunXr7PDhwxYREeHMIA8AAAAAAP65NM8e77V582b75ptv7NSpU5YnT56rGRMAAAAAALArKNrj4uKsSZMmVrp0aWvVqpXt27fPzMx69eplgwcPvuoBAgAAAABwvUpz0T5w4EDLlCmT7dy508LDw53tnTt3tq+//vqqBgcAAAAAwPUszfe0f/vtt/bNN99Y4cKF/baXKlXKduzYcdUCAwAAAADgepfmK+0nTpzwu8LudejQIQsLC7sqQQEAAAAAgCso2uvXr2+zZs1yHns8HktJSbEpU6ZYo0aNrmpwAAAAAABcz9I8PH7KlCnWsGFDW7ZsmZ05c8aGDRtma9eutcOHD9vPP/+cHjECAAAAAHBdSvOV9rJly9qqVausRo0a1qxZMztx4oR17NjRli9fbjfeeGN6xAgAAAAAwHUpzVfazcyioqJs7NixVzsWAAAAAADg44qK9tOnT9uqVavs4MGDlpKS4vdc27Ztr0pgAAAAAABc79JctH/99dfWrVs3O3To0HnPeTweS05OviqBAQAAAABwvUvzPe39+/e3O+64w/bt22cpKSl+PxTsAAAAAABcPWku2g8ePGiDBg2yAgUKpEc8AAAAAADg/0tz0d6pUydbsGBBOoQCAAAAAAB8pfme9hkzZtgdd9xhP/74o1WoUMEyZcrk9/wjjzxy1YIDAAAAAOB6luai/b333rNvvvnGsmbNagsWLDCPx+M85/F4KNoBAAAAALhK0ly0jxo1ysaNG2fDhw+3DBnSPLoeAAAAAABcpjRX3WfOnLHOnTtTsAMAAAAAkM7SXHl3797dPvjgg/SIBQAAAAAA+Ejz8Pjk5GSbPHmyffPNN1axYsXzJqKbNm3aVQsOAAAAAIDrWZqL9tWrV1vlypXNzGzNmjV+z/lOSgcAAAAAAP6ZNBftP/zwQ3rEAQAAAAAAzsFscgAAAAAAuBRFOwAAAAAALkXRDgAAAACAS6X5nnYAAIBLKTb8i2vyOtsntr4mrwMAQCBxpR0AAAAAAJeiaAcAAAAAwKUo2gEAAAAAcCmKdgAAAAAAXIqJ6AAAAC7hWkysx6R6AICL4Uo7AAAAAAAuxZV2AAAAwAUY1QHgQrjSDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuNQNgQ4AuN4VG/7FNXmd7RNbX5PXAQAAAHD1BPRK+6JFi+y2226zggULmsfjsU8//dTveUk2ZswYK1iwoGXNmtUaNmxoa9euDUywAAAAAABcYwEt2k+cOGG33HKLzZgx44LPT5482aZNm2YzZsywpUuXWlRUlDVr1syOHz9+jSMFAAAAAODaC+jw+JYtW1rLli0v+Jwkmz59uj3++OPWsWNHMzN76623rECBAvbee+/Zgw8+eC1DBQAAAADgmnPtRHTbtm2z/fv3W2xsrLMtLCzMGjRoYIsXL77o7yUmJlp8fLzfDwAAAAAAwci1Rfv+/fvNzKxAgQJ+2wsUKOA8dyETJkywXLlyOT8xMTHpGicAAAAAAOnFtUW7l8fj8Xss6bxtvkaMGGHHjh1zfnbt2pXeIQIAAAAAkC5cu+RbVFSUmaVecY+Ojna2Hzx48Lyr777CwsIsLCws3eMDAAAAACC9ufZKe/HixS0qKsrmzZvnbDtz5owtXLjQ6tSpE8DIAAAAAAC4NgJ6pT0hIcE2b97sPN62bZutWLHC8uTJY0WKFLEBAwbY+PHjrVSpUlaqVCkbP368hYeH2z333BPAqAEAAAAAuDYCWrQvW7bMGjVq5DweNGiQmZl1797d3nzzTRs2bJidOnXK+vbta0eOHLGaNWvat99+azly5AhUyAAAAAAAXDMBLdobNmxoki76vMfjsTFjxtiYMWOuXVAAAAAAALiEa+9pBwAAAADgekfRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSAV2nHbgSxYZ/cU1eZ/vE1tfkdQAAAADgYrjSDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4lKuL9jFjxpjH4/H7iYqKCnRYAAAAAABcEzcEOoC/U65cOfvuu++cxxkzZgxgNAAAAAD+TrHhX1yT19k+sfU1eR0gkFxftN9www1cXQcAAAAAXJdcPTzezGzTpk1WsGBBK168uN111122devWS+6fmJho8fHxfj8AAAAAAAQjVxftNWvWtFmzZtk333xjr776qu3fv9/q1KljcXFxF/2dCRMmWK5cuZyfmJiYaxgxAAAAAABXj6uL9pYtW9rtt99uFSpUsKZNm9oXX6TeG/PWW29d9HdGjBhhx44dc3527dp1rcIFAAAAAOCqcv097b6yZctmFSpUsE2bNl10n7CwMAsLC7uGUQEAAAAAkD5cfaX9XImJibZ+/XqLjo4OdCgAAAAAAKQ7VxftQ4YMsYULF9q2bdvs119/tU6dOll8fLx179490KEBAAAAAJDuXD08fvfu3Xb33XfboUOHLH/+/FarVi1bsmSJFS1aNNChAQAAAACQ7lxdtM+ePTvQIQAAAAAAEDCuHh4PAAAAAMD1jKIdAAAAAACXomgHAAAAAMClKNoBAAAAAHApV09EBwAAAACBVmz4F9fkdbZPbH1NXgfBhSvtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAudUOgA8C1UWz4F+n+Gtsntk731wAAAACA6wlX2gEAAAAAcCmKdgAAAAAAXIqiHQAAAAAAl6JoBwAAAADApZiI7iKuxcRtZkzeBgAAAAC4OK60AwAAAADgUlxpBwAAAIDrCMtBBxeKdgAAAABAULoebmtmeDwAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUhTtAAAAAAC4FEU7AAAAAAAuRdEOAAAAAIBLUbQDAAAAAOBSFO0AAAAAALgURTsAAAAAAC5F0Q4AAAAAgEtRtAMAAAAA4FIU7QAAAAAAuBRFOwAAAAAALkXRDgAAAACAS1G0AwAAAADgUkFRtL/44otWvHhxy5Ili1WtWtV+/PHHQIcEAAAAAEC6c33R/sEHH9iAAQPs8ccft+XLl1u9evWsZcuWtnPnzkCHBgAAAABAunJ90T5t2jS7//77rVevXnbzzTfb9OnTLSYmxl566aVAhwYAAAAAQLpyddF+5swZ+/333y02NtZve2xsrC1evDhAUQEAAAAAcG3cEOgALuXQoUOWnJxsBQoU8NteoEAB279//wV/JzEx0RITE53Hx44dMzOz+Pj4NL12SuLJNEZ7ZdIa15W6Fu0JpbaY0Z4rxXst7WjPleG9lna058rwXks72nNleK+lHe25MrzX0i6t7fHuL+kfv7ZHV+OvpJO9e/daoUKFbPHixVa7dm1n+zPPPGNvv/22bdiw4bzfGTNmjI0dO/ZahgkAAAAAwHl27dplhQsX/kd/w9VX2vPly2cZM2Y876r6wYMHz7v67jVixAgbNGiQ8zglJcUOHz5sefPmNY/Hk26xxsfHW0xMjO3atcty5syZbq9zrYRSe0KpLWa0x81CqS1mtMfNQqktZrTHzUKpLWa0x81CqS1mtMfNrlVbJNnx48etYMGC//hvubpoz5w5s1WtWtXmzZtnHTp0cLbPmzfP2rVrd8HfCQsLs7CwML9tuXPnTs8w/eTMmTPo38i+Qqk9odQWM9rjZqHUFjPa42ah1BYz2uNmodQWM9rjZqHUFjPa42bXoi25cuW6Kn/H1UW7mdmgQYOsa9euVq1aNatdu7a98sortnPnTnvooYcCHRoAAAAAAOnK9UV7586dLS4uzsaNG2f79u2z8uXL25dffmlFixYNdGgAAAAAAKQr1xftZmZ9+/a1vn37BjqMSwoLC7PRo0efNzQ/WIVSe0KpLWa0x81CqS1mtMfNQqktZrTHzUKpLWa0x81CqS1mtMfNgrEtrp49HgAAAACA61mGQAcAAAAAAAAujKIdAAAAAACXomgHAAAAAMClKNoBAAAAAHApinYAIY/5NoELO336tKWkpAQ6DFwAuXEvcuNu5AehiKLdRfbu3WsbN24MdBhXTagVSqGWH68NGzbYa6+9Fugw0sXBgwfNzMzj8YTM+zFU2hGMDh065LynQsHatWutXbt2tmjRoqA/wSU37hZK+SE37hZq+fF16NAhS0pKCnQY6So5OTnQIVw1V/t8jaLdJTZv3myFCxe2Pn362Pr16wMdzj/2559/2syZM+3o0aOBDuWqCLX8eMXFxVnZsmXtgQcesOeeey7Q4VxVGzdutKioKOvSpYuZBX/hvnHjRvvpp5+Cuh2bN2+2hQsXBjqMK7JhwwYrUqSIjRkzxg4cOBDocP6x5ORk69Wrl82bN8+GDRtmS5YsCXRIV4zcuFso5YfcuFuo5cfX+vXrLTIy0vr16xdyhfuqVavsjjvuMDOzjBkzBn3hnm7na4IrLFq0SB6PRx6PRw0aNNCGDRsCHdIV++uvv5y2TJ06VfHx8YEO6R8Lpfyc6/bbb9fNN98sj8ejJ598MtDhXDVvvfWWk7M77rjD2Z6SkhLAqK7Mvn37nLZ88803koKvHXv27DmvDcFk5syZTvwPPvig/vrrr0CH9I999NFHatKkiUqXLq3ChQvr559/DnRIV4TcuFuo5YfcuFso5cfXm2++6eSqZ8+eSk5ODnRIV8XJkyedc9CGDRs625OSkgIY1ZVLz/M1rrS7gCSrXr269e/f35588kmLi4uzu+66K2iHYufLl8/atm1r0dHRNnToUJs2bZqdPHky0GFdsVDLj5ckS0lJsUqVKtlNN91k77zzjk2aNMlGjx4d6NCuiooVK1rNmjVt9OjRtnTpUuvQoYOZBecV96SkJKtSpYqVLFnSWrRoYZ9++mnQtSN79ux2yy23WGRkpLVv394+++yzQIeUJh07drTu3btb//797Z133rGBAwdaXFxcoMP6R0qUKGEZMmSw//znP1a/fn27/fbbbfHixYEOK83IjbuFWn7IjbuFUn581atXz9q2bWsjR460L774wrp06RJU5wAXkyVLFmvXrp01atTI9u/fb7fccouZBe8V9/Q8X6NodwGPx2NZsmSxfPny2ffff28//PCDeTwe69KlS9AVht4PWPPmza1jx442c+ZMGzt2rE2aNCloC/dQyo8vj8djGTJksP79+9uyZcssPj7e3nzzTRs/fryNHTs20OFdMe97sFKlSlamTBn74YcfbMaMGbZ48WJn+FWwFbz58uWzUqVKWfny5W3MmDHWsWNH++yzz4KmHZIsLCzM6tSpYx07drShQ4dap06dgqpwj4iIMEmWlJRkS5YssU8//dQGDRoUVCe4Z8+e9XtcuXJlK1WqlI0YMcJeffVVq1atmt15551Bd4JLbtwt2PNDbtwtlPNj9n/3RZcoUcKyZctmq1atss8//9y+++4769atW1CcA1yKx+Ox6tWr28qVK+3JJ5+08PBwq1y5spkFZ+GerudrV+V6PdJsz5492rRp03lDx2vUqKHXX39dcXFxKlOmjKpXrx6UQ7G3bdumPHny6JNPPtEnn3wij8ej0aNH68SJE4EO7bKEan7Wrl2rf/3rX1q3bp3f9qlTp+qhhx5SUlKSXnnlFWXMmFFjxowJUJRXZv/+/edtW7VqlVq3bq1FixZp7ty5ypUrlzp16uQ8H0zDy1avXq1ixYrp3Xff1eDBg+XxePTZZ59JCp6h8r/88oty5cqlb7/9ViNHjlTmzJmdNrjNX3/9pUOHDvm9R3bt2qWiRYtq0aJFWrJkibJmzapu3boFxZDSlStXql69epo0aZIWL17sbN+zZ4+aNWum3377TZLUpEkTFSpUyNVDSsmNe3MjhVZ+yI27hVp+fB08eFBJSUl+w8S3bdummjVr6rvvvtP333+vnDlzqmvXrkFzDnApPXr0UN++fbVw4UKVLl1aVapUcZ4LtqHy6XW+RtEeAH/++ac8Ho9uvvlmtWvXTj///LOOHDkiSXrqqad09913S0r9ci1durRq166ttWvXBjDiS1u/fr1mzJihJUuW+G2fMWOGcy/xa6+9Jo/HozFjxri+cA+1/HjFxcU599m0adNGt912m9auXauEhAStXr1aefLkcQ5wr776qrJkyaKhQ4cGOOrL481Zo0aNNG3aNK1evVqSdPz4ccXGxmrYsGGSpLlz5yoiIkJ33XVXIMP9Wxs3btT//vc/HT9+3NkWHx+v3r17a9q0aUpISFCfPn38DgRu64DYuHGjvv32W+3du9dv+yOPPKLRo0fr1KlT6tu3r8LCwlxXuK9du1Yej0fNmjVT//79FRcXp5MnT0qSHnroIY0YMUKS9MMPPyhr1qzq2bOnDh48GMiQLyk5OVm1atVy7hnMmTOnRowYoU8++USS1KlTJ/Xs2dPZv3Xr1sqSJct53+luQG7cmxsptPJDbtybGyn08uNrw4YN8ng8io2N1YgRI7Rr1y5J0unTp3XnnXdq1KhRkqRvv/1WOXPmVI8ePVx3DnAxq1evVrt27bRw4UJt2bLF2f6f//xHrVq1UmJiopYtW6ZSpUoFReF+Lc/XKNoDYOXKlfJ4PCpSpIjuuecexcTEqHv37nrllVe0Y8cORUREaO7cuZJSC60CBQqoUaNGOnPmTIAjP9+hQ4ecQrBGjRpq27atli5dqri4OK1fv1433XSTli9fLim1cM+cObOGDBni6sI9lPLjKyUlRcOGDZPH49GQIUN0zz33qEqVKmrfvr1+/PFH9e7dW127dtWpU6ckSS+88ILy5cvn+t72lJQUzZo1Sx6PR/ny5dPAgQOVM2dOjR8/XsuWLdOGDRt08803a/Xq1UpJSdH//vc/eTwede/ePdChX5DvJCYPPvighg4dqsTEREnShx9+qLx58+rAgQNKSEhQv379lDlzZs2ZMyfAUfvbu3ev04YOHTpo2LBhOnr0qFJSUvTRRx+pWLFiOnr0qCSpX79+yp49u6va8N5778nj8ahSpUqqW7euypUrp0GDBumXX37RggULlC9fPv3555+SpB9//FEej0d9+vRx9UnT3r17VbJkSdWvX18zZ87Uvffeq7p166p9+/aaMmWKcufOrZ9++snZ//bbb3fa6Cbkxr25kUIvP+TGvbmRQis/vt5//315PB7deOON6ty5s/Lnz6+nnnpKv/zyi1auXKnIyEjn4sT333/vnC+4XUJCgqpUqSKPx6NWrVqpUaNGzoTVycnJqlSpkjPKc8mSJSpXrpyKFy8e4Kgv7lqfr1G0X2PeL7/ffvvNKWA/+ugjvfTSSypcuLBuv/125ciRQz169FBCQoIk6fDhw9q8eXMgw76khx9+WB6PR0888YRatWqlli1bql69elq6dKnatGmjtm3b6vTp05Kkl19+WXny5HFtIRiK+TmXt6hdtGiRFi1apMmTJysqKkoFChRQkSJF/HLjLazcLj4+XjNnzlSmTJk0depUff/997rnnntUtmxZNWrUSMWKFdOrr74qSUpMTNRXX33l2tsatmzZojZt2sjj8WjkyJFq2bKlKlSooJEjR2rDhg3q2rWrpk6dKkk6cOCA7rvvPuXJk0fHjx93zRC5I0eOqFmzZs7omsqVK6tx48bq27evDhw4oKZNmzorFZw5c0Y9e/ZUgQIF/HqqA23GjBnyeDx66aWX9OKLL2rkyJEKDw/Xo48+qsyZM2vs2LHOwfmXX37R+vXrAxzx+RISEnTixAnt2bNHUuow2Hz58qlTp0769ddfdejQIfXs2VNNmjSRx+MJiitQErlxu2DPD7lxb26k0M6Pr5dfflkej0evvPKK3n77bT3yyCPKly+f7rnnHhUoUEDTp093jvkLFy507TmNr1OnTumDDz5Q0aJFVadOHX322WcqVaqUWrVqpYEDB2ratGlq3769jh07puTkZC1atEjVq1fX1q1bAx36BW3dulVt27a9ZudrFO3XkHdoh/e/ixYtUqZMmdS7d28dPnxYCQkJevHFF9W6dWu98847ktw35NWX71CVBx98UPnz59d///tfLV68WBMmTFCZMmV04403KioqSrt373b2dWshGGr58XVunA899JCyZ8+uTz/9VFJqoThz5kx9/PHHgQjvivm268SJE5o2bZo8Ho/eeOMNSdLmzZvVpUsXFStWTLNnzw5QlGm3c+dONW/eXMWKFdPevXv1wQcfqGfPnsqdO7fCw8PVoEEDZ2THwYMHL3g/f6DFx8erQYMGqlSpktauXasPP/xQ3bp1U3R0tCIjI1W3bl2nSE9MTAx4Gw4cOKBffvlF//vf/5yD6fjx45UpUyZNmzZNSUlJWrFihYYPH66bbrpJH374oST3ziewbt06tWnTRuXLl1fp0qWd3v2dO3cqMjJS9evX186dOyWlXq36448/AhnuJZEb9+ZGCq38kBv35kYKvfz4OnjwoDPqwXtRaNKkScqQIYNmzpyps2fPav369XrggQdUunRpJ1fBwNsRdPbsWUmpy/JlzZpVo0eP1tGjR/W///1PsbGxypIlizwejzMaIiUlxRn96VZbtmxR69atr8n5GkX7NXDuVeXk5GTnjbto0SJlzpxZd999t+Li4pzng4XvkPCePXsqR44cevfddyVJmzZt0vvvv++qYa8XEqr52bZtm7788ku/bd52SanDkrNkyaKPPvroWof2jyUkJDgnFb4nF6dOndKzzz4rj8ejZ599VlJqvvbt2xeQOC/X9u3bNWPGDI0fP97pSNm/f7/q1KmjG2+80bmfbcGCBerXr5/zGXOT7du367nnntPYsWP1/vvvS0rtSKlWrZrKlSunNWvWSJK+++47PfbYY65qw6pVq1SpUiXFxMQoU6ZMqlatmtOhMHHiRHk8Ho0fP955r7lpRMCFLF++XDly5NBDDz2kxx57TC1btvS7n2737t2KiopSvXr1/K7OuPFEndykcmNupNDKD7lxb26k0MuPr1WrVqly5coqXLiwsmfPrtq1azvnnFOmTFGGDBk0ZcoUSann3d7ngsGGDRvUq1cvNWjQQPfff78zd9IHH3ygsLAwPfroo86+n3/+uX744QdJ7s/buRME1q9fP93P1yja09mff/6pTJkyqVGjRvrwww/Pm7VbSr13KHPmzLr33nv9rki70ebNm/Xtt9/6bfMOfZekBx54QFmyZNG7777r2kkjfIVafrwOHTqk7Nmzy+Px6N5779Urr7zi9Nz66t+/v7JkyeIUisFg/fr1qly5sjp37qytW7fq8OHDfs/7XnGfPHmys92tB4CVK1eqcOHCqlWrlrJly6aCBQvqiSeekJQ67K9BgwYqVKiQMzzMO3GQm6xYsUIxMTGqVauW8ubNq2zZsunxxx+XlHoieOutt6p48eLO58tN3w0rVqxQeHi4hgwZop9++kmvv/66ChcurIYNGzr7TJ06VR6PRxMnTtSxY8cCGO3fW7dunTJnzqyJEyc627Zs2aLy5curTZs2zvtnz549ioqKUtOmTZ17I92G3Lg3N1Jo5YfcuDc3Uujlx9eKFSuULVs2DRo0SIsXL9Zzzz2nEiVKqEWLFs5V5unTp/tdjAgWK1asUEREhDp37qw2bdqoVKlSKleunFatWiVJmjNnjsLDw9WrV68AR3p59u3b58R+rm3btqlhw4bper5G0Z7Oli1bpooVK6pdu3bq0aOHChYsqGnTpp13j83ChQuVLVs2tW/f/rzZlt3i0KFDuuGGG+TxeNSvXz9NmjTpglede/furaxZs2r27NmuH9YSSvnxlZCQoN69e+vll1/WqFGj1LJlS8XExOg///mPli5d6rdvv3795PF49L///S9A0abNyy+/rCpVqqht27aqUaOGOnfurDlz5vgVgidOnNDUqVOVOXNmPf300wGM9tJWrVqlrFmzatSoUTp27Jg2btyo7t27KyYmxnkPbt26VY0bN1ahQoW0fft2Se4a7bFy5UqFh4dr5MiRSkhI0Pr16/Xoo48qZ86cmj9/vqTUW2Lq16+v4sWLu2qlhS1btihz5szO/fVS6v/b4cOHKzo62hlmKcl5P40ZM+a8pSDd4sSJE7rrrruUJUsWZ1Ze7+iaO+64Q507d5b0fyOk9u7dq8yZM+u2225zhi+6Bblxb26k0MoPuXFvbqTQy4+vzZs3Kzw83Fnhxuvhhx9W0aJF/UaCPv/88woLC3P1OY2vNWvWKGvWrH7xfvrpp8qbN6+eeeYZSak5++ijjxQeHq5+/foFKtTLsmfPHuXKlUt58+bV0KFD/SY49NqyZYsaNWqUbudrFO3pbP/+/erataszBPn1119XkyZN1LBhQ/Xq1UsrV650hrnMnz9fBQoUcCbXcKP+/ftr+PDhmjRpkurVq6fSpUtr2rRpzgzxXr1795bH43H90OtQy4+vwYMHKzY2VlLqlc2pU6fqrrvuUoECBTR+/Hi/L5zhw4e7csKZC1m8eLEqVqyo7du3a/ny5Ro7dqxy586t7t27a+rUqX5X1MeNG6c8efK4cijZzp07lT9/frVr185v+6JFi5Q1a1Z99913zrZt27apWbNmCg8P9zvhCrTdu3crOjparVu39tu+aNEi5ciRw29UzrFjx9S4cWNFRES44r2WkpKiF154QZGRkectbfjGG2+oRIkS2rNnj19n0NNPP62IiAgdOnToWod72ebOnet0aHnv59yyZYvCw8P14osvOvt5T3r37dunjRs3BiTWiwnV3Hz++edBnxsp9SR0xowZIZWfTz/9NCRyw2fH3fk518SJExUdHa3Ro0f73W765ptvqmTJktq9e7ffOc3EiRNde07j68iRIypXrpzKly9/3mjIGjVqaMiQIc7jpKQk/fe//5XH49GgQYOudaiXbd26dWratKneeustdezYUbGxsapbt65+//13v1G4O3bsUOPGjdPlfI2iPZ34fiFOnjxZxYsX14EDBySlTsjgXfLg1ltvVZMmTZz7vt18ZTolJUWjR492ejWl1HttevXqpZw5c2rSpEn6/PPPneeGDh3qipPzvxMq+fHyfsEnJyerWrVqeu6555znYmNjFR0drQoVKqhGjRqqVq1a0Awh89WnTx+1bdvWGdK3e/duFShQQB6PR5UrV9b48eO1YsUKSXLtwW3VqlWqUKGCbr/9dn311VfO9sWLFytnzpzn9eJu2bJFbdu2ddVyNb///rsaNWqk2NhYv7XWly5dqhw5cmjRokV++x89elStW7fWpk2brnWoF3To0CFNmzZN5cuXV9++fSWlznGRO3dujR071tnPt6fcre8nX19++aVatWqlunXr6quvvlLx4sX10EMPOc97vyPcdJvCueLi4kIiN+devfzmm2+CPjdSai6CPT8bN27UW2+95Tz++uuvQyY3U6dODercXEgofK+dKz4+XqNGjVLNmjWdQvbw4cPn5crXuUWwW40ZM8Yp0L3DxTdu3KiwsDBnMmevpKQkzZ071/U1Q6tWrZwRAevWrVP37t3VsGFD3Xrrrfrwww918OBBSalX5du0aXPVz9co2q+y3bt3O4Wd94vj5MmTatWqld58801J0n333aeiRYvqjz/+0Mcff6xOnTo5a/m5lffL/eTJkypevLgmTJjgPNe6dWtFRESoYcOGuvnmm1W1alVnogk38/bIhkJ+znXmzBklJSXpscceU58+fSRJ3bp1U4ECBbRjxw799ddf+vTTT9WkSZOg6I328h6UFy1apEaNGjlL7fXq1UvFihXTihUrNHToUNWoUUOFChW64H38brJkyRLV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0kSJiYmu/KzMnj1bZcuW1VNPPaWbb775vCvuR48eDYrcxMfHq0yZMipVqpSk1O/bO+64QzVr1lTGjBnVpEkTvfDCC87+33zzjetz8/rrr6tZs2aSUtszbtw49ezZU927d3cKJu8VdzfnZ8uWLcqVK5ffe+s///mPihYt6tzS59tJ/O2337o6NydOnFDLli39Rmc999xzzvJZviOJ3J4br6+++kp16tTxWzN+/vz5zhK2vmt6B8Nnx+vMmTPq1KmT7r//fmfbzz//rJ49eypz5szO8dJ7xf3WW29Vv379guJK++bNm505he6//36/Sdl8458wYYIqV66sYcOGnTcq1E3WrFmjBg0aOKMDpk2bpuTkZCUnJyslJUUJCQlq0aKFSpQo4cyddK1QtP9Da9euVcaMGZ1e2+PHj6tz587q0KGDEhIS/Aqk+fPnKzw83NVLax04cEBlypRxDmp79+71u9/ba+7cuSpfvryzLJWbhyY99dRT6tq1q9+2/v37y+PxqESJEtq3b5+k1AO02/Pja8OGDbrpppv8JpI7duyY3n//feXJk8fv4NCyZUvVrVs3KA4AcXFxatGihbp166Z69eqpcOHC+v777y/4Hps1a5ayZ89+0XUz3WDv3r2qXLmy320z+/btU0REhP773/9K+r8Oh19//VWNGzdWvXr1NG/evIDEeyFxcXFq3ry57rzzTjVp0kRVqlTRRx99dMHC/eeff9Ydd9yhSpUq6csvvwxUyI6zZ89q0qRJ6ty5s/766y8n1i1btqhQoUJOx503B0eOHNG4ceNUp04d7d+/P2BxX0pCQoI+++wzvytMzzzzjDwej5o3b65bb71V+fLlc4bwSanHHzetPHCuzZs3q1u3bjp48KDGjh2rsmXL6umnn1bfvn319ttvS0rtVHF7bk6dOqVXXnlF4eHhGj58uNq0aaPY2Fh9+OGH+vDDD9WnTx8VK1ZMs2bNcn7H7bkZO3as7r77bklSzZo1FRsbqzvuuENNmzZVxowZnZE1f/31l6vz8/PPP593LiNJ5cqV0x133OE8Pve8za25OXTokMqVK6d3333X2TZs2DCVLFlS1apV00033eR03rk9N16fffaZoqKi/Iqh33//Xc2bN9fUqVOVJ08evw5JN+fH18mTJ1WlShU99dRTftt37NihXr16KU+ePFq4cKGk1I6/AQMGqGnT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zx7Vr19fLVu2VN++fdW7d29FREQE/MT8Qi4nP75F3sqVK3XvvfcqIiLC1V+Ux44dU4cOHXT33Xerb9++qlKlisaMGXPBwv3333/XzJkz1blzZ40YMcKVV9l87d69W9WrV1eZMmVUrFgxjRw58pKF+6lTp9SnTx+FhYU59726SVrb065dO2XPnt1VV3h37tzpN5pD+r/P0Y4dOzR48GC/UQSnTp3Sgw8+qPDw8IDnZPXq1cqZM6ffjPzek4rt27crOjra73aEQ4cOKW/evIqIiHBlB9Dx48dVsGBBeTweZ+Zur61btyoqKkoPPPCA34nG0qVLNWnSJNeN2Lic3IwePVpScCxJeSW5WbJkicaMGePKUWppyY+U+t2wcOFCrV692nXnN1eSGze/5y4nN2PGjJGU2o6PPvpIjz/+uJ577jlXXjC6kvycOnVKixcvduXkZb7Onj2r4cOHq3379vrss8/UpEkTNW/e3K9w971daf369frwww/1v//975LnCm7wdyvIXGgE1fDhw+XxeFxZM1xOB4T3HOi5555T586dNWnSJIWFhQWsYJco2i/Lrl275PF45PF4nOG43hPZrVu3qnDhwurTp4/f73gTf+4skW6Qll7Or776Sr1791b16tXVvXv3gJ+YX0ha85OSkqJt27Zp9OjRrmyPrxMnTmjcuHH66KOPJKUu53Khwj0Yff7552rVqpX+/PNP/etf/1KhQoUuWeju379fd911l7Mqg9tcbnu897U2bdpUHo/HNZ0r27Ztk8fjUcWKFZ3JcLwHsm3btikqKuq8mVITEhLUvn17Z+nHQElJSVGfPn3k8XiUMWNGzZ4923lu9+7dypw5s/r06XPe/azDhw935ZU1KfU7rE+fPmrUqJGio6PVvHlz57nJkyerS5cuF7wy4Lb5U64kN253pblx461KoZafK82NG6UlN27uePAVSvm5kF9//VVvvfWWpNT1vhs3bnxe4e7G74G/czkryPjmLTExUf/9739deQtjWjsg/v3vf8vj8SgiIiLgF1go2i/Dhg0bVKpUKRUqVEjh4eFO0k6dOqWyZcuqa9euQfMlc7m9nL6FYGJiopKSklw7odGV5idYDnLx8fF+8Q8dOlRVqlTR6NGjnfufTp8+HXQHgn379jnLuknS9OnTnULX9x483wn43PoelC6/Pd7P1v79+111X+tnn32m7Nmzq0yZMqpatapzT+GxY8dUpEgR3XfffZe8fy3QPvroI9WtW1cPPPCAPB6P0/F47NgxjR071u/zHiyf/ZkzZ6pw4cL68MMPVbJkSWcGWym4TvzSkptgESq5kUIvP+TGPROBXkgo5edc5/5/X7ly5QWvuHvXZA8Wl7uCjG+nsVvfg5fTAeFrzpw58ng8rrjIR9F+GY4eParWrVurR48e6t+/v99w1p07dwbVAS0tvZwXm5XYbUIpP5fi+2U4ZMgQ54r7vn379Oijj+r22293fa7+zvPPP+8Uut51yp9//nlX9tZejou1x43Dsbdt26abbrpJPXv2VOfOnVW1alVnsqZffvnFde+tc1dTOHLkiG688UYNGTJEEydOlMfjcWZNdlvsadG2bVs99thj+vLLL5U/f36/E1y3XVX3IjfuzY10feSH3LhbsOYnLbz5WbFihVO4f/zxx3rkkUeUO3duHTp0KChz+HcryIwaNSrAEV7a5XZA+Bbw3pGtgUbRfgm+H6bvvvtOMTEx+uSTT9S1a1dlzZrVmSzHLVeaLleo9HKGan4uxTc/Q4cOVfXq1VW+fHlly5bNlRNpXS7fjhXfoeVdunRRjhw5XHeP7t8Jpvb4fj5efPFFVa9eXW+++aaaNGmiatWqOYW72z5HvsvleL3//vu67bbbtHz5cg0ePFgej0fvv/++JLl+COmaNWvUq1cvrVixwrlPOCkpybmfLiUlRd98843y58+v1q1bO7/ntrxI5MbLjbmRQis/5Ma9uZFCLz9/59y5ErztWLlypZo1a6bcuXMrR44cAb0v+mpw+woyl+vvOiBGjhwpyT2j8yjaL2Dz5s369ddf/YbiHj58WF27dtVrr72mgwcPqmPHjgoPD3c+eMH2BRPMvZyhnJ+0rE6QlJSkm266SREREUFxJfrvluHyfd9Nnz5dHo9HuXLlcu1EgcHenl27dp23NN3y5cvVunVr/fTTT/r555916623qnr16tq7d68k93w3rF27Vnnz5tWTTz7pzGotpZ4YVapUyRl6OGjQIHk8Hn3wwQeS3HtlKj4+XjfeeKM8Ho86dOigpk2bavbs2Tp79qzi4+MVExOjl19+WZL09ddfq1ChQqpbt26Ao74wcuPe3EihlR9y497cSKGXnwu5nCUFvfnp3LmzcufO7cqZ1M8VrCvIXIlg6oCgaD/H7t27nUnNHnnkEU2cONF57l//+peKFy+uxMREHThwQJ06dVKuXLkCPgnTpYRaL2eo5ccrrasTJCYm6v7771eWLFlcfwA4fvy48+9zl6vZt2+f/vvf//oNCUxKStLAgQMVERHhmknafIVCe7Zt26ZMmTIpT548mj59ujMzsST17NlTzZo1kyTNnz9fDRo0UO3atS+59vy1dPbsWT366KPOpHl33HGHqlSpooULF+rkyZP697//rZo1ayohIUFHjhxxZrD1TuboRgkJCXrppZeUN29etWrVSm+88YYKFSqk9u3ba+LEiZo0aZK6dOmiU6dO6ezZs/r8889VunRp55YLtyA37s2NFHr5ITfuzY0UWvnxdSVLCj7++OOunUndVzCvIHMxodQBQdF+jjVr1qh27dryeDwaM2aMqlatqrp162ry5Mnas2eP2rZtq9dee01S6hu4RYsWKliwoE6fPu263s5Q7OUMpfx4XcnqBJI0cOBA13dIrFu3ToULF9Ybb7zhbPNdriZ//vx+HS9S6gQtGTNmPG+tZjcIhfakpKTo448/VrFixZQpUyYNGjRIFSpUUMeOHTVv3jytXr1abdu21eLFiyVJ3377rSpVqqTGjRv7LTEYSFu3blW3bt2UNWtWLViwQEOGDFFsbKwqVqyoAQMGqHLlys68FgcPHtTo0aNd02Hiy/s59/4/femll5QhQwa9+uqr2rdvn9555x1Vq1ZNmTNnVpYsWZyT2aSkpAsOo3UDcuPe3EihkR9y497cSKGbH+nKlqyTUs/z3H6BJZhXkLmQUOyAoGg/R0pKilatWqVatWqpWrVqOnTokF588UV17NhRefPmVc6cOdWhQwfnA7lr1y5nlmW3OXnypGbOnBlSvZyhlB+vtM5+74ai6XIkJSXpoYceksfjUaFChZwOIknas2ePsmbNqoceeuiC7fnrr7+uZaiXJZTak5CQoA8++EA333yz2rVrp/379+uBBx5QixYtVLhwYUVEROiJJ55w9p8/f77fDPiBEB8frz179jhD9f/66y/FxsaqSJEi2rFjh/766y+99tpruummm5QhQwZ9++23zu+65X40X3/++acefvhhtWnTRoMHD3bWVPYuLzN58mRn33fffVdff/11oEL9W+TGvbmRQis/5Ma9uZFCLz/nupIl69w6cvVcwb6CjK9Q64Dwomj34fsFuHr1at18882q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c1AWiUlJSXrttdeCrrPrYpYuXSqPx6O1a9cGOpR/bM2aNbrzzjtDoi2haOXKlWrdurXfRYpgdqHbz4JZSkqKqy9GpEVcXJz2798fFJNqSlKTJk00ZswYSXLuvR82bJg6duyoihUr6qWXXpLk/iUFfXlXwbn//vudbTt27NBDDz2kKlWqOO2VUq+4u20FmXNd7HzNt3B36xK26em6Ktolem3dLth6baVLHwAqVaqkF198UceOHVPRokWDZvb7vzsA+LajS5cuqlWrlqvvzdu/f79uvfVWNWnSRPny5dMdd9zhdyDw7UTp2rWra9tzuUujJSUlKS4uTrly5VLGjBld3fH1d4KhgystQuVEXQq+yTOvN8E0YSOQ3lJSUnTixAnVq1dPXbt2dW6L3b17t4oWLarXX39d9957r+rVq+f8TjAsWefVrFkz9ezZU9L/HTf37t2rAQMGqFatWnrzzTedfcePH++6FWS8/u58zbdwd+MStunpuivavei1da9g6bW93ANA/fr1lZSUFFSz30t/fwB46623JEnff/+9YmJinIlc3Oirr75Sly5d9Ntvv+nXX39Vnjx5zjsQeL8Pvv/+exUqVMi17bmcpdHOnj2rQ4cO6euvvw6ZK20AAPxTP/30kzJkyKD69eura9euypYtm3r16iUpddm97NmzB9VxMykpSWfOnNF9992nDh06OCteeM9pduzYoZYtW6pt27ZBsYLM5Zyv+RbublzCNr1ct0U7cLX83QEgW7ZsOnDggOtnv/e63APAbbfdJim1iGzcuLFrJmm7kIMHD/otffbLL784BwLftc2l1NESLVq0cHV7pEsvjTZw4EB16NAhaDqIAAC4Vn777Tfde++96tWrl1544QVn+2effaabb775vPMCNzp3/qoFCxYoY8aMev75551t3vO23377TR6PR8uXL/dbYcaNLvd8zXekl1s7IK42inbgKrjUAeCmm24Kii+UKzkAeO+TduOolYtNyOiNdcmSJX49uGfOnNG//vUv/fbbb649mJ3rYkujZcyYMWTuAwcA4Gq70HF+yJAhatiw4UXnvXGLC60gI6Uu7ZYhQwa9+uqrftvXrVuncuXKaePGjdcyzMt2JedrL774or766itJ7u2AuNpuMAD/WPXq1W3WrFnm8Xj8tv/4448WFRVlN9zg7o/an3/+aZ9//rndc889Fh0d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", "text/plain": [ "
" ] @@ -169,7 +209,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 7, "id": "60399f79", "metadata": {}, "outputs": [], @@ -196,7 +236,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 8, "id": "145fb875", "metadata": {}, "outputs": [], @@ -250,7 +290,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 9, "id": "cc1a07e0", "metadata": {}, "outputs": [], @@ -302,7 +342,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "acb1d6a0", "metadata": {}, "outputs": [], @@ -343,7 +383,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 11, "id": "c3456c55", "metadata": {}, "outputs": [], @@ -432,254 +472,49 @@ "source": [ "## 4. Distribution classes\n", "\n", - "To help with controlling sampling `numpy` distributions are packaged up into classes that allow easy control of random numbers.\n", + "To help with controlling sampling distributions classes are imported from `sim-tools`. \n", + "These are all wrappers from a `numpy` random number generated and are packaged in a way that allow easy control of random numbers.\n", "\n", "**Distributions included:**\n", "* Exponential\n", "* Log Normal\n", "* Bernoulli\n", "* Normal\n", - "* Uniform" + "* Uniform\n", + "\n" ] }, { - "cell_type": "code", - "execution_count": 10, - "id": "0508d46d", + "cell_type": "markdown", + "id": "172d99f5", "metadata": {}, - "outputs": [], "source": [ - "class Exponential:\n", - " '''\n", - " Convenience class for the exponential distribution.\n", - " packages up distribution parameters, seed and random generator.\n", - " '''\n", - " def __init__(self, mean, random_seed=None):\n", - " '''\n", - " Constructor\n", - " \n", - " Params:\n", - " ------\n", - " mean: float\n", - " The mean of the exponential distribution\n", - " \n", - " random_seed: int, optional (default=None)\n", - " A random seed to reproduce samples. If set to none then a unique\n", - " sample is created.\n", - " '''\n", - " self.rng = np.random.default_rng(seed=random_seed)\n", - " self.mean = mean\n", - " \n", - " def sample(self, size=None):\n", - " '''\n", - " Generate a sample from the exponential distribution\n", - " \n", - " Params:\n", - " -------\n", - " size: int, optional (default=None)\n", - " the number of samples to return. If size=None then a single\n", - " sample is returned.\n", - " '''\n", - " return self.rng.exponential(self.mean, size=size)\n", - "\n", - " \n", - "class Bernoulli:\n", - " '''\n", - " Convenience class for the Bernoulli distribution.\n", - " packages up distribution parameters, seed and random generator.\n", - " '''\n", - " def __init__(self, p, random_seed=None):\n", - " '''\n", - " Constructor\n", - " \n", - " Params:\n", - " ------\n", - " p: float\n", - " probability of drawing a 1\n", - " \n", - " random_seed: int, optional (default=None)\n", - " A random seed to reproduce samples. If set to none then a unique\n", - " sample is created.\n", - " '''\n", - " self.rng = np.random.default_rng(seed=random_seed)\n", - " self.p = p\n", - " \n", - " def sample(self, size=None):\n", - " '''\n", - " Generate a sample from the exponential distribution\n", - " \n", - " Params:\n", - " -------\n", - " size: int, optional (default=None)\n", - " the number of samples to return. If size=None then a single\n", - " sample is returned.\n", - " '''\n", - " return self.rng.binomial(n=1, p=self.p, size=size)\n", - "\n", - "class Lognormal:\n", - " \"\"\"\n", - " Encapsulates a lognormal distirbution\n", - " \"\"\"\n", - " def __init__(self, mean, stdev, random_seed=None):\n", - " \"\"\"\n", - " Params:\n", - " -------\n", - " mean: float\n", - " mean of the lognormal distribution\n", - " \n", - " stdev: float\n", - " standard dev of the lognormal distribution\n", - " \n", - " random_seed: int, optional (default=None)\n", - " Random seed to control sampling\n", - " \"\"\"\n", - " self.rng = np.random.default_rng(seed=random_seed)\n", - " mu, sigma = self.normal_moments_from_lognormal(mean, stdev**2)\n", - " self.mu = mu\n", - " self.sigma = sigma\n", - " \n", - " def normal_moments_from_lognormal(self, m, v):\n", - " '''\n", - " Returns mu and sigma of normal distribution\n", - " underlying a lognormal with mean m and variance v\n", - " source: https://blogs.sas.com/content/iml/2014/06/04/simulate-lognormal\n", - " -data-with-specified-mean-and-variance.html\n", + "## 5. Model parameterisation\n", "\n", - " Params:\n", - " -------\n", - " m: float\n", - " mean of lognormal distribution\n", - " v: float\n", - " variance of lognormal distribution\n", - " \n", - " Returns:\n", - " -------\n", - " (float, float)\n", - " '''\n", - " phi = math.sqrt(v + m**2)\n", - " mu = math.log(m**2/phi)\n", - " sigma = math.sqrt(math.log(phi**2/m**2))\n", - " return mu, sigma\n", - " \n", - " def sample(self):\n", - " \"\"\"\n", - " Sample from the normal distribution\n", - " \"\"\"\n", - " return self.rng.lognormal(self.mu, self.sigma)" + "For convienience a container class is used to hold the large number of model parameters. The `Scenario` class includes defaults these can easily be changed and at runtime to experiments with different designs." ] }, { "cell_type": "code", - "execution_count": 11, - "id": "071144e4", + "execution_count": 12, + "id": "c31929a6", "metadata": {}, "outputs": [], "source": [ - "class Normal:\n", - " '''\n", - " Convenience class for the normal distribution.\n", - " packages up distribution parameters, seed and random generator.\n", - "\n", - " Use the minimum parameter to truncate the distribution\n", - " '''\n", - " def __init__(self, mean, sigma, minimum=None, random_seed=None):\n", - " '''\n", - " Constructor\n", - " \n", - " Params:\n", - " ------\n", - " mean: float\n", - " The mean of the normal distribution\n", - " \n", - " sigma: float\n", - " The stdev of the normal distribution\n", - " \n", - " minimum: float, optional (default=None)\n", - " Used to truncate the distribution (e.g. to 0.0 or 0.5)\n", - " \n", - " random_seed: int, optional (default=None)\n", - " A random seed to reproduce samples. If set to none then a unique\n", - " sample is created.\n", - " '''\n", - " self.rng = np.random.default_rng(seed=random_seed)\n", - " self.mean = mean\n", - " self.sigma = sigma\n", - " self.minimum = minimum\n", - " \n", - " def sample(self, size=None):\n", - " '''\n", - " Generate a sample from the normal distribution\n", - " \n", - " Params:\n", - " -------\n", - " size: int, optional (default=None)\n", - " the number of samples to return. If size=None then a single\n", - " sample is returned.\n", - " '''\n", - " samples = self.rng.normal(self.mean, self.sigma, size=size)\n", - "\n", - " if self.minimum is None:\n", - " return samples\n", - " elif size is None:\n", - " return max(self.minimum, samples)\n", - " else:\n", - " # index of samples with negative value\n", - " neg_idx = np.where(samples < 0)[0]\n", - " samples[neg_idx] = self.minimum\n", - " return samples\n", - "\n", - " \n", - "class Uniform():\n", - " '''\n", - " Convenience class for the Uniform distribution.\n", - " packages up distribution parameters, seed and random generator.\n", - " '''\n", - " def __init__(self, low, high, random_seed=None):\n", - " '''\n", - " Constructor\n", - " \n", - " Params:\n", - " ------\n", - " low: float\n", - " lower range of the uniform\n", - " \n", - " high: float\n", - " upper range of the uniform\n", - " \n", - " random_seed: int, optional (default=None)\n", - " A random seed to reproduce samples. If set to none then a unique\n", - " sample is created.\n", - " '''\n", - " self.rand = np.random.default_rng(seed=random_seed)\n", - " self.low = low\n", - " self.high = high\n", - " \n", - " def sample(self, size=None):\n", - " '''\n", - " Generate a sample from the uniform distribution\n", - " \n", - " Params:\n", - " -------\n", - " size: int, optional (default=None)\n", - " the number of samples to return. If size=None then a single\n", - " sample is returned.\n", - " '''\n", - " return self.rand.uniform(low=self.low, high=self.high, size=size)" - ] - }, - { - "cell_type": "markdown", - "id": "172d99f5", - "metadata": {}, - "source": [ - "## 5. Model parameterisation\n", - "\n", - "For convienience a container class is used to hold the large number of model parameters. The `Scenario` class includes defaults these can easily be changed and at runtime to experiments with different designs." + "def load_arrival_profile(profile_path):\n", + "\n", + " df = (pd.read_csv(NSPP_PATH)\n", + " .reset_index()\n", + " .rename(columns={'index':'t'})\n", + " .assign(mean_iat = lambda x: 60.0 / x.arrival_rate,\n", + " t = lambda x: x.t * 60.0)\n", + " )\n", + " return df" ] }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 13, "id": "7f554b27", "metadata": {}, "outputs": [], @@ -898,19 +733,15 @@ " def init_nspp(self):\n", " \n", " # read arrival profile\n", - " self.arrivals = pd.read_csv(NSPP_PATH)\n", - " self.arrivals['mean_iat'] = 60 / self.arrivals['arrival_rate']\n", - " \n", + " self.arrivals = load_arrival_profile(NSPP_PATH)\n", + "\n", " # maximum arrival rate (smallest time between arrivals)\n", " self.lambda_max = self.arrivals['arrival_rate'].max()\n", " \n", - " # thinning exponential\n", - " self.arrival_dist = Exponential(60.0 / self.lambda_max,\n", - " random_seed=self.seeds[8])\n", - " \n", - " # thinning uniform rng\n", - " self.thinning_rng = Uniform(low=0.0, high=1.0, \n", - " random_seed=self.seeds[9])" + " # NSPP thinning distribution\n", + " self.thinning_dist = NSPPThinning(self.arrivals,\n", + " self.seeds[8],\n", + " self.seeds[9])" ] }, { @@ -925,7 +756,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 14, "id": "e0c23537", "metadata": {}, "outputs": [], @@ -957,7 +788,7 @@ " \n", " '''\n", " # initialise Trace\n", - " super().__init__(debug=True)\n", + " super().__init__(debug=args.debug)\n", " \n", " self.identifier = identifier\n", " self.env = env\n", @@ -1048,39 +879,30 @@ " self.trace(self.env.now, 'arrival at centre 🚑', self.identifier)\n", " \n", " def triage_begin(self):\n", - " '''Enter triage event\n", - " '''\n", + " '''Enter triage event'''\n", " self.trace(self.env.now, f'enter triage. Waiting time: {self.wait_triage:.3f}', \n", " self.identifier)\n", " \n", " def trauma_begin(self):\n", - " '''Enter trauma stabilisation\n", - " '''\n", + " '''Enter trauma stabilisation'''\n", " self.trace(self.env.now, f'enter stabilisation. Waiting time: {self.wait_trauma:.3f}', \n", " self.identifier)\n", " \n", " def treatment_begin(self):\n", - " '''Enter trauma treatment post stabilisation\n", - " '''\n", + " '''Enter trauma treatment post stabilisation'''\n", " self.trace(self.env.now, f'enter treatment. Waiting time: {self.wait_treat:.3f}', \n", " self.identifier)\n", " \n", " def triage_complete(self):\n", - " '''\n", - " Triage complete event\n", - " '''\n", + " '''Triage complete event'''\n", " self.trace(self.env.now, 'triage complete', self.identifier)\n", " \n", " def trauma_complete(self):\n", - " '''\n", - " Patient stay in trauma is complete.\n", - " '''\n", + " '''Patient stay in trauma is complete.'''\n", " self.trace(self.env.now, 'stabilisation complete.', self.identifier)\n", " \n", " def treatment_complete(self):\n", - " '''\n", - " Treatment complete event\n", - " '''\n", + " '''Treatment complete event'''\n", " self.trace(self.env.now, f'patient {self.identifier} treatment complete; '\n", " f'waiting time was {self.wait_treat:.3f}', self.identifier)\n", " \n", @@ -1088,10 +910,8 @@ " '''Override trace config'''\n", " config = {\n", " \"name\":\"Trauma\", \n", - " \"time_dp\":2,\n", " \"name_colour\":\"bold magenta\", \n", " \"time_colour\":'bold magenta', \n", - " \"message_colour\":'black',\n", " \"tracked\":self.args.tracked\n", " }\n", " return config" @@ -1099,7 +919,7 @@ }, { "cell_type": "code", - "execution_count": 31, + "execution_count": 15, "id": "cf40186c", "metadata": {}, "outputs": [], @@ -1130,7 +950,7 @@ " Container class for the simulation parameters\n", " \n", " '''\n", - " super().__init__(debug=True)\n", + " super().__init__(debug=args.debug)\n", " self.identifier = identifier\n", " self.env = env\n", " self.args = args\n", @@ -1159,8 +979,9 @@ " 1. request and wait for sign-in/triage\n", " 2. patient registration\n", " 3. examination\n", - " 4.1 40% discharged\n", - " 4.2 60% treatment then discharge\n", + " 4. split patients\n", + " 4.1 % discharged\n", + " 4.2 % treatment then discharge\n", " '''\n", " # record the time of arrival and entered the triage queue\n", " self.arrival = self.env.now\n", @@ -1266,7 +1087,6 @@ " \"name\":\"Non-trauma\", \n", " \"name_colour\":\"bold green\", \n", " \"time_colour\":'bold green', \n", - " \"time_dp\":2,\n", " \"message_colour\":'black',\n", " \"tracked\":self.args.tracked\n", " }\n", @@ -1286,15 +1106,7 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "0a1a0a5f", - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "b1690504", "metadata": {}, "outputs": [], @@ -1400,24 +1212,12 @@ " '''\n", " for patient_count in itertools.count():\n", "\n", - " # this give us the index of dataframe to use\n", - " t = int(self.env.now // 60) % self.args.arrivals.shape[0]\n", - " lambda_t = self.args.arrivals['arrival_rate'].iloc[t]\n", - "\n", - " #set to a large number so that at least 1 sample taken!\n", - " u = np.Inf\n", - " \n", - " interarrival_time = 0.0\n", - "\n", - " # reject samples if u >= lambda_t / lambda_max\n", - " while u >= (lambda_t / self.args.lambda_max):\n", - " interarrival_time += self.args.arrival_dist.sample()\n", - " u = self.args.thinning_rng.sample()\n", + " # sample iat via thinning\n", + " interarrival_time = self.args.thinning_dist.sample(self.env.now)\n", "\n", - " # iat\n", + " # delay until next arrival\n", " yield self.env.timeout(interarrival_time)\n", " \n", - "\n", " # sample if the patient is trauma or non-trauma\n", " trauma = self.args.p_trauma_dist.sample()\n", " if trauma:\n", @@ -1426,8 +1226,7 @@ " self.trauma_patients.append(new_patient)\n", " else:\n", " # create and store a non-trauma patient to update KPIs.\n", - " new_patient = NonTraumaPathway(patient_count, self.env, \n", - " self.args)\n", + " new_patient = NonTraumaPathway(patient_count, self.env, self.args)\n", " self.non_trauma_patients.append(new_patient)\n", " \n", " # start the pathway process for the patient\n", @@ -1446,7 +1245,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "fc06f50f", "metadata": {}, "outputs": [], @@ -1650,7 +1449,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, "id": "d751c883", "metadata": {}, "outputs": [], @@ -1698,7 +1497,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 19, "id": "512a699c", "metadata": {}, "outputs": [], @@ -1747,13 +1546,13 @@ "**Try:**\n", "\n", "* Changing the `random_no_set` of the `single_run` call.\n", - "* Assigning the value `True` to `DEBUG`\n", + "* Assigning the value `True` to `debug`\n", "* Tracking patient arrivals 10 and 19 through the model by setting `tracked=[10, 19]`" ] }, { "cell_type": "code", - "execution_count": 33, + "execution_count": 20, "id": "54eab6a5", "metadata": {}, "outputs": [ @@ -1761,209 +1560,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running simulation ... => " - ] - }, - { - "data": { - "text/html": [ - "
[169.77]:<Non-trauma 10>: arrival to centre.\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m169.77\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30marrival to centre.\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[175.62]:<Non-trauma 10>: entering triage. Waiting time: 5.849 mins\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m175.62\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mentering triage. Waiting time: \u001b[0m\u001b[1;30m5.849\u001b[0m\u001b[30m mins\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[175.79]:<Non-trauma 10>: triage complete\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m175.79\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[188.70]:<Non-trauma 10>: starting patient registration.\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m188.70\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mstarting patient registration.\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[193.51]:<Non-trauma 10>: patient registered;waiting time was 5.849\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mpatient registered;waiting time was \u001b[0m\u001b[1;30m5.849\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[193.51]:<Non-trauma 10>: enter examination. Waiting time: 0.000\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter examination. Waiting time: \u001b[0m\u001b[1;30m0.000\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[204.96]:<Trauma 19>: arrival at centre 🚑\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m204.96\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30marrival at centre 🚑\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[208.94]:<Non-trauma 10>: examination complete.\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mexamination complete.\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[208.94]:<Non-trauma 10>: enter treatment. Waiting time:0.000\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter treatment. Waiting tim\u001b[0m\u001b[1;30me:0\u001b[0m\u001b[30m.\u001b[0m\u001b[1;30m000\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[214.79]:<Trauma 19>: enter triage. Waiting time: 9.833\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m214.79\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter triage. Waiting time: \u001b[0m\u001b[1;30m9.833\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[221.03]:<Trauma 19>: triage complete\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m221.03\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[221.63]:<Non-trauma 10>: treatment complete ⛔\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;32m[\u001b[0m\u001b[1;32m221.63\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtreatment complete ⛔\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[384.82]:<Trauma 19>: enter stabilisation. Waiting time: 163.799\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m384.82\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter stabilisation. Waiting time: \u001b[0m\u001b[1;30m163.799\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[441.65]:<Trauma 19>: stabilisation complete.\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m441.65\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mstabilisation complete.\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
[473.10]:<Trauma 19>: enter treatment. Waiting time: 31.447\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1;35m[\u001b[0m\u001b[1;35m473.10\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter treatment. Waiting time: \u001b[0m\u001b[1;30m31.447\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "simulation complete.\n" + "Running simulation ... => simulation complete.\n" ] }, { @@ -1993,100 +1590,100 @@ " \n", " \n", " 00_arrivals\n", - " 118.000000\n", + " 7211.000000\n", " \n", " \n", " 01a_triage_wait\n", - " 16.822191\n", + " 212.500563\n", " \n", " \n", " 01b_triage_util\n", - " 0.635315\n", + " 1.002164\n", " \n", " \n", " 02a_registration_wait\n", - " 33.176241\n", + " 88.508416\n", " \n", " \n", " 02b_registration_util\n", - " 0.701956\n", + " 0.975184\n", " \n", " \n", " 03a_examination_wait\n", - " 11.976192\n", + " 15.253445\n", " \n", " \n", " 03b_examination_util\n", - " 0.688563\n", + " 0.961721\n", " \n", " \n", " 04a_treatment_wait(non_trauma)\n", - " 50.027313\n", + " 77.048844\n", " \n", " \n", " 04b_treatment_util(non_trauma)\n", - " 0.633359\n", + " 0.923395\n", " \n", " \n", " 05_total_time(non-trauma)\n", - " 92.469682\n", + " 248.052640\n", " \n", " \n", " 06a_trauma_wait\n", - " 89.715777\n", + " 164.340881\n", " \n", " \n", " 06b_trauma_util\n", - " 0.841324\n", + " 1.088690\n", " \n", " \n", " 07a_treatment_wait(trauma)\n", - " 14.706091\n", + " 12.062970\n", " \n", " \n", " 07b_treatment_util(trauma)\n", - " 0.234110\n", + " 0.475466\n", " \n", " \n", " 08_total_time(trauma)\n", - " 271.271242\n", + " 378.322912\n", " \n", " \n", " 09_throughput\n", - " 50.000000\n", + " 74.000000\n", " \n", " \n", "\n", "" ], "text/plain": [ - "rep 1\n", - "00_arrivals 118.000000\n", - "01a_triage_wait 16.822191\n", - "01b_triage_util 0.635315\n", - "02a_registration_wait 33.176241\n", - "02b_registration_util 0.701956\n", - "03a_examination_wait 11.976192\n", - "03b_examination_util 0.688563\n", - "04a_treatment_wait(non_trauma) 50.027313\n", - "04b_treatment_util(non_trauma) 0.633359\n", - "05_total_time(non-trauma) 92.469682\n", - "06a_trauma_wait 89.715777\n", - "06b_trauma_util 0.841324\n", - "07a_treatment_wait(trauma) 14.706091\n", - "07b_treatment_util(trauma) 0.234110\n", - "08_total_time(trauma) 271.271242\n", - "09_throughput 50.000000" + "rep 1\n", + "00_arrivals 7211.000000\n", + "01a_triage_wait 212.500563\n", + "01b_triage_util 1.002164\n", + "02a_registration_wait 88.508416\n", + "02b_registration_util 0.975184\n", + "03a_examination_wait 15.253445\n", + "03b_examination_util 0.961721\n", + "04a_treatment_wait(non_trauma) 77.048844\n", + "04b_treatment_util(non_trauma) 0.923395\n", + "05_total_time(non-trauma) 248.052640\n", + "06a_trauma_wait 164.340881\n", + "06b_trauma_util 1.088690\n", + "07a_treatment_wait(trauma) 12.062970\n", + "07b_treatment_util(trauma) 0.475466\n", + "08_total_time(trauma) 378.322912\n", + "09_throughput 74.000000" ] }, - "execution_count": 33, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# create the default scenario. \n", - "args = Scenario(debug=True, tracked=[10, 19])\n", + "args = Scenario(debug=False)\n", "\n", "# use the single_run() func\n", "# try changing `random_no_set` to see different run results\n", @@ -2112,12 +1709,97 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 21, "id": "ac6bd328", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
+       "\"ipywidgets\" for Jupyter support\n",
+       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
\n" + ], + "text/plain": [ + "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", + "\"ipywidgets\" for Jupyter support\n", + " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
+       "\"ipywidgets\" for Jupyter support\n",
+       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
\n" + ], + "text/plain": [ + "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", + "\"ipywidgets\" for Jupyter support\n", + " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
+       "\"ipywidgets\" for Jupyter support\n",
+       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
\n" + ], + "text/plain": [ + "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", + "\"ipywidgets\" for Jupyter support\n", + " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "ename": "KeyboardInterrupt",
+     "evalue": "",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[16], line 103\u001b[0m, in \u001b[0;36mTreatmentCentreModel.arrivals_generator\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    100\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m patient_count \u001b[38;5;129;01min\u001b[39;00m itertools\u001b[38;5;241m.\u001b[39mcount():\n\u001b[1;32m    101\u001b[0m \n\u001b[1;32m    102\u001b[0m     \u001b[38;5;66;03m# sample iat via thinning\u001b[39;00m\n\u001b[0;32m--> 103\u001b[0m     interarrival_time \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mthinning_dist\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msample\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnow\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    105\u001b[0m     \u001b[38;5;66;03m# delay until next arrival\u001b[39;00m\n",
+      "File \u001b[0;32m~/Documents/code/sim-tools/sim_tools/time_dependent.py:98\u001b[0m, in \u001b[0;36mNSPPThinning.sample\u001b[0;34m(self, simulation_time)\u001b[0m\n\u001b[1;32m     97\u001b[0m     interarrival_time \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39marr_rng\u001b[38;5;241m.\u001b[39mexponential(\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m/\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlambda_max)\n\u001b[0;32m---> 98\u001b[0m     u \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mthinning_rng\u001b[38;5;241m.\u001b[39muniform(\u001b[38;5;241m0.0\u001b[39m, \u001b[38;5;241m1.0\u001b[39m)\n\u001b[1;32m    100\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m interarrival_time\n",
+      "\u001b[0;31mKeyboardInterrupt\u001b[0m: ",
+      "\nThe above exception was the direct cause of the following exception:\n",
+      "\u001b[0;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[21], line 6\u001b[0m\n\u001b[1;32m      4\u001b[0m \u001b[38;5;66;03m#run multiple replications.\u001b[39;00m\n\u001b[1;32m      5\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m console\u001b[38;5;241m.\u001b[39mstatus(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m[magenta]Running multiple replications...\u001b[39m\u001b[38;5;124m\"\u001b[39m, spinner\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdots\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m status:\n\u001b[0;32m----> 6\u001b[0m     results  \u001b[38;5;241m=\u001b[39m \u001b[43mmultiple_replications\u001b[49m\u001b[43m(\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_reps\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m50\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m      8\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAll replications complete.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m     10\u001b[0m results\u001b[38;5;241m.\u001b[39mhead(\u001b[38;5;241m3\u001b[39m)\n",
+      "Cell \u001b[0;32mIn[19], line 23\u001b[0m, in \u001b[0;36mmultiple_replications\u001b[0;34m(scenario, rc_period, n_reps)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmultiple_replications\u001b[39m(scenario, rc_period\u001b[38;5;241m=\u001b[39mDEFAULT_RESULTS_COLLECTION_PERIOD, \n\u001b[1;32m      2\u001b[0m                           n_reps\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m):\n\u001b[1;32m      3\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m'''\u001b[39;00m\n\u001b[1;32m      4\u001b[0m \u001b[38;5;124;03m    Perform multiple replications of the model.\u001b[39;00m\n\u001b[1;32m      5\u001b[0m \u001b[38;5;124;03m    \u001b[39;00m\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m     20\u001b[0m \u001b[38;5;124;03m    pandas.DataFrame\u001b[39;00m\n\u001b[1;32m     21\u001b[0m \u001b[38;5;124;03m    '''\u001b[39;00m\n\u001b[0;32m---> 23\u001b[0m     results \u001b[38;5;241m=\u001b[39m \u001b[43m[\u001b[49m\u001b[43msingle_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43mscenario\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrc_period\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrandom_no_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrep\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m     24\u001b[0m \u001b[43m               \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mrange\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mn_reps\u001b[49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m     26\u001b[0m     \u001b[38;5;66;03m#format and return results in a dataframe\u001b[39;00m\n\u001b[1;32m     27\u001b[0m     df_results \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mconcat(results)\n",
+      "Cell \u001b[0;32mIn[19], line 23\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m      1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmultiple_replications\u001b[39m(scenario, rc_period\u001b[38;5;241m=\u001b[39mDEFAULT_RESULTS_COLLECTION_PERIOD, \n\u001b[1;32m      2\u001b[0m                           n_reps\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m):\n\u001b[1;32m      3\u001b[0m \u001b[38;5;250m    \u001b[39m\u001b[38;5;124;03m'''\u001b[39;00m\n\u001b[1;32m      4\u001b[0m \u001b[38;5;124;03m    Perform multiple replications of the model.\u001b[39;00m\n\u001b[1;32m      5\u001b[0m \u001b[38;5;124;03m    \u001b[39;00m\n\u001b[0;32m   (...)\u001b[0m\n\u001b[1;32m     20\u001b[0m \u001b[38;5;124;03m    pandas.DataFrame\u001b[39;00m\n\u001b[1;32m     21\u001b[0m \u001b[38;5;124;03m    '''\u001b[39;00m\n\u001b[0;32m---> 23\u001b[0m     results \u001b[38;5;241m=\u001b[39m [\u001b[43msingle_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43mscenario\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrc_period\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrandom_no_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrep\u001b[49m\u001b[43m)\u001b[49m \n\u001b[1;32m     24\u001b[0m                \u001b[38;5;28;01mfor\u001b[39;00m rep \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(n_reps)]\n\u001b[1;32m     26\u001b[0m     \u001b[38;5;66;03m#format and return results in a dataframe\u001b[39;00m\n\u001b[1;32m     27\u001b[0m     df_results \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mconcat(results)\n",
+      "Cell \u001b[0;32mIn[18], line 33\u001b[0m, in \u001b[0;36msingle_run\u001b[0;34m(scenario, rc_period, random_no_set)\u001b[0m\n\u001b[1;32m     30\u001b[0m model \u001b[38;5;241m=\u001b[39m TreatmentCentreModel(scenario)\n\u001b[1;32m     32\u001b[0m \u001b[38;5;66;03m# run the model\u001b[39;00m\n\u001b[0;32m---> 33\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresults_collection_period\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrc_period\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m     35\u001b[0m \u001b[38;5;66;03m# run results\u001b[39;00m\n\u001b[1;32m     36\u001b[0m summary \u001b[38;5;241m=\u001b[39m SimulationSummary(model)\n",
+      "Cell \u001b[0;32mIn[16], line 87\u001b[0m, in \u001b[0;36mTreatmentCentreModel.run\u001b[0;34m(self, results_collection_period)\u001b[0m\n\u001b[1;32m     84\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrc_period \u001b[38;5;241m=\u001b[39m results_collection_period\n\u001b[1;32m     86\u001b[0m \u001b[38;5;66;03m# run\u001b[39;00m\n\u001b[0;32m---> 87\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43muntil\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mresults_collection_period\u001b[49m\u001b[43m)\u001b[49m\n",
+      "File \u001b[0;32m~/miniconda3/envs/sim_tools/lib/python3.11/site-packages/simpy/core.py:246\u001b[0m, in \u001b[0;36mEnvironment.run\u001b[0;34m(self, until)\u001b[0m\n\u001b[1;32m    244\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m    245\u001b[0m     \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m--> 246\u001b[0m         \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstep\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m    247\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m StopSimulation \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m    248\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m exc\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m]  \u001b[38;5;66;03m# == until.value\u001b[39;00m\n",
+      "File \u001b[0;32m~/miniconda3/envs/sim_tools/lib/python3.11/site-packages/simpy/core.py:204\u001b[0m, in \u001b[0;36mEnvironment.step\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m    202\u001b[0m exc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mtype\u001b[39m(event\u001b[38;5;241m.\u001b[39m_value)(\u001b[38;5;241m*\u001b[39mevent\u001b[38;5;241m.\u001b[39m_value\u001b[38;5;241m.\u001b[39margs)\n\u001b[1;32m    203\u001b[0m exc\u001b[38;5;241m.\u001b[39m__cause__ \u001b[38;5;241m=\u001b[39m event\u001b[38;5;241m.\u001b[39m_value\n\u001b[0;32m--> 204\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n",
+      "\u001b[0;31mKeyboardInterrupt\u001b[0m: "
+     ]
+    }
+   ],
    "source": [
-    "args = Scenario(trace_level=2)\n",
+    "console = Console()\n",
+    "args = Scenario()\n",
     "\n",
     "#run multiple replications.\n",
     "with console.status(\"[magenta]Running multiple replications...\", spinner=\"dots\") as status:\n",
@@ -2409,7 +2091,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.10.14"
+   "version": "3.11.6"
   }
  },
  "nbformat": 4,
diff --git a/docs/03_trace/data/ed_arrivals.csv b/docs/03_trace/data/ed_arrivals.csv
new file mode 100644
index 0000000..a0c4e5d
--- /dev/null
+++ b/docs/03_trace/data/ed_arrivals.csv
@@ -0,0 +1,19 @@
+period,arrival_rate
+6AM-7AM,2.36666666666667
+7AM-8AM,2.8
+8AM-9AM,8.83333333333333
+9AM-10AM,10.4333333333333
+10AM-11AM,14.8
+11AM-12PM,26.2666666666667
+12PM-1PM,31.4
+1PM-2PM,18.0666666666667
+2PM-3PM,16.4666666666667
+3PM-4PM,12.0333333333333
+4PM-5PM,11.6
+5PM-6PM,28.8666666666667
+6PM-7PM,18.0333333333333
+7PM-8PM,11.5
+8PM-9PM,5.3
+9PM-10PM,4.06666666666667
+10PM-11PM,2.2
+11PM-12AM,2.1
diff --git a/docs/03_trace/data/tbl_row_headers.csv b/docs/03_trace/data/tbl_row_headers.csv
new file mode 100644
index 0000000..972bdb3
--- /dev/null
+++ b/docs/03_trace/data/tbl_row_headers.csv
@@ -0,0 +1,7 @@
+Mean waiting time (mins)
+Triage
+Registation
+Examination
+Non-trauma treatment
+Trauma stabilisation
+Trauma treatment
\ No newline at end of file
diff --git a/docs/03_trace/output/table_3.txt b/docs/03_trace/output/table_3.txt
new file mode 100644
index 0000000..c27b1f0
--- /dev/null
+++ b/docs/03_trace/output/table_3.txt
@@ -0,0 +1,16 @@
+\begin{table}
+\tbl{Simulation results that can be verified by our example reproducible pipeline.}
+\label{tab:table3}
+\begin{tabular}{lrrrrr}
+\toprule
+Mean waiting time (mins) & base & triage+1 & exam+1 & treat+1 & triage+exam \\
+\midrule
+Triage & 32.560000 & 1.260000 & 32.560000 & 32.560000 & 1.260000 \\
+Registation & 104.690000 & 131.820000 & 104.690000 & 104.690000 & 131.820000 \\
+Examination & 23.360000 & 24.440000 & 0.140000 & 23.360000 & 0.140000 \\
+Non-trauma treatment & 130.730000 & 133.090000 & 144.500000 & 2.150000 & 147.810000 \\
+Trauma stabilisation & 166.980000 & 189.670000 & 166.980000 & 166.980000 & 189.670000 \\
+Trauma treatment & 14.390000 & 14.770000 & 14.390000 & 14.390000 & 14.770000 \\
+\bottomrule
+\end{tabular}
+\end{table}

From 6d86eefc1c8c34d1774ebf8c498ab0d83d51f75d Mon Sep 17 00:00:00 2001
From: TomMonks 
Date: Thu, 20 Jun 2024 17:03:39 +0100
Subject: [PATCH 06/11] feat(trace): added trace module

---
 sim_tools/trace.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 92 insertions(+)
 create mode 100644 sim_tools/trace.py

diff --git a/sim_tools/trace.py b/sim_tools/trace.py
new file mode 100644
index 0000000..e123ca0
--- /dev/null
+++ b/sim_tools/trace.py
@@ -0,0 +1,92 @@
+"""
+Define 
+"""
+
+from abc import ABC
+from rich import Console
+
+CONFIG_ERROR = ("Your trace has not been initialised. " 
+                "Call super__init__(debug=True) in class initialiser" 
+                "or omit debug for default of no trace.")
+
+
+## single rich console
+_console = Console()
+
+class Traceable(ABC):
+    '''Provides basic trace functionality for a process to subclass
+    
+    Abstract base class Traceable
+    
+    Subclasses must call 
+    
+    super().__init__(debug=True) in their __init__() method to 
+    initialise trace.
+    
+    Subclasses inherit 
+
+    trace() - use this function print out a traceable event
+
+    _trace_config(): use this function to return a dict containing
+    the trace configuration for the class.
+    '''
+    def __init__(self, debug=False):
+        self.debug = debug
+        self._config = self._default_config()
+    
+    def _default_config(self):
+        """Returns a default trace configuration"""
+        config = {
+            "name":None, 
+            "name_colour":"bold blue", 
+            "time_colour":'bold blue', 
+            "time_dp":2,
+            "message_colour":'black',
+            "tracked":None
+        }
+        return config
+        
+    
+    def _trace_config(self):
+        config = {
+            "name":None, 
+            "name_colour":"bold blue", 
+            "time_colour":'bold blue', 
+            "time_dp":2,
+            "message_colour":'black',
+            "tracked":None
+        }
+        return config
+    
+    
+    def trace(self, time, msg=None, process_id=None):
+        '''
+        Display a trace of an event
+        '''
+        
+        if not hasattr(self, '_config'):
+            raise AttributeError(CONFIG_ERROR)
+        
+        # if in debug mode
+        if self.debug:
+            
+            # check for override to default configs
+            process_config = self._trace_config()
+            self._config.update(process_config)
+            
+            # conditional logic to limit tracking to specific processes/entities
+            if self._config['tracked'] is None or process_id in self._config['tracked']:
+
+                # display and format time stamp
+                out = f"[{self._config['time_colour']}][{time:.{self._config['time_dp']}f}]:[/{self._config['time_colour']}]"
+                
+                # if provided display and format a process ID 
+                if self._config['name'] is not None and process_id is not None:
+                    out += f"[{self._config['name_colour']}]<{self._config['name']} {process_id}>: [/{self._config['name_colour']}]"
+
+                # format traced event message
+                out += f"[{self._config['message_colour']}]{msg}[/{self._config['message_colour']}]"
+
+                # print to rich console
+                _console.print(out)
+        
\ No newline at end of file

From 0f20aee8272152b19f658f0f27d72b0d1715b05a Mon Sep 17 00:00:00 2001
From: TomMonks 
Date: Thu, 20 Jun 2024 17:05:44 +0100
Subject: [PATCH 07/11] feat(trace): +docstrings

---
 sim_tools/trace.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/sim_tools/trace.py b/sim_tools/trace.py
index e123ca0..2deb671 100644
--- a/sim_tools/trace.py
+++ b/sim_tools/trace.py
@@ -1,5 +1,6 @@
 """
-Define 
+Simple functionality aiming to enhanced a users a
+ability to trace and debug simulation models.
 """
 
 from abc import ABC
@@ -10,7 +11,7 @@
                 "or omit debug for default of no trace.")
 
 
-## single rich console
+## single rich console - module level.
 _console = Console()
 
 class Traceable(ABC):
@@ -23,7 +24,7 @@ class Traceable(ABC):
     super().__init__(debug=True) in their __init__() method to 
     initialise trace.
     
-    Subclasses inherit 
+    Subclasses inherit the following methods:
 
     trace() - use this function print out a traceable event
 

From 75c5daf0227f6836ea7634460b188cd86505325f Mon Sep 17 00:00:00 2001
From: TomMonks 
Date: Thu, 20 Jun 2024 17:12:04 +0100
Subject: [PATCH 08/11] feat(trace): import trace.Traceable

---
 docs/03_trace/04_model.ipynb | 1480 ++++++++++++++++++++++++++--------
 sim_tools/trace.py           |    6 +-
 2 files changed, 1167 insertions(+), 319 deletions(-)

diff --git a/docs/03_trace/04_model.ipynb b/docs/03_trace/04_model.ipynb
index ac00d91..4d84ebc 100644
--- a/docs/03_trace/04_model.ipynb
+++ b/docs/03_trace/04_model.ipynb
@@ -77,13 +77,14 @@
    "source": [
     "from sim_tools.distributions import (\n",
     "    Exponential, \n",
-    "    Uniform, \n",
     "    Normal, \n",
     "    Lognormal, \n",
     "    Bernoulli\n",
     ")\n",
     "\n",
-    "from sim_tools.time_dependent import NSPPThinning"
+    "from sim_tools.time_dependent import NSPPThinning\n",
+    "\n",
+    "from sim_tools.trace import Traceable"
    ]
   },
   {
@@ -96,13 +97,11 @@
     "import numpy as np\n",
     "import pandas as pd\n",
     "import itertools\n",
-    "import math\n",
     "import matplotlib.pyplot as plt\n",
     "import fileinput\n",
     "\n",
     "from rich.console import Console\n",
-    "from rich.progress import track\n",
-    "#console = Console()"
+    "from rich.progress import track"
    ]
   },
   {
@@ -381,90 +380,6 @@
     "    return decorator"
    ]
   },
-  {
-   "cell_type": "code",
-   "execution_count": 11,
-   "id": "c3456c55",
-   "metadata": {},
-   "outputs": [],
-   "source": [
-    "from abc import ABC, abstractmethod\n",
-    "\n",
-    "class Traceable(ABC):\n",
-    "    '''Provide basic trace functionality to subclass\n",
-    "    \n",
-    "    Abstract base class Traceable\n",
-    "    \n",
-    "    Subclasses must call \n",
-    "    \n",
-    "    super().__init__(debug=True) in their __init__() method to \n",
-    "    initialise trace.\n",
-    "    \n",
-    "    This adds \n",
-    "    '''\n",
-    "    def __init__(self, debug=False):\n",
-    "        self.debug = debug\n",
-    "        self.config = self._default_config()\n",
-    "        self.console = Console()\n",
-    "    \n",
-    "    def _default_config(self):\n",
-    "        config = {\n",
-    "            \"name\":None, \n",
-    "            \"name_colour\":\"bold blue\", \n",
-    "            \"time_colour\":'bold blue', \n",
-    "            \"time_dp\":2,\n",
-    "            \"message_colour\":'black',\n",
-    "            \"tracked\":None\n",
-    "        }\n",
-    "        return config\n",
-    "        \n",
-    "    \n",
-    "    def _trace_config(self):\n",
-    "        config = {\n",
-    "            \"name\":None, \n",
-    "            \"name_colour\":\"bold blue\", \n",
-    "            \"time_colour\":'bold blue', \n",
-    "            \"time_dp\":2,\n",
-    "            \"message_colour\":'black',\n",
-    "            \"tracked\":None\n",
-    "        }\n",
-    "        return config\n",
-    "    \n",
-    "    \n",
-    "    def trace(self, time, msg=None, process_id=None):\n",
-    "        '''\n",
-    "        Display a trace of an event\n",
-    "        '''\n",
-    "        \n",
-    "        if not hasattr(self, 'config'):\n",
-    "            raise AttributeError(\"Your trace has not been initialised. Call super__init__(debug=True) in class initialiser\"\n",
-    "                                 \"or omit debug for default of no trace.\")\n",
-    "        \n",
-    "        # if in debug mode\n",
-    "        if self.debug:\n",
-    "            \n",
-    "            # check for override to default configs\n",
-    "            process_config = self._trace_config()\n",
-    "            self.config.update(process_config)\n",
-    "            \n",
-    "            # conditional logic to limit tracking to specific processes/entities\n",
-    "            if self.config['tracked'] is None or process_id in self.config['tracked']:\n",
-    "\n",
-    "                # display and format time stamp\n",
-    "                out = f\"[{self.config['time_colour']}][{time:.{self.config['time_dp']}f}]:[/{self.config['time_colour']}]\"\n",
-    "                \n",
-    "                # if provided display and format a process ID \n",
-    "                if self.config['name'] is not None and process_id is not None:\n",
-    "                    out += f\"[{self.config['name_colour']}]<{self.config['name']} {process_id}>: [/{self.config['name_colour']}]\"\n",
-    "\n",
-    "                # format traced event message\n",
-    "                out += f\"[{self.config['message_colour']}]{msg}[/{self.config['message_colour']}]\"\n",
-    "\n",
-    "                # print to rich console\n",
-    "                self.console.print(out)\n",
-    "        "
-   ]
-  },
   {
    "cell_type": "markdown",
    "id": "3160552e",
@@ -496,7 +411,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 11,
    "id": "c31929a6",
    "metadata": {},
    "outputs": [],
@@ -512,6 +427,201 @@
     "       return df"
    ]
   },
+  {
+   "cell_type": "code",
+   "execution_count": 12,
+   "id": "4de97305",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
tperiodarrival_ratemean_iat
00.06AM-7AM2.36666725.352113
160.07AM-8AM2.80000021.428571
2120.08AM-9AM8.8333336.792453
3180.09AM-10AM10.4333335.750799
4240.010AM-11AM14.8000004.054054
5300.011AM-12PM26.2666672.284264
6360.012PM-1PM31.4000001.910828
7420.01PM-2PM18.0666673.321033
8480.02PM-3PM16.4666673.643725
9540.03PM-4PM12.0333334.986150
10600.04PM-5PM11.6000005.172414
11660.05PM-6PM28.8666672.078522
12720.06PM-7PM18.0333333.327172
13780.07PM-8PM11.5000005.217391
14840.08PM-9PM5.30000011.320755
15900.09PM-10PM4.06666714.754098
16960.010PM-11PM2.20000027.272727
171020.011PM-12AM2.10000028.571429
\n", + "
" + ], + "text/plain": [ + " t period arrival_rate mean_iat\n", + "0 0.0 6AM-7AM 2.366667 25.352113\n", + "1 60.0 7AM-8AM 2.800000 21.428571\n", + "2 120.0 8AM-9AM 8.833333 6.792453\n", + "3 180.0 9AM-10AM 10.433333 5.750799\n", + "4 240.0 10AM-11AM 14.800000 4.054054\n", + "5 300.0 11AM-12PM 26.266667 2.284264\n", + "6 360.0 12PM-1PM 31.400000 1.910828\n", + "7 420.0 1PM-2PM 18.066667 3.321033\n", + "8 480.0 2PM-3PM 16.466667 3.643725\n", + "9 540.0 3PM-4PM 12.033333 4.986150\n", + "10 600.0 4PM-5PM 11.600000 5.172414\n", + "11 660.0 5PM-6PM 28.866667 2.078522\n", + "12 720.0 6PM-7PM 18.033333 3.327172\n", + "13 780.0 7PM-8PM 11.500000 5.217391\n", + "14 840.0 8PM-9PM 5.300000 11.320755\n", + "15 900.0 9PM-10PM 4.066667 14.754098\n", + "16 960.0 10PM-11PM 2.200000 27.272727\n", + "17 1020.0 11PM-12AM 2.100000 28.571429" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "load_arrival_profile(NSPP_PATH)" + ] + }, { "cell_type": "code", "execution_count": 13, @@ -1560,171 +1670,30 @@ "name": "stdout", "output_type": "stream", "text": [ - "Running simulation ... => simulation complete.\n" + "Running simulation ... => " ] }, { "data": { "text/html": [ - "
\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "
rep1
00_arrivals7211.000000
01a_triage_wait212.500563
01b_triage_util1.002164
02a_registration_wait88.508416
02b_registration_util0.975184
03a_examination_wait15.253445
03b_examination_util0.961721
04a_treatment_wait(non_trauma)77.048844
04b_treatment_util(non_trauma)0.923395
05_total_time(non-trauma)248.052640
06a_trauma_wait164.340881
06b_trauma_util1.088690
07a_treatment_wait(trauma)12.062970
07b_treatment_util(trauma)0.475466
08_total_time(trauma)378.322912
09_throughput74.000000
\n", - "
" + "
[169.77]:<Non-trauma 10>: arrival to centre.\n",
+       "
\n" ], "text/plain": [ - "rep 1\n", - "00_arrivals 7211.000000\n", - "01a_triage_wait 212.500563\n", - "01b_triage_util 1.002164\n", - "02a_registration_wait 88.508416\n", - "02b_registration_util 0.975184\n", - "03a_examination_wait 15.253445\n", - "03b_examination_util 0.961721\n", - "04a_treatment_wait(non_trauma) 77.048844\n", - "04b_treatment_util(non_trauma) 0.923395\n", - "05_total_time(non-trauma) 248.052640\n", - "06a_trauma_wait 164.340881\n", - "06b_trauma_util 1.088690\n", - "07a_treatment_wait(trauma) 12.062970\n", - "07b_treatment_util(trauma) 0.475466\n", - "08_total_time(trauma) 378.322912\n", - "09_throughput 74.000000" + "\u001b[1;32m[\u001b[0m\u001b[1;32m169.77\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30marrival to centre.\u001b[0m\n" ] }, - "execution_count": 20, "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "# create the default scenario. \n", - "args = Scenario(debug=False)\n", - "\n", - "# use the single_run() func\n", - "# try changing `random_no_set` to see different run results\n", - "print('Running simulation ...', end=' => ')\n", - "results = single_run(args, rc_period=500.0, random_no_set=42)\n", - "print('simulation complete.')\n", - "\n", - "# show results (transpose replication for easier view)\n", - "results.T" - ] - }, - { - "cell_type": "markdown", - "id": "7636b290", - "metadata": {}, - "source": [ - "### 9.2 Multiple independent replications\n", - "\n", - "Given the set up it is now easy to perform multiple replications of the model.\n", - "\n", - "> Notes: here we also use the `rich` library to provide a **status spinner**. This is not a full progress bar implementation, but provides a small amount of user feedback that the model is still running.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 21, - "id": "ac6bd328", - "metadata": {}, - "outputs": [ + "output_type": "display_data" + }, { "data": { "text/html": [ - "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
-       "\"ipywidgets\" for Jupyter support\n",
-       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
[175.62]:<Non-trauma 10>: entering triage. Waiting time: 5.849 mins\n",
        "
\n" ], "text/plain": [ - "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", - "\"ipywidgets\" for Jupyter support\n", - " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + "\u001b[1;32m[\u001b[0m\u001b[1;32m175.62\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mentering triage. Waiting time: \u001b[0m\u001b[1;30m5.849\u001b[0m\u001b[30m mins\u001b[0m\n" ] }, "metadata": {}, @@ -1733,15 +1702,11 @@ { "data": { "text/html": [ - "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
-       "\"ipywidgets\" for Jupyter support\n",
-       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
[175.79]:<Non-trauma 10>: triage complete\n",
        "
\n" ], "text/plain": [ - "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", - "\"ipywidgets\" for Jupyter support\n", - " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + "\u001b[1;32m[\u001b[0m\u001b[1;32m175.79\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" ] }, "metadata": {}, @@ -1750,15 +1715,11 @@ { "data": { "text/html": [ - "
/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n",
-       "\"ipywidgets\" for Jupyter support\n",
-       "  warnings.warn('install \"ipywidgets\" for Jupyter support')\n",
+       "
[188.70]:<Non-trauma 10>: starting patient registration.\n",
        "
\n" ], "text/plain": [ - "/home/tom/miniconda3/envs/sim_tools/lib/python3.11/site-packages/rich/live.py:231: UserWarning: install \n", - "\"ipywidgets\" for Jupyter support\n", - " warnings.warn('install \"ipywidgets\" for Jupyter support')\n" + "\u001b[1;32m[\u001b[0m\u001b[1;32m188.70\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mstarting patient registration.\u001b[0m\n" ] }, "metadata": {}, @@ -1767,55 +1728,556 @@ { "data": { "text/html": [ - "
\n"
+       "
[193.51]:<Non-trauma 10>: patient registered;waiting time was 5.849\n",
+       "
\n" ], - "text/plain": [] + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mpatient registered;waiting time was \u001b[0m\u001b[1;30m5.849\u001b[0m\n" + ] }, "metadata": {}, "output_type": "display_data" }, { - "ename": "KeyboardInterrupt", - "evalue": "", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[16], line 103\u001b[0m, in \u001b[0;36mTreatmentCentreModel.arrivals_generator\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 100\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m patient_count \u001b[38;5;129;01min\u001b[39;00m itertools\u001b[38;5;241m.\u001b[39mcount():\n\u001b[1;32m 101\u001b[0m \n\u001b[1;32m 102\u001b[0m \u001b[38;5;66;03m# sample iat via thinning\u001b[39;00m\n\u001b[0;32m--> 103\u001b[0m interarrival_time \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43margs\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mthinning_dist\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msample\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mnow\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 105\u001b[0m \u001b[38;5;66;03m# delay until next arrival\u001b[39;00m\n", - "File \u001b[0;32m~/Documents/code/sim-tools/sim_tools/time_dependent.py:98\u001b[0m, in \u001b[0;36mNSPPThinning.sample\u001b[0;34m(self, simulation_time)\u001b[0m\n\u001b[1;32m 97\u001b[0m interarrival_time \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39marr_rng\u001b[38;5;241m.\u001b[39mexponential(\u001b[38;5;241m1\u001b[39m \u001b[38;5;241m/\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mlambda_max)\n\u001b[0;32m---> 98\u001b[0m u \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mthinning_rng\u001b[38;5;241m.\u001b[39muniform(\u001b[38;5;241m0.0\u001b[39m, \u001b[38;5;241m1.0\u001b[39m)\n\u001b[1;32m 100\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m interarrival_time\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: ", - "\nThe above exception was the direct cause of the following exception:\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[21], line 6\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m#run multiple replications.\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m console\u001b[38;5;241m.\u001b[39mstatus(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m[magenta]Running multiple replications...\u001b[39m\u001b[38;5;124m\"\u001b[39m, spinner\u001b[38;5;241m=\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mdots\u001b[39m\u001b[38;5;124m\"\u001b[39m) \u001b[38;5;28;01mas\u001b[39;00m status:\n\u001b[0;32m----> 6\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43mmultiple_replications\u001b[49m\u001b[43m(\u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mn_reps\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m50\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mAll replications complete.\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[1;32m 10\u001b[0m results\u001b[38;5;241m.\u001b[39mhead(\u001b[38;5;241m3\u001b[39m)\n", - "Cell \u001b[0;32mIn[19], line 23\u001b[0m, in \u001b[0;36mmultiple_replications\u001b[0;34m(scenario, rc_period, n_reps)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmultiple_replications\u001b[39m(scenario, rc_period\u001b[38;5;241m=\u001b[39mDEFAULT_RESULTS_COLLECTION_PERIOD, \n\u001b[1;32m 2\u001b[0m n_reps\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m):\n\u001b[1;32m 3\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m'''\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;124;03m Perform multiple replications of the model.\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;124;03m \u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;124;03m pandas.DataFrame\u001b[39;00m\n\u001b[1;32m 21\u001b[0m \u001b[38;5;124;03m '''\u001b[39;00m\n\u001b[0;32m---> 23\u001b[0m results \u001b[38;5;241m=\u001b[39m \u001b[43m[\u001b[49m\u001b[43msingle_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43mscenario\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrc_period\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrandom_no_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrep\u001b[49m\u001b[43m)\u001b[49m\u001b[43m \u001b[49m\n\u001b[1;32m 24\u001b[0m \u001b[43m \u001b[49m\u001b[38;5;28;43;01mfor\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43mrep\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;129;43;01min\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;28;43mrange\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43mn_reps\u001b[49m\u001b[43m)\u001b[49m\u001b[43m]\u001b[49m\n\u001b[1;32m 26\u001b[0m \u001b[38;5;66;03m#format and return results in a dataframe\u001b[39;00m\n\u001b[1;32m 27\u001b[0m df_results \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mconcat(results)\n", - "Cell \u001b[0;32mIn[19], line 23\u001b[0m, in \u001b[0;36m\u001b[0;34m(.0)\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mmultiple_replications\u001b[39m(scenario, rc_period\u001b[38;5;241m=\u001b[39mDEFAULT_RESULTS_COLLECTION_PERIOD, \n\u001b[1;32m 2\u001b[0m n_reps\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m5\u001b[39m):\n\u001b[1;32m 3\u001b[0m \u001b[38;5;250m \u001b[39m\u001b[38;5;124;03m'''\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;124;03m Perform multiple replications of the model.\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;124;03m \u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 20\u001b[0m \u001b[38;5;124;03m pandas.DataFrame\u001b[39;00m\n\u001b[1;32m 21\u001b[0m \u001b[38;5;124;03m '''\u001b[39;00m\n\u001b[0;32m---> 23\u001b[0m results \u001b[38;5;241m=\u001b[39m [\u001b[43msingle_run\u001b[49m\u001b[43m(\u001b[49m\u001b[43mscenario\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrc_period\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrandom_no_set\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrep\u001b[49m\u001b[43m)\u001b[49m \n\u001b[1;32m 24\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m rep \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mrange\u001b[39m(n_reps)]\n\u001b[1;32m 26\u001b[0m \u001b[38;5;66;03m#format and return results in a dataframe\u001b[39;00m\n\u001b[1;32m 27\u001b[0m df_results \u001b[38;5;241m=\u001b[39m pd\u001b[38;5;241m.\u001b[39mconcat(results)\n", - "Cell \u001b[0;32mIn[18], line 33\u001b[0m, in \u001b[0;36msingle_run\u001b[0;34m(scenario, rc_period, random_no_set)\u001b[0m\n\u001b[1;32m 30\u001b[0m model \u001b[38;5;241m=\u001b[39m TreatmentCentreModel(scenario)\n\u001b[1;32m 32\u001b[0m \u001b[38;5;66;03m# run the model\u001b[39;00m\n\u001b[0;32m---> 33\u001b[0m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43mresults_collection_period\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mrc_period\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 35\u001b[0m \u001b[38;5;66;03m# run results\u001b[39;00m\n\u001b[1;32m 36\u001b[0m summary \u001b[38;5;241m=\u001b[39m SimulationSummary(model)\n", - "Cell \u001b[0;32mIn[16], line 87\u001b[0m, in \u001b[0;36mTreatmentCentreModel.run\u001b[0;34m(self, results_collection_period)\u001b[0m\n\u001b[1;32m 84\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mrc_period \u001b[38;5;241m=\u001b[39m results_collection_period\n\u001b[1;32m 86\u001b[0m \u001b[38;5;66;03m# run\u001b[39;00m\n\u001b[0;32m---> 87\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43menv\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrun\u001b[49m\u001b[43m(\u001b[49m\u001b[43muntil\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mresults_collection_period\u001b[49m\u001b[43m)\u001b[49m\n", - "File \u001b[0;32m~/miniconda3/envs/sim_tools/lib/python3.11/site-packages/simpy/core.py:246\u001b[0m, in \u001b[0;36mEnvironment.run\u001b[0;34m(self, until)\u001b[0m\n\u001b[1;32m 244\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m 245\u001b[0m \u001b[38;5;28;01mwhile\u001b[39;00m \u001b[38;5;28;01mTrue\u001b[39;00m:\n\u001b[0;32m--> 246\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mstep\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 247\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m StopSimulation \u001b[38;5;28;01mas\u001b[39;00m exc:\n\u001b[1;32m 248\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m exc\u001b[38;5;241m.\u001b[39margs[\u001b[38;5;241m0\u001b[39m] \u001b[38;5;66;03m# == until.value\u001b[39;00m\n", - "File \u001b[0;32m~/miniconda3/envs/sim_tools/lib/python3.11/site-packages/simpy/core.py:204\u001b[0m, in \u001b[0;36mEnvironment.step\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 202\u001b[0m exc \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mtype\u001b[39m(event\u001b[38;5;241m.\u001b[39m_value)(\u001b[38;5;241m*\u001b[39mevent\u001b[38;5;241m.\u001b[39m_value\u001b[38;5;241m.\u001b[39margs)\n\u001b[1;32m 203\u001b[0m exc\u001b[38;5;241m.\u001b[39m__cause__ \u001b[38;5;241m=\u001b[39m event\u001b[38;5;241m.\u001b[39m_value\n\u001b[0;32m--> 204\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m exc\n", - "\u001b[0;31mKeyboardInterrupt\u001b[0m: " - ] - } - ], - "source": [ - "console = Console()\n", - "args = Scenario()\n", - "\n", - "#run multiple replications.\n", - "with console.status(\"[magenta]Running multiple replications...\", spinner=\"dots\") as status:\n", - " results = multiple_replications(args, n_reps=50)\n", - "\n", - "print(\"All replications complete.\")\n", - "\n", - "results.head(3)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9bce241b", - "metadata": {}, - "outputs": [], + "data": { + "text/html": [ + "
[193.51]:<Non-trauma 10>: enter examination. Waiting time: 0.000\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m193.51\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter examination. Waiting time: \u001b[0m\u001b[1;30m0.000\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[204.96]:<Trauma 19>: arrival at centre 🚑\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m204.96\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30marrival at centre 🚑\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[208.94]:<Non-trauma 10>: examination complete.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mexamination complete.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[208.94]:<Non-trauma 10>: enter treatment. Waiting time:0.000\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m208.94\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30menter treatment. Waiting tim\u001b[0m\u001b[1;30me:0\u001b[0m\u001b[30m.\u001b[0m\u001b[1;30m000\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[214.79]:<Trauma 19>: enter triage. Waiting time: 9.833\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m214.79\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter triage. Waiting time: \u001b[0m\u001b[1;30m9.833\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[221.03]:<Trauma 19>: triage complete\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m221.03\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mtriage complete\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[221.63]:<Non-trauma 10>: treatment complete ⛔\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;32m[\u001b[0m\u001b[1;32m221.63\u001b[0m\u001b[1;32m]\u001b[0m\u001b[1;32m:\u001b[0m\u001b[1;32m<\u001b[0m\u001b[1;32mNon-trauma\u001b[0m\u001b[1;32m \u001b[0m\u001b[1;32m10\u001b[0m\u001b[1;32m>\u001b[0m\u001b[1;32m: \u001b[0m\u001b[30mtreatment complete ⛔\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[384.82]:<Trauma 19>: enter stabilisation. Waiting time: 163.799\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m384.82\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter stabilisation. Waiting time: \u001b[0m\u001b[1;30m163.799\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[441.65]:<Trauma 19>: stabilisation complete.\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m441.65\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mstabilisation complete.\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[473.10]:<Trauma 19>: enter treatment. Waiting time: 31.447\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m473.10\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30menter treatment. Waiting time: \u001b[0m\u001b[1;30m31.447\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
[505.03]:<Trauma 19>: patient 19 treatment complete; waiting time was 31.447\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[1;35m[\u001b[0m\u001b[1;35m505.03\u001b[0m\u001b[1;35m]\u001b[0m\u001b[1;35m:\u001b[0m\u001b[1;35m<\u001b[0m\u001b[1;35mTrauma\u001b[0m\u001b[1;35m \u001b[0m\u001b[1;35m19\u001b[0m\u001b[1;35m>\u001b[0m\u001b[1;35m: \u001b[0m\u001b[30mpatient \u001b[0m\u001b[1;30m19\u001b[0m\u001b[30m treatment complete; waiting time was \u001b[0m\u001b[1;30m31.447\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "simulation complete.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
rep1
00_arrivals209.000000
01a_triage_wait16.622674
01b_triage_util0.527512
02a_registration_wait111.161345
02b_registration_util0.801061
03a_examination_wait24.927965
03b_examination_util0.851285
04a_treatment_wait(non_trauma)172.435861
04b_treatment_util(non_trauma)0.845652
05_total_time(non-trauma)248.848441
06a_trauma_wait201.144403
06b_trauma_util0.919830
07a_treatment_wait(trauma)22.620880
07b_treatment_util(trauma)0.493576
08_total_time(trauma)310.384648
09_throughput155.000000
\n", + "
" + ], + "text/plain": [ + "rep 1\n", + "00_arrivals 209.000000\n", + "01a_triage_wait 16.622674\n", + "01b_triage_util 0.527512\n", + "02a_registration_wait 111.161345\n", + "02b_registration_util 0.801061\n", + "03a_examination_wait 24.927965\n", + "03b_examination_util 0.851285\n", + "04a_treatment_wait(non_trauma) 172.435861\n", + "04b_treatment_util(non_trauma) 0.845652\n", + "05_total_time(non-trauma) 248.848441\n", + "06a_trauma_wait 201.144403\n", + "06b_trauma_util 0.919830\n", + "07a_treatment_wait(trauma) 22.620880\n", + "07b_treatment_util(trauma) 0.493576\n", + "08_total_time(trauma) 310.384648\n", + "09_throughput 155.000000" + ] + }, + "execution_count": 20, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create the default scenario. \n", + "args = Scenario(debug=True, tracked=[10, 19])\n", + "\n", + "# use the single_run() func\n", + "# try changing `random_no_set` to see different run results\n", + "print('Running simulation ...', end=' => ')\n", + "results = single_run(args, random_no_set=42)\n", + "print('simulation complete.')\n", + "\n", + "# show results (transpose replication for easier view)\n", + "results.T" + ] + }, + { + "cell_type": "markdown", + "id": "7636b290", + "metadata": {}, + "source": [ + "### 9.2 Multiple independent replications\n", + "\n", + "Given the set up it is now easy to perform multiple replications of the model.\n", + "\n", + "> Notes: here we also use the `rich` library to provide a **status spinner**. This is not a full progress bar implementation, but provides a small amount of user feedback that the model is still running.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "ac6bd328", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b2c2584c461c40748dd2ade1f2f31a67", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "All replications complete.\n"
+     ]
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
00_arrivals01a_triage_wait01b_triage_util02a_registration_wait02b_registration_util03a_examination_wait03b_examination_util04a_treatment_wait(non_trauma)04b_treatment_util(non_trauma)05_total_time(non-trauma)06a_trauma_wait06b_trauma_util07a_treatment_wait(trauma)07b_treatment_util(trauma)08_total_time(trauma)09_throughput
rep
1230.024.2809430.613250103.2422920.85450431.0896800.861719152.4833940.890904234.759918236.5084441.02888713.7017830.607996346.698079171.0
2227.057.1201140.62134890.0023850.83668514.6884920.847295120.2454740.912127233.882040133.8139010.8341243.7154460.367507301.521195161.0
3229.028.6593830.573698112.2425030.84851421.3740920.85630694.0198850.868888208.361290276.4225660.87424512.2521750.464740440.515502167.0
\n", + "
" + ], + "text/plain": [ + " 00_arrivals 01a_triage_wait 01b_triage_util 02a_registration_wait \\\n", + "rep \n", + "1 230.0 24.280943 0.613250 103.242292 \n", + "2 227.0 57.120114 0.621348 90.002385 \n", + "3 229.0 28.659383 0.573698 112.242503 \n", + "\n", + " 02b_registration_util 03a_examination_wait 03b_examination_util \\\n", + "rep \n", + "1 0.854504 31.089680 0.861719 \n", + "2 0.836685 14.688492 0.847295 \n", + "3 0.848514 21.374092 0.856306 \n", + "\n", + " 04a_treatment_wait(non_trauma) 04b_treatment_util(non_trauma) \\\n", + "rep \n", + "1 152.483394 0.890904 \n", + "2 120.245474 0.912127 \n", + "3 94.019885 0.868888 \n", + "\n", + " 05_total_time(non-trauma) 06a_trauma_wait 06b_trauma_util \\\n", + "rep \n", + "1 234.759918 236.508444 1.028887 \n", + "2 233.882040 133.813901 0.834124 \n", + "3 208.361290 276.422566 0.874245 \n", + "\n", + " 07a_treatment_wait(trauma) 07b_treatment_util(trauma) \\\n", + "rep \n", + "1 13.701783 0.607996 \n", + "2 3.715446 0.367507 \n", + "3 12.252175 0.464740 \n", + "\n", + " 08_total_time(trauma) 09_throughput \n", + "rep \n", + "1 346.698079 171.0 \n", + "2 301.521195 161.0 \n", + "3 440.515502 167.0 " + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "console = Console()\n", + "args = Scenario(debug=False)\n", + "\n", + "#run multiple replications.\n", + "with console.status(\"[magenta]Running multiple replications...\", spinner=\"dots\") as status:\n", + " results = multiple_replications(args, n_reps=50)\n", + "\n", + "print(\"All replications complete.\")\n", + "\n", + "results.head(3)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "9bce241b", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "00_arrivals 227.72\n", + "01a_triage_wait 35.24\n", + "01b_triage_util 0.61\n", + "02a_registration_wait 105.57\n", + "02b_registration_util 0.84\n", + "03a_examination_wait 25.55\n", + "03b_examination_util 0.85\n", + "04a_treatment_wait(non_trauma) 136.66\n", + "04b_treatment_util(non_trauma) 0.87\n", + "05_total_time(non-trauma) 234.34\n", + "06a_trauma_wait 151.68\n", + "06b_trauma_util 0.83\n", + "07a_treatment_wait(trauma) 14.31\n", + "07b_treatment_util(trauma) 0.50\n", + "08_total_time(trauma) 292.28\n", + "09_throughput 162.16\n", + "dtype: float64" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# summarise the results (2.dp)\n", "results.mean().round(2)" @@ -1831,10 +2293,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 23, "id": "cf20feb7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA/IAAAFfCAYAAADtf/chAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8g+/7EAAAACXBIWXMAAA9hAAAPYQGoP6dpAABAs0lEQVR4nO3deVjVZf7/8deR5YgGKCrLKRQ0cxcdLRXLJdMys9SZTC21rH5Z5saYymRftVLUJrcYJWw1p7GZcZnMKcUSzMxcwNyXEsVUYszkoCYofH5/OJ7pBCjHzkrPx3Wd6+Lc9/05nxfdN+Sbz2YyDMMQAAAAAADwCVU8HQAAAAAAAFQchTwAAAAAAD6EQh4AAAAAAB9CIQ8AAAAAgA+hkAcAAAAAwIdQyAMAAAAA4EMo5AEAAAAA8CH+ng7gjUpKSnTixAkFBwfLZDJ5Og4AAAAAoJIzDEMFBQWyWCyqUuXqx9wp5Mtw4sQJRUdHezoGAAAAAOA35tixY7rpppuuOoZCvgzBwcGSLv8HDAkJ8XAaAAAAAEBlZ7VaFR0dbatHr4ZCvgxXTqcPCQmhkAcAAAAAuE1FLu/mZncAAAAAAPgQCnkAAAAAAHwIhTwAAAAAAD6EQh4AAAAAAB/Cze6AX4iZuNrTESqdIzN6eToCAAAAUGlwRB4AAAAAAB9CIQ8AAAAAgA+hkAcAAAAAwIdQyAMAAAAA4EMo5AEAAAAA8CEU8gAAAAAA+BAKeQAAAAAAfAiFPAAAAAAAPoRCHgAAAAAAH0IhDwAAAACAD6GQBwAAAADAh1DIAwAAAADgQyjkAQAAAADwIRTyAAAAAAD4EAp5AAAAAAB8iNsK+c8//1yPPPKIOnTooOPHj0uS3nvvPW3cuNFdEQAAAAAA8HluKeSXLVumu+++W0FBQcrKylJhYaEkqaCgQNOnT3dHBAAAAAAAKgW3FPIvv/yyUlJStGjRIgUEBNja4+PjlZmZ6dBnbdiwQb1795bFYpHJZNLKlSvt+g3D0JQpU2SxWBQUFKQuXbpoz549zvg2AAAAAADwOLcU8gcOHFCnTp1KtYeEhOjMmTMOfda5c+cUFxen5OTkMvtnzZql2bNnKzk5WVu3blVkZKS6d++ugoKC64kOAAAAAIBX8XfHTqKiovTNN98oJibGrn3jxo2qX7++Q5/Vs2dP9ezZs8w+wzA0d+5cPf/88+rXr58k6d1331VERITef/99PfXUU9eVHwAAAAAAb+GWI/JPPfWURo8era+++komk0knTpzQX//6V40bN07PPPOM0/aTnZ2t3Nxc9ejRw9ZmNpvVuXNnbdq0qdztCgsLZbVa7V4AAAAAAHgjtxyRHz9+vPLz89W1a1dduHBBnTp1ktls1rhx4/Tss886bT+5ubmSpIiICLv2iIgIHT16tNztkpKSNHXqVKflAAAAAADAVdz2+Llp06bp1KlT2rJlizZv3qz//Oc/eumll1yyL5PJZPfeMIxSbT+XmJio/Px82+vYsWMuyQUAAAAAwK/lliPyV1SrVk1t27Z12edHRkZKunxkPioqytael5dX6ij9z5nNZpnNZpflAgAAAADAWdxSyPft27fMI+Imk0lVq1bVzTffrEGDBqlRo0a/aj+xsbGKjIxUWlqaWrduLUkqKipSRkaGZs6c+as+GwAAAAAAb+CWU+tDQ0P12WefKTMz01bQZ2Vl6bPPPtOlS5f0wQcfKC4uTl988cU1P+vs2bPasWOHduzYIenyDe527NihnJwcmUwmjRkzRtOnT9eKFSu0e/duPfroo6pWrZoGDRrkym8RAAAAAAC3cMsR+cjISA0aNEjJycmqUuXy3w5KSko0evRoBQcHa+nSpRo+fLgmTJigjRs3XvWztm3bpq5du9reJyQkSJKGDh2qd955R+PHj9dPP/2kZ555Rj/++KPatWuntWvXKjg42HXfIAAAAAAAbmIyDMNw9U7q1KmjL774Qrfccotd+8GDBxUfH69Tp05p165duuOOO3TmzBlXx7kmq9Wq0NBQ5efnKyQkxNNx4GYxE1d7OkKlc2RGL09HAAAAALyaI3WoW06tv3Tpkvbv31+qff/+/SouLpYkVa1a9ap3lgcAAAAAAG46tX7w4MF6/PHH9ac//Um33nqrTCaTtmzZounTp2vIkCGSpIyMDDVr1swdcQAAAAAA8FluKeTnzJmjiIgIzZo1S99//70kKSIiQmPHjtWECRMkST169NA999zjjjgAAAAAAPgst1wj/3NWq1WSvPrac66R/23jGnnn4xp5AAAA4OocqUPdckT+5yiMAQAAAAC4fm4r5P/5z3/q73//u3JyclRUVGTXl5mZ6a4YAAAAAAD4NLfctX7+/Pl67LHHFB4erqysLN12222qVauWDh8+rJ49e7oj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+vX14osvqqSkxDaGufMOFPKwc+7cOcXFxSk5ObnM/lmzZmn27NlKTk7W1q1bFRkZqe7du6ugoMDNSfFzGRkZGjFihDZv3qy0tDRdunRJPXr00Llz52xjmDvvc9NNN2nGjBnatm2btm3bpjvvvFMPPPCA7X+GzJn327p1q1JTU9WyZUu7dubOOzVr1kwnT560vXbt2mXrY868148//qiOHTsqICBAH3/8sfbu3atXX31VNWrUsI1h/rzP1q1b7X7e0tLSJEkPPvigJObMW82cOVMpKSlKTk7Wvn37NGvWLL3yyit67bXXbGOYOy9hAOWQZKxYscL2vqSkxIiMjDRmzJhha7tw4YIRGhpqpKSkeCAhypOXl2dIMjIyMgzDYO58Sc2aNY033niDOfMBBQUFRsOGDY20tDSjc+fOxujRow3D4OfNW02ePNmIi4srs485824TJkwwbr/99nL7mT/fMHr0aKNBgwZGSUkJc+bFevXqZQwbNsyurV+/fsYjjzxiGAY/b96EI/KosOzsbOXm5qpHjx62NrPZrM6dO2vTpk0eTIZfys/PlySFhYVJYu58QXFxsZYuXapz586pQ4cOzJkPGDFihHr16qW77rrLrp25816HDh2SxWJRbGysBgwYoMOHD0tizrzdhx9+qLZt2+rBBx9UeHi4WrdurUWLFtn6mT/vV1RUpCVLlmjYsGEymUzMmRe7/fbb9emnn+rgwYOSpK+//lobN27UvffeK4mfN2/i7+kA8B25ubmSpIiICLv2iIgIHT161BORUAbDMJSQkKDbb79dzZs3l8TcebNdu3apQ4cOunDhgm644QatWLFCTZs2tf3PkDnzTkuXLlVmZqa2bt1aqo+fN+/Url07LV68WLfccou+//57vfzyy4qPj9eePXuYMy93+PBhLVy4UAkJCfrTn/6kLVu2aNSoUTKbzRoyZAjz5wNWrlypM2fO6NFHH5XE70lvNmHCBOXn56tx48by8/NTcXGxpk2bpoEDB0pi7rwJhTwcZjKZ7N4bhlGqDZ7z7LPPaufOndq4cWOpPubO+zRq1Eg7duzQmTNntGzZMg0dOlQZGRm2fubM+xw7dkyjR4/W2rVrVbVq1XLHMXfepWfPnravW7RooQ4dOqhBgwZ699131b59e0nMmbcqKSlR27ZtNX36dElS69attWfPHi1cuFBDhgyxjWP+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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "fig, ax = plt.subplots(2, 1, figsize=(12,4))\n", "ax[0].hist(results['01a_triage_wait']);\n", @@ -1857,12 +2330,12 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 24, "id": "98c70169", "metadata": {}, "outputs": [], "source": [ - "def get_scenarios(trace_level=2):\n", + "def get_scenarios():\n", " '''\n", " Creates a dictionary object containing\n", " objects of type `Scenario` to run.\n", @@ -1873,30 +2346,26 @@ " Contains the scenarios for the model\n", " '''\n", " scenarios = {}\n", - " scenarios['base'] = Scenario(trace_level=trace_level)\n", + " scenarios['base'] = Scenario()\n", " \n", " # extra triage capacity\n", - " scenarios['triage+1'] = Scenario(n_triage=DEFAULT_N_TRIAGE+1,\n", - " trace_level=trace_level)\n", + " scenarios['triage+1'] = Scenario(n_triage=DEFAULT_N_TRIAGE+1)\n", " \n", " # extra examination capacity\n", - " scenarios['exam+1'] = Scenario(n_exam=DEFAULT_N_EXAM+1,\n", - " trace_level=trace_level)\n", + " scenarios['exam+1'] = Scenario(n_exam=DEFAULT_N_EXAM+1)\n", " \n", " # extra non-trauma treatment capacity\n", - " scenarios['treat+1'] = Scenario(n_cubicles_1=DEFAULT_N_CUBICLES_1+1,\n", - " trace_level=trace_level)\n", + " scenarios['treat+1'] = Scenario(n_cubicles_1=DEFAULT_N_CUBICLES_1+1)\n", " \n", " scenarios['triage+exam'] = Scenario(n_triage=DEFAULT_N_TRIAGE+1,\n", - " n_exam=DEFAULT_N_EXAM+1,\n", - " trace_level=trace_level)\n", + " n_exam=DEFAULT_N_EXAM+1)\n", " \n", " return scenarios" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 25, "id": "09906659", "metadata": {}, "outputs": [], @@ -1945,10 +2414,74 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 26, "id": "69008d35", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
# Scenario analysis\n",
+       "
\n" + ], + "text/plain": [ + "# Scenario analysis\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "5a69f528ca0e40a990e88524765c310f", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
\n"
+      ],
+      "text/plain": []
+     },
+     "metadata": {},
+     "output_type": "display_data"
+    },
+    {
+     "data": {
+      "text/html": [
+       "
\n",
+       "
\n" + ], + "text/plain": [ + "\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
─────────────────────────────────────────── Scenario analysis complete. ───────────────────────────────────────────\n",
+       "
\n" + ], + "text/plain": [ + "\u001b[92m─────────────────────────────────────────── \u001b[0mScenario analysis complete.\u001b[92m ───────────────────────────────────────────\u001b[0m\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "#number of replications\n", "N_REPS = 20\n", @@ -1966,7 +2499,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 27, "id": "5769e14f", "metadata": {}, "outputs": [], @@ -1997,10 +2530,196 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 28, "id": "158f2fae", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
basetriage+1exam+1treat+1triage+exam
00_arrivals227.25227.25227.25227.25227.25
01a_triage_wait32.561.2632.5632.561.26
01b_triage_util0.610.310.610.610.31
02a_registration_wait104.69131.82104.69104.69131.82
02b_registration_util0.850.850.850.850.85
03a_examination_wait23.3624.440.1423.360.14
03b_examination_util0.860.860.670.860.67
04a_treatment_wait(non_trauma)130.73133.09144.502.15147.81
04b_treatment_util(non_trauma)0.880.880.880.620.88
05_total_time(non-trauma)229.04226.38218.29187.98215.06
06a_trauma_wait166.98189.67166.98166.98189.67
06b_trauma_util0.840.860.840.840.86
07a_treatment_wait(trauma)14.3914.7714.3914.3914.77
07b_treatment_util(trauma)0.520.520.520.520.52
08_total_time(trauma)306.46298.22306.46306.46298.22
09_throughput165.85166.65169.10196.85169.85
\n", + "
" + ], + "text/plain": [ + " base triage+1 exam+1 treat+1 triage+exam\n", + "00_arrivals 227.25 227.25 227.25 227.25 227.25\n", + "01a_triage_wait 32.56 1.26 32.56 32.56 1.26\n", + "01b_triage_util 0.61 0.31 0.61 0.61 0.31\n", + "02a_registration_wait 104.69 131.82 104.69 104.69 131.82\n", + "02b_registration_util 0.85 0.85 0.85 0.85 0.85\n", + "03a_examination_wait 23.36 24.44 0.14 23.36 0.14\n", + "03b_examination_util 0.86 0.86 0.67 0.86 0.67\n", + "04a_treatment_wait(non_trauma) 130.73 133.09 144.50 2.15 147.81\n", + "04b_treatment_util(non_trauma) 0.88 0.88 0.88 0.62 0.88\n", + "05_total_time(non-trauma) 229.04 226.38 218.29 187.98 215.06\n", + "06a_trauma_wait 166.98 189.67 166.98 166.98 189.67\n", + "06b_trauma_util 0.84 0.86 0.84 0.84 0.86\n", + "07a_treatment_wait(trauma) 14.39 14.77 14.39 14.39 14.77\n", + "07b_treatment_util(trauma) 0.52 0.52 0.52 0.52 0.52\n", + "08_total_time(trauma) 306.46 298.22 306.46 306.46 298.22\n", + "09_throughput 165.85 166.65 169.10 196.85 169.85" + ] + }, + "execution_count": 28, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# as well as rounding you may want to rename the cols/rows to \n", "# more readable alternatives.\n", @@ -2018,10 +2737,113 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 29, "id": "c4f5d832", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Mean waiting time (mins)basetriage+1exam+1treat+1triage+exam
0Triage32.561.2632.5632.561.26
1Registation104.69131.82104.69104.69131.82
2Examination23.3624.440.1423.360.14
3Non-trauma treatment130.73133.09144.502.15147.81
4Trauma stabilisation166.98189.67166.98166.98189.67
5Trauma treatment14.3914.7714.3914.3914.77
\n", + "
" + ], + "text/plain": [ + " Mean waiting time (mins) base triage+1 exam+1 treat+1 triage+exam\n", + "0 Triage 32.56 1.26 32.56 32.56 1.26\n", + "1 Registation 104.69 131.82 104.69 104.69 131.82\n", + "2 Examination 23.36 24.44 0.14 23.36 0.14\n", + "3 Non-trauma treatment 130.73 133.09 144.50 2.15 147.81\n", + "4 Trauma stabilisation 166.98 189.67 166.98 166.98 189.67\n", + "5 Trauma treatment 14.39 14.77 14.39 14.39 14.77" + ] + }, + "execution_count": 29, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "HEADER_URL = 'data/tbl_row_headers.csv'\n", "WAIT = 'wait'\n", @@ -2043,10 +2865,34 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 30, "id": "262c1013", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\\begin{table}\n", + "\\tbl{Simulation results that can be verified by our example reproducible pipeline.}\n", + "\\label{tab:table3}\n", + "\\begin{tabular}{lrrrrr}\n", + "\\toprule\n", + "Mean waiting time (mins) & base & triage+1 & exam+1 & treat+1 & triage+exam \\\\\n", + "\\midrule\n", + "Triage & 32.560000 & 1.260000 & 32.560000 & 32.560000 & 1.260000 \\\\\n", + "Registation & 104.690000 & 131.820000 & 104.690000 & 104.690000 & 131.820000 \\\\\n", + "Examination & 23.360000 & 24.440000 & 0.140000 & 23.360000 & 0.140000 \\\\\n", + "Non-trauma treatment & 130.730000 & 133.090000 & 144.500000 & 2.150000 & 147.810000 \\\\\n", + "Trauma stabilisation & 166.980000 & 189.670000 & 166.980000 & 166.980000 & 189.670000 \\\\\n", + "Trauma treatment & 14.390000 & 14.770000 & 14.390000 & 14.390000 & 14.770000 \\\\\n", + "\\bottomrule\n", + "\\end{tabular}\n", + "\\end{table}\n", + "\n" + ] + } + ], "source": [ "# output to file as LaTeX\n", "OUTPUT_FILE = 'output/table_3.txt'\n", diff --git a/sim_tools/trace.py b/sim_tools/trace.py index 2deb671..e5cfb28 100644 --- a/sim_tools/trace.py +++ b/sim_tools/trace.py @@ -4,7 +4,9 @@ """ from abc import ABC -from rich import Console +from rich.console import Console + +DEFAULT_DEBUG = False CONFIG_ERROR = ("Your trace has not been initialised. " "Call super__init__(debug=True) in class initialiser" @@ -31,7 +33,7 @@ class Traceable(ABC): _trace_config(): use this function to return a dict containing the trace configuration for the class. ''' - def __init__(self, debug=False): + def __init__(self, debug=DEFAULT_DEBUG): self.debug = debug self._config = self._default_config() From 5d72142c1efb9c8d8fdca6b28d324ff7089baed9 Mon Sep 17 00:00:00 2001 From: TomMonks Date: Thu, 20 Jun 2024 17:16:10 +0100 Subject: [PATCH 09/11] docs(trace): updated TOC to include trace nb --- docs/03_trace/{04_model.ipynb => 01_model.ipynb} | 0 docs/_toc.yml | 3 +++ 2 files changed, 3 insertions(+) rename docs/03_trace/{04_model.ipynb => 01_model.ipynb} (100%) diff --git a/docs/03_trace/04_model.ipynb b/docs/03_trace/01_model.ipynb similarity index 100% rename from docs/03_trace/04_model.ipynb rename to docs/03_trace/01_model.ipynb diff --git a/docs/_toc.yml b/docs/_toc.yml index 04448d6..a982332 100644 --- a/docs/_toc.yml +++ b/docs/_toc.yml @@ -5,6 +5,9 @@ parts: chapters: - file: 01_sampling/01_distributions_examples - file: 01_sampling/02_time_dependent_examples +- caption: Debugging + chapters: + - file: 03_trace/01_model - caption: Optimisation chapters: - file: 02_ovs/03_sw21_tutorial From 9d906e079351b3076f7b90cad5b16fbeaa0f19b6 Mon Sep 17 00:00:00 2001 From: TomMonks Date: Fri, 21 Jun 2024 10:49:10 +0100 Subject: [PATCH 10/11] chore(change): changelog 0.5.0 --- CHANGES.md | 10 ++++++++++ feature_list.md | 10 +++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 4b30070..872fb3a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Change log +## v0.5.0 + +### Added + +* EXPERIMENTAL: added `trace` module with `Traceable` class for colour coding output from different processes and tracking individual patients. + +### Fixed + +* DIST: fix to `NSPPThinning` sampling to pre-calcualte mean IAT to ensure that correct exponential mean is used. + ## v0.4.0 * BUILD: Dropped legacy `setuptools` and migrated package build to `hatch` diff --git a/feature_list.md b/feature_list.md index f7c9ca6..9a6c322 100644 --- a/feature_list.md +++ b/feature_list.md @@ -25,6 +25,10 @@ module: warm-up * MSER- 5 +module: trace +* enhanced trace functions and classes. + + module: results visualisation ** Standard ways to compare scenarios? @@ -51,4 +55,8 @@ module: distributions module: distributions * Empirical -* NSPP via thinning \ No newline at end of file +* NSPP via thinning + +## v0.5.0 + +Enhanced trace functionality \ No newline at end of file From 5a8c188275f689cfee4a20f0bf6811d9c94cd9dc Mon Sep 17 00:00:00 2001 From: TomMonks Date: Fri, 21 Jun 2024 10:50:55 +0100 Subject: [PATCH 11/11] chore(change): added normal dist fix --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 872fb3a..b863dc7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,7 @@ ### Fixed * DIST: fix to `NSPPThinning` sampling to pre-calcualte mean IAT to ensure that correct exponential mean is used. +* DIST: normal distribution allows minimum value and truncates automaticalled instead of resampling. ## v0.4.0