Skip to content

Commit 4780d25

Browse files
committed
style(notebooks): 🎨 amendments to markdown and comments to help improve clarity and/or grammar, and addition of some illustrations
1 parent 47a94d2 commit 4780d25

12 files changed

+890
-600
lines changed

content/01_sampling.ipynb

Lines changed: 24 additions & 20 deletions
Large diffs are not rendered by default.

content/02_basic_simpy.ipynb

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,33 @@
55
"id": "50446585-a13f-4c90-9700-58015b670d9c",
66
"metadata": {},
77
"source": [
8-
"# A first look at simpy\n",
8+
"# An introduction to SimPy\n",
99
"\n",
10-
"In this tutorial we will make use of **Free and Open Source Software (FOSS)** for discrete-event simulation called `simpy`. \n",
10+
"In this tutorial, we will use **SimPy**, a free and open-source software (FOSS) framework for discrete-event simulation.\n",
1111
"\n",
12-
"## Why FOSS and simpy?\n",
13-
"An strength of `simpy` is its simplicity and flexibility. As it is part of Python, it is often straightforward to use `simpy` to model complex logic and make use of the SciPy stack! Initially, you will need to write a lot of code (or borrow code from other simulation studies you find online!). But don't worry. As you use `simpy` you will build your own library of reusable code that you can draw on (and build on) for future simulation projects. As `simpy` is FOSS it is useful for research: the model logic is transparent, can be readily shared with others, and can easily link to other data science tools such as those from Machine Learning (e.g. `sklearn` or `pytorch`). \n"
12+
"## 1. Why choose FOSS and SimPy?\n",
13+
"\n",
14+
"💪 A strength of SimPy is its **simplicity and flexibility**.\n",
15+
"\n",
16+
"* As it is part of Python, it is often straightforward to use SimPy to model complex logic and make use of the [SciPy stack](https://projects.scipy.org/stackspec.html)!\n",
17+
"\n",
18+
"📝 You will initially need to **write lots of code** - or borrow code from existing simulation studies online. Do not worry though! As you use SimPy, you will build up your own library of reusable code that you can draw upon and build on for future simulation projects.\n",
19+
"\n",
20+
"♻️ SimPy is **FOSS** - the benefits of this for research are that:\n",
21+
"\n",
22+
"* Model logic is **transparent**\n",
23+
"* It can be readily **shared** with others\n",
24+
"* It can easily **link to other data science tools** (e.g. `sklearn` or `pytorch` for machine learning)"
1425
]
1526
},
1627
{
1728
"cell_type": "markdown",
1829
"id": "71ec6d31-c791-4945-83fd-c96670e4b492",
1930
"metadata": {},
2031
"source": [
21-
"## 1. Imports\n",
32+
"## 2. Imports\n",
2233
"\n",
23-
"The first library we will import is `simpy`. The typical style is to import the whole package as follows:"
34+
"For `simpy`, the typical style is to import the whole package as follows:"
2435
]
2536
},
2637
{
@@ -33,90 +44,75 @@
3344
"import simpy"
3445
]
3546
},
36-
{
37-
"cell_type": "markdown",
38-
"id": "dda6f531-3e2d-4e65-8ad6-0703f09df810",
39-
"metadata": {},
40-
"source": [
41-
"We will also need a few other packages in our simulation model. "
42-
]
43-
},
44-
{
45-
"cell_type": "code",
46-
"execution_count": 2,
47-
"id": "9f083908-66a9-47ea-af29-c2baaaeef4c4",
48-
"metadata": {},
49-
"outputs": [],
50-
"source": [
51-
"import numpy as np\n",
52-
"import pandas as pd\n",
53-
"import matplotlib.pyplot as plt\n",
54-
"import itertools\n",
55-
"import math"
56-
]
57-
},
5847
{
5948
"cell_type": "markdown",
6049
"id": "968d7a3d-763d-48a0-9915-421631d1f650",
6150
"metadata": {},
6251
"source": [
63-
"## 2. A first example: a hospital pharmacy\n",
52+
"## 3. An example: a hospital pharmacy\n",
6453
"\n",
65-
"In this first example, let's assume (unrealistically) that prescriptions arrive **exactly** 5 minutes apart. To build this model we need the following components:\n",
54+
"In this first example, let's assume (unrealistically) that prescriptions arrive **exactly** 5 minutes apart.\n",
6655
"\n",
67-
"#### **A simpy environment**\n",
56+
"![Pharmacy with prescriptions every 5 minutes](../img/pharmacy.png)\n",
57+
"\n",
58+
"## 4. The model building blocks\n",
59+
"\n",
60+
"To build our model, we will need the following components...\n",
61+
"\n",
62+
"### 4.1 A SimPy environment\n",
6863
"\n",
6964
"`simpy` has process based worldview. These processes take place in an environment. You can create a environment with the following line of code:\n",
7065
"\n",
7166
"```python\n",
7267
"env = simpy.Environment()\n",
7368
"```\n",
7469
"\n",
75-
"#### **simpy timeouts**\n",
70+
"### 4.2 SimPy timeouts\n",
7671
"\n",
77-
"We can introduce **delays** or **activities** into a process. For example these might be the duration of a stay on a ward, or the duration of a operation. In this case we are going to introduce a delay between arrivals (inter-arrival time). In `simpy` you control this with the following method:\n",
72+
"We can introduce **delays** or **activities** into a process. For example these might be the duration of a stay on a ward, or the duration of a operation - or, in this case, a **delay between arrivals (inter-arrival time)**. In `simpy` you control this with the following method:\n",
7873
"\n",
7974
"```python\n",
80-
"activity_duration = 20\n",
81-
"env.timeout(activity_duration)\n",
75+
"env.timeout(5.0)\n",
8276
"```\n",
8377
"\n",
84-
"#### **generators**\n",
78+
"### 4.3 Generators\n",
79+
"\n",
80+
"The events in the DES are modelled and scheduled in `simpy` using python **generators** (i.e. they are the \"event-processing mechanism\"). A generator is a function that behaves like an iterator, meaning it can yield a **sequence of values** when iterated over.\n",
8581
"\n",
86-
"The event process mechanism in `simpy` is implemented using python generators. A basic generator function that yields a new arrival every 5 minutes looks like this:\n",
82+
"For example, below is a basic generator function that yields a new arrival every 5 minutes. It takes the **environment** as a parameter. It then internally calls the `env.timeout()` method in an infinite loop.\n",
8783
"\n",
8884
"```python\n",
8985
"def prescription_arrival_generator(env):\n",
9086
" while True:\n",
9187
" yield env.timeout(5.0)\n",
9288
"```\n",
9389
"\n",
94-
"Notice that the generator takes the environment as a parameter. It then internally calls the `env.timeout()` method in an infinite loop.\n",
95-
"\n",
96-
"#### **running a `simpy` model**\n",
90+
"### 4.4 SimPy process and run\n",
9791
"\n",
9892
"Once we have coded the model logic and created an environment instance, there are two remaining instructions we need to code.\n",
9993
"\n",
100-
"1. set the generator up as a simpy process\n",
94+
"1. Set the generator up as a **SimPy process** using `env.process()`\n",
10195
"\n",
10296
"```python\n",
10397
"env.process(prescription_arrival_generator(env))\n",
10498
"```\n",
10599
"\n",
106-
"2. run the environment for a user specified run length\n",
100+
"2. Run the environment for a user specified **run length** using `env.run()`\n",
107101
"\n",
108102
"```python\n",
109103
"env.run(until=25)\n",
110104
"```\n",
111105
"\n",
112106
"The run method handle the infinite loop we set up in `prescription_arrival_generator`. The simulation model has an internal concept of time. It will end execution when its internal clock reaches 25 time units.\n",
113107
"\n",
108+
"## 5. Create the model\n",
109+
"\n",
114110
"**Now that we have covered the basic building blocks, let's code the actual model.** It makes sense to create our model logic first. The code below will generate arrivals every 5 minutes. Note that the function takes an environment object as a parameter."
115111
]
116112
},
117113
{
118114
"cell_type": "code",
119-
"execution_count": 3,
115+
"execution_count": 2,
120116
"id": "a6fd524c-7dc4-41c0-876d-3507ce480dfb",
121117
"metadata": {},
122118
"outputs": [],
@@ -149,12 +145,14 @@
149145
"id": "aa6042f3-b7a7-4c3a-a5d8-7eb7f3796bf2",
150146
"metadata": {},
151147
"source": [
152-
"Now that we have our generator function we can setup the environment, process and call run. We will create a `RUN_LENGTH` parameter that you can change to run the model for different time lengths. What would happen if this was set to 50?"
148+
"Now that we have our generator function we can setup the environment, process and call run. We will create a `RUN_LENGTH` parameter that you can change to run the model for different time lengths.\n",
149+
"\n",
150+
"**Consider:** What would happen if we set `RUN_LENGTH` to 50?"
153151
]
154152
},
155153
{
156154
"cell_type": "code",
157-
"execution_count": 6,
155+
"execution_count": 3,
158156
"id": "f6f74ff5-4c95-400e-8494-42e438b18b90",
159157
"metadata": {},
160158
"outputs": [
@@ -190,9 +188,11 @@
190188
"id": "c3aa041b-6b8f-4b15-becc-bd966d0eb794",
191189
"metadata": {},
192190
"source": [
193-
"## Exercise\n",
191+
"## 6. Exercise\n",
192+
"\n",
193+
"Before we learn anything more about `simpy`, have a go at the [generators exercise](./03a_exercise1.ipynb).\n",
194194
"\n",
195-
"Before we learn anything more about `simpy` have a go at the generators exercise. In the exercise you will need to modify the `prescription_arrival_generator` so that it has random arrivals. This exercise test that you have understood the basics of `simpy` and random sampling in `numpy`\n"
195+
"In the exercise you will need to modify the `prescription_arrival_generator` so that it has random arrivals. This exercise tests that you have understood the basics of `simpy` and random sampling in `numpy`\n"
196196
]
197197
}
198198
],
@@ -212,7 +212,7 @@
212212
"name": "python",
213213
"nbconvert_exporter": "python",
214214
"pygments_lexer": "ipython3",
215-
"version": "3.11.9"
215+
"version": "3.1.undefined"
216216
}
217217
},
218218
"nbformat": 4,

