epiworldpy 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- epiworldpy-0.0.1/LICENSE +36 -0
- epiworldpy-0.0.1/PKG-INFO +346 -0
- epiworldpy-0.0.1/README.md +316 -0
- epiworldpy-0.0.1/README.rst +379 -0
- epiworldpy-0.0.1/pyproject.toml +101 -0
- epiworldpy-0.0.1/setup.cfg +4 -0
- epiworldpy-0.0.1/setup.py +7 -0
- epiworldpy-0.0.1/src/epiworldpy/__init__.py +10 -0
- epiworldpy-0.0.1/src/epiworldpy.egg-info/PKG-INFO +346 -0
- epiworldpy-0.0.1/src/epiworldpy.egg-info/SOURCES.txt +15 -0
- epiworldpy-0.0.1/src/epiworldpy.egg-info/dependency_links.txt +1 -0
- epiworldpy-0.0.1/src/epiworldpy.egg-info/requires.txt +16 -0
- epiworldpy-0.0.1/src/epiworldpy.egg-info/top_level.txt +1 -0
- epiworldpy-0.0.1/tests/test_basic.py +4 -0
- epiworldpy-0.0.1/tests/test_db.py +49 -0
- epiworldpy-0.0.1/tests/test_models.py +75 -0
- epiworldpy-0.0.1/tests/test_saver.py +18 -0
epiworldpy-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
Copyright (c) 2016 The Pybind Development Team, All rights reserved.
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without
|
|
4
|
+
modification, are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
7
|
+
list of conditions and the following disclaimer.
|
|
8
|
+
|
|
9
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
|
11
|
+
and/or other materials provided with the distribution.
|
|
12
|
+
|
|
13
|
+
3. Neither the name of the copyright holder nor the names of its contributors
|
|
14
|
+
may be used to endorse or promote products derived from this software
|
|
15
|
+
without specific prior written permission.
|
|
16
|
+
|
|
17
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
18
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
19
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
20
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
21
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
22
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
23
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
24
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
25
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
26
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
27
|
+
|
|
28
|
+
You are under no obligation whatsoever to provide any bug fixes, patches, or
|
|
29
|
+
upgrades to the features, functionality or performance of the source code
|
|
30
|
+
("Enhancements") to anyone; however, if you choose to make your Enhancements
|
|
31
|
+
available either publicly, or directly to the author of this software, without
|
|
32
|
+
imposing a separate written license agreement for such Enhancements, then you
|
|
33
|
+
hereby grant the following license: a non-exclusive, royalty-free perpetual
|
|
34
|
+
license to install, use, modify, prepare derivative works, incorporate into
|
|
35
|
+
other computer software, distribute, and sublicense such enhancements or
|
|
36
|
+
derivative works thereof, in binary and source code form.
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: epiworldpy
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python bindings for epiworld
|
|
5
|
+
Author-email: "George G. Vega Yon" <g.vegayon@gmail.com>, Milo Banks <milobanks@rowlandhall.org>
|
|
6
|
+
Classifier: Development Status :: 4 - Beta
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.7
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Requires-Python: >=3.7
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
Requires-Dist: pytest; extra == "test"
|
|
20
|
+
Provides-Extra: doc
|
|
21
|
+
Requires-Dist: nbformat; extra == "doc"
|
|
22
|
+
Requires-Dist: nbclient; extra == "doc"
|
|
23
|
+
Requires-Dist: jupyter; extra == "doc"
|
|
24
|
+
Requires-Dist: epiworldpy[viz]; extra == "doc"
|
|
25
|
+
Provides-Extra: viz
|
|
26
|
+
Requires-Dist: ipympl>=0.8; extra == "viz"
|
|
27
|
+
Requires-Dist: matplotlib>=3.5.0; extra == "viz"
|
|
28
|
+
Requires-Dist: networkx>=3.0; extra == "viz"
|
|
29
|
+
Requires-Dist: scipy>=1.0; extra == "viz"
|
|
30
|
+
|
|
31
|
+
# epiworldpy: Python bindings for epiworld
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
[](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml)
|
|
36
|
+
|
|
37
|
+
This is a python wrapper of the [`epiworld c++`
|
|
38
|
+
library](https://github.com/UofUEpiBio/epiworld/), an ABM simulation
|
|
39
|
+
engine. This is possible using the
|
|
40
|
+
[`pybind11`](https://pybind11.readthedocs.io/en/stable/) library (which
|
|
41
|
+
rocks!).
|
|
42
|
+
|
|
43
|
+
The `epiworld` module is already
|
|
44
|
+
<a href="https://github.com/UofUEpiBio/epiworldR"
|
|
45
|
+
target="_blank">implemented in R</a>.
|
|
46
|
+
|
|
47
|
+
# Installation
|
|
48
|
+
|
|
49
|
+
- clone this repository
|
|
50
|
+
- `pip install ./epiworldpy`
|
|
51
|
+
|
|
52
|
+
# Examples
|
|
53
|
+
|
|
54
|
+
## Basic
|
|
55
|
+
|
|
56
|
+
Here we show how to create a `SEIR` object and add terms to it. We will
|
|
57
|
+
use the following data:
|
|
58
|
+
|
|
59
|
+
``` python
|
|
60
|
+
# Loading the module
|
|
61
|
+
import epiworldpy as epiworld
|
|
62
|
+
|
|
63
|
+
# Create a SEIR model (susceptible, exposed, infectious, recovered), representing COVID-19.
|
|
64
|
+
covid19 = epiworld.ModelSEIR(
|
|
65
|
+
name = 'covid-19',
|
|
66
|
+
n = 10000,
|
|
67
|
+
prevalence = .01,
|
|
68
|
+
contact_rate = 2.0,
|
|
69
|
+
transmission_rate = .1,
|
|
70
|
+
incubation_days = 7.0,
|
|
71
|
+
recovery_rate = 0.14
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# Taking a look
|
|
75
|
+
covid19.print(False)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
________________________________________________________________________________
|
|
79
|
+
________________________________________________________________________________
|
|
80
|
+
SIMULATION STUDY
|
|
81
|
+
|
|
82
|
+
Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
|
|
83
|
+
Population size : 10000
|
|
84
|
+
Agents' data : (none)
|
|
85
|
+
Number of entities : 0
|
|
86
|
+
Days (duration) : 0 (of 0)
|
|
87
|
+
Number of viruses : 1
|
|
88
|
+
Last run elapsed t : -
|
|
89
|
+
Rewiring : off
|
|
90
|
+
|
|
91
|
+
Global events:
|
|
92
|
+
- Update infected individuals (runs daily)
|
|
93
|
+
|
|
94
|
+
Virus(es):
|
|
95
|
+
- covid-19 (baseline prevalence: 1.00%)
|
|
96
|
+
|
|
97
|
+
Tool(s):
|
|
98
|
+
(none)
|
|
99
|
+
|
|
100
|
+
Model parameters:
|
|
101
|
+
- Avg. Incubation days : 7.0000
|
|
102
|
+
- Contact rate : 2.0000
|
|
103
|
+
- Prob. Recovery : 0.1400
|
|
104
|
+
- Prob. Transmission : 0.1000
|
|
105
|
+
|
|
106
|
+
<epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
|
|
107
|
+
|
|
108
|
+
Let’s run it and to see what we get:
|
|
109
|
+
|
|
110
|
+
``` python
|
|
111
|
+
# Run for 100 days with a seed of 223.
|
|
112
|
+
covid19.run(100, 223)
|
|
113
|
+
|
|
114
|
+
# Print an overview.
|
|
115
|
+
covid19.print(False)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
_________________________________________________________________________
|
|
119
|
+
Running the model...
|
|
120
|
+
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done.
|
|
121
|
+
done.
|
|
122
|
+
________________________________________________________________________________
|
|
123
|
+
________________________________________________________________________________
|
|
124
|
+
SIMULATION STUDY
|
|
125
|
+
|
|
126
|
+
Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
|
|
127
|
+
Population size : 10000
|
|
128
|
+
Agents' data : (none)
|
|
129
|
+
Number of entities : 0
|
|
130
|
+
Days (duration) : 100 (of 100)
|
|
131
|
+
Number of viruses : 1
|
|
132
|
+
Last run elapsed t : 14.00ms
|
|
133
|
+
Last run speed : 70.84 million agents x day / second
|
|
134
|
+
Rewiring : off
|
|
135
|
+
|
|
136
|
+
Global events:
|
|
137
|
+
- Update infected individuals (runs daily)
|
|
138
|
+
|
|
139
|
+
Virus(es):
|
|
140
|
+
- covid-19 (baseline prevalence: 1.00%)
|
|
141
|
+
|
|
142
|
+
Tool(s):
|
|
143
|
+
(none)
|
|
144
|
+
|
|
145
|
+
Model parameters:
|
|
146
|
+
- Avg. Incubation days : 7.0000
|
|
147
|
+
- Contact rate : 2.0000
|
|
148
|
+
- Prob. Recovery : 0.1400
|
|
149
|
+
- Prob. Transmission : 0.1000
|
|
150
|
+
|
|
151
|
+
Distribution of the population at time 100:
|
|
152
|
+
- (0) Susceptible : 9900 -> 7275
|
|
153
|
+
- (1) Exposed : 100 -> 269
|
|
154
|
+
- (2) Infected : 0 -> 292
|
|
155
|
+
- (3) Recovered : 0 -> 2164
|
|
156
|
+
|
|
157
|
+
Transition Probabilities:
|
|
158
|
+
- Susceptible 1.00 0.00 0.00 0.00
|
|
159
|
+
- Exposed 0.00 0.85 0.15 0.00
|
|
160
|
+
- Infected 0.00 0.00 0.86 0.14
|
|
161
|
+
- Recovered 0.00 0.00 0.00 1.00
|
|
162
|
+
|
|
163
|
+
<epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
|
|
164
|
+
|
|
165
|
+
We can now visualize the model’s compartments:
|
|
166
|
+
|
|
167
|
+
``` python
|
|
168
|
+
import numpy as np
|
|
169
|
+
import matplotlib.pyplot as plt
|
|
170
|
+
|
|
171
|
+
# Get the data from the database
|
|
172
|
+
history = covid19.get_db().get_hist_total()
|
|
173
|
+
|
|
174
|
+
# Extract unique states and dates
|
|
175
|
+
unique_states = np.unique(history['states'])
|
|
176
|
+
unique_dates = np.unique(history['dates'])
|
|
177
|
+
|
|
178
|
+
# Remove some data that will mess with scaling
|
|
179
|
+
unique_states = np.delete(unique_states, np.where(unique_states == 'Susceptible'))
|
|
180
|
+
|
|
181
|
+
# Initialize a dictionary to store time series data for each state
|
|
182
|
+
time_series_data = {state: [] for state in unique_states}
|
|
183
|
+
|
|
184
|
+
# Populate the time series data for each state
|
|
185
|
+
for state in unique_states:
|
|
186
|
+
for date in unique_dates:
|
|
187
|
+
# Get the count for the current state and date
|
|
188
|
+
mask = (history['states'] == state) & (history['dates'] == date)
|
|
189
|
+
count = history['counts'][mask][0]
|
|
190
|
+
time_series_data[state].append(count)
|
|
191
|
+
|
|
192
|
+
# Start the plotting!
|
|
193
|
+
plt.figure(figsize=(10, 6))
|
|
194
|
+
|
|
195
|
+
for state in unique_states:
|
|
196
|
+
plt.plot(unique_dates, time_series_data[state], label=state)
|
|
197
|
+
|
|
198
|
+
plt.xlabel('Day')
|
|
199
|
+
plt.ylabel('Count')
|
|
200
|
+
plt.title('COVID-19 SEIR Model Data')
|
|
201
|
+
plt.legend()
|
|
202
|
+
plt.grid(True)
|
|
203
|
+
plt.show()
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+

|
|
208
|
+
|
|
209
|
+
We can get the effective reproductive number, over time, too:
|
|
210
|
+
|
|
211
|
+
``` python
|
|
212
|
+
reproductive_data = covid19.get_db().get_reproductive_number()
|
|
213
|
+
|
|
214
|
+
# Start the plotting!
|
|
215
|
+
plt.figure(figsize=(10, 6))
|
|
216
|
+
|
|
217
|
+
for virus_id, virus_data in enumerate(reproductive_data):
|
|
218
|
+
average_rts = list()
|
|
219
|
+
|
|
220
|
+
for date_data in virus_data:
|
|
221
|
+
if not date_data:
|
|
222
|
+
continue
|
|
223
|
+
|
|
224
|
+
keys_array = np.array(list(date_data.values()), dtype=np.float64)
|
|
225
|
+
average_rts.append(np.mean(keys_array))
|
|
226
|
+
|
|
227
|
+
plt.plot(range(0, len(virus_data)-1), average_rts, label=f"Virus {virus_id}")
|
|
228
|
+
|
|
229
|
+
plt.xlabel('Date')
|
|
230
|
+
plt.ylabel('Effective Reproductive Rate')
|
|
231
|
+
plt.title('COVID-19 SEIR Model Effective Reproductive Rate')
|
|
232
|
+
plt.legend()
|
|
233
|
+
plt.grid(True)
|
|
234
|
+
plt.show()
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+

|
|
239
|
+
|
|
240
|
+
Let’s do the same for generation time:
|
|
241
|
+
|
|
242
|
+
``` python
|
|
243
|
+
from collections import defaultdict
|
|
244
|
+
|
|
245
|
+
generation_time = covid19.get_db().get_generation_time()
|
|
246
|
+
agents = generation_time['agents']
|
|
247
|
+
viruses = generation_time['viruses']
|
|
248
|
+
times = generation_time['times']
|
|
249
|
+
gentimes = generation_time['gentimes']
|
|
250
|
+
|
|
251
|
+
# Data formatting
|
|
252
|
+
unique_viruses = np.unique(viruses)
|
|
253
|
+
data = defaultdict(lambda: defaultdict(list))
|
|
254
|
+
|
|
255
|
+
for agent, virus, time, gentime in zip(agents, viruses, times, gentimes):
|
|
256
|
+
data[virus][time].append(gentime)
|
|
257
|
+
|
|
258
|
+
average_data = {virus: {} for virus in unique_viruses}
|
|
259
|
+
|
|
260
|
+
for virus, time_dict in data.items():
|
|
261
|
+
for time, gentime_list in time_dict.items():
|
|
262
|
+
average_data[virus][time] = np.mean(gentime_list)
|
|
263
|
+
|
|
264
|
+
# Plotting
|
|
265
|
+
plt.figure(figsize=(10, 6))
|
|
266
|
+
for virus, time_dict in average_data.items():
|
|
267
|
+
times = sorted(time_dict.keys())
|
|
268
|
+
gentimes = [time_dict[time] for time in times]
|
|
269
|
+
plt.plot(times, gentimes, label=f'Virus {virus}')
|
|
270
|
+
|
|
271
|
+
plt.xlabel('Date')
|
|
272
|
+
plt.ylabel('Generation Time')
|
|
273
|
+
plt.title('COVID-19 SEIR Model Generation Time')
|
|
274
|
+
plt.legend()
|
|
275
|
+
plt.grid(True)
|
|
276
|
+
plt.show()
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+

|
|
281
|
+
|
|
282
|
+
Epiworld records agent-agent interactions, and we can graph those too.
|
|
283
|
+
In the below example, we only track all cases stemming from a specific
|
|
284
|
+
index case, despite the model having a prevalence of 0.01.
|
|
285
|
+
|
|
286
|
+
``` python
|
|
287
|
+
import networkx as nx
|
|
288
|
+
from matplotlib.animation import FuncAnimation
|
|
289
|
+
|
|
290
|
+
transmissions = covid19.get_db().get_transmissions()
|
|
291
|
+
start = transmissions['source_exposure_dates']
|
|
292
|
+
end = transmissions['dates']
|
|
293
|
+
source = transmissions['sources']
|
|
294
|
+
target = transmissions['targets']
|
|
295
|
+
days = max(end)
|
|
296
|
+
|
|
297
|
+
graph = nx.Graph()
|
|
298
|
+
fig, ax = plt.subplots(figsize=(6,4))
|
|
299
|
+
|
|
300
|
+
# Animation function
|
|
301
|
+
to_track = { source[0] }
|
|
302
|
+
def update(frame):
|
|
303
|
+
ax.clear()
|
|
304
|
+
|
|
305
|
+
agents_involved_today = set()
|
|
306
|
+
agents_relationships_we_care_about = []
|
|
307
|
+
|
|
308
|
+
# Get only the agents involved in the current frame.
|
|
309
|
+
for i in range(len(start)):
|
|
310
|
+
if start[i] <= frame <= end[i]:
|
|
311
|
+
agents_involved_today.add((source[i], target[i]))
|
|
312
|
+
|
|
313
|
+
# Get only today's agents who have some connection to agents
|
|
314
|
+
# we've seen before.
|
|
315
|
+
for agent in agents_involved_today:
|
|
316
|
+
if agent[0] in to_track or agent[1] in to_track:
|
|
317
|
+
to_track.add(agent[0])
|
|
318
|
+
to_track.add(agent[1])
|
|
319
|
+
graph.add_edge(agent[0], agent[1])
|
|
320
|
+
|
|
321
|
+
# Lay and space them out.
|
|
322
|
+
pos = nx.kamada_kawai_layout(graph)
|
|
323
|
+
|
|
324
|
+
options = {
|
|
325
|
+
"with_labels": True,
|
|
326
|
+
"node_size": 300,
|
|
327
|
+
"font_size": 6,
|
|
328
|
+
"node_color": "white",
|
|
329
|
+
"edgecolors": "white",
|
|
330
|
+
"linewidths": 1,
|
|
331
|
+
"width": 1,
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
# Graph!
|
|
335
|
+
nx.draw_networkx(graph, pos, **options)
|
|
336
|
+
ax.set_title(f"COVID-19 SEIR Model Agent Contact (Day {frame})")
|
|
337
|
+
|
|
338
|
+
ani = FuncAnimation(fig, update, frames=int(days/3), interval=200, repeat=False)
|
|
339
|
+
plt.show()
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
<!-- I couldn't figure out a way to get Quarto to do animations correctly so we're
|
|
343
|
+
hardcoding a GIF. -->
|
|
344
|
+
|
|
345
|
+

|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
# epiworldpy: Python bindings for epiworld
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
[](https://github.com/UofUEpiBio/epiworldpy/actions/workflows/pip.yaml)
|
|
6
|
+
|
|
7
|
+
This is a python wrapper of the [`epiworld c++`
|
|
8
|
+
library](https://github.com/UofUEpiBio/epiworld/), an ABM simulation
|
|
9
|
+
engine. This is possible using the
|
|
10
|
+
[`pybind11`](https://pybind11.readthedocs.io/en/stable/) library (which
|
|
11
|
+
rocks!).
|
|
12
|
+
|
|
13
|
+
The `epiworld` module is already
|
|
14
|
+
<a href="https://github.com/UofUEpiBio/epiworldR"
|
|
15
|
+
target="_blank">implemented in R</a>.
|
|
16
|
+
|
|
17
|
+
# Installation
|
|
18
|
+
|
|
19
|
+
- clone this repository
|
|
20
|
+
- `pip install ./epiworldpy`
|
|
21
|
+
|
|
22
|
+
# Examples
|
|
23
|
+
|
|
24
|
+
## Basic
|
|
25
|
+
|
|
26
|
+
Here we show how to create a `SEIR` object and add terms to it. We will
|
|
27
|
+
use the following data:
|
|
28
|
+
|
|
29
|
+
``` python
|
|
30
|
+
# Loading the module
|
|
31
|
+
import epiworldpy as epiworld
|
|
32
|
+
|
|
33
|
+
# Create a SEIR model (susceptible, exposed, infectious, recovered), representing COVID-19.
|
|
34
|
+
covid19 = epiworld.ModelSEIR(
|
|
35
|
+
name = 'covid-19',
|
|
36
|
+
n = 10000,
|
|
37
|
+
prevalence = .01,
|
|
38
|
+
contact_rate = 2.0,
|
|
39
|
+
transmission_rate = .1,
|
|
40
|
+
incubation_days = 7.0,
|
|
41
|
+
recovery_rate = 0.14
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# Taking a look
|
|
45
|
+
covid19.print(False)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
________________________________________________________________________________
|
|
49
|
+
________________________________________________________________________________
|
|
50
|
+
SIMULATION STUDY
|
|
51
|
+
|
|
52
|
+
Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
|
|
53
|
+
Population size : 10000
|
|
54
|
+
Agents' data : (none)
|
|
55
|
+
Number of entities : 0
|
|
56
|
+
Days (duration) : 0 (of 0)
|
|
57
|
+
Number of viruses : 1
|
|
58
|
+
Last run elapsed t : -
|
|
59
|
+
Rewiring : off
|
|
60
|
+
|
|
61
|
+
Global events:
|
|
62
|
+
- Update infected individuals (runs daily)
|
|
63
|
+
|
|
64
|
+
Virus(es):
|
|
65
|
+
- covid-19 (baseline prevalence: 1.00%)
|
|
66
|
+
|
|
67
|
+
Tool(s):
|
|
68
|
+
(none)
|
|
69
|
+
|
|
70
|
+
Model parameters:
|
|
71
|
+
- Avg. Incubation days : 7.0000
|
|
72
|
+
- Contact rate : 2.0000
|
|
73
|
+
- Prob. Recovery : 0.1400
|
|
74
|
+
- Prob. Transmission : 0.1000
|
|
75
|
+
|
|
76
|
+
<epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
|
|
77
|
+
|
|
78
|
+
Let’s run it and to see what we get:
|
|
79
|
+
|
|
80
|
+
``` python
|
|
81
|
+
# Run for 100 days with a seed of 223.
|
|
82
|
+
covid19.run(100, 223)
|
|
83
|
+
|
|
84
|
+
# Print an overview.
|
|
85
|
+
covid19.print(False)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
_________________________________________________________________________
|
|
89
|
+
Running the model...
|
|
90
|
+
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| done.
|
|
91
|
+
done.
|
|
92
|
+
________________________________________________________________________________
|
|
93
|
+
________________________________________________________________________________
|
|
94
|
+
SIMULATION STUDY
|
|
95
|
+
|
|
96
|
+
Name of the model : Susceptible-Exposed-Infected-Removed (SEIR) (connected)
|
|
97
|
+
Population size : 10000
|
|
98
|
+
Agents' data : (none)
|
|
99
|
+
Number of entities : 0
|
|
100
|
+
Days (duration) : 100 (of 100)
|
|
101
|
+
Number of viruses : 1
|
|
102
|
+
Last run elapsed t : 14.00ms
|
|
103
|
+
Last run speed : 70.84 million agents x day / second
|
|
104
|
+
Rewiring : off
|
|
105
|
+
|
|
106
|
+
Global events:
|
|
107
|
+
- Update infected individuals (runs daily)
|
|
108
|
+
|
|
109
|
+
Virus(es):
|
|
110
|
+
- covid-19 (baseline prevalence: 1.00%)
|
|
111
|
+
|
|
112
|
+
Tool(s):
|
|
113
|
+
(none)
|
|
114
|
+
|
|
115
|
+
Model parameters:
|
|
116
|
+
- Avg. Incubation days : 7.0000
|
|
117
|
+
- Contact rate : 2.0000
|
|
118
|
+
- Prob. Recovery : 0.1400
|
|
119
|
+
- Prob. Transmission : 0.1000
|
|
120
|
+
|
|
121
|
+
Distribution of the population at time 100:
|
|
122
|
+
- (0) Susceptible : 9900 -> 7275
|
|
123
|
+
- (1) Exposed : 100 -> 269
|
|
124
|
+
- (2) Infected : 0 -> 292
|
|
125
|
+
- (3) Recovered : 0 -> 2164
|
|
126
|
+
|
|
127
|
+
Transition Probabilities:
|
|
128
|
+
- Susceptible 1.00 0.00 0.00 0.00
|
|
129
|
+
- Exposed 0.00 0.85 0.15 0.00
|
|
130
|
+
- Infected 0.00 0.00 0.86 0.14
|
|
131
|
+
- Recovered 0.00 0.00 0.00 1.00
|
|
132
|
+
|
|
133
|
+
<epiworldpy._core.ModelSEIRCONN at 0x1058e6e70>
|
|
134
|
+
|
|
135
|
+
We can now visualize the model’s compartments:
|
|
136
|
+
|
|
137
|
+
``` python
|
|
138
|
+
import numpy as np
|
|
139
|
+
import matplotlib.pyplot as plt
|
|
140
|
+
|
|
141
|
+
# Get the data from the database
|
|
142
|
+
history = covid19.get_db().get_hist_total()
|
|
143
|
+
|
|
144
|
+
# Extract unique states and dates
|
|
145
|
+
unique_states = np.unique(history['states'])
|
|
146
|
+
unique_dates = np.unique(history['dates'])
|
|
147
|
+
|
|
148
|
+
# Remove some data that will mess with scaling
|
|
149
|
+
unique_states = np.delete(unique_states, np.where(unique_states == 'Susceptible'))
|
|
150
|
+
|
|
151
|
+
# Initialize a dictionary to store time series data for each state
|
|
152
|
+
time_series_data = {state: [] for state in unique_states}
|
|
153
|
+
|
|
154
|
+
# Populate the time series data for each state
|
|
155
|
+
for state in unique_states:
|
|
156
|
+
for date in unique_dates:
|
|
157
|
+
# Get the count for the current state and date
|
|
158
|
+
mask = (history['states'] == state) & (history['dates'] == date)
|
|
159
|
+
count = history['counts'][mask][0]
|
|
160
|
+
time_series_data[state].append(count)
|
|
161
|
+
|
|
162
|
+
# Start the plotting!
|
|
163
|
+
plt.figure(figsize=(10, 6))
|
|
164
|
+
|
|
165
|
+
for state in unique_states:
|
|
166
|
+
plt.plot(unique_dates, time_series_data[state], label=state)
|
|
167
|
+
|
|
168
|
+
plt.xlabel('Day')
|
|
169
|
+
plt.ylabel('Count')
|
|
170
|
+
plt.title('COVID-19 SEIR Model Data')
|
|
171
|
+
plt.legend()
|
|
172
|
+
plt.grid(True)
|
|
173
|
+
plt.show()
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+

|
|
178
|
+
|
|
179
|
+
We can get the effective reproductive number, over time, too:
|
|
180
|
+
|
|
181
|
+
``` python
|
|
182
|
+
reproductive_data = covid19.get_db().get_reproductive_number()
|
|
183
|
+
|
|
184
|
+
# Start the plotting!
|
|
185
|
+
plt.figure(figsize=(10, 6))
|
|
186
|
+
|
|
187
|
+
for virus_id, virus_data in enumerate(reproductive_data):
|
|
188
|
+
average_rts = list()
|
|
189
|
+
|
|
190
|
+
for date_data in virus_data:
|
|
191
|
+
if not date_data:
|
|
192
|
+
continue
|
|
193
|
+
|
|
194
|
+
keys_array = np.array(list(date_data.values()), dtype=np.float64)
|
|
195
|
+
average_rts.append(np.mean(keys_array))
|
|
196
|
+
|
|
197
|
+
plt.plot(range(0, len(virus_data)-1), average_rts, label=f"Virus {virus_id}")
|
|
198
|
+
|
|
199
|
+
plt.xlabel('Date')
|
|
200
|
+
plt.ylabel('Effective Reproductive Rate')
|
|
201
|
+
plt.title('COVID-19 SEIR Model Effective Reproductive Rate')
|
|
202
|
+
plt.legend()
|
|
203
|
+
plt.grid(True)
|
|
204
|
+
plt.show()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+

|
|
209
|
+
|
|
210
|
+
Let’s do the same for generation time:
|
|
211
|
+
|
|
212
|
+
``` python
|
|
213
|
+
from collections import defaultdict
|
|
214
|
+
|
|
215
|
+
generation_time = covid19.get_db().get_generation_time()
|
|
216
|
+
agents = generation_time['agents']
|
|
217
|
+
viruses = generation_time['viruses']
|
|
218
|
+
times = generation_time['times']
|
|
219
|
+
gentimes = generation_time['gentimes']
|
|
220
|
+
|
|
221
|
+
# Data formatting
|
|
222
|
+
unique_viruses = np.unique(viruses)
|
|
223
|
+
data = defaultdict(lambda: defaultdict(list))
|
|
224
|
+
|
|
225
|
+
for agent, virus, time, gentime in zip(agents, viruses, times, gentimes):
|
|
226
|
+
data[virus][time].append(gentime)
|
|
227
|
+
|
|
228
|
+
average_data = {virus: {} for virus in unique_viruses}
|
|
229
|
+
|
|
230
|
+
for virus, time_dict in data.items():
|
|
231
|
+
for time, gentime_list in time_dict.items():
|
|
232
|
+
average_data[virus][time] = np.mean(gentime_list)
|
|
233
|
+
|
|
234
|
+
# Plotting
|
|
235
|
+
plt.figure(figsize=(10, 6))
|
|
236
|
+
for virus, time_dict in average_data.items():
|
|
237
|
+
times = sorted(time_dict.keys())
|
|
238
|
+
gentimes = [time_dict[time] for time in times]
|
|
239
|
+
plt.plot(times, gentimes, label=f'Virus {virus}')
|
|
240
|
+
|
|
241
|
+
plt.xlabel('Date')
|
|
242
|
+
plt.ylabel('Generation Time')
|
|
243
|
+
plt.title('COVID-19 SEIR Model Generation Time')
|
|
244
|
+
plt.legend()
|
|
245
|
+
plt.grid(True)
|
|
246
|
+
plt.show()
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+

|
|
251
|
+
|
|
252
|
+
Epiworld records agent-agent interactions, and we can graph those too.
|
|
253
|
+
In the below example, we only track all cases stemming from a specific
|
|
254
|
+
index case, despite the model having a prevalence of 0.01.
|
|
255
|
+
|
|
256
|
+
``` python
|
|
257
|
+
import networkx as nx
|
|
258
|
+
from matplotlib.animation import FuncAnimation
|
|
259
|
+
|
|
260
|
+
transmissions = covid19.get_db().get_transmissions()
|
|
261
|
+
start = transmissions['source_exposure_dates']
|
|
262
|
+
end = transmissions['dates']
|
|
263
|
+
source = transmissions['sources']
|
|
264
|
+
target = transmissions['targets']
|
|
265
|
+
days = max(end)
|
|
266
|
+
|
|
267
|
+
graph = nx.Graph()
|
|
268
|
+
fig, ax = plt.subplots(figsize=(6,4))
|
|
269
|
+
|
|
270
|
+
# Animation function
|
|
271
|
+
to_track = { source[0] }
|
|
272
|
+
def update(frame):
|
|
273
|
+
ax.clear()
|
|
274
|
+
|
|
275
|
+
agents_involved_today = set()
|
|
276
|
+
agents_relationships_we_care_about = []
|
|
277
|
+
|
|
278
|
+
# Get only the agents involved in the current frame.
|
|
279
|
+
for i in range(len(start)):
|
|
280
|
+
if start[i] <= frame <= end[i]:
|
|
281
|
+
agents_involved_today.add((source[i], target[i]))
|
|
282
|
+
|
|
283
|
+
# Get only today's agents who have some connection to agents
|
|
284
|
+
# we've seen before.
|
|
285
|
+
for agent in agents_involved_today:
|
|
286
|
+
if agent[0] in to_track or agent[1] in to_track:
|
|
287
|
+
to_track.add(agent[0])
|
|
288
|
+
to_track.add(agent[1])
|
|
289
|
+
graph.add_edge(agent[0], agent[1])
|
|
290
|
+
|
|
291
|
+
# Lay and space them out.
|
|
292
|
+
pos = nx.kamada_kawai_layout(graph)
|
|
293
|
+
|
|
294
|
+
options = {
|
|
295
|
+
"with_labels": True,
|
|
296
|
+
"node_size": 300,
|
|
297
|
+
"font_size": 6,
|
|
298
|
+
"node_color": "white",
|
|
299
|
+
"edgecolors": "white",
|
|
300
|
+
"linewidths": 1,
|
|
301
|
+
"width": 1,
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
# Graph!
|
|
305
|
+
nx.draw_networkx(graph, pos, **options)
|
|
306
|
+
ax.set_title(f"COVID-19 SEIR Model Agent Contact (Day {frame})")
|
|
307
|
+
|
|
308
|
+
ani = FuncAnimation(fig, update, frames=int(days/3), interval=200, repeat=False)
|
|
309
|
+
plt.show()
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
<!-- I couldn't figure out a way to get Quarto to do animations correctly so we're
|
|
313
|
+
hardcoding a GIF. -->
|
|
314
|
+
|
|
315
|
+

|