generated from pythonhealthdatascience/stars-simpy-jupterlite
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from pythonhealthdatascience/update
notebooks for SW25
- Loading branch information
Showing
10 changed files
with
2,256 additions
and
2,496 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "50446585-a13f-4c90-9700-58015b670d9c", | ||
"metadata": {}, | ||
"source": [ | ||
"# A first look at simpy\n", | ||
"\n", | ||
"In this tutorial we will make use of **Free and Open Source Software (FOSS)** for discrete-event simulation called `simpy`. \n", | ||
"\n", | ||
"## Why FOSS and simpy?\n", | ||
"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" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "71ec6d31-c791-4945-83fd-c96670e4b492", | ||
"metadata": {}, | ||
"source": [ | ||
"## 1. Imports\n", | ||
"\n", | ||
"The first library we will import is `simpy`. The typical style is to import the whole package as follows:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 1, | ||
"id": "3e306312-aaa9-425b-b778-ef859f8f882a", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import simpy" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "dda6f531-3e2d-4e65-8ad6-0703f09df810", | ||
"metadata": {}, | ||
"source": [ | ||
"We will also need a few other packages in our simulation model. " | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 2, | ||
"id": "9f083908-66a9-47ea-af29-c2baaaeef4c4", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import numpy as np\n", | ||
"import pandas as pd\n", | ||
"import matplotlib.pyplot as plt\n", | ||
"import itertools\n", | ||
"import math" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "968d7a3d-763d-48a0-9915-421631d1f650", | ||
"metadata": {}, | ||
"source": [ | ||
"## 2. A first example: a hospital pharmacy\n", | ||
"\n", | ||
"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", | ||
"\n", | ||
"#### **A simpy environment**\n", | ||
"\n", | ||
"`simpy` has process based worldview. These processes take place in an environment. You can create a environment with the following line of code:\n", | ||
"\n", | ||
"```python\n", | ||
"env = simpy.Environment()\n", | ||
"```\n", | ||
"\n", | ||
"#### **simpy timeouts**\n", | ||
"\n", | ||
"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", | ||
"\n", | ||
"```python\n", | ||
"activity_duration = 20\n", | ||
"env.timeout(activity_duration)\n", | ||
"```\n", | ||
"\n", | ||
"#### **generators**\n", | ||
"\n", | ||
"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", | ||
"\n", | ||
"```python\n", | ||
"def prescription_arrival_generator(env):\n", | ||
" while True:\n", | ||
" yield env.timeout(5.0)\n", | ||
"```\n", | ||
"\n", | ||
"Notice that the generator takes the environment as a parameter. It then internally calls the `env.timeout()` method in an infinite loop.\n", | ||
"\n", | ||
"#### **running a `simpy` model**\n", | ||
"\n", | ||
"Once we have coded the model logic and created an environment instance, there are two remaining instructions we need to code.\n", | ||
"\n", | ||
"1. set the generator up as a simpy process\n", | ||
"\n", | ||
"```python\n", | ||
"env.process(prescription_arrival_generator(env))\n", | ||
"```\n", | ||
"\n", | ||
"2. run the environment for a user specified run length\n", | ||
"\n", | ||
"```python\n", | ||
"env.run(until=25)\n", | ||
"```\n", | ||
"\n", | ||
"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", | ||
"\n", | ||
"**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." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 3, | ||
"id": "a6fd524c-7dc4-41c0-876d-3507ce480dfb", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def prescription_arrival_generator(env):\n", | ||
" '''\n", | ||
" Prescriptions arrive with a fixed duration of 5 minutes.\n", | ||
"\n", | ||
" Parameters:\n", | ||
" ------\n", | ||
" env: simpy.Environment\n", | ||
" '''\n", | ||
" \n", | ||
" # don't worry about the infinite while loop, simpy will\n", | ||
" # exit at the correct time.\n", | ||
" while True:\n", | ||
" \n", | ||
" # sample an inter-arrival time.\n", | ||
" inter_arrival_time = 5.0\n", | ||
" \n", | ||
" # we use the yield keyword instead of return\n", | ||
" yield env.timeout(inter_arrival_time)\n", | ||
" \n", | ||
" # print out the time of the arrival\n", | ||
" print(f'Prescription arrives at: {env.now}')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "aa6042f3-b7a7-4c3a-a5d8-7eb7f3796bf2", | ||
"metadata": {}, | ||
"source": [ | ||
"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?" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": 6, | ||
"id": "f6f74ff5-4c95-400e-8494-42e438b18b90", | ||
"metadata": {}, | ||
"outputs": [ | ||
{ | ||
"name": "stdout", | ||
"output_type": "stream", | ||
"text": [ | ||
"Prescription arrives at: 5.0\n", | ||
"Prescription arrives at: 10.0\n", | ||
"Prescription arrives at: 15.0\n", | ||
"Prescription arrives at: 20.0\n", | ||
"end of run. simulation clock time = 25\n" | ||
] | ||
} | ||
], | ||
"source": [ | ||
"# model parameters\n", | ||
"RUN_LENGTH = 25\n", | ||
"\n", | ||
"# create the simpy environment object\n", | ||
"env = simpy.Environment()\n", | ||
"\n", | ||
"# tell simpy that the `prescription_arrival_generator` is a process\n", | ||
"env.process(prescription_arrival_generator(env))\n", | ||
"\n", | ||
"# run the simulation model\n", | ||
"env.run(until=RUN_LENGTH)\n", | ||
"print(f'end of run. simulation clock time = {env.now}')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "c3aa041b-6b8f-4b15-becc-bd966d0eb794", | ||
"metadata": {}, | ||
"source": [ | ||
"## Exercise\n", | ||
"\n", | ||
"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" | ||
] | ||
} | ||
], | ||
"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.9" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"id": "f2147e34-ce39-4d8c-a7db-e8acec2b63e0", | ||
"metadata": {}, | ||
"source": [ | ||
"# Generator exercise\n", | ||
"\n", | ||
"To see the solutions please see the [generator exercise solutions notebook](./03b_exercise1_solutions.ipynb)\n" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "c034caf3-6766-4c69-af8f-949f45283b37", | ||
"metadata": {}, | ||
"source": [ | ||
"## Imports" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "85fd4a85-bb77-498c-96ee-c14de89994a2", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import simpy\n", | ||
"import numpy as np" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "bfab7f97-9cad-419a-8d75-a2ba190edee8", | ||
"metadata": {}, | ||
"source": [ | ||
"## Example code\n", | ||
"\n", | ||
"The code below is taken from the simple pharmacy example. In this code arrivals occur with an IAT of exactly 5 minutes." | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "ee2439f2-0d35-41fd-a5e2-4b95954dd5c5", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"def prescription_arrival_generator(env):\n", | ||
" '''\n", | ||
" Prescriptions arrive with a fixed duration of\n", | ||
" 5 minutes.\n", | ||
"\n", | ||
" Parameters:\n", | ||
" ------\n", | ||
" env: simpy.Environment\n", | ||
" '''\n", | ||
" \n", | ||
" # don't worry about the infinite while loop, simpy will\n", | ||
" # exit at the correct time.\n", | ||
" while True:\n", | ||
" \n", | ||
" # sample an inter-arrival time.\n", | ||
" inter_arrival_time = 5.0\n", | ||
" \n", | ||
" # we use the yield keyword instead of return\n", | ||
" yield env.timeout(inter_arrival_time)\n", | ||
" \n", | ||
" # print out the time of the arrival\n", | ||
" print(f'Prescription arrives at: {env.now}')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "40c495d5-6f55-4c93-99e3-5bfa6cdff36d", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# model parameters\n", | ||
"RUN_LENGTH = 25\n", | ||
"\n", | ||
"# create the simpy environment object\n", | ||
"env = simpy.Environment()\n", | ||
"\n", | ||
"# tell simpy that the `prescription_arrival_generator` is a process\n", | ||
"env.process(prescription_arrival_generator(env))\n", | ||
"\n", | ||
"# run the simulation model\n", | ||
"env.run(until=RUN_LENGTH)\n", | ||
"print(f'end of run. simulation clock time = {env.now}')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"id": "6b1bd501-1614-4891-a876-d07bd40c0496", | ||
"metadata": {}, | ||
"source": [ | ||
"### Exercise: modelling a poisson arrival process for prescriptions\n", | ||
"\n", | ||
"**Task:**\n", | ||
"\n", | ||
"* Update `prescription_arrival_generator()` so that inter-arrival times follow an exponential distribution with a mean of 5.0 minutes between arrivals.\n", | ||
"* Use a run length of 25 minutes.\n", | ||
"\n", | ||
"> **Bonus**: try this initially **without** setting a random seed. Then update the method choosing an approach to control random sampling.\n", | ||
"\n", | ||
"**Hints:**\n", | ||
"\n", | ||
"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", | ||
"\n", | ||
"```python\n", | ||
"rng = np.random.default_rng()\n", | ||
"sample = rng.exponential(scale=12.0)\n", | ||
"```" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "65823eff-8d0f-4eaf-a531-173c8ab6290c", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"# your code here." | ||
] | ||
} | ||
], | ||
"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.9" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
Oops, something went wrong.