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.
Files changed (71) hide show
  1. emod_api/__init__.py +1 -0
  2. emod_api/campaign.py +170 -0
  3. emod_api/channelreports/__init__.py +0 -0
  4. emod_api/channelreports/channels.py +433 -0
  5. emod_api/channelreports/icj_to_csv.py +65 -0
  6. emod_api/channelreports/plot_icj_means.py +149 -0
  7. emod_api/channelreports/plot_prop_report.py +205 -0
  8. emod_api/channelreports/utils.py +326 -0
  9. emod_api/config/__init__.py +0 -0
  10. emod_api/config/default_from_schema.py +16 -0
  11. emod_api/config/default_from_schema_no_validation.py +177 -0
  12. emod_api/config/from_overrides.py +135 -0
  13. emod_api/demographics/__init__.py +0 -0
  14. emod_api/demographics/age_distribution.py +163 -0
  15. emod_api/demographics/base_input_file.py +28 -0
  16. emod_api/demographics/calculators.py +159 -0
  17. emod_api/demographics/demographic_exceptions.py +54 -0
  18. emod_api/demographics/demographics.py +249 -0
  19. emod_api/demographics/demographics_base.py +752 -0
  20. emod_api/demographics/demographics_overlay.py +41 -0
  21. emod_api/demographics/fertility_distribution.py +235 -0
  22. emod_api/demographics/implicit_functions.py +112 -0
  23. emod_api/demographics/mortality_distribution.py +227 -0
  24. emod_api/demographics/node.py +456 -0
  25. emod_api/demographics/overlay_node.py +16 -0
  26. emod_api/demographics/properties_and_attributes.py +737 -0
  27. emod_api/demographics/service/__init__.py +0 -0
  28. emod_api/demographics/service/grid_construction.py +143 -0
  29. emod_api/demographics/service/service.py +55 -0
  30. emod_api/demographics/susceptibility_distribution.py +170 -0
  31. emod_api/demographics/updateable.py +58 -0
  32. emod_api/legacy/__init__.py +0 -0
  33. emod_api/legacy/plotAllCharts.py +230 -0
  34. emod_api/migration/__init__.py +0 -0
  35. emod_api/migration/__main__.py +22 -0
  36. emod_api/migration/migration.py +782 -0
  37. emod_api/multidim_plotter.py +80 -0
  38. emod_api/schema_to_class.py +440 -0
  39. emod_api/serialization/__init__.py +0 -0
  40. emod_api/serialization/census_and_mod_pop.py +48 -0
  41. emod_api/serialization/dtk_file_support.py +61 -0
  42. emod_api/serialization/dtk_file_tools.py +1378 -0
  43. emod_api/serialization/dtk_file_utility.py +141 -0
  44. emod_api/serialization/serialized_population.py +205 -0
  45. emod_api/spatialreports/__init__.py +0 -0
  46. emod_api/spatialreports/__main__.py +67 -0
  47. emod_api/spatialreports/plot_spat_means.py +99 -0
  48. emod_api/spatialreports/spatial.py +210 -0
  49. emod_api/utils/__init__.py +26 -0
  50. emod_api/utils/distributions/__init__.py +0 -0
  51. emod_api/utils/distributions/base_distribution.py +38 -0
  52. emod_api/utils/distributions/bimodal_distribution.py +64 -0
  53. emod_api/utils/distributions/constant_distribution.py +58 -0
  54. emod_api/utils/distributions/demographic_distribution_flag.py +16 -0
  55. emod_api/utils/distributions/distribution_type.py +15 -0
  56. emod_api/utils/distributions/dual_constant_distribution.py +68 -0
  57. emod_api/utils/distributions/dual_exponential_distribution.py +75 -0
  58. emod_api/utils/distributions/exponential_distribution.py +63 -0
  59. emod_api/utils/distributions/gaussian_distribution.py +69 -0
  60. emod_api/utils/distributions/log_normal_distribution.py +61 -0
  61. emod_api/utils/distributions/poisson_distribution.py +59 -0
  62. emod_api/utils/distributions/uniform_distribution.py +70 -0
  63. emod_api/utils/distributions/weibull_distribution.py +69 -0
  64. emod_api/utils/str_enum.py +6 -0
  65. emod_api/weather/__init__.py +0 -0
  66. emod_api/weather/weather.py +428 -0
  67. emod_api-3.0.2.dist-info/METADATA +131 -0
  68. emod_api-3.0.2.dist-info/RECORD +71 -0
  69. emod_api-3.0.2.dist-info/WHEEL +5 -0
  70. emod_api-3.0.2.dist-info/licenses/LICENSE +21 -0
  71. emod_api-3.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,737 @@
