Skip to content

Commit

Permalink
Merge pull request #1 from pythonhealthdatascience/update
Browse files Browse the repository at this point in the history
notebooks for SW25
  • Loading branch information
TomMonks authored Oct 3, 2024
2 parents 1585715 + dbdc5cc commit ee40b7a
Show file tree
Hide file tree
Showing 10 changed files with 2,256 additions and 2,496 deletions.
474 changes: 474 additions & 0 deletions content/01_sampling.ipynb

Large diffs are not rendered by default.

2,453 changes: 0 additions & 2,453 deletions content/01_urgent_care_model.ipynb

This file was deleted.

220 changes: 220 additions & 0 deletions content/02_basic_simpy.ipynb
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
}
150 changes: 150 additions & 0 deletions content/03a_exercise1.ipynb
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
}
Loading

0 comments on commit ee40b7a

Please sign in to comment.