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,54 @@
|
|
|
1
|
+
class InvalidFixedValueException(ValueError):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class InvalidPopulationGroupLengthException(ValueError):
|
|
6
|
+
pass
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InvalidDataDimensionality(ValueError):
|
|
10
|
+
pass
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidDataDimensionLength(ValueError):
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class InvalidDataDimensionDim0Exception(ValueError):
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class InvalidDataDimensionDim1Exception(ValueError):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AgeOutOfRangeException(ValueError):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TimeOutOfRangeException(ValueError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DistributionOutOfRangeException(ValueError):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NonMonotonicAgeException(ValueError):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class NonMonotonicTimeException(ValueError):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class NonMonotonicDistributionException(ValueError):
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class InvalidNodeIdException(ValueError):
|
|
50
|
+
pass
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ConflictingDistributionsException(ValueError):
|
|
54
|
+
pass
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Union
|
|
7
|
+
|
|
8
|
+
from emod_api.demographics.demographics_base import DemographicsBase
|
|
9
|
+
from emod_api.demographics.node import Node
|
|
10
|
+
from emod_api.demographics.properties_and_attributes import NodeAttributes
|
|
11
|
+
from emod_api.demographics.service import service
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Demographics(DemographicsBase):
|
|
15
|
+
"""
|
|
16
|
+
This class is a container of data necessary to produce a EMOD-valid demographics input file.
|
|
17
|
+
"""
|
|
18
|
+
def __init__(self, nodes: list[Node], idref: str = None, default_node: Node = None, set_defaults: bool = True):
|
|
19
|
+
"""
|
|
20
|
+
Object representation of an EMOD Demographics input (json) file.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
nodes: list(Node) nodes to include in the Demographics object.
|
|
24
|
+
idref: (string, optional) an identifier for the Demographics file. Used to co-identify sets of
|
|
25
|
+
Demographics/overlay files. No value will utilize a default (via inheritance).
|
|
26
|
+
default_node: (Node, optional) Represents default values for all nodes, unless overridden on a per-node
|
|
27
|
+
basis. If not provided, one will be generated by the superclass.
|
|
28
|
+
set_defaults: (bool) Whether to set default node attributes on the default node. Defaults to True. Should
|
|
29
|
+
always be True unless loading via Demographics.from_file() (to replicate in-file data fully).
|
|
30
|
+
"""
|
|
31
|
+
super().__init__(nodes=nodes, idref=idref, default_node=default_node)
|
|
32
|
+
|
|
33
|
+
# set some standard EMOD defaults. set_defaults should always be True unless reading from a demographics file,
|
|
34
|
+
# as False allows setting default_node.node_attributes exactly as they are in the file. Loading via
|
|
35
|
+
# Demographics.from_file() is deprecated, see below.
|
|
36
|
+
if set_defaults:
|
|
37
|
+
self.default_node.node_attributes.airport = 1
|
|
38
|
+
self.default_node.node_attributes.seaport = 1
|
|
39
|
+
self.default_node.node_attributes.region = 1
|
|
40
|
+
|
|
41
|
+
def to_file(self, path: Union[str, Path] = "demographics.json", indent: int = 4) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Write the Demographics object to an EMOD demograhpics json file.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
path: (str) the filepath to write the file to. Default is "demographics.json".
|
|
47
|
+
indent: (int, optional) The number of spaces to indent for nested JSON elements (Default is 4, None means
|
|
48
|
+
no nesting (one line printing)).
|
|
49
|
+
Returns:
|
|
50
|
+
Nothing
|
|
51
|
+
"""
|
|
52
|
+
with open(path, "w") as output:
|
|
53
|
+
if indent is None:
|
|
54
|
+
json.dump(self.to_dict(), output, sort_keys=True)
|
|
55
|
+
else:
|
|
56
|
+
json.dump(self.to_dict(), output, indent=indent, sort_keys=True)
|
|
57
|
+
|
|
58
|
+
def generate_file(self, path: Union[str, Path] = "demographics.json", indent: int = 4):
|
|
59
|
+
import warnings
|
|
60
|
+
warnings.warn("generate_file() is deprecated. Please use to_file()", DeprecationWarning, stacklevel=2)
|
|
61
|
+
self.to_file(path=path, indent=indent)
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def from_file(cls, path: str) -> "Demographics":
|
|
65
|
+
"""
|
|
66
|
+
Create a Demographics object from an EMOD-compatible demographics json file.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
path (str): the file path to read from.:
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
a Demographics object
|
|
73
|
+
"""
|
|
74
|
+
import warnings
|
|
75
|
+
warnings.warn("Loading Demographics from JSON files is deprecated. Objects should be created via Python code "
|
|
76
|
+
"whenever possible as that route is by far the most tested for modern EMOD compatibility. Please "
|
|
77
|
+
"ensure, for example, that the read-in json file (or resultant demographics object) does not "
|
|
78
|
+
"contain conflicting distributions for IndividualAttributes that can be represented with simple "
|
|
79
|
+
"or complex distributions (use of only one at a time is valid).",
|
|
80
|
+
DeprecationWarning, stacklevel=2)
|
|
81
|
+
|
|
82
|
+
with open(path, "r") as src:
|
|
83
|
+
demographics_dict = json.load(src)
|
|
84
|
+
demographics_dict["Defaults"]["NodeID"] = 0 # This is a requirement of all emod-api Demographics objects
|
|
85
|
+
implicit_functions = []
|
|
86
|
+
nodes = []
|
|
87
|
+
for node_dict in demographics_dict["Nodes"]:
|
|
88
|
+
node, implicits = Node.from_data(data=node_dict)
|
|
89
|
+
implicit_functions.extend(implicits)
|
|
90
|
+
nodes.append(node)
|
|
91
|
+
default_node, implicits = Node.from_data(data=demographics_dict["Defaults"])
|
|
92
|
+
implicit_functions.extend(implicits)
|
|
93
|
+
metadata = demographics_dict["Metadata"]
|
|
94
|
+
idref = demographics_dict["Metadata"]["IdReference"]
|
|
95
|
+
|
|
96
|
+
demographics = cls(nodes=nodes, default_node=default_node, idref=idref, set_defaults=False)
|
|
97
|
+
demographics.metadata = metadata
|
|
98
|
+
demographics.implicits.extend(implicit_functions)
|
|
99
|
+
return demographics
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def from_template_node(cls,
|
|
103
|
+
lat: float = 0,
|
|
104
|
+
lon: float = 0,
|
|
105
|
+
pop: int = 1000000,
|
|
106
|
+
name: str = "Erewhon",
|
|
107
|
+
forced_id: int = 1) -> "Demographics":
|
|
108
|
+
"""
|
|
109
|
+
Creates a basic, single-node Demographics object.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
lat: (float, optional) latitude of node to be created. Default is 0.
|
|
113
|
+
lon: (float, optional) longitude of node to be created. Default is 0.
|
|
114
|
+
pop: (int, optional) number of people in the node to be created. Default is 1000000.
|
|
115
|
+
name: (str, optional) name of node to be created. Default is Erewhon.
|
|
116
|
+
forced_id: (int, optional) id of node to be created. Default is 1.
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
A Demographics object
|
|
120
|
+
"""
|
|
121
|
+
new_nodes = [Node(lat=lat, lon=lon, pop=pop, forced_id=forced_id, name=name)]
|
|
122
|
+
return cls(nodes=new_nodes)
|
|
123
|
+
|
|
124
|
+
# The below implements the standard naming convention for DTK nodes based on latitude and longitude.
|
|
125
|
+
# The node ID encodes both lat and long at a specified pixel resolution, and I've maintained this
|
|
126
|
+
# convention even when running on spatial setups that are not non-uniform grids.
|
|
127
|
+
@staticmethod
|
|
128
|
+
def _node_id_from_lat_lon_res(lat: float, lon: float, res: float = 30 / 3600) -> int:
|
|
129
|
+
node_id = int((np.floor((lon + 180) / res) * (2 ** 16)).astype(np.uint) + (np.floor((lat + 90) / res) + 1).astype(np.uint))
|
|
130
|
+
return node_id
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def from_csv(cls,
|
|
134
|
+
input_file: str,
|
|
135
|
+
res: float = 30 / 3600,
|
|
136
|
+
id_ref: str = "from_csv") -> "Demographics":
|
|
137
|
+
"""
|
|
138
|
+
Create an EMOD-compatible :py:class:`Demographics` instance from a csv population-by-node file.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
input_file (str): the csv filepath to read from.
|
|
142
|
+
res (float, optional): spatial resolution of the nodes in arc-seconds
|
|
143
|
+
id_ref (str, optional): Description of the file source for co-identification of demographics objects/files.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
A Demographics object
|
|
147
|
+
"""
|
|
148
|
+
def get_value(row, headers):
|
|
149
|
+
for h in headers:
|
|
150
|
+
if row.get(h) is not None:
|
|
151
|
+
return float(row.get(h))
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
print(f"{input_file} found and being read for demographics.json file creation.")
|
|
155
|
+
node_info = pd.read_csv(input_file, encoding='iso-8859-1')
|
|
156
|
+
out_nodes = []
|
|
157
|
+
for index, row in node_info.iterrows():
|
|
158
|
+
if 'under5_pop' in row:
|
|
159
|
+
pop = int(6 * row['under5_pop'])
|
|
160
|
+
if pop < 25000:
|
|
161
|
+
continue
|
|
162
|
+
else:
|
|
163
|
+
pop = int(row['pop'])
|
|
164
|
+
|
|
165
|
+
latitude_headers = ["lat", "latitude", "LAT", "LATITUDE", "Latitude", "Lat"]
|
|
166
|
+
lat = get_value(row, latitude_headers)
|
|
167
|
+
|
|
168
|
+
longitude_headers = ["lon", "longitude", "LON", "LONGITUDE", "Longitude", "Lon"]
|
|
169
|
+
lon = get_value(row, longitude_headers)
|
|
170
|
+
|
|
171
|
+
birth_rate_headers = ["birth", "Birth", "birth_rate", "birthrate", "BirthRate", "Birth_Rate", "BIRTH",
|
|
172
|
+
"birth rate", "Birth Rate"]
|
|
173
|
+
birth_rate = get_value(row, birth_rate_headers)
|
|
174
|
+
if birth_rate is not None and birth_rate < 0.0:
|
|
175
|
+
raise ValueError("Birth rate defined in " + input_file + " must be greater 0.")
|
|
176
|
+
|
|
177
|
+
node_id = row.get('node_id')
|
|
178
|
+
if node_id is not None and int(node_id) == 0:
|
|
179
|
+
raise ValueError("Node ids can not be '0'.")
|
|
180
|
+
|
|
181
|
+
forced_id = int(cls._node_id_from_lat_lon_res(lat=lat, lon=lon, res=res)) if node_id is None else int(node_id)
|
|
182
|
+
|
|
183
|
+
if 'loc' in row:
|
|
184
|
+
place_name = str(row['loc'])
|
|
185
|
+
else:
|
|
186
|
+
place_name = None
|
|
187
|
+
meta = {}
|
|
188
|
+
"""
|
|
189
|
+
meta = {'dot_name': (row['ADM0_NAME']+':'+row['ADM1_NAME']+':'+row['ADM2_NAME']),
|
|
190
|
+
'GUID': row['GUID'],
|
|
191
|
+
'density': row['under5_pop_weighted_density']}
|
|
192
|
+
"""
|
|
193
|
+
node_attributes = NodeAttributes(name=place_name, birth_rate=birth_rate)
|
|
194
|
+
node = Node(lat, lon, pop,
|
|
195
|
+
node_attributes=node_attributes,
|
|
196
|
+
forced_id=forced_id, meta=meta)
|
|
197
|
+
|
|
198
|
+
out_nodes.append(node)
|
|
199
|
+
return cls(nodes=out_nodes, idref=id_ref)
|
|
200
|
+
|
|
201
|
+
# This will be the long-term API for this function.
|
|
202
|
+
@classmethod
|
|
203
|
+
def from_pop_raster_csv(cls,
|
|
204
|
+
pop_filename_in: str,
|
|
205
|
+
res: float = 1 / 120,
|
|
206
|
+
id_ref: str = "from_raster",
|
|
207
|
+
pop_dirname_out: str = "spatial_gridded_pop_dir",
|
|
208
|
+
site: str = "No_Site") -> "Demographics":
|
|
209
|
+
"""
|
|
210
|
+
Take a csv of a population-counts raster and build a grid for use with EMOD simulations.
|
|
211
|
+
Grid size is specified by grid resolution in arcs or in kilometers. The population counts
|
|
212
|
+
from the raster csv are then assigned to their nearest grid center and a new intermediate
|
|
213
|
+
grid file is generated with latitude, longitude and population. This file is then fed to
|
|
214
|
+
from_csv to generate a demographics object.
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
pop_filename_in (str): The filepath of the population-counts raster in CSV format.
|
|
218
|
+
res (float, optional): The grid resolution in arcs or kilometers. Default is 1/120.
|
|
219
|
+
id_ref (str, optional): Identifier reference for the grid. Default is "from_raster".
|
|
220
|
+
pop_dirname_out (str, optional): The output directory name to hold the intermediate grid file.
|
|
221
|
+
Default is "spatial_gridded_pop_dir".
|
|
222
|
+
site (str, optional): The site name or identifier. Default is "No_Site".
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
A Demographics object based on the input grid file.
|
|
226
|
+
|
|
227
|
+
Raises:
|
|
228
|
+
|
|
229
|
+
"""
|
|
230
|
+
grid_file_path = service._create_grid_files(point_records_file_in=pop_filename_in,
|
|
231
|
+
final_grid_files_dir=pop_dirname_out,
|
|
232
|
+
site=site)
|
|
233
|
+
print(f"{grid_file_path} grid file created.")
|
|
234
|
+
return cls.from_csv(input_file=grid_file_path, res=res, id_ref=id_ref)
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def from_pop_csv(cls,
|
|
238
|
+
pop_filename_in: str,
|
|
239
|
+
res: float = 1 / 120,
|
|
240
|
+
id_ref: str = "from_raster",
|
|
241
|
+
pop_dirname_out: str = "spatial_gridded_pop_dir",
|
|
242
|
+
site: str = "No_Site") -> "Demographics":
|
|
243
|
+
import warnings
|
|
244
|
+
warnings.warn("from_pop_csv is deprecated. Please use from_pop_csv.", DeprecationWarning, stacklevel=2)
|
|
245
|
+
return cls.from_pop_raster_csv(pop_filename_in=pop_filename_in,
|
|
246
|
+
res=res,
|
|
247
|
+
id_ref=id_ref,
|
|
248
|
+
pop_dirname_out=pop_dirname_out,
|
|
249
|
+
site=site)
|