Skip to content

Commit 8a5b665

Browse files
authored
DemograhpicsTemplates: Remove CrudeRate/YearlyRate/DtkRate (#40)
1 parent ff70092 commit 8a5b665

File tree

3 files changed

+56
-65
lines changed

3 files changed

+56
-65
lines changed

emod_api/demographics/demographics_base.py

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414

1515
from emod_api.demographics import demographics_templates as DT
1616
from emod_api.demographics.base_input_file import BaseInputFile
17-
from emod_api.demographics.demographics_templates import CrudeRate, YearlyRate
1817
from emod_api.demographics.node import Node
1918
from emod_api.demographics.age_distribution_old import AgeDistributionOld as AgeDistribution
2019
from emod_api.demographics.demographic_exceptions import InvalidNodeIdException
@@ -400,38 +399,40 @@ def SetMinimalNodeAttributes(self):
400399
# DTK is births per person per day.
401400
def SetBirthRate(self,
402401
birth_rate: float,
403-
node_ids: List = None):
402+
node_ids: List[int] = None) -> None:
404403
"""
405404
Set Default birth rate to birth_rate. Turn on Vital Dynamics and Births implicitly.
405+
406+
Args:
407+
birth_rate: (float) The birth rate in units of births/year/1000-women
408+
node_ids: a list of node_ids. None or 0 indicates the default node.
409+
410+
Returns:
411+
Nothing
406412
"""
407413
warnings.warn('SetBirthRate() is deprecated. Default nodes should now be represented by Node '
408414
'objects and passed to the Demographics object during the constructor call. They can be modified '
409415
'afterward, if needed.',
410416
DeprecationWarning, stacklevel=2)
411-
if type(birth_rate) is float or type(birth_rate) is int:
412-
birth_rate = CrudeRate(birth_rate)
413-
dtk_birthrate = birth_rate.get_dtk_rate()
417+
dtk_birthrate = birth_rate / 365 / 1000
418+
414419
if node_ids is None:
415420
self.raw['Defaults']['NodeAttributes'].update({
416421
"BirthRate": dtk_birthrate
417422
})
418423
else:
419-
for node_id in node_ids:
420-
self.get_node_by_id(node_id=node_id).birth_rate = dtk_birthrate
424+
nodes = self.get_nodes_by_id(node_ids=node_ids)
425+
for _, node in nodes.items():
426+
node.birth_rate = dtk_birthrate
421427
self.implicits.append(DT._set_population_dependent_birth_rate)
422428

423429
def SetMortalityRate(self,
424-
mortality_rate: CrudeRate, node_ids: List[int] = None):
430+
mortality_rate: float, node_ids: List[int] = None):
425431
"""
426432
Set constant mortality rate to mort_rate. Turn on Enable_Natural_Mortality implicitly.
427433
"""
428434
warnings.warn('SetMortalityRate() is deprecated. Please use the emodpy Demographics method: '
429435
'set_mortality_distribution()', DeprecationWarning, stacklevel=2)
430-
431-
# yearly_mortality_rate = YearlyRate(mortality_rate)
432-
if type(mortality_rate) is float or type(mortality_rate) is int:
433-
mortality_rate = CrudeRate(mortality_rate)
434-
mortality_rate = mortality_rate.get_dtk_rate()
435436
if node_ids is None:
436437
# setting = {"MortalityDistribution": DT._ConstantMortality(yearly_mortality_rate).to_dict()}
437438
setting = {"MortalityDistribution": DT._ConstantMortality(mortality_rate).to_dict()}
@@ -643,7 +644,8 @@ def SetDefaultNodeAttributes(self, birth=True):
643644
"Region": 1,
644645
"Seaport": 1}
645646
if birth:
646-
self.SetBirthRate(YearlyRate(math.log(1.03567)))
647+
# raise Exception("This will be removed in a new issue/PR shortly. Do not use.")
648+
self.SetBirthRate(birth_rate=math.log(1.03567))
647649