1
+ from typing import Union, Optional, Callable, Tuple
2
+
3
+ from emod_api.demographics.age_distribution import AgeDistribution
4
+ from emod_api.demographics.demographic_exceptions import ConflictingDistributionsException
5
+ from emod_api.demographics.fertility_distribution import FertilityDistribution
6
+ from emod_api.demographics.implicit_functions import _set_age_simple, _set_age_complex, _set_suscept_simple, \
7
+ _set_suscept_complex, _set_init_prev, _set_migration_model_fixed_rate, _set_enable_migration_model_heterogeneity, \
8
+ _set_enable_natural_mortality, _set_mortality_age_gender_year, _set_mortality_age_gender, _set_enable_demog_risk, \
9
+ _set_fertility_age_year
10
+ from emod_api.demographics.mortality_distribution import MortalityDistribution
11
+ from emod_api.demographics.susceptibility_distribution import SusceptibilityDistribution
12
+ from emod_api.demographics.updateable import Updateable
13
+
14
+
15
+ # TODO: most of the documentation in this file consists of stand-in stubs. Needs to be filled in.
16
+ # https://github.com/InstituteforDiseaseModeling/emod-api/issues/695
17
+
18
+
19
+ class IndividualProperty(Updateable):
20
+ def __init__(self,
21
+ property: str,
22
+ values: Union[list[float], list[str]],
23
+ initial_distribution: list[float] = None,
24
+ transitions: list[dict] = None,
25
+ transmission_matrix: list[list[float]] = None,
26
+ transmission_route: str = "Contact"):
27
+ """
28
+ Add Individual Properties, including an optional HINT configuration matrix.
29
+
30
+ Individual properties act as 'labels' on model agents that can be used for identifying and targeting
31
+ subpopulations in campaign elements and reports. E.g. model agents may be given a property ('Accessibility')
32
+ that labels them as either having access to health care (value: 'Yes') or not (value: 'No').
33
+
34
+ Property-based heterogeneous disease transmission (HINT) is available for generic, environmental, typhoid,
35
+ airborne, or TBHIV simulations as other simulation types have parameters for modeling the heterogeneity of
36
+ transmission. By default, transmission is assumed to occur homogeneously among the population within a node.
37
+
38
+ Note: EMOD requires individual property key and values (property and values args) to be the same across all
39
+ nodes. The individual distributions of individual properties (initial_distribution) can vary acros nodes.
40
+
41
+ Note: For HINT, you will also need to set config parameter `Enable_Heterogeneous_Intranode_Transmission` to 1
42
+ likely with config.parameters.Enable_Heterogeneous_Intranode_Transmission = 1
43
+
44
+ Documentation of individual properties and HINT:
45
+ For malaria, see :doc:`emod-malaria:emod/model-properties`
46
+ and for HIV, see :doc:`emod-hiv:emod/model-properties`.
47
+ For malaria, see :doc:`emod-malaria:emod/model-hint`
48
+ and for HIV, see :doc:`emod-hiv:emod/model-hint`.
49
+
50
+ Args:
51
+ property: a new individual property key to add. If property already exists an exception is raised
52
+ unless overwrite_existing is True.
53
+ values: A list of valid values for the property, or, if creating age-based transmission, age edges for
54
+ the 'Age_Bin' property. E.g. ['Yes', 'No'] for an 'Accessibility' property.
55
+ initial_distribution: The fractional, between 0 and 1, initial distribution of each valid values entry.
56
+ Order must match values argument. The values must add up to 1.
57
+ transmission_matrix: HINT transmission matrix. For malaria, see :doc:`emod-malaria:emod/model-hint`
58
+ and for HIV, see :doc:`emod-hiv:emod/model-hint`.
59
+ transmission_route: The route of transmission. Default is 'Contact'. Available routes are 'Contact' and
60
+ 'Environmental'.
61
+ transitions: A list of dictionaries that each define how an individual transitions
62
+ from one property value to another. For malaria, see :doc:`emod-malaria:emod/parameter-demographics`
63
+ and for HIV, see :doc:`emod-hiv:emod/parameter-demographics`.
64
+ """
65
+ super().__init__()
66
+ if property == "Age_Bin":
67
+ if not isinstance(values, list) or not all(isinstance(i, float) or isinstance(i, int) for i in values):
68
+ raise ValueError("For property 'Age_Bin' values must be a list of floats representing "
69
+ "age bin edges in years.")
70
+ if values[0] != 0 or values[-1] != -1:
71
+ raise ValueError("For property 'Age_Bin', first value must be 0 and last value must be -1.")
72
+ if not transmission_matrix:
73
+ raise ValueError("For property 'Age_Bin', transmission_matrix and transmission_routes must be defined.")
74
+ num_age_buckets = len(values) - 1
75
+ if len(transmission_matrix) != num_age_buckets:
76
+ raise ValueError("For property 'Age_Bin', transmission_matrix must match number of age buckets, which "
77
+ " is number of edges in 'values' - 1.")
78
+ for mtx_row in transmission_matrix:
79
+ if len(mtx_row) != num_age_buckets:
80
+ raise ValueError("For property 'Age_Bin', each row of transmission_matrix must match number of age "
81
+ "buckets, which is number of edges in 'values' - 1.")
82
+ if initial_distribution:
83
+ for i in initial_distribution:
84
+ if i < 0 or i > 1:
85
+ raise ValueError("initial_distribution values must be between 0 and 1.")
86
+ if sum(initial_distribution) != 1:
87
+ raise ValueError("initial_distribution values must sum to 1.")
88
+ if len(initial_distribution) != len(values):
89
+ raise ValueError("initial_distribution must have the same number of entries as values.")
90
+
91
+ if transmission_matrix and transmission_route not in ["Contact", "Environmental"]:
92
+ raise ValueError(f"Invalid transmission route: {transmission_route}. "
93
+ f"Valid routes are 'Contact' and 'Environmental'.")
94
+ if transmission_matrix and property != "Age_Bin":
95
+ if len(transmission_matrix) != len(values):
96
+ raise ValueError("For property other than 'Age_Bin', size of transmission_matrix must match number "
97
+ "of values.")
98
+ for mtx_row in transmission_matrix:
99
+ if len(mtx_row) != len(values):
100
+ raise ValueError("For property other than 'Age_Bin', each row of transmission_matrix must match "
101
+ "number of values.")
102
+ for transition in transitions or []:
103
+ if not isinstance(transition, dict):
104
+ raise ValueError("Transitions must be a list of dictionaries. Please see the documentation for correct "
105
+ "format: ")
106
+
107
+ self.initial_distribution = initial_distribution
108
+ self.property = property
109
+ self.values = values
110
+ self.transitions = transitions
111
+ self.transmission_matrix = transmission_matrix
112
+ self.transmission_route = transmission_route
113
+
114
+ def to_dict(self) -> dict:
115
+ individual_property = self.parameter_dict
116
+ individual_property.update({"Property": self.property})
117
+ if self.property == "Age_Bin":
118
+ individual_property.update({"Age_Bin_Edges_In_Years": self.values})
119
+ else:
120
+ individual_property.update({"Values": self.values})
121
+
122
+ if self.initial_distribution:
123
+ individual_property.update({"Initial_Distribution": self.initial_distribution})
124
+
125
+ if self.transitions is not None:
126
+ individual_property.update({"Transitions": self.transitions})
127
+
128
+ if self.transmission_matrix is not None:
129
+ individual_property.update({"TransmissionMatrix": {"Route": self.transmission_route,
130
+ "Matrix": self.transmission_matrix}})
131
+ return individual_property
132
+
133
+ @classmethod
134
+ def from_dict(cls, ip_dict: dict) -> '__class__':
135
+ available_args = ['initial_distribution', 'property', 'values', 'transitions',
136
+ 'transmission_matrix', 'transmission_route']
137
+ args = {key: ip_dict[key] for key in available_args if key in ip_dict}
138
+ return cls(**args)
139
+
140
+ def __eq__(self, other) -> bool:
141
+ return self.to_dict() == other.to_dict()
142
+
143
+
144
+ class IndividualProperties(Updateable):
145
+ """
146
+ A container class for holding IndividualProperty objects used by Node objects. It simply contains functionality for
147
+ adding, removing, and retrieving contained IndividualProperty objects with some light consistency checking
148
+ (preventing duplicate-named IndividualProperties).
149
+ """
150
+
151
+ class DuplicateIndividualPropertyException(Exception):
152
+ pass
153
+
154
+ class NoSuchIndividualPropertyException(Exception):
155
+ pass
156
+
157
+ def __init__(self, individual_properties: list[IndividualProperty] = None):
158
+ """
159
+ https://docs.idmod.org/projects/emod-generic/en/latest/model-properties.html
160
+
161
+ Args:
162
+ individual_properties (list[IndividualProperty]): list of individual properties to include. Default is
163
+ no individual_properties.
164
+ """
165
+ super().__init__()
166
+ self.individual_properties = [] if individual_properties is None else individual_properties
167
+
168
+ def add(self, individual_property: IndividualProperty, overwrite=False) -> None:
169
+ has_ip = self.has_individual_property(property_key=individual_property.property)
170
+ if has_ip:
171
+ if overwrite:
172
+ # remove existing then add
173
+ self.remove_individual_property(property_key=individual_property.property)
174
+ else:
175
+ msg = f"Property {individual_property.property} already present in IndividualProperties"
176
+ raise self.DuplicateIndividualPropertyException(msg)
177
+ self.individual_properties.append(individual_property)
178
+
179
+ def add_parameter(self, key, value):
180
+ raise NotImplementedError("A parameter cannot be added to IndividualProperties.")
181
+
182
+ @property
183
+ def ip_by_name(self):
184
+ return {ip.property: ip for ip in self.individual_properties}
185
+
186
+ def has_individual_property(self, property_key: str) -> bool:
187
+ return property_key in self.ip_by_name.keys()
188
+
189
+ def get_individual_property(self, property_key: str) -> IndividualProperty:
190
+ ip = self.ip_by_name.get(property_key, None)
191
+ if ip is None:
192
+ msg = f"No IndividualProperty exists with the property key: {property_key}"
193
+ raise self.NoSuchIndividualPropertyException(msg)
194
+ return ip
195
+
196
+ def remove_individual_property(self, property_key: str):
197
+ ips_to_keep = [ip for ip in self.individual_properties if ip.property != property_key]
198
+ self.individual_properties = ips_to_keep
199
+
200
+ def to_dict(self) -> list[dict]:
201
+ data = [ip.to_dict() for ip in self.individual_properties]
202
+ return data
203
+
204
+ def __getitem__(self, index: int):
205
+ return self.individual_properties[index]
206
+
207
+ def __len__(self):
208
+ return len(self.individual_properties)
209
+
210
+
211
+ class IndividualAttributes(Updateable):
212
+ # TODO: consider refactoring to use objects instead of a big list of potential parameters here:
213
+ # https://github.com/InstituteforDiseaseModeling/emod-api-old/issues/750
214
+ def __init__(self,
215
+ age_distribution_flag: int = None,
216
+ age_distribution1: int = None,
217
+ age_distribution2: int = None,
218
+ age_distribution: AgeDistribution = None,
219
+ susceptibility_distribution_flag: int = None,
220
+ susceptibility_distribution1: int = None,
221
+ susceptibility_distribution2: int = None,
222
+ susceptibility_distribution: SusceptibilityDistribution = None,
223
+ prevalence_distribution_flag: int = None,
224
+ prevalence_distribution1: int = None,
225
+ prevalence_distribution2: int = None,
226
+ risk_distribution_flag: int = None,
227
+ risk_distribution1: int = None,
228
+ risk_distribution2: int = None,
229
+ migration_heterogeneity_distribution_flag: int = None,
230
+ migration_heterogeneity_distribution1: int = None,
231
+ migration_heterogeneity_distribution2: int = None,
232
+ fertility_distribution: FertilityDistribution = None,
233
+ mortality_distribution_male: MortalityDistribution = None,
234
+ mortality_distribution_female: MortalityDistribution = None,
235
+ innate_immune_distribution_flag: int = None,
236
+ innate_immune_distribution1: int = None,
237
+ innate_immune_distribution2: int = None
238
+ ):
239
+ """
240
+ Defines the initial distribution of attributes for model agents for all disease setups. These are used by Node
241
+ objects and can be defined separately per-node. Some attributes utilize simple distributions, some utilize
242
+ complex distributions, some can utilize either simple or complex. For those that can utilize simple or complex
243
+ distributions, only one may be specified (it is a user choice). It is highly unlikely a user will utilize this
244
+ class directly, as it exists primarily for ensuring proper serialization to JSON for EMOD input file
245
+ representation. The standard, user-facing interface for updating the distributions used is in the Demographics
246
+ class in emodpy.demographics .
247
+
248
+ Supported simple distributions and the meaning of their parameters are defined in the
249
+ emod_api.utils.distributions submodule.
250
+
251
+ Further information can be found at:
252
+ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#individual-attributes
253
+
254
+ Args:
255
+ age_distribution_flag (int, optional): Toggles the type of simple distribution for representing age,
256
+ determining the distribution-specific interpretation of age_distribution1 and age_distribution2.
257
+ Mutually exclusive with a complex age distribution (age_distribution).
258
+ age_distribution1 (int, optional): If age_distribution_flag is not None, the specified simple
259
+ distribution-dependent first argument.
260
+ age_distribution2 (int, optional): If age_distribution_flag is not None, the specified simple
261
+ distribution-dependent second argument (if any).
262
+ age_distribution (AgeDistribution, optional): If provided, defines a complex age distribution. Mutually
263
+ exclusive with a simple age distribution (age_distribution_flag).
264
+
265
+ susceptibility_distribution_flag (int, optional): Toggles the type of simple distribution for representing
266
+ susceptibility, determining the distribution-specific interpretation of susceptibility_distribution1
267
+ and susceptibility_distribution2. Mutually exclusive with a complex susceptibility distribution
268
+ (susceptibility_distribution).
269
+ susceptibility_distribution1 (int, optional): If susceptibility_distribution_flag is not None, the
270
+ specified simple distribution-dependent first argument.
271
+ susceptibility_distribution2 (int, optional): If susceptibility_distribution_flag is not None, the
272
+ specified simple distribution-dependent second argument (if any).
273
+ susceptibility_distribution (SusceptibilityDistribution, optional): If provided, defines a complex
274
+ susceptibility distribution. Mutually exclusive with a simple susceptibility distribution
275
+ (susceptibility_distribution_flag).
276
+
277
+ prevalence_distribution_flag (int, optional): Toggles the type of simple distribution for representing
278
+ prevalence, determining the distribution-specific interpretation of prevalence_distribution1 and
279
+ prevalence_distribution2.
280
+ prevalence_distribution1 (int, optional): If prevalence_distribution_flag is not None, the specified simple
281
+ distribution-dependent first argument.
282
+ prevalence_distribution2 (int, optional): If prevalence_distribution_flag is not None, the specified simple
283
+ distribution-dependent second argument (if any).
284
+
285
+ risk_distribution_flag (int, optional): Toggles the type of simple distribution for representing risk,
286
+ determining the distribution-specific interpretation of risk_distribution1 and risk_distribution2.
287
+ risk_distribution1 (int, optional): If risk_distribution_flag is not None, the specified simple
288
+ distribution-dependent first argument.
289
+ risk_distribution2 (int, optional): If risk_distribution_flag is not None, the specified simple
290
+ distribution-dependent second argument (if any).
291
+
292
+ migration_heterogeneity_distribution_flag (int, optional): Toggles the type of simple distribution for
293
+ representing migration heterogeneity, determining the distribution-specific interpretation of
294
+ migration_heterogeneity_distribution1 and migration_heterogeneity_distribution2.
295
+ migration_heterogeneity_distribution1 (int, optional): If migration_heterogeneity_distribution_flag is not
296
+ None, the specified simple distribution-dependent first argument.
297
+ migration_heterogeneity_distribution2 (int, optional): If migration_heterogeneity_distribution_flag is not
298
+ None, the specified simple distribution-dependent second argument (if any).
299
+
300
+ fertility_distribution (FertilityDistribution, optional): If provided, defines a complex fertility
301
+ distribution for females.
302
+
303
+ mortality_distribution_male (MortalityDistribution, optional): If provided, defines a complex mortality
304
+ distribution for males.
305
+ mortality_distribution_female (MortalityDistribution, optional): If provided, defines a complex mortality
306
+ distribution for females.
307
+
308
+ innate_immune_distribution_flag (int, optional): Toggles the type of simple distribution for representing
309
+ innate immunity, determining the distribution-specific interpretation of innate_immune_distribution1
310
+ and innate_immune_distribution2.
311
+ innate_immune_distribution1 (int, optional): If innate immune_distribution_flag is not None, the specified
312
+ simple distribution-dependent first argument.
313
+ innate_immune_distribution2 (int, optional): If innate immune_distribution_flag is not None, the specified
314
+ simple distribution-dependent second argument (if any).
315
+ """
316
+ super().__init__()
317
+
318
+ # users can either use a simple age distribution (toggled with age_distribution_flag, defined by
319
+ # age_distribution1 and age_distribution2) OR a complex one (passed in via age_distribution)
320
+ if (age_distribution is not None) and (age_distribution_flag is not None):
321
+ raise ValueError("Cannot set both a simple age distribution via age_distribution_flag AND a complex "
322
+ "age distribution via age_distribution. Must choose one or the other. Or choose neither "
323
+ "to get default age distribution behavior.")
324
+ self.age_distribution_flag = age_distribution_flag
325
+ self.age_distribution1 = age_distribution1
326
+ self.age_distribution2 = age_distribution2
327
+ self.age_distribution = age_distribution
328
+
329
+ # users can either use a simple susceptibility distribution (toggled with susceptibility_distribution_flag,
330
+ # defined by susceptibility_distribution1 and susceptibility_distribution2) OR a complex one (passed in via
331
+ # susceptibility_distribution)
332
+ if (susceptibility_distribution is not None) and (susceptibility_distribution_flag is not None):
333
+ raise ValueError("Cannot set both a simple susceptibility distribution via "
334
+ "susceptibility_distribution_flag AND a complex susceptibility distribution via "
335
+ "susceptibility_distribution. Must choose one or the other. Or choose neither to get "
336
+ "default susceptibility distribution behavior.")
337
+ self.susceptibility_distribution_flag = susceptibility_distribution_flag
338
+ self.susceptibility_distribution1 = susceptibility_distribution1
339
+ self.susceptibility_distribution2 = susceptibility_distribution2
340
+ self.susceptibility_distribution = susceptibility_distribution
341
+
342
+ self.prevalence_distribution_flag = prevalence_distribution_flag
343
+ self.prevalence_distribution1 = prevalence_distribution1
344
+ self.prevalence_distribution2 = prevalence_distribution2
345
+
346
+ self.migration_heterogeneity_distribution_flag = migration_heterogeneity_distribution_flag
347
+ self.migration_heterogeneity_distribution1 = migration_heterogeneity_distribution1
348
+ self.migration_heterogeneity_distribution2 = migration_heterogeneity_distribution2
349
+
350
+ self.mortality_distribution_male = mortality_distribution_male
351
+ self.mortality_distribution_female = mortality_distribution_female
352
+ self.mortality_distribution = None # This should ONLY be set via from_dict() loading (deprecated).
353
+ # fertility is only used by HIV
354
+
355
+ self.fertility_distribution = fertility_distribution
356
+
357
+ # risk and innate_immune are only used by malaria
358
+
359
+ self.risk_distribution_flag = risk_distribution_flag
360
+ self.risk_distribution1 = risk_distribution1
361
+ self.risk_distribution2 = risk_distribution2
362
+
363
+ self.innate_immune_distribution_flag = innate_immune_distribution_flag
364
+ self.innate_immune_distribution1 = innate_immune_distribution1
365
+ self.innate_immune_distribution2 = innate_immune_distribution2
366
+
367
+ # New names for by-gender mortality distributions to support emodpy Demographics setting of all distributions
368
+ # using the same code (see properties here).
369
+
370
+ @property
371
+ def mortality_male_distribution(self):
372
+ return self.mortality_distribution_male
373
+
374
+ @mortality_male_distribution.setter
375
+ def mortality_male_distribution(self, value):
376
+ self.mortality_distribution_male = value
377
+
378
+ @property
379
+ def mortality_female_distribution(self):
380
+ return self.mortality_distribution_female
381
+
382
+ @mortality_female_distribution.setter
383
+ def mortality_female_distribution(self, value):
384
+ self.mortality_distribution_female = value
385
+
386
+ @staticmethod
387
+ def _ensure_valid_value2_value(distribution_dict: dict, value2_key: str):
388
+ # change any None to 0 for value2. Demographics demands it or EMOD fails.
389
+ distribution_dict[value2_key] = 0 if distribution_dict[value2_key] is None else distribution_dict[value2_key]
390
+
391
+ def to_dict(self) -> dict:
392
+ # TODO: Consider updating how/where we check for consistency of attributes of IndividualProperties objects,
393
+ # as a user MAY alter validity after constructor call which currently enforces consistency:
394
+ # https://github.com/InstituteforDiseaseModeling/emod-api-old/issues/751
395
+ individual_attributes = self.parameter_dict
396
+
397
+ # Set age distribution as complex or simple if specified, but not both.
398
+ both_types_selected = ((self.age_distribution is not None) and (self.age_distribution_flag is not None))
399
+ if both_types_selected:
400
+ raise ConflictingDistributionsException('Both a simple and complex distribution for age has been set. '
401
+ 'Only type is allowed.')
402
+ if self.age_distribution is not None:
403
+ # complex distribution
404
+ age_distribution_dict = {"AgeDistribution": self.age_distribution.to_dict()}
405
+ individual_attributes.update(age_distribution_dict)
406
+ elif self.age_distribution_flag is not None:
407
+ # simple distribution
408
+ age_distribution_dict = {
409
+ "AgeDistributionFlag": self.age_distribution_flag,
410
+ "AgeDistribution1": self.age_distribution1,
411
+ "AgeDistribution2": self.age_distribution2
412
+ }
413
+ self._ensure_valid_value2_value(distribution_dict=age_distribution_dict, value2_key="AgeDistribution2")
414
+ individual_attributes.update(age_distribution_dict)
415
+
416
+ # Set susceptibility distribution as complex or simple if specified, but not both.
417
+ both_types_selected = ((self.susceptibility_distribution is not None) and (self.susceptibility_distribution_flag is not None))
418
+ if both_types_selected:
419
+ raise ConflictingDistributionsException('Both a simple and complex distribution for susceptibility has '
420
+ 'been set. Only type is allowed.')
421
+ if self.susceptibility_distribution is not None:
422
+ # complex distribution
423
+ susceptibility_distribution_dict = {"SusceptibilityDistribution": self.susceptibility_distribution.to_dict()}
424
+ individual_attributes.update(susceptibility_distribution_dict)
425
+ elif self.susceptibility_distribution_flag is not None:
426
+ # simple distribution
427
+ susceptibility_distribution_dict = {
428
+ "SusceptibilityDistributionFlag": self.susceptibility_distribution_flag,
429
+ "SusceptibilityDistribution1": self.susceptibility_distribution1,
430
+ "SusceptibilityDistribution2": self.susceptibility_distribution2
431
+ }
432
+ self._ensure_valid_value2_value(distribution_dict=susceptibility_distribution_dict,
433
+ value2_key="SusceptibilityDistribution2")
434
+ individual_attributes.update(susceptibility_distribution_dict)
435
+
436
+ # The following distributions can only be simple, not complex
437
+
438
+ if self.prevalence_distribution_flag is not None:
439
+ prevalence_distribution_dict = {
440
+ "PrevalenceDistributionFlag": self.prevalence_distribution_flag,
441
+ "PrevalenceDistribution1": self.prevalence_distribution1,
442
+ "PrevalenceDistribution2": self.prevalence_distribution2
443
+ }
444
+ self._ensure_valid_value2_value(distribution_dict=prevalence_distribution_dict,
445
+ value2_key="PrevalenceDistribution2")
446
+ individual_attributes.update(prevalence_distribution_dict)
447
+
448
+ if self.migration_heterogeneity_distribution_flag is not None:
449
+ migration_heterogeneity_distribution_dict = {
450
+ "MigrationHeterogeneityDistributionFlag": self.migration_heterogeneity_distribution_flag,
451
+ "MigrationHeterogeneityDistribution1": self.migration_heterogeneity_distribution1,
452
+ "MigrationHeterogeneityDistribution2": self.migration_heterogeneity_distribution2
453
+ }
454
+ self._ensure_valid_value2_value(distribution_dict=migration_heterogeneity_distribution_dict,
455
+ value2_key="MigrationHeterogeneityDistribution2")
456
+ individual_attributes.update(migration_heterogeneity_distribution_dict)
457
+
458
+ # malaria only - possible to move this to emodpy-malaria in the future if desired.
459
+ if self.risk_distribution_flag is not None:
460
+ risk_distribution_dict = {
461
+ "RiskDistributionFlag": self.risk_distribution_flag,
462
+ "RiskDistribution1": self.risk_distribution1,
463
+ "RiskDistribution2": self.risk_distribution2
464
+ }
465
+ self._ensure_valid_value2_value(distribution_dict=risk_distribution_dict, value2_key="RiskDistribution2")
466
+ individual_attributes.update(risk_distribution_dict)
467
+
468
+ # malaria only - possible to move this to emodpy-malaria in the future if desired.
469
+ if self.innate_immune_distribution_flag is not None:
470
+ innate_immune_distribution_dict = {
471
+ "InnateImmuneDistributionFlag": self.innate_immune_distribution_flag,
472
+ "InnateImmuneDistribution1": self.innate_immune_distribution1,
473
+ "InnateImmuneDistribution2": self.innate_immune_distribution2
474
+ }
475
+ self._ensure_valid_value2_value(distribution_dict=innate_immune_distribution_dict,
476
+ value2_key="InnateImmuneDistribution2")
477
+ individual_attributes.update(innate_immune_distribution_dict)
478
+
479
+ # The following distributions can only be complex, not simple
480
+
481
+ if self.fertility_distribution is not None:
482
+ individual_attributes.update({"FertilityDistribution": self.fertility_distribution.to_dict()})
483
+
484
+ if self.mortality_distribution_male is not None:
485
+ individual_attributes.update({"MortalityDistributionMale": self.mortality_distribution_male.to_dict()})
486
+
487
+ if self.mortality_distribution_female is not None:
488
+ individual_attributes.update({"MortalityDistributionFemale": self.mortality_distribution_female.to_dict()})
489
+
490
+ # # This should ONLY be set via from_dict() loading (deprecated).
491
+ if self.mortality_distribution is not None:
492
+ individual_attributes.update({"MortalityDistribution": self.mortality_distribution.to_dict()})
493
+
494
+ return individual_attributes
495
+
496
+ def from_dict(self, individual_attributes: dict) -> Tuple["IndividualAttributes", list[Callable]]:
497
+ implicit_functions = []
498
+
499
+ age_distribution_dict = individual_attributes.get("AgeDistribution", None)
500
+ if age_distribution_dict is None:
501
+ self.age_distribution = None
502
+ self.age_distribution_flag = individual_attributes.get("AgeDistributionFlag", None)
503
+ self.age_distribution1 = individual_attributes.get("AgeDistribution1", None)
504
+ self.age_distribution2 = individual_attributes.get("AgeDistribution2", None)
505
+ implicit_functions.append(_set_age_simple)
506
+ else:
507
+ self.age_distribution = AgeDistribution.from_dict(distribution_dict=age_distribution_dict)
508
+ self.age_distribution_flag = None
509
+ self.age_distribution1 = None
510
+ self.age_distribution2 = None
511
+ implicit_functions.append(_set_age_complex)
512
+
513
+ susceptibility_distribution_dict = individual_attributes.get("SusceptibilityDistribution", None)
514
+ if susceptibility_distribution_dict is None:
515
+ self.susceptibility_distribution = None
516
+ self.susceptibility_distribution_flag = individual_attributes.get("SusceptibilityDistributionFlag", None)
517
+ self.susceptibility_distribution1 = individual_attributes.get("SusceptibilityDistribution1", None)
518
+ self.susceptibility_distribution2 = individual_attributes.get("SusceptibilityDistribution2", None)
519
+ implicit_functions.append(_set_suscept_simple)
520
+ else:
521
+ self.susceptibility_distribution = SusceptibilityDistribution.from_dict(
522
+ distribution_dict=susceptibility_distribution_dict)
523
+ self.susceptibility_distribution_flag = None
524
+ self.susceptibility_distribution1 = None
525
+ self.susceptibility_distribution2 = None
526
+ implicit_functions.append(_set_suscept_complex)
527
+
528
+ self.prevalence_distribution_flag = individual_attributes.get("PrevalenceDistributionFlag", None)
529
+ self.prevalence_distribution1 = individual_attributes.get("PrevalenceDistribution1", None)
530
+ self.prevalence_distribution2 = individual_attributes.get("PrevalenceDistribution2", None)
531
+ if self.prevalence_distribution_flag is not None:
532
+ implicit_functions.append(_set_init_prev)
533
+
534
+ self.migration_heterogeneity_distribution_flag = individual_attributes.get(
535
+ "MigrationHeterogeneityDistributionFlag", None)
536
+ self.migration_heterogeneity_distribution1 = individual_attributes.get("MigrationHeterogeneityDistribution1",
537
+ None)
538
+ self.migration_heterogeneity_distribution2 = individual_attributes.get("MigrationHeterogeneityDistribution2",
539
+ None)
540
+ if self.migration_heterogeneity_distribution_flag is not None:
541
+ implicit_functions.extend([_set_migration_model_fixed_rate, _set_enable_migration_model_heterogeneity])
542
+
543
+ loaded_mortality = False
544
+ distribution_dict = individual_attributes.get("MortalityDistributionMale", None)
545
+ if distribution_dict is None:
546
+ self.mortality_distribution_male = None
547
+ else:
548
+ self.mortality_distribution_male = MortalityDistribution.from_dict(distribution_dict=distribution_dict)
549
+ loaded_mortality = True
550
+
551
+ distribution_dict = individual_attributes.get("MortalityDistributionFemale", None)
552
+ if distribution_dict is None:
553
+ self.mortality_distribution_female = None
554
+ else:
555
+ self.mortality_distribution_female = MortalityDistribution.from_dict(distribution_dict=distribution_dict)
556
+ loaded_mortality = True
557
+
558
+ if loaded_mortality:
559
+ implicit_functions.extend([_set_enable_natural_mortality, _set_mortality_age_gender_year])
560
+
561
+ # Even though we do NOT support NEW CREATION of all-gender mortality distributions, they are still valid in
562
+ # deprecated "from_dict()"(files)-type demographics loading. This is the only way self.mortality_distribution
563
+ # can/should be set in this class.
564
+ distribution_dict = individual_attributes.get("MortalityDistribution", None)
565
+ if distribution_dict is None:
566
+ self.mortality_distribution = None
567
+ else:
568
+ self.mortality_distribution = MortalityDistribution.from_dict(distribution_dict=distribution_dict)
569
+ implicit_functions.extend([_set_enable_natural_mortality, _set_mortality_age_gender])
570
+
571
+ # malaria only - possible to move this to emodpy-malaria in the future if desired.
572
+ self.innate_immune_distribution_flag = individual_attributes.get("InnateImmuneDistributionFlag", None)
573
+ self.innate_immune_distribution1 = individual_attributes.get("InnateImmuneDistribution1", None)
574
+ self.innate_immune_distribution2 = individual_attributes.get("InnateImmuneDistribution2", None)
575
+ if self.innate_immune_distribution_flag is not None:
576
+ import warnings
577
+ warnings.warn("InnateImmuneDistribution loaded by file. Pyrogenic vs. cytokine-killing vs NONE (ignore) is "
578
+ "unknown. Config may need updating to ensure parameter Innate_Immune_Variation_Type is set "
579
+ "properly.",
580
+ Warning, stacklevel=2)
581
+
582
+ # malaria only - possible to move this to emodpy-malaria in the future if desired.
583
+ self.risk_distribution_flag = individual_attributes.get("RiskDistributionFlag", None)
584
+ self.risk_distribution1 = individual_attributes.get("RiskDistribution1", None)
585
+ self.risk_distribution2 = individual_attributes.get("RiskDistribution2", None)
586
+ if self.risk_distribution_flag is not None:
587
+ implicit_functions.append(_set_enable_demog_risk)
588
+
589
+ distribution_dict = individual_attributes.get("FertilityDistribution", None)
590
+ if distribution_dict is None:
591
+ self.fertility_distribution = None
592
+ else:
593
+ self.fertility_distribution = FertilityDistribution.from_dict(distribution_dict)
594
+ implicit_functions.append(_set_fertility_age_year)
595
+
596
+ return self, implicit_functions
597
+
598
+
599
+ class NodeAttributes(Updateable):
600
+ def __init__(self,
601
+ airport: int = None,
602
+ altitude: float = None,
603
+ area: float = None,
604
+ birth_rate: float = None,
605
+ country: str = None,
606
+ growth_rate: float = None,
607
+ name: str = None,
608
+ latitude: float = None,
609
+ longitude: float = None,
610
+ metadata: dict = None,
611
+ initial_population: int = None,
612
+ region: int = None,
613
+ seaport: int = None,
614
+ larval_habitat_multiplier: Optional[list[float]] = None,
615
+ initial_vectors_per_species: Union[dict, int, None] = None,
616
+ infectivity_multiplier: float = None,
617
+ extra_attributes: dict = None):
618
+ """
619
+ Defines node-specific attributes for all disease setups, utilized by Node objects.
620
+
621
+ Further information can be found at:
622
+ https://docs.idmod.org/projects/emod-generic/en/latest/parameter-demographics.html#nodeattributes
623
+ https://docs.idmod.org/projects/emod-malaria/en/latest/parameter-demographics.html#nodeattributes
624
+
625
+ Args:
626
+ airport (int, optional): Whether the node has an airport (1 for true, 0 for false).
627
+ altitude (float, optional): Altitude of the node (in meters).
628
+ area (float, optional): Spatial size of the node (TODO: unknown units)
629
+ birth_rate (float, optional): The birth rate in births/day/woman .
630
+ country (str, optional): Name of the country the node is in.
631
+ growth_rate (float, optional): TODO: unknown
632
+ name (str, optional): Name of the node
633
+ latitude (float, optional): Latitude of the node in degrees.
634
+ longitude (float, optional): Longitude of the node in degrees.
635
+ metadata (dict, optional): An arbitrary dict of metaaata key/values to add to the node for notation.
636
+ initial_population (int, optional): The initial number of people/agents in the node.
637
+ region (int, optional): Whether the node has a road network (1 for true, 0 for false).
638
+ seaport (int, optional): Whether the node has a seaport (1 for true, 0 for false).
639
+ larval_habitat_multiplier (list(float), optional): The value(s) by which to scale the larval habitat
640
+ availability specified in the configuration file with Larval_Habitat_Types.
641
+ initial_vectors_per_species ((dict or int), optional): The initial number of vectors per species in the
642
+ node.
643
+ infectivity_multiplier (float, optional): TODO: unknown
644
+ extra_attributes (dict, optional): An arbitrary dict of attribute key/values to add to the node.
645
+ """
646
+ super().__init__()
647
+ self.airport = airport
648
+ self.altitude = altitude
649
+ self.area = area
650
+ self.birth_rate = birth_rate
651
+ self.country = country
652
+ self.growth_rate = growth_rate
653
+ self.initial_population = initial_population
654
+ self.initial_vectors_per_species = initial_vectors_per_species
655
+ self.larval_habitat_multiplier = larval_habitat_multiplier
656
+ self.latitude = latitude
657
+ self.longitude = longitude
658
+ self.metadata = metadata
659
+ self.name = name
660
+ self.region = region
661
+ self.seaport = seaport
662
+ self.infectivity_multiplier = infectivity_multiplier
663
+ self.extra_attributes = extra_attributes
664
+
665
+ def from_dict(self, node_attributes: dict):
666
+ self.airport = node_attributes.get("Airport")
667
+ self.altitude = node_attributes.get("Altitude")
668
+ self.area = node_attributes.get("Area")
669
+ self.country = node_attributes.get("country")
670
+ self.growth_rate = node_attributes.get("GrowthRate")
671
+ self.name = node_attributes.get("FacilityName")
672
+ self.latitude = node_attributes.get("Latitude")
673
+ self.longitude = node_attributes.get("Longitude")
674
+ self.metadata = node_attributes.get("Metadata")
675
+ self.initial_population = node_attributes.get("InitialPopulation")
676
+ self.larval_habitat_multiplier = node_attributes.get("LarvalHabitatMultiplier")
677
+ self.initial_vectors_per_species = node_attributes.get("InitialVectorsPerSpecies")
678
+ self.birth_rate = node_attributes.get("BirthRate")
679
+ self.seaport = node_attributes.get("Seaport")
680
+ self.region = node_attributes.get("Region")
681
+ self.infectivity_multiplier = node_attributes.get("InfectivityMultiplier")
682
+ return self
683
+
684
+ def to_dict(self) -> dict:
685
+ node_attributes = self.parameter_dict
686
+ if self.birth_rate is not None:
687
+ node_attributes.update({"BirthRate": self.birth_rate})
688
+
689
+ if self.area is not None:
690
+ node_attributes.update({"Area": self.area})
691
+
692
+ if self.latitude is not None:
693
+ node_attributes.update({"Latitude": self.latitude})
694
+
695
+ if self.longitude is not None:
696
+ node_attributes.update({"Longitude": self.longitude})
697
+
698
+ if self.initial_population is not None:
699
+ node_attributes.update({"InitialPopulation": int(self.initial_population)})
700
+
701
+ if self.name:
702
+ node_attributes.update({"FacilityName": self.name})
703
+
704
+ if self.larval_habitat_multiplier is not None:
705
+ node_attributes.update({"LarvalHabitatMultiplier": self.larval_habitat_multiplier})
706
+
707
+ if self.initial_vectors_per_species:
708
+ node_attributes.update({"InitialVectorsPerSpecies": self.initial_vectors_per_species})
709
+
710
+ if self.airport is not None:
711
+ node_attributes.update({"Airport": self.airport})
712
+
713
+ if self.altitude is not None:
714
+ node_attributes.update({"Altitude": self.altitude})
715
+
716
+ if self.seaport is not None:
717
+ node_attributes.update({"Seaport": self.seaport})
718
+
719
+ if self.region is not None:
720
+ node_attributes.update({"Region": self.region})
721
+
722
+ if self.country is not None:
723
+ node_attributes.update({"country": self.country})
724
+
725
+ if self.growth_rate is not None:
726
+ node_attributes.update({"GrowthRate": self.growth_rate})
727
+
728
+ if self.metadata is not None:
729
+ node_attributes.update({"Metadata": self.metadata})
730
+
731
+ if self.infectivity_multiplier is not None:
732
+ node_attributes.update({"InfectivityMultiplier": self.infectivity_multiplier})
733
+
734
+ if self.extra_attributes is not None:
735
+ node_attributes.update(self.extra_attributes)
736
+
737
+ return node_attributes