content/03a_exercise1.ipynb

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,20 @@
77
"source": [
88
"# Generator exercise\n",
99
"\n",
10-
"To see the solutions please see the [generator exercise solutions notebook](./03b_exercise1_solutions.ipynb)\n"
10+
"🧐 For the solutions, please see the [generator exercise solutions notebook](./03b_exercise1_solutions.ipynb)\n"
1111
]
1212
},
1313
{
1414
"cell_type": "markdown",
1515
"id": "c034caf3-6766-4c69-af8f-949f45283b37",
1616
"metadata": {},
1717
"source": [
18-
"## Imports"
18+
"## 1. Imports"
1919
]
2020
},
2121
{
2222
"cell_type": "code",
23-
"execution_count": null,
23+
"execution_count": 1,
2424
"id": "85fd4a85-bb77-498c-96ee-c14de89994a2",
2525
"metadata": {},
2626
"outputs": [],
@@ -34,14 +34,14 @@
3434
"id": "bfab7f97-9cad-419a-8d75-a2ba190edee8",
3535
"metadata": {},
3636
"source": [
37-
"## Example code\n",
37+
"## 2. Example code\n",
3838
"\n",
39-
"The code below is taken from the simple pharmacy example. In this code arrivals occur with an IAT of exactly 5 minutes."
39+
"The code below is taken from the simple pharmacy example. In this code arrivals occur with an inter-arrival time (IAT) of exactly 5 minutes."
4040
]
4141
},
4242
{
4343
"cell_type": "code",
44-
"execution_count": null,
44+
"execution_count": 2,
4545
"id": "ee2439f2-0d35-41fd-a5e2-4b95954dd5c5",
4646
"metadata": {},
4747
"outputs": [],
@@ -72,10 +72,22 @@
7272
},
7373
{
7474
"cell_type": "code",
75-
"execution_count": null,
75+
"execution_count": 3,
7676
"id": "40c495d5-6f55-4c93-99e3-5bfa6cdff36d",
7777
"metadata": {},
78-
"outputs": [],
78+
"outputs": [
79+
{
80+
"name": "stdout",
81+
"output_type": "stream",
82+
"text": [
83+
"Prescription arrives at: 5.0\n",
84+
"Prescription arrives at: 10.0\n",
85+
"Prescription arrives at: 15.0\n",
86+
"Prescription arrives at: 20.0\n",
87+
"end of run. simulation clock time = 25\n"
88+
]
89+
}
90+
],
7991
"source": [
8092
"# model parameters\n",
8193
"RUN_LENGTH = 25\n",
@@ -96,28 +108,29 @@
96108
"id": "6b1bd501-1614-4891-a876-d07bd40c0496",
97109
"metadata": {},
98110
"source": [
99-
"### Exercise: modelling a poisson arrival process for prescriptions\n",
111+
"## 3. Exercise: Modelling a poisson arrival process for prescriptions\n",
100112
"\n",
101113
"**Task:**\n",
102114
"\n",
103-
"* Update `prescription_arrival_generator()` so that inter-arrival times follow an exponential distribution with a mean of 5.0 minutes between arrivals.\n",
104-
"* Use a run length of 25 minutes.\n",
115+
"Update `prescription_arrival_generator()` so that inter-arrival times follow an **exponential distribution** with a mean of 5.0 minutes between arrivals. Use a run length of 25 minutes.\n",
105116
"\n",
106-
"> **Bonus**: try this initially **without** setting a random seed. Then update the method choosing an approach to control random sampling.\n",
117+
"**Bonus challenge:**\n",
107118
"\n",
108-
"**Hints:**\n",
119+
"* First, try implementing this **without** setting a random seed.\n",
120+
"* Then, update the method with an approach to control the randomness,\n",
109121
"\n",
110-
"We learnt how to sample using a `numpy` random number generator in the [sampling notebook](./01_sampling.ipynb). The basic form to draw a single sample followed this pattern (note this excludes a random seed).\n",
122+
"**Hints:**\n",
111123
"\n",
112-
"```python\n",
113-
"rng = np.random.default_rng()\n",
114-
"sample = rng.exponential(scale=12.0)\n",
115-
"```"
124+
"* We learnt how to sample using a `numpy` random number generator in the [sampling notebook](./01_sampling.ipynb). Excluding a random seed, the basic method for drawing a single sample follows this pattern:\n",
125+
" ```python\n",
126+
" rng = np.random.default_rng()\n",
127+
" sample = rng.exponential(scale=12.0)\n",
128+
" ```"
116129
]
117130
},
118131
{
119132
"cell_type": "code",
120-
"execution_count": null,
133+
"execution_count": 4,
121134
"id": "65823eff-8d0f-4eaf-a531-173c8ab6290c",
122135
"metadata": {},
123136
"outputs": [],
@@ -142,7 +155,7 @@
142155
"name": "python",
143156
"nbconvert_exporter": "python",
144157
"pygments_lexer": "ipython3",
145-
"version": "3.11.9"
158+
"version": "3.11.10"
146159
}
147160
},
148161
"nbformat": 4,

0 commit comments

Comments
 (0)