Skip to content

Commit aa3db65

Browse files
authored
Merge pull request #2 from pythonhealthdatascience/dev
Dev
2 parents 02aa96a + 34059c9 commit aa3db65

File tree

8 files changed

+1039
-143
lines changed

8 files changed

+1039
-143
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
[![python](https://img.shields.io/badge/-Python_3.13.1-blue?logo=python&logoColor=white)](https://www.python.org/)
66
![licence](https://img.shields.io/badge/Licence-MIT-green.svg?labelColor=gray)
7+
[![Tests](https://github.com/pythonhealthdatascience/stroke_rap_python/actions/workflows/tests.yaml/badge.svg)](https://github.com/pythonhealthdatascience/stroke_rap_python/actions/workflows/tests.yaml)
8+
[![Linting](https://github.com/pythonhealthdatascience/stroke_rap_python/actions/workflows/lint.yaml/badge.svg)](https://github.com/pythonhealthdatascience/stroke_rap_python/actions/workflows/lint.yaml)
79
[![ORCID: Heather](https://img.shields.io/badge/ORCID_Amy_Heather-0000--0002--6596--3479-brightgreen)](https://orcid.org/0000-0002-6596-3479)
810

911
</div>

docs/log.md

Lines changed: 511 additions & 5 deletions
Large diffs are not rendered by default.

environment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ dependencies:
2020
- simpy=4.1.1
2121
- pip:
2222
- kaleido==0.2.1
23+
- sim-tools==0.8.0
2324
- -e .[dev]

notebooks/analysis.ipynb

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 1,
6+
"metadata": {},
7+
"outputs": [],
8+
"source": [
9+
"# pylint: disable=missing-module-docstring\n",
10+
"%load_ext autoreload\n",
11+
"%autoreload 1\n",
12+
"%aimport simulation\n",
13+
"\n",
14+
"# pylint: disable=wrong-import-position\n",
15+
"from simulation.parameters import Param\n",
16+
"from simulation.model import Model"
17+
]
18+
},
19+
{
20+
"cell_type": "code",
21+
"execution_count": 2,
22+
"metadata": {},
23+
"outputs": [
24+
{
25+
"name": "stdout",
26+
"output_type": "stream",
27+
"text": [
28+
"stroke patient arrival at asu: 1.8363299834121842\n",
29+
"stroke patient arrival at asu: 1.9743678169650352\n",
30+
"other patient arrival at asu: 2.569934103240965\n",
31+
"stroke patient arrival at asu: 3.38794282930665\n",
32+
"other patient arrival at asu: 3.7923395830252455\n",
33+
"other patient arrival at asu: 4.957702796571509\n",
34+
"stroke patient arrival at asu: 5.9842644124165965\n",
35+
"stroke patient arrival at asu: 6.445098118218787\n",
36+
"other patient arrival at asu: 6.6520207774300815\n",
37+
"stroke patient arrival at asu: 7.043900632615749\n",
38+
"stroke patient arrival at asu: 7.452473171227655\n",
39+
"stroke patient arrival at asu: 8.59342618026359\n",
40+
"stroke patient arrival at rehab: 8.879197835777973\n",
41+
"stroke patient arrival at asu: 9.667940702617951\n",
42+
"other patient arrival at rehab: 9.80202397507623\n",
43+
"stroke patient arrival at asu: 9.96129265198861\n",
44+
"stroke patient arrival at asu: 9.97016696680622\n",
45+
"stroke patient arrival at asu: 10.476123075312099\n",
46+
"stroke patient arrival at asu: 11.511320121224648\n",
47+
"neuro patient arrival at asu: 11.85670004691538\n",
48+
"stroke patient arrival at asu: 12.144466902435695\n",
49+
"tia patient arrival at asu: 13.470857817085893\n",
50+
"stroke patient arrival at asu: 13.921070694209979\n",
51+
"stroke patient arrival at asu: 14.261395167818655\n",
52+
"tia patient arrival at asu: 14.37419348578647\n",
53+
"neuro patient arrival at asu: 14.603970704254838\n",
54+
"other patient arrival at asu: 15.574833616307146\n",
55+
"stroke patient arrival at asu: 16.305207341437182\n",
56+
"stroke patient arrival at asu: 17.393245485572375\n",
57+
"neuro patient arrival at asu: 19.11249234567026\n",
58+
"neuro patient arrival at asu: 19.9259105341734\n"
59+
]
60+
}
61+
],
62+
"source": [
63+
"model = Model(param=Param(), run_number=0)\n",
64+
"model.run()"
65+
]
66+
}
67+
],
68+
"metadata": {
69+
"kernelspec": {
70+
"display_name": "stroke-rap-python",
71+
"language": "python",
72+
"name": "python3"
73+
},
74+
"language_info": {
75+
"codemirror_mode": {
76+
"name": "ipython",
77+
"version": 3
78+
},
79+
"file_extension": ".py",
80+
"mimetype": "text/x-python",
81+
"name": "python",
82+
"nbconvert_exporter": "python",
83+
"pygments_lexer": "ipython3",
84+
"version": "3.13.1"
85+
}
86+
},
87+
"nbformat": 4,
88+
"nbformat_minor": 2
89+
}

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ pytest==8.3.4
1414
pytest-xdist==3.6.1
1515
rich==13.9.4
1616
simpy==4.1.1
17+
sim-tools==0.8.0
1718
-e .[dev]

simulation/model.py

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
"""
2+
Code for the simulation.
3+
"""
4+
5+
import numpy as np
6+
import simpy
7+
from sim_tools.distributions import Exponential
8+
9+
10+
class Patient:
11+
"""
12+
Represents a patient.
13+
14+
Attributes
15+
----------
16+
patient_id: int, float or str
17+
Unique patient identifier.
18+
patient_type: str
19+
Patient type ("stroke", "tia", "neuro" or "other").
20+
arrival_time: float
21+
Arrival time for the patient (in days).
22+
"""
23+
def __init__(self, patient_id, patient_type):
24+
"""
25+
Parameters
26+
----------
27+
patient_id: int, float or str
28+
Unique patient identifier.
29+
patient_type: str
30+
Patient type ("stroke", "tia", "neuro" or "other").
31+
"""
32+
self.patient_id = patient_id
33+
self.patient_type = patient_type
34+
self.arrival_time = np.nan
35+
36+
37+
class Model:
38+
"""
39+
Simulation model.
40+
41+
Attributes
42+
----------
43+
param: Param
44+
Run parameters.
45+
run_number: int
46+
Replication / run number.
47+
env: simpy.Environment
48+
Simulation environment.
49+
patients: list
50+
Stores the Patient objects.
51+
distributions: dictionary
52+
Contains the sampling distributions.
53+
"""
54+
def __init__(self, param, run_number):
55+
"""
56+
Parameters
57+
----------
58+
param: Param
59+
Run parameters.
60+
run_number: int
61+
Replication / run number.
62+
"""
63+
# Set parameters
64+
self.param = param
65+
self.run_number = run_number
66+
67+
# Create SimPy environment
68+
self.env = simpy.Environment()
69+
70+
# Create attributes to store results
71+
# The patients list will store a reference to the patient objects, so
72+
# any updates to the patient attributes after they are saved to the
73+
# list, will be reflected in the list as well
74+
self.patients = []
75+
76+
# Create seeds
77+
ss = np.random.SeedSequence(entropy=self.run_number)
78+
seed_generator = iter(ss.spawn(20))
79+
80+
# Create distributions
81+
self.create_distributions(seed_generator)
82+
83+
def create_distributions(self, seed_generator):
84+
"""
85+
Creates distributions for sampling arrivals for all units and patient
86+
types.
87+
88+
Parameters
89+
----------
90+
seed_generator: Iterator
91+
Iterator that generates random seeds.
92+
"""
93+
# Create dictionary to store distributions
94+
self.distributions = {}
95+
96+
# Loop through each unit
97+
for unit, unit_param in [("asu", self.param.asu_arrivals),
98+
("rehab", self.param.rehab_arrivals)]:
99+
100+
# Make sub-dictionary to store that unit's distributions
101+
self.distributions[unit] = {}
102+
103+
# Get a list of the patients in that unit (ignore other attributes)
104+
patient_types = [attr for attr in dir(unit_param) if attr in
105+
["stroke", "tia", "neuro", "other"]]
106+
107+
for patient_type in patient_types:
108+
109+
# Create distributions for each patient type in that unti
110+
self.distributions[unit][patient_type] = Exponential(
111+
mean=getattr(unit_param, patient_type),
112+
random_seed=next(seed_generator)
113+
)
114+
115+
def patient_generator(self, patient_type, distribution, unit):
116+
"""
117+
Generic patient generator for any patient type and unit.
118+
119+
Parameters
120+
----------
121+
patient_type: str
122+
Type of patient ("stroke", "tia", "neuro", "other").
123+
distribution: Distribution
124+
The inter-arrival time distribution to sample from.
125+
unit: str
126+
The unit the patient is arriving at ("asu", "rehab").
127+
"""
128+
while True:
129+
# Sample and pass time to arrival
130+
sampled_iat = distribution.sample()
131+
yield self.env.timeout(sampled_iat)
132+
133+
# Create a new patient
134+
p = Patient(
135+
patient_id=len(self.patients)+1,
136+
patient_type=patient_type
137+
)
138+
139+
# Record arrival time
140+
p.arrival_time = self.env.now
141+
142+
# Print arrival time
143+
print(f"{patient_type} patient arrive at {unit}: {p.arrival_time}")
144+
145+
# Add to the patients list
146+
self.patients.append(p)
147+
148+
def run(self):
149+
"""
150+
Run the simulation.
151+
"""
152+
# Calculate the total run length
153+
run_length = (self.param.warm_up_period +
154+
self.param.data_collection_period)
155+
156+
# Schedule patient generators to run during the simulation
157+
for unit, patient_types in self.distributions.items():
158+
for patient_type, distribution in patient_types.items():
159+
self.env.process(
160+
self.patient_generator(
161+
patient_type=patient_type,
162+
distribution=distribution,
163+
unit=unit
164+
)
165+
)
166+
167+
# Run the simulation
168+
self.env.run(until=run_length)

0 commit comments

Comments
 (0)