emod-api 3.0.2__py3-none-any.whl
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.
- emod_api/__init__.py +1 -0
- emod_api/campaign.py +170 -0
- emod_api/channelreports/__init__.py +0 -0
- emod_api/channelreports/channels.py +433 -0
- emod_api/channelreports/icj_to_csv.py +65 -0
- emod_api/channelreports/plot_icj_means.py +149 -0
- emod_api/channelreports/plot_prop_report.py +205 -0
- emod_api/channelreports/utils.py +326 -0
- emod_api/config/__init__.py +0 -0
- emod_api/config/default_from_schema.py +16 -0
- emod_api/config/default_from_schema_no_validation.py +177 -0
- emod_api/config/from_overrides.py +135 -0
- emod_api/demographics/__init__.py +0 -0
- emod_api/demographics/age_distribution.py +163 -0
- emod_api/demographics/base_input_file.py +28 -0
- emod_api/demographics/calculators.py +159 -0
- emod_api/demographics/demographic_exceptions.py +54 -0
- emod_api/demographics/demographics.py +249 -0
- emod_api/demographics/demographics_base.py +752 -0
- emod_api/demographics/demographics_overlay.py +41 -0
- emod_api/demographics/fertility_distribution.py +235 -0
- emod_api/demographics/implicit_functions.py +112 -0
- emod_api/demographics/mortality_distribution.py +227 -0
- emod_api/demographics/node.py +456 -0
- emod_api/demographics/overlay_node.py +16 -0
- emod_api/demographics/properties_and_attributes.py +737 -0
- emod_api/demographics/service/__init__.py +0 -0
- emod_api/demographics/service/grid_construction.py +143 -0
- emod_api/demographics/service/service.py +55 -0
- emod_api/demographics/susceptibility_distribution.py +170 -0
- emod_api/demographics/updateable.py +58 -0
- emod_api/legacy/__init__.py +0 -0
- emod_api/legacy/plotAllCharts.py +230 -0
- emod_api/migration/__init__.py +0 -0
- emod_api/migration/__main__.py +22 -0
- emod_api/migration/migration.py +782 -0
- emod_api/multidim_plotter.py +80 -0
- emod_api/schema_to_class.py +440 -0
- emod_api/serialization/__init__.py +0 -0
- emod_api/serialization/census_and_mod_pop.py +48 -0
- emod_api/serialization/dtk_file_support.py +61 -0
- emod_api/serialization/dtk_file_tools.py +1378 -0
- emod_api/serialization/dtk_file_utility.py +141 -0
- emod_api/serialization/serialized_population.py +205 -0
- emod_api/spatialreports/__init__.py +0 -0
- emod_api/spatialreports/__main__.py +67 -0
- emod_api/spatialreports/plot_spat_means.py +99 -0
- emod_api/spatialreports/spatial.py +210 -0
- emod_api/utils/__init__.py +26 -0
- emod_api/utils/distributions/__init__.py +0 -0
- emod_api/utils/distributions/base_distribution.py +38 -0
- emod_api/utils/distributions/bimodal_distribution.py +64 -0
- emod_api/utils/distributions/constant_distribution.py +58 -0
- emod_api/utils/distributions/demographic_distribution_flag.py +16 -0
- emod_api/utils/distributions/distribution_type.py +15 -0
- emod_api/utils/distributions/dual_constant_distribution.py +68 -0
- emod_api/utils/distributions/dual_exponential_distribution.py +75 -0
- emod_api/utils/distributions/exponential_distribution.py +63 -0
- emod_api/utils/distributions/gaussian_distribution.py +69 -0
- emod_api/utils/distributions/log_normal_distribution.py +61 -0
- emod_api/utils/distributions/poisson_distribution.py +59 -0
- emod_api/utils/distributions/uniform_distribution.py +70 -0
- emod_api/utils/distributions/weibull_distribution.py +69 -0
- emod_api/utils/str_enum.py +6 -0
- emod_api/weather/__init__.py +0 -0
- emod_api/weather/weather.py +428 -0
- emod_api-3.0.2.dist-info/METADATA +131 -0
- emod_api-3.0.2.dist-info/RECORD +71 -0
- emod_api-3.0.2.dist-info/WHEEL +5 -0
- emod_api-3.0.2.dist-info/licenses/LICENSE +21 -0
- emod_api-3.0.2.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
emod-api Weather module - Weather, Metadata, and WeatherNode objects along with IDREF and CLIMATE_UPDATE constants.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from collections import namedtuple
|
|
8
|
+
from functools import reduce
|
|
9
|
+
import csv
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
import getpass
|
|
12
|
+
import json
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
IDREF_LEGACY = "Legacy"
|
|
17
|
+
IDREF_GRUMP30ARCSEC = "Gridded world grump30arcsec"
|
|
18
|
+
IDREF_GRUMP2PT5ARCMIN = "Gridded world grump2.5arcmin"
|
|
19
|
+
IDREF_GRUMP1DEGREE = "Gridded world grump1degree"
|
|
20
|
+
|
|
21
|
+
CLIMATE_UPDATE_YEAR = "CLIMATE_UPDATE_YEAR"
|
|
22
|
+
CLIMATE_UPDATE_MONTH = "CLIMATE_UPDATE_MONTH"
|
|
23
|
+
CLIMATE_UPDATE_WEEK = "CLIMATE_UPDATE_WEEK"
|
|
24
|
+
CLIMATE_UPDATE_DAY = "CLIMATE_UPDATE_DAY"
|
|
25
|
+
CLIMATE_UPDATE_HOUR = "CLIMATE_UPDATE_HOUR"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class WeatherNode(object):
|
|
29
|
+
"""Represents information for a single node: ID and timeseries data."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, node_id: int, data):
|
|
32
|
+
|
|
33
|
+
self._id = node_id
|
|
34
|
+
self._data = data
|
|
35
|
+
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def id(self) -> int:
|
|
40
|
+
"""Node ID"""
|
|
41
|
+
return self._id
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def data(self):
|
|
45
|
+
"""Time series data for this node."""
|
|
46
|
+
return self._data
|
|
47
|
+
|
|
48
|
+
# index into node by time step
|
|
49
|
+
def __getitem__(self, item: int) -> float:
|
|
50
|
+
return self._data[item]
|
|
51
|
+
|
|
52
|
+
def __setitem__(self, key: int, value: float) -> None:
|
|
53
|
+
self._data[key] = value
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _is_iterable(obj) -> bool:
|
|
58
|
+
try:
|
|
59
|
+
_ = iter(obj)
|
|
60
|
+
return True
|
|
61
|
+
except TypeError:
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _isinteger(obj) -> bool:
|
|
66
|
+
return obj and isinstance(obj, (int, np.integer))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Metadata(object):
|
|
70
|
+
|
|
71
|
+
"""
|
|
72
|
+
Metadata:
|
|
73
|
+
|
|
74
|
+
* [DateCreated]
|
|
75
|
+
* [Author]
|
|
76
|
+
* [OriginalDataYears]
|
|
77
|
+
* [StartDayOfYear]
|
|
78
|
+
* [DataProvenance]
|
|
79
|
+
* IdReference
|
|
80
|
+
* NodeCount
|
|
81
|
+
* DatavalueCount
|
|
82
|
+
* UpdateResolution
|
|
83
|
+
* NodeOffsets
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
node_ids: list[int],
|
|
89
|
+
datavalue_count: int,
|
|
90
|
+
author: str = None,
|
|
91
|
+
created: datetime = None,
|
|
92
|
+
frequency: str = None,
|
|
93
|
+
provenance: str = None,
|
|
94
|
+
reference: str = None,
|
|
95
|
+
):
|
|
96
|
+
|
|
97
|
+
assert int(datavalue_count) > 0, "datavalue_count must be > 0"
|
|
98
|
+
self._data_count = int(datavalue_count)
|
|
99
|
+
self._author = f"{author}" if author else getpass.getuser()
|
|
100
|
+
self._creation = (
|
|
101
|
+
created if created and isinstance(created, datetime) else datetime.now()
|
|
102
|
+
)
|
|
103
|
+
self._id_reference = f"{reference}" if reference else IDREF_LEGACY
|
|
104
|
+
self._provenance = f"{provenance}" if provenance else "unknown"
|
|
105
|
+
self._update_frequency = f"{frequency}" if frequency else CLIMATE_UPDATE_DAY
|
|
106
|
+
|
|
107
|
+
assert _is_iterable(node_ids), "node_ids must be iterable"
|
|
108
|
+
concrete = list(node_ids) # if node_ids is a generator, make a concrete list
|
|
109
|
+
assert len(concrete) > 0, "node_ids must not be empty"
|
|
110
|
+
assert all(
|
|
111
|
+
map(lambda i: isinstance(i, int), concrete)
|
|
112
|
+
), "node_ids must be integers"
|
|
113
|
+
assert len(set(concrete)) == len(concrete), "node_ids must be unique"
|
|
114
|
+
sorted_ids = sorted(concrete)
|
|
115
|
+
self._nodes = {
|
|
116
|
+
node_id: datavalue_count * sorted_ids.index(node_id) * 4
|
|
117
|
+
for node_id in sorted_ids
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def author(self) -> str:
|
|
124
|
+
"""Author of this file."""
|
|
125
|
+
return self._author
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def creation_date(self) -> datetime:
|
|
129
|
+
"""Creation date of this file."""
|
|
130
|
+
return self._creation
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def datavalue_count(self) -> int:
|
|
134
|
+
"""Number of data values in each timeseries, should be > 0."""
|
|
135
|
+
return self._data_count
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def id_reference(self) -> str:
|
|
139
|
+
"""
|
|
140
|
+
'Schema' for node IDs. Commonly `Legacy`, `Gridded world grump2.5arcmin`, and `Gridded world grump30arcsec`.
|
|
141
|
+
|
|
142
|
+
`Legacy` usually indicates a 0 or 1 based scheme with increasing ID numbers.
|
|
143
|
+
|
|
144
|
+
`Gridded world grump2.5arcmin` and `Gridded world grump30arcsec` encode latitude and longitude values in the node ID with the following formula::
|
|
145
|
+
|
|
146
|
+
latitude = (((nodeid - 1) & 0xFFFF) * resolution) - 90
|
|
147
|
+
longitude = ((nodeid >> 16) * resolution) - 180
|
|
148
|
+
# nodeid = 90967271 @ 2.5 arcmin resolution
|
|
149
|
+
# longitude = -122.1667, latitude = 47.5833
|
|
150
|
+
"""
|
|
151
|
+
return self._id_reference
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def node_count(self) -> int:
|
|
155
|
+
return len(self.nodes)
|
|
156
|
+
|
|
157
|
+
@property
|
|
158
|
+
def node_ids(self) -> list[int]:
|
|
159
|
+
return sorted(self.nodes.keys())
|
|
160
|
+
|
|
161
|
+
@property
|
|
162
|
+
def provenance(self) -> str:
|
|
163
|
+
return self._provenance
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def update_resolution(self) -> str:
|
|
167
|
+
return self._update_frequency
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def nodes(self) -> dict[int, int]:
|
|
171
|
+
"""WeatherNodes offsets keyed by node id."""
|
|
172
|
+
return self._nodes
|
|
173
|
+
|
|
174
|
+
def write_file(self, filename: str) -> None:
|
|
175
|
+
|
|
176
|
+
metadata = dict(
|
|
177
|
+
DateCreated=f"{self.creation_date:%a %B %d %Y %H:%M:%S}",
|
|
178
|
+
Author=self.author,
|
|
179
|
+
DataProvenance=self.provenance,
|
|
180
|
+
IdReference=self.id_reference,
|
|
181
|
+
NodeCount=len(self.nodes),
|
|
182
|
+
DatavalueCount=self.datavalue_count,
|
|
183
|
+
UpdateResolution=self.update_resolution,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
node_offsets = reduce(
|
|
187
|
+
lambda s, n: s + f"{n:08X}{self.nodes[n]:08X}", self.node_ids, ""
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
jason = dict(Metadata=metadata, NodeOffsets=node_offsets)
|
|
191
|
+
|
|
192
|
+
with open(filename, "wt") as file:
|
|
193
|
+
json.dump(jason, file, indent=2, separators=(",", ": "))
|
|
194
|
+
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
@classmethod
|
|
198
|
+
def from_file(cls, filename: str):
|
|
199
|
+
"""
|
|
200
|
+
Read weather metadata file.
|
|
201
|
+
Metadata' and 'NodeOffsets' keys required.
|
|
202
|
+
DatavalueCount', 'UpdateResolution', and 'IdReference' required in 'Metadata'.
|
|
203
|
+
"""
|
|
204
|
+
with open(filename, "rb") as file:
|
|
205
|
+
jason = json.load(file)
|
|
206
|
+
|
|
207
|
+
meta = jason["Metadata"]
|
|
208
|
+
offsets = jason["NodeOffsets"]
|
|
209
|
+
node_ids = sorted(
|
|
210
|
+
[int(offsets[(i * 16):(i * 16 + 8)], 16) for i in range(len(offsets) // 16)]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
metadata = Metadata(
|
|
214
|
+
node_ids,
|
|
215
|
+
meta["DatavalueCount"],
|
|
216
|
+
author=meta["Author"] if "Author" in meta else None,
|
|
217
|
+
created=meta["DateCreated"] if "DateCreated" in meta else None,
|
|
218
|
+
frequency=meta["UpdateResolution"],
|
|
219
|
+
provenance=meta["DataProvenance"] if "DataProvenance" in meta else None,
|
|
220
|
+
reference=meta["IdReference"],
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
return metadata
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Weather(object):
|
|
227
|
+
def __init__(
|
|
228
|
+
self,
|
|
229
|
+
filename: str = None,
|
|
230
|
+
node_ids: list[int] = None,
|
|
231
|
+
datavalue_count: int = None,
|
|
232
|
+
author: str = None,
|
|
233
|
+
created: datetime = None,
|
|
234
|
+
frequency: str = None,
|
|
235
|
+
provenance: str = None,
|
|
236
|
+
reference: str = None,
|
|
237
|
+
data: np.array = None,
|
|
238
|
+
):
|
|
239
|
+
|
|
240
|
+
if filename and isinstance(filename, str):
|
|
241
|
+
self._from_file(filename)
|
|
242
|
+
else:
|
|
243
|
+
# create "empty" Weather object
|
|
244
|
+
assert _is_iterable(node_ids), "node_ids must be provided and be iterable"
|
|
245
|
+
assert _isinteger(datavalue_count), "datavalue_count must be provided"
|
|
246
|
+
assert datavalue_count > 0, "datavalue_count must be >= 1"
|
|
247
|
+
|
|
248
|
+
self._metadata = Metadata(
|
|
249
|
+
node_ids,
|
|
250
|
+
datavalue_count,
|
|
251
|
+
author,
|
|
252
|
+
created,
|
|
253
|
+
frequency,
|
|
254
|
+
provenance,
|
|
255
|
+
reference,
|
|
256
|
+
)
|
|
257
|
+
node_ids = self._metadata.node_ids
|
|
258
|
+
self._data = (
|
|
259
|
+
data
|
|
260
|
+
if data is not None
|
|
261
|
+
else np.zeros(
|
|
262
|
+
(len(node_ids), self._metadata.datavalue_count), dtype=np.float32
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
self._nodes_and_map()
|
|
266
|
+
|
|
267
|
+
return
|
|
268
|
+
|
|
269
|
+
def _nodes_and_map(self):
|
|
270
|
+
node_ids = self._metadata.node_ids
|
|
271
|
+
self._node_id_to_index_map = {node_ids[n]: n for n in range(len(node_ids))}
|
|
272
|
+
self._nodes = {
|
|
273
|
+
node_id: WeatherNode(
|
|
274
|
+
node_id, self._data[self._node_id_to_index_map[node_id], :]
|
|
275
|
+
)
|
|
276
|
+
for node_id in node_ids
|
|
277
|
+
}
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
@property
|
|
281
|
+
def data(self) -> np.array:
|
|
282
|
+
"""Raw data as numpy array[node index, time step]."""
|
|
283
|
+
return self._data
|
|
284
|
+
|
|
285
|
+
@property
|
|
286
|
+
def metadata(self) -> Metadata:
|
|
287
|
+
return self._metadata
|
|
288
|
+
|
|
289
|
+
# begin pass-through
|
|
290
|
+
|
|
291
|
+
@property
|
|
292
|
+
def author(self) -> str:
|
|
293
|
+
return self._metadata.author
|
|
294
|
+
|
|
295
|
+
@property
|
|
296
|
+
def creation_date(self) -> datetime:
|
|
297
|
+
return self._metadata.creation_date
|
|
298
|
+
|
|
299
|
+
@property
|
|
300
|
+
def datavalue_count(self) -> int:
|
|
301
|
+
""">= 1"""
|
|
302
|
+
return self._metadata.datavalue_count
|
|
303
|
+
|
|
304
|
+
@property
|
|
305
|
+
def id_reference(self) -> str:
|
|
306
|
+
return self._metadata.id_reference
|
|
307
|
+
|
|
308
|
+
@property
|
|
309
|
+
def node_count(self) -> int:
|
|
310
|
+
""">= 1"""
|
|
311
|
+
return self._metadata.node_count
|
|
312
|
+
|
|
313
|
+
@property
|
|
314
|
+
def node_ids(self) -> list[int]:
|
|
315
|
+
return self._metadata.node_ids
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def provenance(self) -> str:
|
|
319
|
+
return self._metadata.provenance
|
|
320
|
+
|
|
321
|
+
@property
|
|
322
|
+
def update_resolution(self) -> str:
|
|
323
|
+
return self._metadata.update_resolution
|
|
324
|
+
|
|
325
|
+
# end pass-through
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def nodes(self) -> dict[int, WeatherNode]:
|
|
329
|
+
"""WeatherNodes indexed by node id."""
|
|
330
|
+
return self._nodes
|
|
331
|
+
|
|
332
|
+
# retrieve node by node id
|
|
333
|
+
def __getitem__(self, item: int):
|
|
334
|
+
return self._nodes[item]
|
|
335
|
+
|
|
336
|
+
def write_file(self, filename: str) -> None:
|
|
337
|
+
"""Writes data to filename and metadata to filename.json."""
|
|
338
|
+
self.metadata.write_file(filename + ".json")
|
|
339
|
+
|
|
340
|
+
with open(filename, "wb") as file:
|
|
341
|
+
self._data.tofile(file)
|
|
342
|
+
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
def _from_file(self, filename: str):
|
|
346
|
+
"""Reads metadata from filename.json and data from filename."""
|
|
347
|
+
self._metadata = Metadata.from_file(filename + ".json")
|
|
348
|
+
data = np.fromfile(filename, dtype=np.float32)
|
|
349
|
+
expected = self._metadata.node_count * self._metadata.datavalue_count
|
|
350
|
+
assert (
|
|
351
|
+
len(data) == expected
|
|
352
|
+
), f"length of data ({len(data)}) != #nodes * #values ({expected})"
|
|
353
|
+
self._data = data.reshape(
|
|
354
|
+
self._metadata.node_count, self._metadata.datavalue_count
|
|
355
|
+
)
|
|
356
|
+
self._nodes_and_map()
|
|
357
|
+
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
@classmethod
|
|
361
|
+
def from_csv(
|
|
362
|
+
cls,
|
|
363
|
+
filename: str,
|
|
364
|
+
var_column: str = "airtemp",
|
|
365
|
+
id_column: str = "node_id",
|
|
366
|
+
step_column: str = "step",
|
|
367
|
+
author: str = None,
|
|
368
|
+
provenance: str = None,
|
|
369
|
+
):
|
|
370
|
+
"""
|
|
371
|
+
Create weather from CSV file with specified variable column, node id column, and time step column.
|
|
372
|
+
|
|
373
|
+
Note:
|
|
374
|
+
* Column order in the CSV file is not significant, but columns names must match what is passed to this function.
|
|
375
|
+
* Because a CSV might hold air temperature (may be negative and well outside 0-1 values), relative humidity (must _not_ be negative, must be in the interval [0-1]), or rainfall (must _not_ be negative, likely > 1), this function does not validate incoming data.
|
|
376
|
+
"""
|
|
377
|
+
Entry = namedtuple("Entry", ["id", "step", "value"])
|
|
378
|
+
|
|
379
|
+
with open(filename) as csv_file:
|
|
380
|
+
reader = csv.DictReader(csv_file)
|
|
381
|
+
entries = [
|
|
382
|
+
Entry(
|
|
383
|
+
int(row[id_column]), int(row[step_column]), float(row[var_column])
|
|
384
|
+
)
|
|
385
|
+
for row in reader
|
|
386
|
+
]
|
|
387
|
+
|
|
388
|
+
node_ids = set([entry.id for entry in entries])
|
|
389
|
+
steps_list = [
|
|
390
|
+
sorted([entry.step for entry in entries if entry.id == node_id])
|
|
391
|
+
for node_id in sorted(node_ids)
|
|
392
|
+
]
|
|
393
|
+
|
|
394
|
+
for i in range(1, len(steps_list)):
|
|
395
|
+
test = len(steps_list[i])
|
|
396
|
+
expected = len(steps_list[0])
|
|
397
|
+
assert (
|
|
398
|
+
test == expected
|
|
399
|
+
), f"number of data values for nodes is not consistent ({len(steps_list[i]) != {len(steps_list[0])}})"
|
|
400
|
+
test = steps_list[i]
|
|
401
|
+
expected = steps_list[0]
|
|
402
|
+
assert (
|
|
403
|
+
test == expected
|
|
404
|
+
), f"time steps for node {sorted(node_ids)[i]} != time steps for node {sorted(node_ids)[0]}"
|
|
405
|
+
|
|
406
|
+
steps = sorted(steps_list[0])
|
|
407
|
+
expected = [i for i in range(1, len(steps) + 1)]
|
|
408
|
+
assert steps == expected, f"time steps do not cover all values 1...{len(steps)}"
|
|
409
|
+
|
|
410
|
+
data_count = len(steps)
|
|
411
|
+
|
|
412
|
+
w = Weather(
|
|
413
|
+
node_ids=node_ids,
|
|
414
|
+
datavalue_count=data_count,
|
|
415
|
+
author=author,
|
|
416
|
+
provenance=provenance,
|
|
417
|
+
)
|
|
418
|
+
for node_id in node_ids:
|
|
419
|
+
|
|
420
|
+
sorted_entries_for_node = sorted(
|
|
421
|
+
[entry for entry in entries if entry.id == node_id],
|
|
422
|
+
key=lambda e: e.step,
|
|
423
|
+
)
|
|
424
|
+
data_for_node = [entry.value for entry in sorted_entries_for_node]
|
|
425
|
+
|
|
426
|
+
w.nodes[node_id][:] = data_for_node
|
|
427
|
+
|
|
428
|
+
return w
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: emod-api
|
|
3
|
+
Version: 3.0.2
|
|
4
|
+
Summary: Core tools for modeling using EMOD
|
|
5
|
+
Author-email: Sharon Chen <sharon.chen@gatesfoundation.org>, Zhaowei Du <zhaowei.du@gatesfoundation.org>, Clark Kirkman IV <clark.kirkmand@gatesfoundation.org>, Daniel Bridenbecker <daniel.bridenbecker@gatesfoundation.org>, Svetlana Titova <svetlana.titova@gatesfoundation.org>, Ye Chen <ye.chen@gatesfoundation.org>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/EMOD-Hub/emod-api
|
|
8
|
+
Project-URL: Issues, https://github.com/EMOD-Hub/emod-api/issues
|
|
9
|
+
Keywords: modeling,IDM
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: matplotlib
|
|
22
|
+
Requires-Dist: scipy
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Requires-Dist: numpy
|
|
25
|
+
Requires-Dist: shapely
|
|
26
|
+
Requires-Dist: pyproj
|
|
27
|
+
Requires-Dist: geographiclib
|
|
28
|
+
Requires-Dist: scikit-learn
|
|
29
|
+
Requires-Dist: lz4
|
|
30
|
+
Provides-Extra: docs
|
|
31
|
+
Requires-Dist: mkdocs-material; extra == "docs"
|
|
32
|
+
Requires-Dist: mkdocs-include-markdown-plugin; extra == "docs"
|
|
33
|
+
Requires-Dist: mkdocstrings-python; extra == "docs"
|
|
34
|
+
Requires-Dist: mkdocs-autoapi; extra == "docs"
|
|
35
|
+
Requires-Dist: mkdocs-glightbox; extra == "docs"
|
|
36
|
+
Provides-Extra: lint
|
|
37
|
+
Requires-Dist: flake8; extra == "lint"
|
|
38
|
+
Provides-Extra: test
|
|
39
|
+
Requires-Dist: emod-common; extra == "test"
|
|
40
|
+
Requires-Dist: emod-generic; extra == "test"
|
|
41
|
+
Requires-Dist: emod-hiv; extra == "test"
|
|
42
|
+
Requires-Dist: emod-malaria; extra == "test"
|
|
43
|
+
Requires-Dist: pytest; extra == "test"
|
|
44
|
+
Requires-Dist: pytest-cov; extra == "test"
|
|
45
|
+
Requires-Dist: pytest-xdist; extra == "test"
|
|
46
|
+
Dynamic: license-file
|
|
47
|
+
|
|
48
|
+
# emod-api
|
|
49
|
+
|
|
50
|
+
[](https://github.com/EMOD-Hub/emod-api/actions/workflows/mkdocs_deploy.yml)
|
|
51
|
+
[](https://github.com/EMOD-Hub/emod-api/actions/workflows/lint.yml)
|
|
52
|
+
[](https://github.com/EMOD-Hub/emod-api/actions/workflows/test_and_bump_version.yml)
|
|
53
|
+
|
|
54
|
+
## Python Version
|
|
55
|
+
|
|
56
|
+
Python 3.13 is the recommended and supported version.
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
Documentation available at https://emod-hub.github.io/emod-api/.
|
|
61
|
+
|
|
62
|
+
To build the documentation locally, do the following:
|
|
63
|
+
|
|
64
|
+
1. Create and activate a venv.
|
|
65
|
+
2. Navigate to the root directory of the repo.
|
|
66
|
+
```
|
|
67
|
+
python -m pip install .[docs]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Dependencies
|
|
71
|
+
|
|
72
|
+
### Linux
|
|
73
|
+
|
|
74
|
+
emod-api can use Snappy [de]compression (python-snappy) as necessary if it is installed which requires libdev-snappy (Debian/Ubuntu) or snappy-devel (RedHat/CentOS) on Linux.
|
|
75
|
+
|
|
76
|
+
Ubuntu: ```[sudo] apt install libdev-snappy```
|
|
77
|
+
|
|
78
|
+
CentOS: ```[sudo] yum install snappy-devel``` (not yet tested)
|
|
79
|
+
|
|
80
|
+
NOTE: The python-snappy version needs to be 0.6.1. Newer versions have problems
|
|
81
|
+
working correctly with emod-api.
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
## User Stories
|
|
85
|
+
|
|
86
|
+
Input
|
|
87
|
+
- User wants to be able to create a minimal working config.json for any sim type guaranteed to work with a given Eradication binary.
|
|
88
|
+
- User wants to be able to create config.json from large static 'defaults' file and small variable parameters-of-interest file.
|
|
89
|
+
- User wants to be able to create guaranteed to work campaigns without having to interact with campaign.json files.
|
|
90
|
+
- User wants to create a migration file without having to grok our custom binary migration format.
|
|
91
|
+
- User wants to be able to create large multi-node demographics files programatically.
|
|
92
|
+
|
|
93
|
+
Output
|
|
94
|
+
- User wants to be able to get post-processed (cleaned up) schema.
|
|
95
|
+
- User wants to be able to get data from InsetChart.json without worrying about exact file format of the files.
|
|
96
|
+
- User wants to be able to extract data of interest from spatial binary files.
|
|
97
|
+
- User wants to be able to work easily with serialization files.
|
|
98
|
+
- User wants to be able to work easily with (pending) events.sql file.
|
|
99
|
+
|
|
100
|
+
## Dev Tips
|
|
101
|
+
|
|
102
|
+
- To build package:
|
|
103
|
+
`python -m build --wheel`
|
|
104
|
+
|
|
105
|
+
- To install package (fill in actual version number in filename):
|
|
106
|
+
`python -m pip install dist/emod_api...whl`
|
|
107
|
+
|
|
108
|
+
## Capability Wishlist
|
|
109
|
+
|
|
110
|
+
- Migration files: users should never have to edit migration binary or header files.
|
|
111
|
+
- Serialization: Population manipulation, such as adding IPs or adding risk factors.
|
|
112
|
+
- Demographics: HINT matrices should not be created directly in demographics.
|
|
113
|
+
- Demographics: Population demographic initalization should be easier and reliable.
|
|
114
|
+
- Config: param_overrides and custom events.
|
|
115
|
+
|
|
116
|
+
## Running tests
|
|
117
|
+
|
|
118
|
+
Please see the documentation for [testing](/tests/README.md).
|
|
119
|
+
|
|
120
|
+
## Community
|
|
121
|
+
|
|
122
|
+
The EMOD Community is made up of researchers and software developers, primarily focused on malaria and HIV research.
|
|
123
|
+
We value mutual respect, openness, and a collaborative spirit. If these values resonate with you, we invite you to join our EMOD Slack Community by completing this form:
|
|
124
|
+
|
|
125
|
+
https://forms.office.com/r/sjncGvBjvZ
|
|
126
|
+
|
|
127
|
+
## Disclaimer
|
|
128
|
+
|
|
129
|
+
The code in this repository was developed by IDM and other collaborators to support our joint research on flexible agent-based modeling.
|
|
130
|
+
We've made it publicly available under the MIT License to provide others with a better understanding of our research and an opportunity to build upon it for their own work. We make no representations that the code works as intended or that we will provide support, address issues that are found, or accept pull requests.
|
|
131
|
+
You are welcome to create your own fork and modify the code to suit your own modeling needs as permitted under the MIT License.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
emod_api/__init__.py,sha256=YKXrr5J7dV2n7ZhaRv0tigylRDtfOuvJC9Y4ouFZpzo,22
|
|
2
|
+
emod_api/campaign.py,sha256=adfUzobd3bjQ_0xIJiYXEGYs-149V185uWoLepB9XAc,5334
|
|
3
|
+
emod_api/multidim_plotter.py,sha256=iFulG4v_C1EP7JaHqV_Z5LQaNgtEPNxqQTqpHASQ1p0,2871
|
|
4
|
+
emod_api/schema_to_class.py,sha256=S9ytQQyTFRadmiYujhoF4Jv5lx2Cs_GcsHwTxg7NUKA,19784
|
|
5
|
+
emod_api/channelreports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
emod_api/channelreports/channels.py,sha256=HqO3Wxj1uERA9xFfjFi_ren7YEIbDKVQPFB2DWVz56M,12867
|
|
7
|
+
emod_api/channelreports/icj_to_csv.py,sha256=5Gcnqthc1HSde0q-Pejbd1EaD_6rqmLcUdTMvh79GCY,1994
|
|
8
|
+
emod_api/channelreports/plot_icj_means.py,sha256=sgPKrQsJuX7-TK4WLFdfxzCj-NPN8aAlf-jWKwqNzkc,6038
|
|
9
|
+
emod_api/channelreports/plot_prop_report.py,sha256=MCJDSQ6AwfmP6hPbKW_py3wtX3qqkilVYN6sQnhHRIU,7916
|
|
10
|
+
emod_api/channelreports/utils.py,sha256=4uBALlotAMQESjL_IqkXirQgF6m7eCaMFdPeO26EHC0,10571
|
|
11
|
+
emod_api/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
emod_api/config/default_from_schema.py,sha256=K6spHA019V0CM_G1O-ezyjmERLB_bmlv39HeMDPdvu0,507
|
|
13
|
+
emod_api/config/default_from_schema_no_validation.py,sha256=z3Glyn0qN3l-AAVSNMsT95JC_GlzL13NAJNNg8WVAaE,7164
|
|
14
|
+
emod_api/config/from_overrides.py,sha256=SyFNRL9NRcSRJEjEEPoLhUatzHkg5PusM5BIKjQpn2s,5799
|
|
15
|
+
emod_api/demographics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
emod_api/demographics/age_distribution.py,sha256=dyIPe1DkJZPilJwbwrEKR32JB0TyLdZHeKyc6aiH70w,8645
|
|
17
|
+
emod_api/demographics/base_input_file.py,sha256=6EZtBpAukjWIE6y1W_2wA-5qKFlejc0KUqYs-phZ-XQ,728
|
|
18
|
+
emod_api/demographics/calculators.py,sha256=4a7L73qk_5opEV9ww8YhtL2tZ87GMYmw9iuCmGgPi_Q,6307
|
|
19
|
+
emod_api/demographics/demographic_exceptions.py,sha256=NyOIYxGPZmvlqXVOQI441LAANJMxl_zy92YBAcPGUUE,825
|
|
20
|
+
emod_api/demographics/demographics.py,sha256=uybCTzu6zvjekivife4LjQpIgoF2i0NTgVWzbhdbD0s,11972
|
|
21
|
+
emod_api/demographics/demographics_base.py,sha256=m2ZwWo4LgMaDf5a9F2Pr_Az4TSMB2Ecm7NRqpdD_oCc,35329
|
|
22
|
+
emod_api/demographics/demographics_overlay.py,sha256=Ren7jNHaeb8vt6K1Fog6NfhNWulhjy3kBSW5RVC7A8o,1752
|
|
23
|
+
emod_api/demographics/fertility_distribution.py,sha256=tXMcgfI-875onmMptwzGkBlU2iTH-vuyXCKdbs9WeNM,13187
|
|
24
|
+
emod_api/demographics/implicit_functions.py,sha256=dVq36_0xU_A5pVHlIPp3WfNmURmEAZlIlpRbAwNsZO8,2860
|
|
25
|
+
emod_api/demographics/mortality_distribution.py,sha256=lTAwIlijHI98ANFFXplgJW_9Zn6HARp_bjXx-VE3m2k,12349
|
|
26
|
+
emod_api/demographics/node.py,sha256=2qudJSuJNEbjfq_A22kh0Gqmj4cq9jJ3kffJeN8i5xM,20130
|
|
27
|
+
emod_api/demographics/overlay_node.py,sha256=H1b7wNu7sEbTRTAvOimtBjEWYq9HBLWoniSHHMY1_Yw,485
|
|
28
|
+
emod_api/demographics/properties_and_attributes.py,sha256=Bdgi3x_o1jiA37yjC0BIF2Gso4qT7BefazngrfhndTg,42600
|
|
29
|
+
emod_api/demographics/susceptibility_distribution.py,sha256=YyqI-0R-K8DvVLekAQh_5b4oh-J9jBMXU4gJQJxc2T8,9140
|
|
30
|
+
emod_api/demographics/updateable.py,sha256=0R_FcCX1T_AtSmJTJhfVsld-eQ8lChlOHVdZ2fkIf8g,2171
|
|
31
|
+
emod_api/demographics/service/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
|
+
emod_api/demographics/service/grid_construction.py,sha256=JAlHTlaseKJT-VN9CRDqBsDpi6iKVSio_kZtzkS68rw,3594
|
|
33
|
+
emod_api/demographics/service/service.py,sha256=L_UPcS7Wc6RuMyzaFqYyMSvUjuQsQCfmDdIzW70Llwk,2574
|
|
34
|
+
emod_api/legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
35
|
+
emod_api/legacy/plotAllCharts.py,sha256=TzhpslzIYQUD6nDEZarthd2BHDDqTP-oA2zLy25YQcE,6997
|
|
36
|
+
emod_api/migration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
emod_api/migration/__main__.py,sha256=soJRb_ocmw3bZ5ZFvyZY9ImF64npdt3RCn4tKMtqm6k,758
|
|
38
|
+
emod_api/migration/migration.py,sha256=JIoKOsSqLRV_QzU00ILVDtXPP6_KxC_3kmjSibKVQkE,30504
|
|
39
|
+
emod_api/serialization/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
40
|
+
emod_api/serialization/census_and_mod_pop.py,sha256=hQwAwmwrjTsCR2-r_pqYOTqqY0ASG8EDqr0_4rnrPsg,1957
|
|
41
|
+
emod_api/serialization/dtk_file_support.py,sha256=kXvLPmPd_b2SwYW5yGLKxIYuwwV-aHAEj_-2QnIGJbU,1312
|
|
42
|
+
emod_api/serialization/dtk_file_tools.py,sha256=4lIEV8CtZC5n68j426520y941BstOXQXPg_eIIeF84s,50967
|
|
43
|
+
emod_api/serialization/dtk_file_utility.py,sha256=R9E1rh0YRFobbg3zmwLJZDXM7z5N2SMavIPrBhyluNY,5441
|
|
44
|
+
emod_api/serialization/serialized_population.py,sha256=vAshs2PuW2zYcUUeDkJMJEUJj1YVKzlnXKwXlAghg-A,7487
|
|
45
|
+
emod_api/spatialreports/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
emod_api/spatialreports/__main__.py,sha256=8eN8Q1EsL2woDcVhMhBzuM10lCZGOlenDJLxp73f-to,1510
|
|
47
|
+
emod_api/spatialreports/plot_spat_means.py,sha256=KS4oOenpfpwGEffIaSjCp51Qf_3KYmvNJuo5apeJCR4,4047
|
|
48
|
+
emod_api/spatialreports/spatial.py,sha256=M-c9hE56TMI8O-SDyxMtAbDu7wxJW976WRmE_BybzDM,7019
|
|
49
|
+
emod_api/utils/__init__.py,sha256=y8Hf-Xrga4PXwezk2Q5Wfcuf17u5UfKHb2vZQq4N_lo,887
|
|
50
|
+
emod_api/utils/str_enum.py,sha256=drehV-Mvg_DTNRYvuxYhhGn6T7SpUNlhM0gfKel7eTo,106
|
|
51
|
+
emod_api/utils/distributions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
52
|
+
emod_api/utils/distributions/base_distribution.py,sha256=0UAHdsUwCHndfj5t6t5WXmil-_4VDPq42UZn46zXYos,1263
|
|
53
|
+
emod_api/utils/distributions/bimodal_distribution.py,sha256=AVejJlql6rrnyHx7XtvPcZswBUusaOHQZXN8WjXYfIQ,2695
|
|
54
|
+
emod_api/utils/distributions/constant_distribution.py,sha256=8-VdBZSAEfSOAKiSJucf5ziIMh5qAIxUvRejEiu3djE,2278
|
|
55
|
+
emod_api/utils/distributions/demographic_distribution_flag.py,sha256=E1lk5e6Ys7D-KAxAVNEV8PIkbsUS19udJlVfcJvOlxw,293
|
|
56
|
+
emod_api/utils/distributions/distribution_type.py,sha256=n-ScBqhoXVOqaJRevtCpbftjb7qI3YkIi6GKTK8p8FU,667
|
|
57
|
+
emod_api/utils/distributions/dual_constant_distribution.py,sha256=hUL3WGcpAH4ytIYO_ANE3z--p9GUxbQTt6__rudLMnQ,2913
|
|
58
|
+
emod_api/utils/distributions/dual_exponential_distribution.py,sha256=CmBww3i0bnBA15UhkoE8A3T_j7aLO6x1kthdC5K4Kkw,3273
|
|
59
|
+
emod_api/utils/distributions/exponential_distribution.py,sha256=g4Hr1AFLYdlcnZeGnRfs3WYtWsAQG1ZL_tXtZ90vvW4,2583
|
|
60
|
+
emod_api/utils/distributions/gaussian_distribution.py,sha256=h_bsSxyxFrVRk_Zn3v_gygX8ddRtggQXcLpAjhVAsK0,2764
|
|
61
|
+
emod_api/utils/distributions/log_normal_distribution.py,sha256=hTQSp_6b82zsWs4_qyEr-Uj_njt9DAPwAXmvk_wSQkE,2408
|
|
62
|
+
emod_api/utils/distributions/poisson_distribution.py,sha256=rncSCJpmLD8EqJbcIqYMJ3tcv0R2Yx6FRxH2Y_UpEvI,2290
|
|
63
|
+
emod_api/utils/distributions/uniform_distribution.py,sha256=LYdDIGbQIj7IU1Y6zbvXKGRuCdK8u2kgjXKX-WB6yTE,2944
|
|
64
|
+
emod_api/utils/distributions/weibull_distribution.py,sha256=DTuya1NIahQTmtxkEN-PgBx1Jbe7ww7RZ7ALgHs4EO0,2946
|
|
65
|
+
emod_api/weather/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
+
emod_api/weather/weather.py,sha256=Jf5AUS8ytcMlwsTb4E9XUKswF1LNBx5H73z9boDKK-g,12950
|
|
67
|
+
emod_api-3.0.2.dist-info/licenses/LICENSE,sha256=7Yw18dJ5Tr9BDVK7GGzOX5cPbozqgGn_FC1iV-XlvLc,1080
|
|
68
|
+
emod_api-3.0.2.dist-info/METADATA,sha256=lUR1lekE3yEkw-xQKjRMJfqqsacGUXCw9SM79YAfCxE,5914
|
|
69
|
+
emod_api-3.0.2.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
|
|
70
|
+
emod_api-3.0.2.dist-info/top_level.txt,sha256=NOmgWa8LR-Kl-ZkOuKhdeD6eUFBmthsGu25xjBQ0D4Q,9
|
|
71
|
+
emod_api-3.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2020 - 2025 Gates Foundation
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
emod_api
|