diff --git a/CHANGELOG.md b/CHANGELOG.md
index 374cdf0..9bc927a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,10 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Added
+* `07a_exercise.ipynb`and `07a_solutions.ipynb`: simple exercises to complement learning in `07_experiments.ipynb`
* `10_multiple_arrival_processes.ipynb`: simulating multiple arrivals classes each with their own distribution
* `11_blocking.ipynb`: simulating the blocking of one resource while queuing for another.
* `12_arrival_classes.ipynb`: simulate unique processes for different classes of arrival to the model.
* `distributions.py`: module containing some distributions to reduce code in notebooks.
+* `basic_model.py`: contains a single activity version of the call centre model to use with `07_exercise.ipynb`
## [v0.2.0 - 11/02/2024](https://github.com/pythonhealthdatascience/intro-open-sim/releases/tag/v0.2.0) [](https://doi.org/10.5281/zenodo.14849934)
diff --git a/README.md b/README.md
index 6c20db6..0dc04d2 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,8 @@ We then move on to some more advanced concepts, and create a full process model:
* `05_basic_results.ipynb`: Collecting results from a single run by storing process metrics during a run and performing calculations at the end
* `06a_basic_results_exercise.ipynb`: An exercise to practice collecting results from a `simpy` model.
* `07_experiments.ipynb`: our approach to setting up a model for multiple replications, experimentation, and common random numbers
+* `07a_exercise.ipynb`: An exercise to practice using an `Experiment` class with a model
+* `07b_solutions.ipynb` Solutions to the `Experiment` exercises.
* `08_full_model.ipynb`: an extended version of the 111 call centre process. We also introduce a warm-up period
* `09_time_weighted_calcs.ipynb`: An alternative approach to collects results for queue length and resource utilisation.
diff --git a/content/07_experiments.ipynb b/content/07_experiments.ipynb
index 9a6b5b0..03c71f3 100644
--- a/content/07_experiments.ipynb
+++ b/content/07_experiments.ipynb
@@ -32,7 +32,7 @@
},
{
"cell_type": "code",
- "execution_count": 1,
+ "execution_count": null,
"id": "09f761a3-c100-4dc8-a3c9-30a5cd101c0d",
"metadata": {},
"outputs": [],
@@ -55,7 +55,7 @@
},
{
"cell_type": "code",
- "execution_count": 2,
+ "execution_count": null,
"id": "a3e7b498-8019-4d38-9192-c3d483361885",
"metadata": {},
"outputs": [],
@@ -94,7 +94,7 @@
},
{
"cell_type": "code",
- "execution_count": 3,
+ "execution_count": null,
"id": "14e8f47f-e90d-40cd-b5fd-63e8975eba83",
"metadata": {},
"outputs": [],
@@ -148,7 +148,7 @@
},
{
"cell_type": "code",
- "execution_count": 4,
+ "execution_count": null,
"id": "56dd64c1-adfa-417b-ad23-43a07672c7cd",
"metadata": {},
"outputs": [],
@@ -204,7 +204,7 @@
},
{
"cell_type": "code",
- "execution_count": 5,
+ "execution_count": null,
"id": "b5110385-e293-47c8-8038-e0ddfa3d6b10",
"metadata": {},
"outputs": [],
@@ -323,7 +323,7 @@
},
{
"cell_type": "code",
- "execution_count": 6,
+ "execution_count": null,
"id": "cda27204-a567-4a83-9d21-aa63646c3a08",
"metadata": {},
"outputs": [],
@@ -342,42 +342,20 @@
},
{
"cell_type": "code",
- "execution_count": 7,
+ "execution_count": null,
"id": "a5a90a1f-b6e2-42c2-8799-d56c37c5569d",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "1.9761166744858965"
- ]
- },
- "execution_count": 7,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"default_experiment.arrival_dist.sample()"
]
},
{
"cell_type": "code",
- "execution_count": 8,
+ "execution_count": null,
"id": "0b88a91c-b502-41bb-a0f6-608db60afea6",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0.6"
- ]
- },
- "execution_count": 8,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"default_experiment.mean_iat"
]
@@ -394,7 +372,7 @@
},
{
"cell_type": "code",
- "execution_count": 9,
+ "execution_count": null,
"id": "ba139ce8-1d5b-437e-a606-d40e9db88d7d",
"metadata": {},
"outputs": [],
@@ -405,21 +383,10 @@
},
{
"cell_type": "code",
- "execution_count": 10,
+ "execution_count": null,
"id": "b74b4b3d-32e5-4176-8549-6b0215aace9e",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "14"
- ]
- },
- "execution_count": 10,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"extra_server.n_operators"
]
@@ -438,7 +405,7 @@
},
{
"cell_type": "code",
- "execution_count": 11,
+ "execution_count": null,
"id": "96c43528-2390-4006-aa31-ec5aa7d19ba9",
"metadata": {},
"outputs": [],
@@ -458,7 +425,7 @@
},
{
"cell_type": "code",
- "execution_count": 12,
+ "execution_count": null,
"id": "564db16d-b2b1-4e2f-b1f3-6ef4b42a59f7",
"metadata": {},
"outputs": [],
@@ -523,7 +490,7 @@
},
{
"cell_type": "code",
- "execution_count": 13,
+ "execution_count": null,
"id": "838b63ac-8add-442a-b25b-ed9c7e5fdfd9",
"metadata": {},
"outputs": [],
@@ -570,7 +537,7 @@
},
{
"cell_type": "code",
- "execution_count": 14,
+ "execution_count": null,
"id": "1bde92ab-c010-4fa9-90cf-ebb715537ae9",
"metadata": {},
"outputs": [],
@@ -624,19 +591,10 @@
},
{
"cell_type": "code",
- "execution_count": 15,
+ "execution_count": null,
"id": "50fcc6ef-9e7d-4caf-8f62-fbe4e4e1d061",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Mean waiting time: 3.87 mins \n",
- "Operator Utilisation 92.90%\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"TRACE = False\n",
"default_scenario = Experiment()\n",
@@ -657,7 +615,7 @@
},
{
"cell_type": "code",
- "execution_count": 16,
+ "execution_count": null,
"id": "9729b480-4499-44be-91e7-77ce2f64dce9",
"metadata": {},
"outputs": [],
@@ -697,85 +655,10 @@
},
{
"cell_type": "code",
- "execution_count": 17,
+ "execution_count": null,
"id": "8263448d-a1d1-488e-8985-55ba07037786",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "
\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " 01_mean_waiting_time | \n",
- " 02_operator_util | \n",
- "
\n",
- " \n",
- " | rep | \n",
- " | \n",
- " | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 1 | \n",
- " 3.872630 | \n",
- " 92.904245 | \n",
- "
\n",
- " \n",
- " | 2 | \n",
- " 3.196233 | \n",
- " 93.642054 | \n",
- "
\n",
- " \n",
- " | 3 | \n",
- " 3.525990 | \n",
- " 94.224088 | \n",
- "
\n",
- " \n",
- " | 4 | \n",
- " 1.536159 | \n",
- " 90.900402 | \n",
- "
\n",
- " \n",
- " | 5 | \n",
- " 3.065927 | \n",
- " 93.531723 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " 01_mean_waiting_time 02_operator_util\n",
- "rep \n",
- "1 3.872630 92.904245\n",
- "2 3.196233 93.642054\n",
- "3 3.525990 94.224088\n",
- "4 1.536159 90.900402\n",
- "5 3.065927 93.531723"
- ]
- },
- "execution_count": 17,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"TRACE = False\n",
"default_scenario = Experiment()\n",
@@ -799,7 +682,7 @@
},
{
"cell_type": "code",
- "execution_count": 18,
+ "execution_count": null,
"id": "a06787e1-d024-40c9-9f38-7e4f01a51166",
"metadata": {},
"outputs": [],
@@ -829,7 +712,7 @@
},
{
"cell_type": "code",
- "execution_count": 19,
+ "execution_count": null,
"id": "2957de2b-c0cf-4eab-a639-0058c8c56326",
"metadata": {},
"outputs": [],
@@ -869,25 +752,10 @@
},
{
"cell_type": "code",
- "execution_count": 20,
+ "execution_count": null,
"id": "1fa0335d-18c6-463b-92f9-f8f83ef535c3",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Model experiments:\n",
- "No. experiments to execute = 2\n",
- "\n",
- "Running base => done.\n",
- "\n",
- "Running operators+1 => done.\n",
- "\n",
- "All experiments are complete.\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# get the experiments\n",
"experiments = get_experiments()\n",
@@ -898,92 +766,17 @@
},
{
"cell_type": "code",
- "execution_count": 21,
+ "execution_count": null,
"id": "63aad945-4817-459f-8d80-51adf8b0c18b",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " 01_mean_waiting_time | \n",
- " 02_operator_util | \n",
- "
\n",
- " \n",
- " | rep | \n",
- " | \n",
- " | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 1 | \n",
- " 1.175090 | \n",
- " 86.268227 | \n",
- "
\n",
- " \n",
- " | 2 | \n",
- " 1.079633 | \n",
- " 87.104060 | \n",
- "
\n",
- " \n",
- " | 3 | \n",
- " 1.195603 | \n",
- " 87.742674 | \n",
- "
\n",
- " \n",
- " | 4 | \n",
- " 0.607000 | \n",
- " 84.407517 | \n",
- "
\n",
- " \n",
- " | 5 | \n",
- " 1.159877 | \n",
- " 87.013792 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " 01_mean_waiting_time 02_operator_util\n",
- "rep \n",
- "1 1.175090 86.268227\n",
- "2 1.079633 87.104060\n",
- "3 1.195603 87.742674\n",
- "4 0.607000 84.407517\n",
- "5 1.159877 87.013792"
- ]
- },
- "execution_count": 21,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"experiment_results[\"operators+1\"]"
]
},
{
"cell_type": "code",
- "execution_count": 22,
+ "execution_count": null,
"id": "b9ec8071-6359-4664-8a3a-3aa145256542",
"metadata": {},
"outputs": [],
@@ -1014,61 +807,10 @@
},
{
"cell_type": "code",
- "execution_count": 23,
+ "execution_count": null,
"id": "cd9cf15a-a8cf-4e96-8328-7be4e37a00b2",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " base | \n",
- " operators+1 | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 01_mean_waiting_time | \n",
- " 3.04 | \n",
- " 1.04 | \n",
- "
\n",
- " \n",
- " | 02_operator_util | \n",
- " 93.04 | \n",
- " 86.51 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " base operators+1\n",
- "01_mean_waiting_time 3.04 1.04\n",
- "02_operator_util 93.04 86.51"
- ]
- },
- "execution_count": 23,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# as well as rounding you may want to rename the cols/rows to\n",
"# more readable alternatives.\n",
@@ -1103,7 +845,7 @@
},
{
"cell_type": "code",
- "execution_count": 24,
+ "execution_count": null,
"id": "7cb70cd7-339f-4701-8b70-c67e93ff4292",
"metadata": {},
"outputs": [],
@@ -1137,85 +879,10 @@
},
{
"cell_type": "code",
- "execution_count": 25,
+ "execution_count": null,
"id": "d687df09-0d17-4371-8cca-bb0e0e120241",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " experiment | \n",
- " n_operators | \n",
- " mean_iat | \n",
- "
\n",
- " \n",
- " | id | \n",
- " | \n",
- " | \n",
- " | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 0 | \n",
- " base | \n",
- " 13 | \n",
- " 0.60 | \n",
- "
\n",
- " \n",
- " | 1 | \n",
- " op+1 | \n",
- " 14 | \n",
- " 0.60 | \n",
- "
\n",
- " \n",
- " | 2 | \n",
- " high_demand | \n",
- " 13 | \n",
- " 0.55 | \n",
- "
\n",
- " \n",
- " | 3 | \n",
- " combination | \n",
- " 14 | \n",
- " 0.55 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " experiment n_operators mean_iat\n",
- "id \n",
- "0 base 13 0.60\n",
- "1 op+1 14 0.60\n",
- "2 high_demand 13 0.55\n",
- "3 combination 14 0.55"
- ]
- },
- "execution_count": 25,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"create_example_csv()\n",
"\n",
@@ -1241,7 +908,7 @@
},
{
"cell_type": "code",
- "execution_count": 26,
+ "execution_count": null,
"id": "bc15c677-458d-4cd0-8e83-94053662d834",
"metadata": {},
"outputs": [],
@@ -1276,19 +943,10 @@
},
{
"cell_type": "code",
- "execution_count": 27,
+ "execution_count": null,
"id": "d8b095d2-c0f3-4875-902c-10e4ddc4f723",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "\n",
- "14.0\n"
- ]
- }
- ],
+ "outputs": [],
"source": [
"# test of the function\n",
"\n",
@@ -1314,85 +972,10 @@
},
{
"cell_type": "code",
- "execution_count": 28,
+ "execution_count": null,
"id": "859a8bb3-1ba8-4516-8e35-3e6b84342582",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Model experiments:\n",
- "No. experiments to execute = 4\n",
- "\n",
- "Running base => done.\n",
- "\n",
- "Running op+1 => done.\n",
- "\n",
- "Running high_demand => done.\n",
- "\n",
- "Running combination => done.\n",
- "\n",
- "All experiments are complete.\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " 01_mean_waiting_time | \n",
- " 02_operator_util | \n",
- "
\n",
- " \n",
- " | rep | \n",
- " | \n",
- " | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 1 | \n",
- " 3.872630 | \n",
- " 92.904245 | \n",
- "
\n",
- " \n",
- " | 2 | \n",
- " 3.196233 | \n",
- " 93.642054 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " 01_mean_waiting_time 02_operator_util\n",
- "rep \n",
- "1 3.872630 92.904245\n",
- "2 3.196233 93.642054"
- ]
- },
- "execution_count": 28,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"results = run_all_experiments(experiments_to_run)\n",
"\n",
@@ -1402,134 +985,20 @@
},
{
"cell_type": "code",
- "execution_count": 29,
+ "execution_count": null,
"id": "a070a8bd-8ec8-45b8-a712-c2e87705542f",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " 01_mean_waiting_time | \n",
- " 02_operator_util | \n",
- "
\n",
- " \n",
- " | rep | \n",
- " | \n",
- " | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 1 | \n",
- " 27.101389 | \n",
- " 98.357059 | \n",
- "
\n",
- " \n",
- " | 2 | \n",
- " 20.139140 | \n",
- " 98.748543 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " 01_mean_waiting_time 02_operator_util\n",
- "rep \n",
- "1 27.101389 98.357059\n",
- "2 20.139140 98.748543"
- ]
- },
- "execution_count": 29,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"results[\"high_demand\"].head(2)"
]
},
{
"cell_type": "code",
- "execution_count": 30,
+ "execution_count": null,
"id": "1ecc0d91-d50f-4271-b2bf-0895044633e4",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " base | \n",
- " op+1 | \n",
- " high_demand | \n",
- " combination | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | 01_mean_waiting_time | \n",
- " 3.04 | \n",
- " 1.04 | \n",
- " 17.98 | \n",
- " 3.57 | \n",
- "
\n",
- " \n",
- " | 02_operator_util | \n",
- " 93.04 | \n",
- " 86.51 | \n",
- " 98.67 | \n",
- " 94.36 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " base op+1 high_demand combination\n",
- "01_mean_waiting_time 3.04 1.04 17.98 3.57\n",
- "02_operator_util 93.04 86.51 98.67 94.36"
- ]
- },
- "execution_count": 30,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"# show results\n",
"# further adaptions might include adding units for figures.\n",
@@ -1538,73 +1007,10 @@
},
{
"cell_type": "code",
- "execution_count": 31,
+ "execution_count": null,
"id": "4ae51707-a06c-4661-b9cf-36ee4424b8e2",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/html": [
- "\n",
- "\n",
- "
\n",
- " \n",
- " \n",
- " | \n",
- " 01_mean_waiting_time | \n",
- " 02_operator_util | \n",
- "
\n",
- " \n",
- " \n",
- " \n",
- " | base | \n",
- " 3.04 | \n",
- " 93.04 | \n",
- "
\n",
- " \n",
- " | op+1 | \n",
- " 1.04 | \n",
- " 86.51 | \n",
- "
\n",
- " \n",
- " | high_demand | \n",
- " 17.98 | \n",
- " 98.67 | \n",
- "
\n",
- " \n",
- " | combination | \n",
- " 3.57 | \n",
- " 94.36 | \n",
- "
\n",
- " \n",
- "
\n",
- "
"
- ],
- "text/plain": [
- " 01_mean_waiting_time 02_operator_util\n",
- "base 3.04 93.04\n",
- "op+1 1.04 86.51\n",
- "high_demand 17.98 98.67\n",
- "combination 3.57 94.36"
- ]
- },
- "execution_count": 31,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
+ "outputs": [],
"source": [
"experiment_summary_frame(results).round(2).T"
]
diff --git a/content/07a_exercise.ipynb b/content/07a_exercise.ipynb
new file mode 100644
index 0000000..77e2663
--- /dev/null
+++ b/content/07a_exercise.ipynb
@@ -0,0 +1,378 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "e27ef2a4-58f2-426e-9db5-7b31bdc6c9a8",
+ "metadata": {},
+ "source": [
+ "# Call Centre Optimization Exercise ๐\n",
+ "\n",
+ "๐ง For the solutions, please see the [experimentation exercise notebook](./07b_solutions.ipynb)\n",
+ "\n",
+ "**Objective**: Apply your knowledge of the `Experiment` class to analyze call centre performance under different scenarios.\n",
+ "\n",
+ "## Learning Goals\n",
+ "- Create `Experiment` objects with different parameter configurations\n",
+ "- Run single experiments and interpret results \n",
+ "- Conduct multiple replications for statistical reliability\n",
+ "- Compare scenarios and make data-driven recommendations\n",
+ "\n",
+ "## Exercise Overview\n",
+ "You will analyze a call centre's performance by testing different staffing levels and demand scenarios. The exercise is divided into 4 tasks."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ca24e08-df89-479f-8ed5-42a25a1a362b",
+ "metadata": {},
+ "source": [
+ "## 1. Imports "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0f9c1cc0-bbbb-4247-a695-fa93f4bbb116",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import simpy\n",
+ "import itertools\n",
+ "\n",
+ "# Set display options for 2dp in pandas\n",
+ "pd.set_option('display.precision', 2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "331ab9b1-a15a-4656-b88f-f03a6e3a866c",
+ "metadata": {},
+ "source": [
+ "## 2. Simulation model imports\n",
+ "\n",
+ "For convenience the model is stored in a python module called `basic_model`. We need to import:\n",
+ "\n",
+ "* The `Experiment` class - to set parameters,\n",
+ "* Functions that wrap the model and allow us to run it. I.e. `single_run` and `multiple_replications`.\n",
+ "* A function to toggle simulation debug info on and off `set_trace`\n",
+ "* A function that will summarise the results of multiple experiments `create_summary_table`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "99acde15-aabc-4117-bdb5-9fd98a717191",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from basic_model import (\n",
+ " Experiment, \n",
+ " single_run, \n",
+ " multiple_replications, \n",
+ " create_summary_table,\n",
+ " set_trace,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "85cb4b91-72c6-4155-a6b5-522ed6c70e40",
+ "metadata": {},
+ "source": [
+ "## 4. Exercises"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36a424f3-419d-445c-9bf5-af50356713b5",
+ "metadata": {},
+ "source": [
+ "### 4.1 Baseline Analysis\n",
+ "\n",
+ "First, let's understand the current system performance.\n",
+ "\n",
+ "**Instructions**:\n",
+ "1. Create a default experiment using the `Experiment()` class\n",
+ "2. Run it **once** to see the simulation events. Toggle trace on and off.\n",
+ "3. Record the mean waiting time and operator utilization.\n",
+ "4. Now run 10 multiple replications of the model and view results.\n",
+ "6. Answer the reflection question below\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "fdb072c7-ea95-4f56-889b-adb10e7a8364",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# TODO: Create a default experiment\n",
+ "# baseline_experiment = ...\n",
+ "\n",
+ "# TODO: toggle trace to False/True\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Run the experiment once and store results\n",
+ "# baseline_results = ...\n",
+ "\n",
+ "# Display results\n",
+ "print(\"=== BASELINE SCENARIO RESULTS ===\")\n",
+ "# print(f\"Mean waiting time: {baseline_results['01_mean_waiting_time']:.2f} minutes\")\n",
+ "# print(f\"Operator utilization: {baseline_results['02_operator_util']:.2f}%\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8180f39d-0e3a-4ec9-b2b9-3ca71f7550f9",
+ "metadata": {},
+ "source": [
+ "We will now run multiple replications of the model. The function returns a `pandas` dataframe containing a a replication per row and a column per KPI."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6b09fbbd-8fd7-433c-8506-1e496d722b51",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Run multiple replications (trace off!)\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Run the experiment once and store results\n",
+ "# baseline_reps = multiple_...\n",
+ "\n",
+ "# show results\n",
+ "# baseline_reps"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "057a944e-a65a-466e-bc77-3c600167e308",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# summary statistics\n",
+ "# baseline_reps.describe()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "75bb3f56-13b4-497e-a873-4414eb60e1ba",
+ "metadata": {},
+ "source": [
+ "๐ก๐ค **Reflection Question**: Based on the baseline results, what do you observe about the system performance? Is the utilization high or low? What might this suggest about the current staffing level?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a439d818-e14e-40f5-9b77-2447111f11a4",
+ "metadata": {},
+ "source": [
+ "**Your Answer**: [Write your analysis here]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "03ca3af9-621b-4ef4-b4c5-17a4b9bf57a0",
+ "metadata": {},
+ "source": [
+ "### 4.2 Staffing Scenarios Analysis\n",
+ "\n",
+ "Now test three different staffing scenarios using multiple replications for statistical reliability.\n",
+ "\n",
+ "**Scenarios to test**:\n",
+ "1. **Reduced Staffing**: 11 operators\n",
+ "2. **Current Staffing**: 13 operators (baseline) \n",
+ "3. **Increased Staffing**: 15 operators\n",
+ "\n",
+ "**Requirements**:\n",
+ "- Run each scenario with **10 replications**\n",
+ "- Calculate mean and standard deviation for both KPIs\n",
+ "- Create a summary comparison table\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "924d6ab1-c240-4832-8737-8adf4390fc33",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Task 2: Staffing Scenarios\n",
+ "# Turn off tracing for cleaner output\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Create three experiments with different staffing levels\n",
+ "# reduced_staffing = Experiment(n_operators=11)\n",
+ "# current_staffing = ...\n",
+ "# increased_staffing = ...\n",
+ "\n",
+ "# TODO: Run 10 replications for each scenario\n",
+ "print(\"Running reduced staffing scenario (11 operators)...\")\n",
+ "# reduced_results = ...\n",
+ "\n",
+ "print(\"Running current staffing scenario (13 operators)...\")\n",
+ "# current_results = ...\n",
+ "\n",
+ "print(\"Running increased staffing scenario (15 operators)...\")\n",
+ "# increased_results = ...\n",
+ "\n",
+ "print(\"All scenarios completed!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "01f4d22d-bddf-4a2b-a231-aeb979acdd70",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# TODO: Calculate summary statistics for each scenario (uncomment and run)\n",
+ "\n",
+ "# Create results dictionary\n",
+ "# scenario_results = {\n",
+ "# 'Reduced (11 ops)': reduced_results,\n",
+ "# 'Current (13 ops)': current_results,\n",
+ "# 'Increased (15 ops)': increased_results\n",
+ "# }\n",
+ "\n",
+ "# # Create summary table\n",
+ "# scenarios_summary = create_summary_table(\n",
+ "# results_dict=scenario_results,\n",
+ "# label_key='Scenario',\n",
+ "# label_order=['Reduced (11 ops)', 'Current (13 ops)', 'Increased (15 ops)']\n",
+ "# )\n",
+ "\n",
+ "# print(\"=== STAFFING SCENARIOS SUMMARY ===\")\n",
+ "# scenarios_summary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "be8beaf1-b274-4a9a-af0c-54a41a644ef8",
+ "metadata": {},
+ "source": [
+ "### 4.3 Demand Variation Analysis\n",
+ "\n",
+ "Test how the baseline staffing (13 operators) performs under different demand levels by varying the mean inter-arrival time.\n",
+ "\n",
+ "**Demand Scenarios**:\n",
+ "1. **Low Demand**: `mean_iat=0.8` (calls arrive less frequently)\n",
+ "2. **Current Demand**: `mean_iat=0.6` (baseline)\n",
+ "3. **High Demand**: `mean_iat=0.4` (calls arrive more frequently)\n",
+ "\n",
+ "**Requirements**:\n",
+ "- Use 10 replications for each demand scenario\n",
+ "- Keep staffing at 13 operators for all tests\n",
+ "- Compare results to identify potential problems\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "bbf3fd54-0b66-4db7-b98c-c4268f65f69e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Task 3: Demand Variation Analysis\n",
+ "# TODO: Create three experiments with different demand levels (mean_iat)\n",
+ "# low_demand = Experiment(n_operators=13, mean_iat=0.8)\n",
+ "# current_demand = ...\n",
+ "# high_demand = ...\n",
+ "\n",
+ "# TODO: Run 10 replications for each demand scenario\n",
+ "print(\"Running low demand scenario (mean_iat=0.8)...\")\n",
+ "# low_demand_results = multiple_replications(low_demand, n_reps=10)\n",
+ "\n",
+ "print(\"Running current demand scenario (mean_iat=0.6)...\")\n",
+ "# current_demand_results = multiple_replications(current_demand, n_reps=10)\n",
+ "\n",
+ "print(\"Running high demand scenario (mean_iat=0.4)...\")\n",
+ "# high_demand_results = multiple_replications(high_demand, n_reps=10)\n",
+ "\n",
+ "print(\"All demand scenarios completed!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b900ef13-9ec6-41d8-8bf3-49193c3b0ed7",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# TODO: Create summary table for demand scenarios (uncomment and run)\n",
+ "\n",
+ "# # Create results dictionary\n",
+ "# scenario_results = {\n",
+ "# 'Low (IAT=0.8)': low_demand_results,\n",
+ "# 'Current (IAT=0.6)': current_demand_results,\n",
+ "# 'High (IAT=0.4)': high_demand_results\n",
+ "# }\n",
+ "\n",
+ "# # Create summary table\n",
+ "# demand_summary = create_summary_table(\n",
+ "# results_dict=scenario_results,\n",
+ "# label_key='Scenario',\n",
+ "# label_order=['Low (IAT=0.8)', 'Current (IAT=0.6)', 'High (IAT=0.4)']\n",
+ "# )\n",
+ "\n",
+ "# print(\"=== STAFFING SCENARIOS SUMMARY ===\")\n",
+ "# demand_summary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46cdd911-0124-4ff6-8bad-0646149b8ae8",
+ "metadata": {},
+ "source": [
+ "### 4.4 Recommendations\n",
+ "\n",
+ "Based on your analysis from the exercises above, provide data-driven recommendations.\n",
+ "\n",
+ "**Questions to address**:\n",
+ "\n",
+ "1. **Optimal Staffing**: What is the \"optimal\" number of operators for current demand levels? Consider both waiting times and utilization efficiency.\n",
+ "\n",
+ "2. **High Demand Response**: What happens to the system under high demand conditions? What would you recommend to management?\n",
+ "\n",
+ "3. **Trade-offs**: Explain the trade-off between operator utilization and customer waiting times that you observed.\n",
+ "\n",
+ "**Your Recommendations**:\n",
+ "\n",
+ "### Question 1 - Optimal Staffing\n",
+ "[Provide your analysis and recommendation here]\n",
+ "\n",
+ "### Question 2 - High Demand Response \n",
+ "[Describe what happens under high demand and your recommendations]\n",
+ "\n",
+ "### Question 3 - Trade-offs\n",
+ "[Explain the utilization vs. waiting time trade-off you observed]\n"
+ ]
+ }
+ ],
+ "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.11.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/07b_solutions.ipynb b/content/07b_solutions.ipynb
new file mode 100644
index 0000000..3546919
--- /dev/null
+++ b/content/07b_solutions.ipynb
@@ -0,0 +1,764 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "e27ef2a4-58f2-426e-9db5-7b31bdc6c9a8",
+ "metadata": {},
+ "source": [
+ "# Call Centre Optimization Exercise - SOLUTIONS ๐\n",
+ "\n",
+ "โ ๏ธ **SOLUTIONS:** This notebook contains example solutions for the [experiments exercise](./07a_exercise.ipynb)\n",
+ "\n",
+ "**Objective**: Apply your knowledge of the `Experiment` class to analyze call centre performance under different scenarios.\n",
+ "\n",
+ "## Learning Goals\n",
+ "- Create `Experiment` objects with different parameter configurations\n",
+ "- Run single experiments and interpret results \n",
+ "- Conduct multiple replications for statistical reliability\n",
+ "- Compare scenarios and make data-driven recommendations\n",
+ "\n",
+ "## Exercise Overview\n",
+ "You will analyze a call centre's performance by testing different staffing levels and demand scenarios. The exercise is divided into 4 tasks."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ca24e08-df89-479f-8ed5-42a25a1a362b",
+ "metadata": {},
+ "source": [
+ "## 1. Imports "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "0f9c1cc0-bbbb-4247-a695-fa93f4bbb116",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import simpy\n",
+ "import itertools\n",
+ "\n",
+ "# Set display options for 2dp in pandas\n",
+ "pd.set_option('display.precision', 2)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "331ab9b1-a15a-4656-b88f-f03a6e3a866c",
+ "metadata": {},
+ "source": [
+ "## 2. Simulation model imports\n",
+ "\n",
+ "For convenience the model is stored in a python module called `basic_model`. We need to import:\n",
+ "\n",
+ "* The `Experiment` class - to set parameters,\n",
+ "* Functions that wrap the model and allow us to run it. I.e. `single_run` and `multiple_replications`.\n",
+ "* A function to toggle simulation debug info on and off `set_trace`\n",
+ "* A function that will summarise the results of multiple experiments `create_summary_table`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "99acde15-aabc-4117-bdb5-9fd98a717191",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from basic_model import (\n",
+ " Experiment, \n",
+ " single_run, \n",
+ " multiple_replications, \n",
+ " create_summary_table,\n",
+ " set_trace,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "85cb4b91-72c6-4155-a6b5-522ed6c70e40",
+ "metadata": {},
+ "source": [
+ "## 4. Exercises"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "36a424f3-419d-445c-9bf5-af50356713b5",
+ "metadata": {},
+ "source": [
+ "### 4.1 Baseline Analysis\n",
+ "\n",
+ "First, let's understand the current system performance.\n",
+ "\n",
+ "**Instructions**:\n",
+ "1. Create a default experiment using the `Experiment()` class\n",
+ "2. Run it **once** to see the simulation events. Toggle trace on and off.\n",
+ "3. Record the mean waiting time and operator utilization.\n",
+ "4. Now run 10 multiple replications of the model and view results.\n",
+ "6. Answer the reflection question below\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "fdb072c7-ea95-4f56-889b-adb10e7a8364",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "=== BASELINE SCENARIO RESULTS ===\n",
+ "Mean waiting time: 3.87 minutes\n",
+ "Operator utilization: 92.90%\n"
+ ]
+ }
+ ],
+ "source": [
+ "# TODO: Create a default experiment\n",
+ "baseline_experiment = Experiment()\n",
+ "\n",
+ "# TODO: toggle trace to False/True\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Run the experiment once and store results\n",
+ "baseline_results = single_run(baseline_experiment)\n",
+ "\n",
+ "# Display results\n",
+ "print(\"=== BASELINE SCENARIO RESULTS ===\")\n",
+ "print(f\"Mean waiting time: {baseline_results['01_mean_waiting_time']:.2f} minutes\")\n",
+ "print(f\"Operator utilization: {baseline_results['02_operator_util']:.2f}%\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8180f39d-0e3a-4ec9-b2b9-3ca71f7550f9",
+ "metadata": {},
+ "source": [
+ "We will now run multiple replications of the model. The function returns a `pandas` dataframe containing a a replication per row and a column per KPI."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "6b09fbbd-8fd7-433c-8506-1e496d722b51",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 01_mean_waiting_time | \n",
+ " 02_operator_util | \n",
+ "
\n",
+ " \n",
+ " | rep | \n",
+ " | \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 1 | \n",
+ " 3.87 | \n",
+ " 92.90 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 3.20 | \n",
+ " 93.64 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 3.53 | \n",
+ " 94.22 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 1.54 | \n",
+ " 90.90 | \n",
+ "
\n",
+ " \n",
+ " | 5 | \n",
+ " 3.07 | \n",
+ " 93.53 | \n",
+ "
\n",
+ " \n",
+ " | 6 | \n",
+ " 2.08 | \n",
+ " 93.60 | \n",
+ "
\n",
+ " \n",
+ " | 7 | \n",
+ " 2.01 | \n",
+ " 91.18 | \n",
+ "
\n",
+ " \n",
+ " | 8 | \n",
+ " 1.82 | \n",
+ " 88.85 | \n",
+ "
\n",
+ " \n",
+ " | 9 | \n",
+ " 2.25 | \n",
+ " 93.23 | \n",
+ "
\n",
+ " \n",
+ " | 10 | \n",
+ " 3.58 | \n",
+ " 94.85 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " 01_mean_waiting_time 02_operator_util\n",
+ "rep \n",
+ "1 3.87 92.90\n",
+ "2 3.20 93.64\n",
+ "3 3.53 94.22\n",
+ "4 1.54 90.90\n",
+ "5 3.07 93.53\n",
+ "6 2.08 93.60\n",
+ "7 2.01 91.18\n",
+ "8 1.82 88.85\n",
+ "9 2.25 93.23\n",
+ "10 3.58 94.85"
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Run multiple replications (trace off!)\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Run the experiment once and store results\n",
+ "baseline_reps = multiple_replications(baseline_experiment, n_reps=10)\n",
+ "\n",
+ "# show results\n",
+ "baseline_reps"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "057a944e-a65a-466e-bc77-3c600167e308",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " 01_mean_waiting_time | \n",
+ " 02_operator_util | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | count | \n",
+ " 10.00 | \n",
+ " 10.00 | \n",
+ "
\n",
+ " \n",
+ " | mean | \n",
+ " 2.69 | \n",
+ " 92.69 | \n",
+ "
\n",
+ " \n",
+ " | std | \n",
+ " 0.84 | \n",
+ " 1.83 | \n",
+ "
\n",
+ " \n",
+ " | min | \n",
+ " 1.54 | \n",
+ " 88.85 | \n",
+ "
\n",
+ " \n",
+ " | 25% | \n",
+ " 2.03 | \n",
+ " 91.61 | \n",
+ "
\n",
+ " \n",
+ " | 50% | \n",
+ " 2.66 | \n",
+ " 93.38 | \n",
+ "
\n",
+ " \n",
+ " | 75% | \n",
+ " 3.44 | \n",
+ " 93.63 | \n",
+ "
\n",
+ " \n",
+ " | max | \n",
+ " 3.87 | \n",
+ " 94.85 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " 01_mean_waiting_time 02_operator_util\n",
+ "count 10.00 10.00\n",
+ "mean 2.69 92.69\n",
+ "std 0.84 1.83\n",
+ "min 1.54 88.85\n",
+ "25% 2.03 91.61\n",
+ "50% 2.66 93.38\n",
+ "75% 3.44 93.63\n",
+ "max 3.87 94.85"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# summary statistics\n",
+ "baseline_reps.describe()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "75bb3f56-13b4-497e-a873-4414eb60e1ba",
+ "metadata": {},
+ "source": [
+ "๐ก๐ค **Reflection Question**: Based on the baseline results, what do you observe about the system performance? Is the utilization high or low? What might this suggest about the current staffing level?"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a439d818-e14e-40f5-9b77-2447111f11a4",
+ "metadata": {},
+ "source": [
+ "**Your Answer**: [Write your analysis here]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "03ca3af9-621b-4ef4-b4c5-17a4b9bf57a0",
+ "metadata": {},
+ "source": [
+ "### 4.2 Staffing Scenarios Analysis\n",
+ "\n",
+ "Now test three different staffing scenarios using multiple replications for statistical reliability.\n",
+ "\n",
+ "**Scenarios to test**:\n",
+ "1. **Reduced Staffing**: 11 operators\n",
+ "2. **Current Staffing**: 13 operators (baseline) \n",
+ "3. **Increased Staffing**: 15 operators\n",
+ "\n",
+ "**Requirements**:\n",
+ "- Run each scenario with **10 replications**\n",
+ "- Calculate mean and standard deviation for both KPIs\n",
+ "- Create a summary comparison table\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "924d6ab1-c240-4832-8737-8adf4390fc33",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Running reduced staffing scenario (11 operators)...\n",
+ "Running current staffing scenario (13 operators)...\n",
+ "Running increased staffing scenario (15 operators)...\n",
+ "All scenarios completed!\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Task 2: Staffing Scenarios\n",
+ "# Turn off tracing for cleaner output\n",
+ "set_trace(trace_on=False)\n",
+ "\n",
+ "# TODO: Create three experiments with different staffing levels\n",
+ "reduced_staffing = Experiment(n_operators=11)\n",
+ "current_staffing = Experiment(n_operators=13) # baseline\n",
+ "increased_staffing = Experiment(n_operators=15)\n",
+ "\n",
+ "# TODO: Run 10 replications for each scenario\n",
+ "print(\"Running reduced staffing scenario (11 operators)...\")\n",
+ "reduced_results = multiple_replications(reduced_staffing, n_reps=10)\n",
+ "\n",
+ "print(\"Running current staffing scenario (13 operators)...\")\n",
+ "current_results = multiple_replications(current_staffing, n_reps=10)\n",
+ "\n",
+ "print(\"Running increased staffing scenario (15 operators)...\")\n",
+ "increased_results = multiple_replications(increased_staffing, n_reps=10)\n",
+ "\n",
+ "print(\"All scenarios completed!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "01f4d22d-bddf-4a2b-a231-aeb979acdd70",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "=== STAFFING SCENARIOS SUMMARY ===\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Scenario | \n",
+ " Mean_Waiting_Time | \n",
+ " Std_Waiting_Time | \n",
+ " Mean_Utilization | \n",
+ " Std_Utilization | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Reduced (11 ops) | \n",
+ " 50.10 | \n",
+ " 11.61 | \n",
+ " 98.95 | \n",
+ " 0.58 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Current (13 ops) | \n",
+ " 2.69 | \n",
+ " 0.84 | \n",
+ " 92.69 | \n",
+ " 1.83 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " Increased (15 ops) | \n",
+ " 0.47 | \n",
+ " 0.10 | \n",
+ " 80.57 | \n",
+ " 1.61 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " Scenario Mean_Waiting_Time Std_Waiting_Time Mean_Utilization \\\n",
+ "0 Reduced (11 ops) 50.10 11.61 98.95 \n",
+ "1 Current (13 ops) 2.69 0.84 92.69 \n",
+ "2 Increased (15 ops) 0.47 0.10 80.57 \n",
+ "\n",
+ " Std_Utilization \n",
+ "0 0.58 \n",
+ "1 1.83 \n",
+ "2 1.61 "
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# TODO: Calculate summary statistics for each scenario\n",
+ "\n",
+ "# Create results dictionary\n",
+ "scenario_results = {\n",
+ " 'Reduced (11 ops)': reduced_results,\n",
+ " 'Current (13 ops)': current_results,\n",
+ " 'Increased (15 ops)': increased_results\n",
+ "}\n",
+ "\n",
+ "# Create summary table\n",
+ "scenarios_summary = create_summary_table(\n",
+ " results_dict=scenario_results,\n",
+ " label_key='Scenario',\n",
+ " label_order=['Reduced (11 ops)', 'Current (13 ops)', 'Increased (15 ops)']\n",
+ ")\n",
+ "\n",
+ "print(\"=== STAFFING SCENARIOS SUMMARY ===\")\n",
+ "scenarios_summary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "be8beaf1-b274-4a9a-af0c-54a41a644ef8",
+ "metadata": {},
+ "source": [
+ "### 4.3 Demand Variation Analysis\n",
+ "\n",
+ "Test how the baseline staffing (13 operators) performs under different demand levels by varying the mean inter-arrival time.\n",
+ "\n",
+ "**Demand Scenarios**:\n",
+ "1. **Low Demand**: `mean_iat=0.8` (calls arrive less frequently)\n",
+ "2. **Current Demand**: `mean_iat=0.6` (baseline)\n",
+ "3. **High Demand**: `mean_iat=0.4` (calls arrive more frequently)\n",
+ "\n",
+ "**Requirements**:\n",
+ "- Use 10 replications for each demand scenario\n",
+ "- Keep staffing at 13 operators for all tests\n",
+ "- Compare results to identify potential problems\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "bbf3fd54-0b66-4db7-b98c-c4268f65f69e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Running low demand scenario (mean_iat=0.8)...\n",
+ "Running current demand scenario (mean_iat=0.6)...\n",
+ "Running high demand scenario (mean_iat=0.4)...\n",
+ "All demand scenarios completed!\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Task 3: Demand Variation Analysis\n",
+ "# TODO: Create three experiments with different demand levels (mean_iat)\n",
+ "low_demand = Experiment(n_operators=13, mean_iat=0.8)\n",
+ "current_demand = Experiment(n_operators=13, mean_iat=0.6) # baseline\n",
+ "high_demand = Experiment(n_operators=13, mean_iat=0.4)\n",
+ "\n",
+ "# TODO: Run 10 replications for each demand scenario\n",
+ "print(\"Running low demand scenario (mean_iat=0.8)...\")\n",
+ "low_demand_results = multiple_replications(low_demand, n_reps=10)\n",
+ "\n",
+ "print(\"Running current demand scenario (mean_iat=0.6)...\")\n",
+ "current_demand_results = multiple_replications(current_demand, n_reps=10)\n",
+ "\n",
+ "print(\"Running high demand scenario (mean_iat=0.4)...\")\n",
+ "high_demand_results = multiple_replications(high_demand, n_reps=10)\n",
+ "\n",
+ "print(\"All demand scenarios completed!\")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "b900ef13-9ec6-41d8-8bf3-49193c3b0ed7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "=== STAFFING SCENARIOS SUMMARY ===\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " Scenario | \n",
+ " Mean_Waiting_Time | \n",
+ " Std_Waiting_Time | \n",
+ " Mean_Utilization | \n",
+ " Std_Utilization | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " Low (IAT=0.8) | \n",
+ " 0.19 | \n",
+ " 0.05 | \n",
+ " 69.44 | \n",
+ " 1.96 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " Current (IAT=0.6) | \n",
+ " 2.69 | \n",
+ " 0.84 | \n",
+ " 92.69 | \n",
+ " 1.83 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " High (IAT=0.4) | \n",
+ " 143.34 | \n",
+ " 9.91 | \n",
+ " 99.28 | \n",
+ " 0.15 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " Scenario Mean_Waiting_Time Std_Waiting_Time Mean_Utilization \\\n",
+ "0 Low (IAT=0.8) 0.19 0.05 69.44 \n",
+ "1 Current (IAT=0.6) 2.69 0.84 92.69 \n",
+ "2 High (IAT=0.4) 143.34 9.91 99.28 \n",
+ "\n",
+ " Std_Utilization \n",
+ "0 1.96 \n",
+ "1 1.83 \n",
+ "2 0.15 "
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# TODO: Create summary table for demand scenarios\n",
+ "\n",
+ "# Create results dictionary\n",
+ "scenario_results = {\n",
+ " 'Low (IAT=0.8)': low_demand_results,\n",
+ " 'Current (IAT=0.6)': current_demand_results,\n",
+ " 'High (IAT=0.4)': high_demand_results\n",
+ "}\n",
+ "\n",
+ "# Create summary table\n",
+ "demand_summary = create_summary_table(\n",
+ " results_dict=scenario_results,\n",
+ " label_key='Scenario',\n",
+ " label_order=['Low (IAT=0.8)', 'Current (IAT=0.6)', 'High (IAT=0.4)']\n",
+ ")\n",
+ "\n",
+ "print(\"=== STAFFING SCENARIOS SUMMARY ===\")\n",
+ "demand_summary"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "46cdd911-0124-4ff6-8bad-0646149b8ae8",
+ "metadata": {},
+ "source": [
+ "### 4.4 Recommendations\n",
+ "\n",
+ "Based on your analysis from the exercises above, provide data-driven recommendations.\n",
+ "\n",
+ "**Questions to address**:\n",
+ "\n",
+ "1. **Optimal Staffing**: What is the \"optimal\" number of operators for current demand levels? Consider both waiting times and utilization efficiency.\n",
+ "\n",
+ "2. **High Demand Response**: What happens to the system under high demand conditions? What would you recommend to management?\n",
+ "\n",
+ "3. **Trade-offs**: Explain the trade-off between operator utilization and customer waiting times that you observed.\n",
+ "\n",
+ "**Your Recommendations**:\n",
+ "\n",
+ "### Question 1 - Optimal Staffing\n",
+ "[Provide your analysis and recommendation here]\n",
+ "\n",
+ "### Question 2 - High Demand Response \n",
+ "[Describe what happens under high demand and your recommendations]\n",
+ "\n",
+ "### Question 3 - Trade-offs\n",
+ "[Explain the utilization vs. waiting time trade-off you observed]\n"
+ ]
+ }
+ ],
+ "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.11.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/content/basic_model.py b/content/basic_model.py
new file mode 100644
index 0000000..16a6536
--- /dev/null
+++ b/content/basic_model.py
@@ -0,0 +1,547 @@
+"""
+Call Centre Simulation Model
+
+A discrete event simulation model of a call centre using SimPy.
+Contains experiment management, distribution classes, and simulation logic.
+
+To be used with 07a_experiments_exercise.ipynb
+
+Author: Tom Monks
+"""
+
+import numpy as np
+import pandas as pd
+import simpy
+import itertools
+
+# =============================================================================
+# CONSTANTS AND DEFAULT VALUES
+# =============================================================================
+
+# Default resources
+N_OPERATORS = 13
+
+# Default mean inter-arrival time (exp)
+MEAN_IAT = 60 / 100
+
+# Default service time parameters (triangular)
+CALL_LOW = 5.0
+CALL_MODE = 7.0
+CALL_HIGH = 10.0
+
+# Sampling settings
+N_STREAMS = 2
+DEFAULT_RND_SET = 0
+
+# Boolean switch to display simulation results as the model runs
+TRACE = False
+
+# Run variables
+RESULTS_COLLECTION_PERIOD = 1000
+
+
+# =============================================================================
+# DISTRIBUTION CLASSES
+# =============================================================================
+
+class Triangular:
+ """
+ Convenience class for the triangular distribution.
+ Packages up distribution parameters, seed and random generator.
+ """
+
+ def __init__(self, low, mode, high, random_seed=None):
+ """
+ Constructor. Accepts and stores parameters of the triangular dist
+ and a random seed.
+
+ Params:
+ ------
+ low: float
+ The smallest values that can be sampled
+
+ mode: float
+ The most frequently sample value
+
+ high: float
+ The highest value that can be sampled
+
+ random_seed: int | SeedSequence, optional (default=None)
+ Used with params to create a series of repeatable samples.
+ """
+ self.rand = np.random.default_rng(seed=random_seed)
+ self.low = low
+ self.high = high
+ self.mode = mode
+
+ def sample(self, size=None):
+ """
+ Generate one or more samples from the triangular distribution
+
+ Params:
+ --------
+ size: int
+ the number of samples to return. If size=None then a single
+ sample is returned.
+
+ Returns:
+ -------
+ float or np.ndarray (if size >=1)
+ """
+ return self.rand.triangular(self.low, self.mode, self.high, size=size)
+
+
+class Exponential:
+ """
+ Convenience class for the exponential distribution.
+ Packages up distribution parameters, seed and random generator.
+ """
+
+ def __init__(self, mean, random_seed=None):
+ """
+ Constructor
+
+ Params:
+ ------
+ mean: float
+ The mean of the exponential distribution
+
+ random_seed: int| SeedSequence, optional (default=None)
+ A random seed to reproduce samples. If set to none then a unique
+ sample is created.
+ """
+ self.rand = np.random.default_rng(seed=random_seed)
+ self.mean = mean
+
+ def sample(self, size=None):
+ """
+ Generate a sample from the exponential distribution
+
+ Params:
+ -------
+ size: int, optional (default=None)
+ the number of samples to return. If size=None then a single
+ sample is returned.
+
+ Returns:
+ -------
+ float or np.ndarray (if size >=1)
+ """
+ return self.rand.exponential(self.mean, size=size)
+
+
+# =============================================================================
+# EXPERIMENT CLASS
+# =============================================================================
+
+class Experiment:
+ """
+ Encapsulates the concept of an experiment ๐งช with the urgent care
+ call centre simulation model.
+
+ An Experiment:
+ 1. Contains a list of parameters that can be left as defaults or varied
+ 2. Provides a place for the experimentor to record results of a run
+ 3. Controls the set & streams of psuedo random numbers used in a run.
+ """
+
+ def __init__(
+ self,
+ random_number_set=DEFAULT_RND_SET,
+ n_operators=N_OPERATORS,
+ mean_iat=MEAN_IAT,
+ call_low=CALL_LOW,
+ call_mode=CALL_MODE,
+ call_high=CALL_HIGH,
+ n_streams=N_STREAMS,
+ ):
+ """
+ The init method sets up our defaults.
+ """
+ # sampling
+ self.random_number_set = random_number_set
+ self.n_streams = n_streams
+
+ # store parameters for the run of the model
+ self.n_operators = n_operators
+ self.mean_iat = mean_iat
+ self.call_low = call_low
+ self.call_mode = call_mode
+ self.call_high = call_high
+
+ # resources: we must init resources after an Environment is created.
+ # But we will store a placeholder for transparency
+ self.operators = None
+
+ # initialise results to zero
+ self.init_results_variables()
+
+ # initialise sampling objects
+ self.init_sampling()
+
+ def set_random_no_set(self, random_number_set):
+ """
+ Controls the random sampling
+ Parameters:
+ ----------
+ random_number_set: int
+ Used to control the set of pseudo random numbers used by
+ the distributions in the simulation.
+ """
+ self.random_number_set = random_number_set
+ self.init_sampling()
+
+ def init_sampling(self):
+ """
+ Create the distributions used by the model and initialise
+ the random seeds of each.
+ """
+ # produce n non-overlapping streams
+ seed_sequence = np.random.SeedSequence(self.random_number_set)
+ self.seeds = seed_sequence.spawn(self.n_streams)
+
+ # create distributions
+
+ # call inter-arrival times
+ self.arrival_dist = Exponential(
+ self.mean_iat, random_seed=self.seeds[0]
+ )
+
+ # duration of call triage
+ self.call_dist = Triangular(
+ self.call_low,
+ self.call_mode,
+ self.call_high,
+ random_seed=self.seeds[1],
+ )
+
+ def init_results_variables(self):
+ """
+ Initialise all of the experiment variables used in results
+ collection. This method is called at the start of each run
+ of the model
+ """
+ # variable used to store results of experiment
+ self.results = {}
+ self.results["waiting_times"] = []
+
+ # total operator usage time for utilisation calculation.
+ self.results["total_call_duration"] = 0.0
+
+
+# =============================================================================
+# UTILITY FUNCTIONS
+# =============================================================================
+
+def trace(msg):
+ """
+ Turning printing of events on and off.
+
+ Params:
+ -------
+ msg: str
+ string to print to screen.
+ """
+ if TRACE:
+ print(msg)
+
+
+# =============================================================================
+# MODEL LOGIC
+# =============================================================================
+
+def service(identifier, env, args):
+ """
+ Simulates the service process for a call operator
+
+ 1. request and wait for a call operator
+ 2. phone triage (triangular)
+ 3. exit system
+
+ Params:
+ ------
+ identifier: int
+ A unique identifer for this caller
+
+ env: simpy.Environment
+ The current environent the simulation is running in
+ We use this to pause and restart the process after a delay.
+
+ args: Experiment
+ The settings and input parameters for the current experiment
+ """
+
+ # record the time that call entered the queue
+ start_wait = env.now
+
+ # request an operator - stored in the Experiment
+ with args.operators.request() as req:
+ yield req
+
+ # record the waiting time for call to be answered
+ waiting_time = env.now - start_wait
+
+ # store the results for an experiment
+ args.results["waiting_times"].append(waiting_time)
+
+ trace(f"operator answered call {identifier} at {env.now:.3f}")
+
+ # the sample distribution is defined by the experiment
+ call_duration = args.call_dist.sample()
+
+ # schedule process to begin again after call_duration
+ yield env.timeout(call_duration)
+
+ # update the total call_duration
+ args.results["total_call_duration"] += call_duration
+
+ # print out information for patient.
+ trace(
+ f"call {identifier} ended {env.now:.3f}; "
+ + f"waiting time was {waiting_time:.3f}"
+ )
+
+
+def arrivals_generator(env, args):
+ """
+ IAT is exponentially distributed
+
+ Parameters:
+ ------
+ env: simpy.Environment
+ The simpy environment for the simulation
+
+ args: Experiment
+ The settings and input parameters for the simulation.
+ """
+ # use itertools as it provides an infinite loop
+ # with a counter variable that we can use for unique Ids
+ for caller_count in itertools.count(start=1):
+
+ # the sample distribution is defined by the experiment
+ inter_arrival_time = args.arrival_dist.sample()
+
+ yield env.timeout(inter_arrival_time)
+
+ trace(f"call arrives at: {env.now:.3f}")
+
+ # we pass the experiment to the service function
+ env.process(service(caller_count, env, args))
+
+
+# =============================================================================
+# EXPERIMENT EXECUTION FUNCTIONS
+# =============================================================================
+
+def single_run(experiment, rep=0, rc_period=RESULTS_COLLECTION_PERIOD):
+ """
+ Perform a single run of the model and return the results
+
+ Parameters:
+ -----------
+ experiment: Experiment
+ The experiment/paramaters to use with model
+
+ rep: int, optional (default=0)
+ The replication number for random seed control
+
+ rc_period: float, optional (default=RESULTS_COLLECTION_PERIOD)
+ Results collection period - how long to run the simulation
+
+ Returns:
+ --------
+ dict: Dictionary containing the key performance indicators
+ """
+
+ # results dictionary. Each KPI is a new entry.
+ run_results = {}
+
+ # reset all result collection variables
+ experiment.init_results_variables()
+
+ # set random number set to the replication no.
+ # this controls sampling for the run.
+ experiment.set_random_no_set(rep)
+
+ # environment is (re)created inside single run
+ env = simpy.Environment()
+
+ # we create simpy resource here - this has to be after we
+ # create the environment object.
+ experiment.operators = simpy.Resource(env, capacity=experiment.n_operators)
+
+ # we pass the experiment to the arrivals generator
+ env.process(arrivals_generator(env, experiment))
+ env.run(until=rc_period)
+
+ # end of run results: calculate mean waiting time
+ run_results["01_mean_waiting_time"] = np.mean(
+ experiment.results["waiting_times"]
+ )
+
+ # end of run results: calculate mean operator utilisation
+ run_results["02_operator_util"] = (
+ experiment.results["total_call_duration"]
+ / (rc_period * experiment.n_operators)
+ ) * 100.0
+
+ # return the results from the run of the model
+ return run_results
+
+
+def multiple_replications(
+ experiment, rc_period=RESULTS_COLLECTION_PERIOD, n_reps=5
+):
+ """
+ Perform multiple replications of the model.
+
+ Params:
+ ------
+ experiment: Experiment
+ The experiment/paramaters to use with model
+
+ rc_period: float, optional (default=RESULTS_COLLECTION_PERIOD)
+ results collection period.
+ the number of minutes to run the model to collect results
+
+ n_reps: int, optional (default=5)
+ Number of independent replications to run.
+
+ Returns:
+ --------
+ pandas.DataFrame
+ DataFrame containing results from all replications
+ """
+
+ # loop over single run to generate results dicts in a python list.
+ results = [single_run(experiment, rep, rc_period) for rep in range(n_reps)]
+
+ # format and return results in a dataframe
+ df_results = pd.DataFrame(results)
+ df_results.index = np.arange(1, len(df_results) + 1)
+ df_results.index.name = "rep"
+ return df_results
+
+
+# =============================================================================
+# CONVENIENCE FUNCTIONS
+# =============================================================================
+
+def set_trace(trace_on=True):
+ """
+ Convenience function to turn tracing on/off globally.
+
+ Its not ideal to use global variables like this, but
+ it is included for simplicity.
+
+ Params:
+ -------
+ trace_on: bool, optional (default=True)
+ Whether to turn tracing on or off
+ """
+ global TRACE
+ TRACE = trace_on
+
+
+def summary_stats(results_df):
+ """
+ Calculate summary statistics for a results DataFrame
+
+ Params:
+ -------
+ results_df: pandas.DataFrame
+ DataFrame from multiple_replications function
+
+ Returns:
+ --------
+ pandas.DataFrame: Summary statistics (mean, std, min, max)
+ """
+ return results_df.describe()
+
+
+def compare_experiments(experiments_dict, n_reps=10, rc_period=RESULTS_COLLECTION_PERIOD):
+ """
+ Compare multiple experiments and return summary results
+
+ Params:
+ -------
+ experiments_dict: dict
+ Dictionary where keys are experiment names and values are Experiment objects
+ n_reps: int, optional (default=10)
+ Number of replications for each experiment
+ rc_period: float, optional (default=RESULTS_COLLECTION_PERIOD)
+ Results collection period
+
+ Returns:
+ --------
+ pandas.DataFrame: Comparison of mean results across experiments
+ """
+ comparison_results = {}
+
+ for name, experiment in experiments_dict.items():
+ results = multiple_replications(experiment, rc_period, n_reps)
+ comparison_results[name] = results.mean()
+
+ return pd.DataFrame(comparison_results).T
+
+
+def create_summary_table(results_dict, label_key, label_order):
+ """
+ Create a summary DataFrame for multiple scenarios or demand levels.
+
+ Parameters:
+ -----------
+ results_dict: dict
+ Dictionary where keys are labels (e.g., scenario or demand level names) and values are
+ pandas DataFrames containing replication results with columns '01_mean_waiting_time' and '02_operator_util'.
+
+ label_key: str
+ The name of the column for the labels in the summary table (e.g., 'Scenario' or 'Demand_Level').
+
+ label_order: list
+ The order of labels to appear in the summary table.
+
+ Returns:
+ --------
+ pandas.DataFrame
+ Summary table with mean and std for waiting time and utilization.
+ """
+ summary_data = {
+ label_key: [],
+ 'Mean_Waiting_Time': [],
+ 'Std_Waiting_Time': [],
+ 'Mean_Utilization': [],
+ 'Std_Utilization': []
+ }
+
+ for label in label_order:
+ df = results_dict[label]
+ summary_data[label_key].append(label)
+ summary_data['Mean_Waiting_Time'].append(df['01_mean_waiting_time'].mean())
+ summary_data['Std_Waiting_Time'].append(df['01_mean_waiting_time'].std())
+ summary_data['Mean_Utilization'].append(df['02_operator_util'].mean())
+ summary_data['Std_Utilization'].append(df['02_operator_util'].std())
+
+ return pd.DataFrame(summary_data)
+
+
+
+# =============================================================================
+# MODULE INFORMATION
+# =============================================================================
+
+__version__ = "1.0.0"
+__author__ = "Tom Monks"
+__all__ = [
+ # Classes
+ 'Experiment', 'Triangular', 'Exponential',
+ # Main functions
+ 'single_run', 'multiple_replications',
+ # Model functions
+ 'service', 'arrivals_generator',
+ # Utility functions
+ 'trace', 'set_trace', 'summary_stats', 'compare_experiments', "create_summary_table",
+ # Constants
+ 'N_OPERATORS', 'MEAN_IAT', 'CALL_LOW', 'CALL_MODE', 'CALL_HIGH',
+ 'RESULTS_COLLECTION_PERIOD', 'TRACE'
+]