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,210 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """emod-api spatial report module. Exposes SpatialReport and SpatialNode objects."""
4
+
5
+ from pathlib import Path
6
+ import numpy as np
7
+
8
+
9
+ class SpatialNode(object):
10
+
11
+ """
12
+ Class representing a single node of a spatial report.
13
+ """
14
+
15
+ def __init__(self, node_id: int, data):
16
+
17
+ self._id = node_id
18
+ self._data = data
19
+
20
+ return
21
+
22
+ @property
23
+ def id(self) -> int:
24
+ """Node ID"""
25
+ return self._id
26
+
27
+ @property
28
+ def data(self):
29
+ """Time series data for this node."""
30
+ return self._data
31
+
32
+ def __getitem__(self, item: int) -> float:
33
+ """index into node data by time step"""
34
+ return self._data[item]
35
+
36
+ def __setitem__(self, key: int, value: float) -> None:
37
+ """index into node data by time step"""
38
+ self._data[key] = value
39
+ return
40
+
41
+
42
+ NUM_STEPS_INDEX = 0
43
+ NUM_NODES_INDEX = 1
44
+
45
+
46
+ class SpatialReport(object):
47
+
48
+ """
49
+ Class for reading (and, optionally, writing) spatial reports in EMOD/DTK format.
50
+ "Filtered" reports will have start > 0 and/or reporting interval > 1.
51
+ """
52
+
53
+ def __init__(self, filename: str = None, node_ids: list[int] = None, data: np.array = None, start: int = 0, interval: int = 1):
54
+
55
+ """
56
+ Args:
57
+ filename: file from which to read data
58
+ node_ids: list of node ids, must be integer values
59
+ data: NumPy array of data, shape must be (#values, #nodes)
60
+ start: time step of first sample (used with filtered reports)
61
+ interval: # of time steps between samples (used with filtered reports)
62
+ """
63
+
64
+ if isinstance(filename, str):
65
+ self._from_file(filename)
66
+ else:
67
+ self._from_node_ids_and_data(node_ids, data, start, interval)
68
+
69
+ return
70
+
71
+ @property
72
+ def data(self) -> np.array:
73
+ """Returns full 2 dimensional NumPy array with report data. Shape is (#values, #nodes)."""
74
+ return self._data
75
+
76
+ @property
77
+ def node_ids(self) -> list[int]:
78
+ """Returns list of node IDs (integers) for nodes in the report."""
79
+ return self._node_ids
80
+
81
+ @property
82
+ def nodes(self) -> dict[int, SpatialNode]:
83
+ """Returns dictionary of SpatialNodes keyed on node ID."""
84
+ return self._nodes
85
+
86
+ # index into report by node id
87
+ def __getitem__(self, item: int) -> SpatialNode:
88
+ return self._nodes[item]
89
+
90
+ @property
91
+ def node_count(self) -> int:
92
+ """Number of nodes in the report."""
93
+ return self.data.shape[NUM_NODES_INDEX]
94
+
95
+ @property
96
+ def time_steps(self) -> int:
97
+ """Number of samples in the report."""
98
+ return self.data.shape[NUM_STEPS_INDEX]
99
+
100
+ @property
101
+ def start(self) -> int:
102
+ """Time step of first sample."""
103
+ return self._start
104
+
105
+ @property
106
+ def interval(self) -> int:
107
+ """Interval, in time steps, between samples."""
108
+ return self._interval
109
+
110
+ def write_file(self, filename: str):
111
+
112
+ """Save current nodes and timeseries data to given file."""
113
+
114
+ with open(filename, "wb") as file:
115
+ np.array([self.node_count], dtype=np.uint32).tofile(file)
116
+ np.array([self.time_steps], dtype=np.uint32).tofile(file)
117
+ if self.start != 0 or self.interval != 1:
118
+ np.array([self.start], dtype=np.float32).tofile(file)
119
+ np.array([self.interval], dtype=np.float32).tofile(file)
120
+ np.array([self.node_ids], dtype=np.uint32).tofile(file)
121
+ self.data.tofile(file)
122
+
123
+ return
124
+
125
+ def _from_file(self, filename: str):
126
+ """
127
+ Read binary spatial report file.
128
+ #nodes,
129
+ #time steps,
130
+ node ids (#nodes values),
131
+ data (#nodes x #time steps values)
132
+ """
133
+ # File format:
134
+ # number of nodes - uint32 * 1
135
+ # number of time steps - uint32 * 1
136
+ # OPTIONAL:
137
+ # starting time step - float32 * 1 (integral value in reality)
138
+ # time step interval - float32 * 1 (integral value in reality)
139
+ # node ids - uint32 * number of nodes
140
+ # data - (float32 * number of nodes) * number of time_steps
141
+
142
+ file_size = Path(filename).stat().st_size
143
+
144
+ with open(filename, "rb") as file:
145
+ num_nodes = np.fromfile(file, dtype=np.uint32, count=1)[0]
146
+ num_time_steps = np.fromfile(file, dtype=np.uint32, count=1)[0]
147
+
148
+ simple_size = (2 + num_nodes + (num_nodes * num_time_steps)) * 4 # num_nodes, num_time_steps, node_ids, and data
149
+ filtered_size = simple_size + 8 # include starting time step and time step interval
150
+
151
+ if file_size == simple_size:
152
+ self._start = 0
153
+ self._interval = 1
154
+ elif file_size == filtered_size:
155
+ self._start = int(np.fromfile(file, dtype=np.float32, count=1)[0])
156
+ self._interval = int(np.fromfile(file, dtype=np.float32, count=1)[0])
157
+ assert self.start >= 0
158
+ assert self.interval >= 1
159
+ else:
160
+ raise RuntimeError(f"Unexpected file size {file_size}, expected {simple_size} (standard spatial report) or {filtered_size} (filtered spatial report).")
161
+
162
+ node_ids = np.fromfile(file, dtype=np.uint32, count=num_nodes)
163
+ data = np.fromfile(file, dtype=np.float32, count=num_nodes * num_time_steps)
164
+
165
+ # let us index data[step, node]
166
+ data = data.reshape((num_time_steps, num_nodes))
167
+ self._from_node_ids_and_data(node_ids, data, self._start, self._interval)
168
+
169
+ return
170
+
171
+ def _from_node_ids_and_data(self, node_ids: list, data: np.array, start: int, interval: int) -> None:
172
+
173
+ assert _is_iterable(node_ids), "node_ids must be specified and iterable"
174
+ concrete = list(node_ids)
175
+ assert len(concrete) > 0, "node_ids must not be empty"
176
+ assert all(map(lambda i: _isinteger(i), concrete)), "node_ids must be integers"
177
+ assert len(set(concrete)) == len(concrete), "node_ids must be unique"
178
+ self._node_ids = sorted(concrete)
179
+ assert data.dtype is np.dtype("float32"), "data must be np.float32"
180
+ assert data.shape[1] == len(
181
+ self._node_ids
182
+ ), "data shape must be (#values, #nodes)"
183
+ self._data = data
184
+
185
+ self._node_id_to_index_map = {
186
+ node_ids[n]: n for n in range(data.shape[NUM_NODES_INDEX])
187
+ }
188
+ self._nodes = {
189
+ node_id: SpatialNode(node_id, data[:, self._node_id_to_index_map[node_id]])
190
+ for node_id in node_ids
191
+ }
192
+
193
+ assert int(start) >= 0, "start sample time must be >= 0"
194
+ self._start = int(start)
195
+ assert int(interval) >= 1, "sample interval must be >= 1"
196
+ self._interval = int(interval)
197
+
198
+ return
199
+
200
+
201
+ def _is_iterable(obj) -> bool:
202
+ try:
203
+ _ = iter(obj)
204
+ return True
205
+ except TypeError:
206
+ return False
207
+
208
+
209
+ def _isinteger(item) -> bool:
210
+ return isinstance(item, (int, np.integer))
@@ -0,0 +1,26 @@
1
+ from collections.abc import Iterable
2
+
3
+
4
+ def check_dimensionality(data, dimensionality: int) -> bool:
5
+ """
6
+ Returns True/False: is the provided data a matrix of the specified dimensionality?
7
+ Args:
8
+ data: An object to check the dimensionality of
9
+ dimensionality: The expected dimensionality
10
+
11
+ Returns:
12
+ True if data dimensionality is as expected, False if not.
13
+ """
14
+ ret = True
15
+ if dimensionality == 0:
16
+ if isinstance(data, Iterable):
17
+ ret = False # expected to NOT be iterable still, but it is
18
+ else:
19
+ if isinstance(data, Iterable):
20
+ for item in data:
21
+ if check_dimensionality(data=item, dimensionality=dimensionality - 1) is False:
22
+ ret = False
23
+ break
24
+ else:
25
+ ret = False # expected to be iterable still, but it isn't
26
+ return ret
File without changes
@@ -0,0 +1,38 @@
1
+ from abc import ABC, abstractmethod
2
+ from emod_api import schema_to_class as s2c
3
+
4
+
5
+ class BaseDistribution(ABC):
6
+ """
7
+ Abstract base class for distribution classes such as UniformDistribution and ExpoentialDistribution. This class
8
+ should not be instantiated directly.
9
+ """
10
+
11
+ @abstractmethod
12
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
13
+ """
14
+ Set the distribution parameters to the intervention object.
15
+
16
+ Args:
17
+ intervention_object (s2c.ReadOnlyDict):
18
+ - The object to set.
19
+ prefix (str):
20
+ - The prefix of the parameters.
21
+ """
22
+ pass
23
+
24
+ @abstractmethod
25
+ def get_demographic_distribution_parameters(self) -> dict:
26
+ """
27
+ Yield the flag and relevant values necessary for setting a demographics distribution of the class type
28
+
29
+ Returns:
30
+ a dict of the form: {'flag': X, 'value1': Y, 'value2': Z}
31
+ """
32
+ pass
33
+
34
+ def _set_parameters(self, emod_object, key, value):
35
+ if hasattr(emod_object, key):
36
+ setattr(emod_object, key, value)
37
+ else:
38
+ raise AttributeError(f"Attribute {key} does not exist in {emod_object.__class__.__name__}")
@@ -0,0 +1,64 @@
1
+ from emod_api import schema_to_class as s2c
2
+ from emod_api.utils.distributions.base_distribution import BaseDistribution
3
+ from emod_api.utils.distributions.demographic_distribution_flag import DemographicDistributionFlag
4
+
5
+
6
+ class BimodalDistribution(BaseDistribution):
7
+ """
8
+ This class represents a bimodal distribution, a type of statistical distribution with two different modes (peaks).
9
+ A bimodal distribution is defined by two parameters: the proportion of the second bin, user defined bin, and the
10
+ constant value of the second bin. The 1-proportion will be the first bin and constant value in the first bin is 1.
11
+
12
+ This distribution is not supported in EMOD interventions.
13
+
14
+ Args:
15
+ proportion (float):
16
+ - The proportion of the second bin.
17
+ - This value should be between 0 and 1.
18
+
19
+ constant (float):
20
+ - The constant value of the second bin.
21
+ - The value should not be negative.
22
+
23
+ Examples:
24
+ >>> # Create a BimodalDistribution object.
25
+ >>> # In the follow example, there will be 20% of the second bin(5) and 80% of the first bin(1).
26
+ >>> bd = BimodalDistribution(0.2, 5)
27
+ >>> # The proportion and constant attributes can be accessed and updated.
28
+ >>> bd.proportion
29
+ 0.2
30
+ >>> bd.constant
31
+ 5
32
+ >>> bd.proportion = 0.6
33
+ >>> bd.proportion
34
+ 0.6
35
+
36
+ """
37
+ DEMOGRAPHIC_DISTRIBUTION_FLAG = DemographicDistributionFlag.BIMODAL.value
38
+
39
+ def __init__(self, proportion: float, constant: float):
40
+ super().__init__()
41
+ if proportion < 0 or proportion > 1:
42
+ raise ValueError("The 'proportion' argument should be between 0 and 1.")
43
+ if constant < 0:
44
+ raise ValueError("The 'constant' argument should not be negative.")
45
+ self.proportion = proportion
46
+ self.constant = constant
47
+
48
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
49
+ """
50
+ This function is not supported in the intervention object. Raise NotImplementedError if called.
51
+ """
52
+ raise NotImplementedError("BimodalDistribution does not support intervention distribution. Please use "
53
+ "other distributions.")
54
+
55
+ def get_demographic_distribution_parameters(self) -> dict:
56
+ """
57
+ Yield the flag and relevant values necessary for setting a demographics bimodal distribution
58
+
59
+ Returns:
60
+ a dict of the form: {'flag': X, 'value1': Y, 'value2': Z}
61
+ """
62
+ return {"flag": self.DEMOGRAPHIC_DISTRIBUTION_FLAG,
63
+ "value1": self.proportion,
64
+ "value2": self.constant}
@@ -0,0 +1,58 @@
1
+ from emod_api import schema_to_class as s2c
2
+ from emod_api.utils.distributions.base_distribution import BaseDistribution
3
+ from emod_api.utils.distributions.demographic_distribution_flag import DemographicDistributionFlag
4
+ from emod_api.utils.distributions.distribution_type import DistributionType
5
+
6
+
7
+ class ConstantDistribution(BaseDistribution):
8
+ """
9
+ This class represents a constant distribution, a type of statistical distribution where all outcomes are equally
10
+ likely. A constant distribution is defined by a single value that is returned for all inputs.
11
+
12
+ Args:
13
+ value (float):
14
+ - The constant value that this distribution returns.
15
+ - The value should not be negative.
16
+
17
+ Raises:
18
+ ValueError: If the 'value' argument is negative.
19
+
20
+ Example:
21
+ >>> # Create a ConstantDistribution object.
22
+ >>> cd = ConstantDistribution(5)
23
+ >>> # The value attribute can be accessed and updated.
24
+ >>> cd.value
25
+ 5
26
+ >>> cd.value = 10
27
+ >>> cd.value
28
+ 10
29
+ """
30
+ DEMOGRAPHIC_DISTRIBUTION_FLAG = DemographicDistributionFlag.CONSTANT.value
31
+
32
+ def __init__(self, value: float):
33
+ super().__init__()
34
+ if value < 0:
35
+ raise ValueError("The 'value' argument should not be negative.")
36
+ self.value = value
37
+
38
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
39
+ """
40
+ Set the distribution parameters to the object.
41
+
42
+ Args:
43
+ intervention_object (s2c.ReadOnlyDict):
44
+ - The object to set.
45
+ prefix (str):
46
+ - The prefix of the parameters.
47
+ """
48
+ self._set_parameters(intervention_object, f"{prefix}_Distribution", DistributionType.CONSTANT_DISTRIBUTION.value)
49
+ self._set_parameters(intervention_object, f"{prefix}_Constant", self.value)
50
+
51
+ def get_demographic_distribution_parameters(self) -> dict:
52
+ """
53
+ Yield the flag and relevant values necessary for setting a demographics constant distribution
54
+
55
+ Returns:
56
+ a dict of the form: {'flag': X, 'value1': Y, 'value2': Z}
57
+ """
58
+ return {"flag": self.DEMOGRAPHIC_DISTRIBUTION_FLAG, "value1": self.value, "value2": None} # value 2 not used
@@ -0,0 +1,16 @@
1
+ from enum import Enum
2
+
3
+
4
+ class DemographicDistributionFlag(Enum):
5
+ CONSTANT = 0
6
+ UNIFORM = 1
7
+ GAUSSIAN = 2
8
+ EXPONENTIAL = 3
9
+ POISSON = 4
10
+ LOG_NORMAL = 5
11
+ BIMODAL = 6
12
+ WEIBULL = 7
13
+
14
+ # Not supported for demographics
15
+ # DUAL_CONSTANT = -1
16
+ # DUAL_EXPONENTIAL = -2
@@ -0,0 +1,15 @@
1
+ from emod_api.utils.str_enum import StrEnum
2
+
3
+
4
+ class DistributionType(StrEnum):
5
+ NOT_INITIALIZED = 'NOT_INITIALIZED'
6
+ CONSTANT_DISTRIBUTION = 'CONSTANT_DISTRIBUTION'
7
+ UNIFORM_DISTRIBUTION = 'UNIFORM_DISTRIBUTION'
8
+ GAUSSIAN_DISTRIBUTION = 'GAUSSIAN_DISTRIBUTION'
9
+ EXPONENTIAL_DISTRIBUTION = 'EXPONENTIAL_DISTRIBUTION'
10
+ POISSON_DISTRIBUTION = 'POISSON_DISTRIBUTION'
11
+ LOG_NORMAL_DISTRIBUTION = 'LOG_NORMAL_DISTRIBUTION'
12
+ DUAL_CONSTANT_DISTRIBUTION = 'DUAL_CONSTANT_DISTRIBUTION'
13
+ WEIBULL_DISTRIBUTION = 'WEIBULL_DISTRIBUTION'
14
+ DUAL_EXPONENTIAL_DISTRIBUTION = 'DUAL_EXPONENTIAL_DISTRIBUTION'
15
+ BIMODAL_DISTRIBUTION = 'BIMODAL_DISTRIBUTION'
@@ -0,0 +1,68 @@
1
+ from emod_api import schema_to_class as s2c
2
+ from emod_api.utils.distributions.base_distribution import BaseDistribution
3
+ from emod_api.utils.distributions.distribution_type import DistributionType
4
+
5
+
6
+ class DualConstantDistribution(BaseDistribution):
7
+ """
8
+ This class represents a dual constant distribution, a type of statistical distribution where the outcomes are
9
+ distributed between a constant value and zero based on a proportion. A dual constant
10
+ distribution is defined by two parameters: the proportion and the constant value.
11
+
12
+ This distribution is not supported in EMOD demographics.
13
+
14
+ Args:
15
+ proportion (float):
16
+ - The proportion of value of zero.
17
+ - This value should be between 0 and 1.
18
+
19
+ constant (float):
20
+ - The second constant value that this distribution returns other than zero.
21
+ - The value should not be negative.
22
+
23
+ Raises:
24
+ ValueError: If 'proportion' argument is not between 0 and 1 or 'constant' argument is negative.
25
+
26
+ Example:
27
+ >>> # Create a DualConstantDistribution object.
28
+ >>> # In the follow example, there will be 20% of zeros and 80% of 5s.
29
+ >>> dcd = DualConstantDistribution(0.2, 5)
30
+ >>> # The proportion and constant attributes can be accessed and updated.
31
+ >>> dcd.proportion
32
+ 0.2
33
+ >>> dcd.constant
34
+ 5
35
+ >>> dcd.proportion = 0.6
36
+ >>> dcd.proportion
37
+ 0.6
38
+ """
39
+ def __init__(self, proportion: float, constant: float):
40
+ if proportion < 0 or proportion > 1:
41
+ raise ValueError("The 'proportion' argument should be between 0 and 1.")
42
+ if constant < 0:
43
+ raise ValueError("The 'constant' argument should not be negative.")
44
+ super().__init__()
45
+ self.proportion = proportion
46
+ self.constant = constant
47
+
48
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
49
+ """
50
+ Set the distribution parameters to the object.
51
+
52
+ Args:
53
+ intervention_object (s2c.ReadOnlyDict):
54
+ - The object to set.
55
+ prefix (str):
56
+ - The prefix of the parameters.
57
+ """
58
+ self._set_parameters(intervention_object, f"{prefix}_Distribution",
59
+ DistributionType.DUAL_CONSTANT_DISTRIBUTION.value)
60
+ self._set_parameters(intervention_object, f"{prefix}_Proportion_0", self.proportion)
61
+ self._set_parameters(intervention_object, f"{prefix}_Peak_2_Value", self.constant)
62
+
63
+ def get_demographic_distribution_parameters(self) -> dict:
64
+ """
65
+ This function is not supported in the demographic object. Raise NotImplementedError if called.
66
+ """
67
+ raise NotImplementedError("DualConstantDistribution does not support demographic distribution. Please use "
68
+ "other distributions.")
@@ -0,0 +1,75 @@
1
+ from emod_api import schema_to_class as s2c
2
+ from emod_api.utils.distributions.base_distribution import BaseDistribution
3
+ from emod_api.utils.distributions.distribution_type import DistributionType
4
+
5
+
6
+ class DualExponentialDistribution(BaseDistribution):
7
+ """
8
+ This class represents a dual exponential distribution, a type of statistical distribution where the outcomes are
9
+ distributed between two exponential distributions based on a proportion. A dual exponential distribution is defined
10
+ by three parameters: the proportion, the first mean, and the second mean.
11
+
12
+ This distribution is not supported in EMOD demographics.
13
+
14
+ Args:
15
+ proportion (float):
16
+ - The proportion of the first exponential distribution.
17
+ - This value should be between 0 and 1.
18
+
19
+ mean_1 (float):
20
+ - The mean of the first exponential distribution.
21
+ - This value should be positive.
22
+
23
+ mean_2 (float):
24
+ - The mean of the second exponential distribution.
25
+ - This value should be positive.
26
+
27
+ Raises:
28
+ ValueError: If 'proportion' argument is not between 0 and 1 or 'mean_1' or 'mean_2' arguments are negative.
29
+
30
+ Example:
31
+ >>> # Create a DualExponentialDistribution object.
32
+ >>> # In the follow example, there will be 20% of the first exponential distribution and 80% of the second.
33
+ >>> ded = DualExponentialDistribution(0.2, 1, 2)
34
+ >>> # The proportion, mean_1, and mean_2 attributes can be accessed and updated.
35
+ >>> ded.proportion
36
+ 0.2
37
+ >>> ded.mean_1
38
+ 1
39
+ >>> ded.mean_2
40
+ 2
41
+ >>> ded.proportion = 0.6
42
+ >>> ded.proportion
43
+ 0.6
44
+ """
45
+ def __init__(self, proportion: float, mean_1: float, mean_2: float):
46
+ if proportion < 0 or proportion > 1:
47
+ raise ValueError("The 'proportion' argument should be between 0 and 1.")
48
+ if mean_1 <= 0 or mean_2 <= 0:
49
+ raise ValueError("The 'mean_1' and 'mean_2' arguments should be positive.")
50
+ super().__init__()
51
+ self.proportion = proportion
52
+ self.mean_1 = mean_1
53
+ self.mean_2 = mean_2
54
+
55
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
56
+ """
57
+ Set the distribution parameters to the object.
58
+
59
+ Args:
60
+ intervention_object (s2c.ReadOnlyDict):
61
+ - The object to set.
62
+ prefix (str):
63
+ - The prefix of the parameters.
64
+ """
65
+ self._set_parameters(intervention_object, f"{prefix}_Distribution", DistributionType.DUAL_EXPONENTIAL_DISTRIBUTION.value)
66
+ self._set_parameters(intervention_object, f"{prefix}_Proportion_1", self.proportion)
67
+ self._set_parameters(intervention_object, f"{prefix}_Mean_1", self.mean_1)
68
+ self._set_parameters(intervention_object, f"{prefix}_Mean_2", self.mean_2)
69
+
70
+ def get_demographic_distribution_parameters(self) -> None:
71
+ """
72
+ This function is not supported in the demographic object. Raise NotImplementedError if called.
73
+ """
74
+ raise NotImplementedError("DualExponentialDistribution does not support demographic distribution. Please use "
75
+ "other distributions.")
@@ -0,0 +1,63 @@
1
+ from emod_api import schema_to_class as s2c
2
+ from emod_api.utils.distributions.base_distribution import BaseDistribution
3
+ from emod_api.utils.distributions.demographic_distribution_flag import DemographicDistributionFlag
4
+ from emod_api.utils.distributions.distribution_type import DistributionType
5
+
6
+
7
+ class ExponentialDistribution(BaseDistribution):
8
+ """
9
+ This class represents an exponential distribution, a type of statistical distribution
10
+ where the probability of an event decreases exponentially with time.
11
+ An exponential distribution is defined by a single parameter: the mean, which represents the average time
12
+ between events.
13
+
14
+ Args:
15
+ mean (float):
16
+ - The mean, also the scale parameter of the exponential distribution.
17
+ - It's the 1/rate parameter.
18
+ - This value is set during the initialization of the class instance. It can be updated using the 'update_attribute()' method.
19
+ - The value should not be negative.
20
+
21
+ Raises:
22
+ ValueError: If 'mean' argument is negative.
23
+
24
+ Example:
25
+ >>> # Create an ExponentialDistribution object.
26
+ >>> ed = ExponentialDistribution(1)
27
+ >>> # The mean attribute can be accessed and updated.
28
+ >>> ed.mean
29
+ 1
30
+ >>> ed.mean = 2
31
+ >>> ed.mean
32
+ 2
33
+ """
34
+ DEMOGRAPHIC_DISTRIBUTION_FLAG = DemographicDistributionFlag.EXPONENTIAL.value
35
+
36
+ def __init__(self, mean: float):
37
+ super().__init__()
38
+ if mean < 0:
39
+ raise ValueError("The 'mean' argument should not be negative.")
40
+ self.mean = mean
41
+
42
+ def set_intervention_distribution(self, intervention_object: s2c.ReadOnlyDict, prefix: str):
43
+ """
44
+ Set the distribution parameters to the object.
45
+
46
+ Args:
47
+ intervention_object (s2c.ReadOnlyDict):
48
+ - The object to set.
49
+ prefix (str):
50
+ - The prefix of the parameters.
51
+ """
52
+ self._set_parameters(intervention_object, f"{prefix}_Distribution",
53
+ DistributionType.EXPONENTIAL_DISTRIBUTION.value)
54
+ self._set_parameters(intervention_object, f"{prefix}_Exponential", self.mean)
55
+
56
+ def get_demographic_distribution_parameters(self) -> dict:
57
+ """
58
+ Yield the flag and relevant values necessary for setting a demographics exponential distribution
59
+
60
+ Returns:
61
+ a dict of the form: {'flag': X, 'value1': Y, 'value2': Z}
62
+ """
63
+ return {"flag": self.DEMOGRAPHIC_DISTRIBUTION_FLAG, "value1": self.mean, "value2": None} # value2 not used