648650
def SetDefaultProperties(self):
649651
"""
@@ -674,26 +676,36 @@ def SetDefaultFromTemplate(self, template, setter_fn=None):
674676

675677
# TODO: is this useful in a way that warrants a special-case function in emodpy built around set_age_distribution?
676678
# https://github.com/InstituteforDiseaseModeling/emod-api-old/issues/788
677-
def SetEquilibriumAgeDistFromBirthAndMortRates(self, CrudeBirthRate=CrudeRate(40), CrudeMortRate=CrudeRate(20),
678-
node_ids=None):
679+
def SetEquilibriumAgeDistFromBirthAndMortRates(self,
680+
birth_rate: float = 40.0,
681+
mortality_rate: float = 20.0,
682+
node_ids: List[int] = None):
679683
"""
680-
Set the inital ages of the population to a sensible equilibrium profile based on the specified input birth and
681-
death rates. Note this does not set the fertility and mortality rates.
684+
Set age distribution based on birth and death rates. Implicit function.
685+
686+
Args:
687+
birth_rate: (float) The birth rate in units of births/year/1000-women
688+
mortality_rate: (float) The mortality rate in units of deaths/year/1000 people
689+
node_ids: a list of node_ids. None or 0 indicates the default node.
690+
Returns:
691+
Nothing
682692
"""
683-
warnings.warn('SetEquilibriumAgeDistFromBirthAndMortRates() is deprecated. Please use the emodpy Demographics method: '
684-
'set_age_distribution()', DeprecationWarning, stacklevel=2)
693+
warnings.warn(
694+
'SetEquilibriumAgeDistFromBirthAndMortRates() is deprecated. Please use the emodpy Demographics method: '
695+
'set_age_distribution()', DeprecationWarning, stacklevel=2)
685696

686-
yearly_birth_rate = YearlyRate(CrudeBirthRate)
687-
yearly_mortality_rate = YearlyRate(CrudeMortRate)
688-
dist = DT._EquilibriumAgeDistFromBirthAndMortRates(yearly_birth_rate, yearly_mortality_rate)
697+
dist = DT._EquilibriumAgeDistFromBirthAndMortRates(birth_rate=birth_rate,
698+
mortality_rate=mortality_rate)
689699
setter_fn = DT._set_age_complex
700+
690701
if node_ids is None:
691702
self.SetDefaultFromTemplate(dist, setter_fn)
692703
else:
693704
new_dist = AgeDistribution()
694705
dist = new_dist.from_dict(dist["AgeDistribution"])
695-
for node in node_ids:
696-
self.get_node_by_id(node_id=node)._set_age_complex_distribution(dist)
706+
nodes = self.get_nodes_by_id(node_ids=node_ids)
707+
for _, node in nodes.items():
708+
node._set_age_complex_distribution(dist)
697709
self.implicits.append(setter_fn)
698710

699711
def SetInitialAgeExponential(self, rate=0.0001068, description=""):

emod_api/demographics/demographics_templates.py

Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,34 +25,6 @@ class DemographicsTemplatesConstants:
2525
32849.6, 34679.5, 34679.6, 36509.5, 36509.6, 38339.5]
2626

2727

28-
class CrudeRate: # would like to derive from float
29-
def __init__(self, init_rate):
30-
self._time_units = 365
31-
self._people_units = 1000
32-
self._rate = init_rate
33-
34-
def get_dtk_rate(self):
35-
return self._rate / self._time_units / self._people_units
36-
37-
38-
class YearlyRate(CrudeRate): # would like to derive from float
39-
def __init__(self, init_rate):
40-
self._time_units = 365
41-
self._people_units = 1
42-
if type(init_rate) is CrudeRate:
43-
self._rate = init_rate._rate / 1000.
44-
else:
45-
self._rate = init_rate
46-
47-
48-
class DtkRate(CrudeRate):
49-
def __init__(self, init_rate):
50-
super().__init__(init_rate)
51-
self._time_units = 1
52-
self._people_units = 1
53-
self._rate = init_rate
54-
55-
5628
# Migration
5729
def _set_migration_model_fixed_rate(config):
5830
config.parameters.Migration_Model = "FIXED_RATE_MIGRATION"
@@ -557,24 +529,30 @@ def AgeStructureUNWPP(demog):
557529
demog.SetDefaultFromTemplate(setting, _set_age_complex)
558530

559531

560-
def _EquilibriumAgeDistFromBirthAndMortRates(birth_rate=YearlyRate(40 / 1000.), mort_rate=YearlyRate(20 / 1000.)):
532+
def _EquilibriumAgeDistFromBirthAndMortRates(birth_rate=40.0, mortality_rate=20.0):
561533
"""
562-
Set age distribution based on birth and death rates.
534+
Set age distribution based on birth and death rates. Implicit function.
563535
564536
Args:
565-
birth_rate: births per person per year.
566-
mort_rate: deaths per person per year.
537+
# birth_rate: births per person per year.
538+
birth_rate: (float) The birth rate in units of births/year/1000-women
539+
# mort_rate: deaths per person per year.
540+
mortality_rate: (float) The mortality rate in units of deaths/year/1000 people
567541
568542
Returns:
569543
dictionary which can be inserted into demographics object.
570544
571545
"""
572-
BirthRate = math.log(1 + birth_rate.get_dtk_rate())
573-
MortRate = -1 * math.log(1 - mort_rate.get_dtk_rate())
546+
# convert to daily rate per person, EMOD units
547+
birth_rate = (birth_rate / 1000) / 365 # what is actually used below
548+
mortality_rate = (mortality_rate / 1000) / 365 # what is actually used below
549+
550+
birth_rate = math.log(1 + birth_rate)
551+
mortality_rate = -1 * math.log(1 - mortality_rate)
574552

575553
# It is important for the age distribution computation that the age-spacing be very fine; I've used 30 days here.
576554
# With coarse spacing, the computation in practice doesn't work as well.
577-
ageDist = _computeAgeDist(BirthRate, [i * 30 for i in range(1200)], 1200 * [MortRate], 12 * [1.0])
555+
ageDist = _computeAgeDist(birth_rate, [i * 30 for i in range(1200)], 1200 * [mortality_rate], 12 * [1.0])
578556

579557
# The final demographics file, though, can use coarser binning interpolated from the finely-spaced computed distribution.
580558
EMODAgeBins = list(range(16)) + [20 + 5 * i for i in range(14)]

tests/test_demog.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ def test_template_age_structure_UNWPP(self):
264264

265265
def test_template_equilibrium_age_dist_from_birth_and_mort_rates(self):
266266
demog = Demographics.from_template_node()
267-
demog.SetEquilibriumAgeDistFromBirthAndMortRates(CrudeBirthRate=20 / 1000, CrudeMortRate=10 / 1000)
267+
demog.SetEquilibriumAgeDistFromBirthAndMortRates(birth_rate=20, mortality_rate=10)
268268
self.assertIn('AgeDistribution', demog.raw['Defaults']['IndividualAttributes'])
269269
self.assertEqual(len(demog.implicits), 2)
270270
print(demog.raw)
@@ -297,13 +297,14 @@ def test_set_default_from_template_mortality_rate_by_age(self):
297297
self.assertEqual(len(mort_rate), len(mort_dist['PopulationGroups'][1]))
298298
self.assertIn('MortalityDistribution', demog.raw['Defaults']['IndividualAttributes']) # Can't use set_default_from_template_test since template is implicit
299299

300+
# TODO: restore/refactor after moving new distribution classes down into emod-api? Or is this duplicative?
300301
def test_set_default_from_template_constant_mortality(self):
301302
demog = Demographics.from_template_node()
302303
demog.implicits = []
303-
mortality_rate = DT.DtkRate(0.0001)
304+
mortality_rate = 0.0001
304305
demog.SetMortalityRate(mortality_rate=mortality_rate) # ca
305306
self.assertIn('MortalityDistribution', demog.raw['Defaults']['IndividualAttributes']) # Can't use set_default_from_template_test since template is implicit
306-
expected_rate = [[-1 * (math.log(1 - mortality_rate.get_dtk_rate()))]] * 2
307+
expected_rate = [[-1 * (math.log(1 - mortality_rate))]] * 2
307308
demog_rate = demog.raw['Defaults']['IndividualAttributes']['MortalityDistribution']['ResultValues']
308309
self.assertListEqual(expected_rate, demog_rate)
309310

@@ -1088,13 +1089,13 @@ def test_set_predefined_mortality_distribution(self):
10881089
def test_mortality_rate_with_node_ids(self):
10891090
input_file = os.path.join(manifest.demo_folder, 'nodes.csv')
10901091
demog = Demographics.from_csv(input_file)
1091-
mortality_rate = 0.1234 # CrudeRate
1092+
mortality_rate = 0.1234 / 365 / 1000
10921093
node_ids = [97, 99]
10931094
demog.SetMortalityRate(mortality_rate, node_ids)
10941095

10951096
set_mortality_dist_97 = demog.get_node_by_id(node_id=97).individual_attributes.mortality_distribution.to_dict()
10961097
set_mortality_dist_99 = demog.get_node_by_id(node_id=99).individual_attributes.mortality_distribution.to_dict()
1097-
expected_mortality_dist = DT._ConstantMortality(DT.CrudeRate(mortality_rate).get_dtk_rate()).to_dict()
1098+
expected_mortality_dist = DT._ConstantMortality(mortality_rate).to_dict()
10981099
self.assertDictEqual(set_mortality_dist_99, expected_mortality_dist)
10991100
self.assertDictEqual(set_mortality_dist_97, expected_mortality_dist)
11001101
self.assertIsNone(demog.get_node_by_id(node_id=96).individual_attributes.mortality_distribution)

0 commit comments

Comments
 (0)