Skip to content

Commit 7718af2

Browse files
authored
2.0-ified direct demograhpics __init__ instantiation including DemographicsOverlay (Fix #29) (#43)
* Reworked direct demograhpics __init__ instantiation to only use Node objects, including similar DemographicsOverlay update. Commented/deactivated many tests that will be fixed in following issues/PRs (Fix #29)
1 parent 8a5b665 commit 7718af2

File tree

8 files changed

+1512
-2285
lines changed

8 files changed

+1512
-2285
lines changed

emod_api/demographics/demographics.py

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
import os
1010
import pandas as pd
1111

12-
from typing import List, Dict
12+
from typing import List
1313

14-
from emod_api.demographics import demographics_templates as DT
1514
from emod_api.demographics.node import Node
1615
from emod_api.demographics.properties_and_attributes import NodeAttributes
1716
from emod_api.demographics.demographics_base import DemographicsBase
@@ -273,37 +272,17 @@ def from_pop_csv(pop_filename_in,
273272

274273
class Demographics(DemographicsBase):
275274
"""
276-
This class is a container of data necessary to produce a EMOD-valid demographics input file. It can be initialized
277-
from an existing valid demographics.joson type file or from an array of valid Nodes.
275+
This class is a container of data necessary to produce a EMOD-valid demographics input file.
278276
"""
279-
def __init__(self, nodes: List[Node], idref: str = "Gridded world grump2.5arcmin", base_file: str = None,
280-
default_node: Node = None):
277+
def __init__(self, nodes: List[Node], idref: str = "Gridded world grump2.5arcmin", default_node: Node = None):
281278
"""
282279
A class to create demographics.
283280
:param nodes: list of Nodes
284281
:param idref: A name/reference
285-
:param base_file: A demographics file in json format
286282
:default_node: An optional node to use for default settings.
287283
"""
288284
super().__init__(nodes=nodes, idref=idref, default_node=default_node)
289285

290-
# HIV is expected to pass a default node. Malaria is not (for now).
291-
if default_node is None:
292-
if base_file:
293-
with open(base_file, "rb") as src:
294-
self.raw = json.load(src)
295-
else:
296-
# adding and using this default configuration (True) as malaria may use it; I don't know. HIV does not.
297-
self.SetMinimalNodeAttributes()
298-
DT.NoInitialPrevalence(self) # does this need to be called?
299-
DT.InitAgeUniform(self)
300-
301-
def to_dict(self) -> Dict:
302-
self.verify_demographics_integrity()
303-
self.raw["Nodes"] = [node.to_dict() for node in self.nodes]
304-
self.raw["Metadata"]["NodeCount"] = len(self.nodes)
305-
return self.raw
306-
307286
def generate_file(self, name="demographics.json"):
308287
"""
309288
Write the contents of the instance to an EMOD-compatible (JSON) file.

emod_api/demographics/demographics_base.py

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,10 @@ class DuplicateNodeNameException(Exception):
3838
pass
3939

4040
def __init__(self, nodes: List[Node], idref: str, default_node: Node = None):
41+
"""
42+
Passed-in default nodes are optional. If one is not passed in, one will be created.
43+
"""
4144
super().__init__(idref=idref)
42-
# TODO: node ids should be required to be UNIQUE to prevent later failures when running EMOD. Any update to
43-
# self.nodes should trigger a check/error if needed.
4445
self.nodes = nodes
4546
self.implicits = list()
4647
self.migration_files = list()
@@ -50,33 +51,28 @@ def __init__(self, nodes: List[Node], idref: str, default_node: Node = None):
5051
if node.id <= 0:
5152
raise InvalidNodeIdException(f"Non-default nodes must have integer ids > 0 . Found id: {node.id}")
5253

53-
# Build the default node if not provided
54-
metadata = self.generate_headers()
55-
if default_node is None: # use raw attribute, current malaria/other disease style
56-
# currently all non-HIV disease route
57-
self.default_node = None
58-
self.metadata = None
59-
self.raw = {"Defaults": dict(), "Metadata": metadata}
60-
self.raw["Defaults"]["NodeAttributes"] = dict()
61-
self.raw["Defaults"]["IndividualAttributes"] = dict()
62-
self.raw["Defaults"]["NodeID"] = 0
63-
self.raw["Defaults"]["IndividualProperties"] = list()
64-
# TODO: remove the following setting of birth_rate on the default node once this EMOD binary issue is fixed
65-
# https://github.com/InstituteforDiseaseModeling/DtkTrunk/issues/4009
66-
self.raw["Defaults"]["NodeAttributes"]["BirthRate"] = 0
67-
else: # HIV style
68-
self.default_node = default_node
69-
self.default_node.name = self.DEFAULT_NODE_NAME
70-
if self.default_node.id != 0:
71-
raise InvalidNodeIdException(f"Default nodes must have an id of 0. It is {self.default_node.id} .")
72-
self.metadata = metadata
73-
# TODO: remove the following setting of birth_rate on the default node once this EMOD binary issue is fixed
74-
# https://github.com/InstituteforDiseaseModeling/DtkTrunk/issues/4009
75-
self.get_node_by_id(node_id=0).birth_rate = 0
54+
# Build the default node if not provided and then perform some setup/verification
55+
default_node = self._generate_default_node() if default_node is None else default_node
56+
self.default_node = default_node
57+
self.default_node.name = self.DEFAULT_NODE_NAME
58+
if self.default_node.id != 0:
59+
raise InvalidNodeIdException(f"Default nodes must have an id of 0. It is {self.default_node.id} .")
60+
self.metadata = self.generate_headers()
61+
# TODO: remove the following setting of birth_rate on the default node once this EMOD binary issue is fixed
62+
# https://github.com/InstituteforDiseaseModeling/DtkTrunk/issues/4009
63+
if self.default_node.birth_rate is None:
64+
self.default_node.birth_rate = 0
7665

7766
# enforce unique node ids and names
7867
self.verify_demographics_integrity()
7968

69+
def _generate_default_node(self) -> Node:
70+
default_node = Node(lat=0, lon=0, pop=0, name=self.DEFAULT_NODE_NAME, forced_id=0)
71+
# TODO: remove the following setting of birth_rate on the default node once this EMOD binary issue is fixed
72+
# https://github.com/InstituteforDiseaseModeling/DtkTrunk/issues/4009
73+
default_node.birth_rate = 0
74+
return default_node
75+
8076
def _select_node_dicts(self, node_ids=None):
8177
if node_ids is None:
8278
node_dicts = [self.raw['Defaults']]
@@ -1060,3 +1056,13 @@ def min_not_nan(x_list):
10601056
}
10611057
self.implicits.append(DT._set_mortality_age_gender_year)
10621058
return dict_female, dict_male
1059+
1060+
def to_dict(self) -> Dict:
1061+
self.verify_demographics_integrity()
1062+
demographics_dict = {
1063+
'Defaults': self.default_node.to_dict(),
1064+
'Nodes': [node.to_dict() for node in self.nodes],
1065+
'Metadata': self.metadata
1066+
}
1067+
demographics_dict["Metadata"]["NodeCount"] = len(self.nodes)
1068+
return demographics_dict

emod_api/demographics/demographics_overlay.py

Lines changed: 10 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
import json
22

33
from emod_api.demographics.demographics_base import DemographicsBase
4+
from emod_api.demographics.node import OverlayNode
45

56

67
class DemographicsOverlay(DemographicsBase):
78
"""
8-
In contrast to class :py:obj:`emod_api:emod_api.demographics.Demographics` this class does not set any defaults.
9-
It inherits from :py:obj:`emod_api:emod_api.demographics.DemographicsBase` so all functions that can be used to
10-
create demographics can also be used to create an overlay file. Parameters can be changed/set specifically by
11-
passing node_id, individual attributes, and individual attributes to the constructor.
9+
This class inherits from :py:obj:`emod_api:emod_api.demographics.DemographicsBase` so all functions that can be used
10+
to create demographics can also be used to create an overlay file. The intended use is for a user to pass a
11+
self-built default OverlayNode object in to represent the Defaults section in the demographics overlay.
1212
"""
1313

14-
def __init__(self, nodes: list = None,
14+
def __init__(self,
15+
nodes: list[OverlayNode] = None,
1516
idref: str = None,
16-
individual_attributes=None,
17-
node_attributes=None):
17+
default_node: OverlayNode = None):
1818
"""
1919
A class to create demographic overlays.
2020
Args:
@@ -26,36 +26,10 @@ def __init__(self, nodes: list = None,
2626
node_attributes: Object of type
2727
:py:obj:`emod_api:emod_api.demographics.PropertiesAndAttributes.NodeAttributes
2828
to overwrite individual attributes
29-
"""
30-
super(DemographicsOverlay, self).__init__(nodes=nodes, idref=idref)
31-
32-
self.individual_attributes = individual_attributes
33-
self.node_attributes = node_attributes
34-
35-
if self.individual_attributes is not None:
36-
self.raw["Defaults"]["IndividualAttributes"] = self.individual_attributes.to_dict()
37-
38-
if self.node_attributes is not None:
39-
self.raw["Defaults"]["NodeAttributes"] = self.node_attributes.to_dict()
40-
41-
def to_dict(self):
42-
self.verify_demographics_integrity()
43-
d = {"Defaults": dict()}
44-
if self.raw["Defaults"]["IndividualAttributes"]:
45-
d["Defaults"]["IndividualAttributes"] = self.raw["Defaults"]["IndividualAttributes"]
29+
default_node: (OverlayNode) An optional node to use for default settings.
4630
47-
if self.raw["Defaults"]["NodeAttributes"]:
48-
d["Defaults"]["NodeAttributes"] = self.raw["Defaults"]["NodeAttributes"]
49-
50-
if self.raw["Metadata"]:
51-
d["Metadata"] = self.raw["Metadata"] # there is no metadata class
52-
d["Metadata"]["NodeCount"] = len(self.nodes)
53-
if self.raw["Defaults"]["IndividualProperties"]:
54-
d["Defaults"]["IndividualProperties"] = self.raw["Defaults"]["IndividualProperties"]
55-
56-
d["Nodes"] = [{"NodeID": n.forced_id} for n in self.nodes]
57-
58-
return d
31+
"""
32+
super(DemographicsOverlay, self).__init__(nodes=nodes, idref=idref, default_node=default_node)
5933

6034
def to_file(self, file_name="demographics_overlay.json"):
6135
"""

0 commit comments

Comments
 (0)