from importlib import resources
from pathlib import Path
from typing import List, Union
import copy
import pandas as pd
import emod_api
from emod_api.schema_to_class import ReadOnlyDict
import emodpy_hiv.countries.zambia as zambia_data
from emodpy_hiv.country_model import Country
import emodpy_hiv.demographics.infer_natural_mortality as infer
from emodpy_hiv.demographics.hiv_demographics import HIVDemographics
from emodpy_hiv.demographics.relationship_types import RelationshipTypes
from emodpy_hiv.demographics.risk_groups import RiskGroups
from emodpy_hiv.parameterized_call import ParameterizedCall
from emodpy_hiv.reporters.reporters import (Reporters, ReportFilter, ReportHIVInfection, ReportHIVART,
ReportHIVMortality, ReportHIVByAgeAndGender,
ReportTransmission, ReportRelationshipStart, InsetChart)
COMMERCIAL = RelationshipTypes.commercial.value
TRANSITORY = RelationshipTypes.transitory.value
INFORMAL = RelationshipTypes.informal.value # noqa: E221
MARITAL = RelationshipTypes.marital.value # noqa: E221
LOW = RiskGroups.low.value # noqa: E221
MEDIUM = RiskGroups.medium.value
HIGH = RiskGroups.high.value # noqa: E221
# The nodes for the country with names and initial population.
# NOTE: In the future, please add information about there this initial population data comes from.
NODE_DATA = [
{"node_id": 1, "name": "Central", "population": 304498}, # noqa: E241
{"node_id": 2, "name": "Copperbelt", "population": 459461}, # noqa: E241
{"node_id": 3, "name": "Eastern", "population": 371018}, # noqa: E241
{"node_id": 4, "name": "Luapula", "population": 231074}, # noqa: E241
{"node_id": 5, "name": "Lusaka", "population": 510456}, # noqa: E241
{"node_id": 6, "name": "Muchinga", "population": 165784}, # noqa: E241
{"node_id": 7, "name": "Northwestern", "population": 169368}, # noqa: E241
{"node_id": 8, "name": "Northern", "population": 257607}, # noqa: E241
{"node_id": 9, "name": "Southern", "population": 370381}, # noqa: E241
{"node_id": 10, "name": "Western", "population": 210352} # noqa: E241
]
# Convert the data above so that we can use the country name
# when creating labels for parameters.
NODE_ID_TO_NODE_NAME = {node_dict["node_id"]: node_dict["name"] for node_dict in NODE_DATA}
[docs]class Zambia(Country):
"""
Zambia country model class. This class is a subclass of the Country class and implements the build_campaign method
to build campaign object to be used with EMODTask.
Example:
>>> from emodpy_hiv.countries.zambia import Zambia
>>> from emodpy.emod_task import EMODTask
>>> import manifest
>>> from functools import partial
>>> # Define a campaign_builder function to pass in the required arguments for the Zambia.build_campaign() method.
>>> def campaign_builder():
>>> return Zambia.build_campaign(schema_path=manifest.schema_file, base_year=Zambia.base_year)
>>> # Alternatively, you can use the partial function to pass in the required arguments for the
>>> # Zambia.build_campaign() method. For example:
>>> # campaign_builder = partial(Zambia.build_campaign, schema_path=manifest.schema_file, base_year=Zambia.base_year)
>>> # Create an EMODTask object using the campaign_builder function
>>> task = EMODTask.from_defaults(config_path="config.json",
>>> eradication_path=manifest.eradication_path,
>>> campaign_builder=campaign_builder, ...)
"""
country_name = "Zambia"
_historical_vmmc_data_file = resources.files(zambia_data).joinpath("historical_vmmc_data.csv")
_inital_demog_cache = None
[docs] @classmethod
def initialize_config(cls, schema_path: Union[str, Path]) -> ReadOnlyDict:
config = super().initialize_config(schema_path=schema_path)
config.parameters.Simulation_Type = "HIV_SIM"
config.parameters.Simulation_Duration = 99.5 * 365
config.parameters.Simulation_Timestep = 30.4166666666667
# This is the current "production scale" population scaling factor for Zambia the model is calibrated to
config.parameters.x_Base_Population = 0.05
# x_Base_Population = 0.05 needs more than 8GB memory, so we're giving it here
config.parameters.Memory_Usage_Halting_Threshold_Working_Set_MB = 12000
config.parameters.Memory_Usage_Warning_Threshold_Working_Set_MB = 11000
config.parameters.Start_Time = 0
config.parameters.AIDS_Duration_In_Months = 9
config.parameters.AIDS_Stage_Infectivity_Multiplier = 4.5
config.parameters.Acute_Duration_In_Months = 2.9
config.parameters.Acute_Stage_Infectivity_Multiplier = 26
config.parameters.Age_Initialization_Distribution_Type = "DISTRIBUTION_COMPLEX"
config.parameters.Base_Individual_Sample_Rate = 1
config.parameters.Base_Infectivity = 0.00065805
config.parameters.Base_Year = 1960.5
config.parameters.Birth_Rate_Dependence = "INDIVIDUAL_PREGNANCIES_BY_AGE_AND_YEAR"
config.parameters.Birth_Rate_Time_Dependence = "NONE"
config.parameters.CD4_At_Death_LogLogistic_Heterogeneity = 0.7
config.parameters.CD4_At_Death_LogLogistic_Scale = 2.96
config.parameters.CD4_Post_Infection_Weibull_Heterogeneity = 0.2756
config.parameters.CD4_Post_Infection_Weibull_Scale = 560.43
config.parameters.Coital_Dilution_Factor_2_Partners = 0.75
config.parameters.Coital_Dilution_Factor_3_Partners = 0.6
config.parameters.Coital_Dilution_Factor_4_Plus_Partners = 0.45
config.parameters.Condom_Transmission_Blocking_Probability = 0.8
config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Heterogeneity = 0.5
config.parameters.Days_Between_Symptomatic_And_Death_Weibull_Scale = 618.341625
config.parameters.Death_Rate_Dependence = "NONDISEASE_MORTALITY_BY_YEAR_AND_AGE_FOR_EACH_GENDER"
config.parameters.Enable_Aging = 1
config.parameters.Enable_Birth = 1
config.parameters.Enable_Coital_Dilution = 1
config.parameters.Enable_Demographics_Reporting = 0 # Demographics reporting is on by default, turning it off
config.parameters.Enable_Demographics_Birth = 0
config.parameters.Enable_Infectivity_Reservoir = 0
config.parameters.Enable_Maternal_Protection = 0
config.parameters.Enable_Natural_Mortality = 1
config.parameters.HIV_Adult_Survival_Scale_Parameter_Intercept = 21.182
config.parameters.HIV_Adult_Survival_Scale_Parameter_Slope = -0.2717
config.parameters.HIV_Adult_Survival_Shape_Parameter = 2
config.parameters.HIV_Age_Max_for_Adult_Age_Dependent_Survival = 50
config.parameters.HIV_Age_Max_for_Child_Survival_Function = 15
config.parameters.HIV_Child_Survival_Rapid_Progressor_Fraction = 0.57
config.parameters.HIV_Child_Survival_Rapid_Progressor_Rate = 1.52
config.parameters.HIV_Child_Survival_Slow_Progressor_Scale = 16
config.parameters.HIV_Child_Survival_Slow_Progressor_Shape = 2.7
config.parameters.Heterogeneous_Infectiousness_LogNormal_Scale = 0
config.parameters.Incubation_Period_Constant = 0
config.parameters.Incubation_Period_Distribution = "CONSTANT_DISTRIBUTION"
config.parameters.Individual_Sampling_Type = "FIXED_SAMPLING"
config.parameters.Infection_Updates_Per_Timestep = 1
config.parameters.Infectivity_Scale_Type = "CONSTANT_INFECTIVITY"
config.parameters.Male_To_Female_Relative_Infectivity_Ages = [0, 15, 25]
config.parameters.Male_To_Female_Relative_Infectivity_Multipliers = [
2.26154624,
2.26154624,
1.112477558
]
config.parameters.Maternal_Infection_Transmission_Probability = 0.3
config.parameters.Maternal_Transmission_ART_Multiplier = 0.03334
config.parameters.Migration_Model = "NO_MIGRATION"
config.parameters.Min_Days_Between_Adding_Relationships = 0
config.parameters.Node_Grid_Size = 0.009
config.parameters.PFA_Burnin_Duration_In_Days = 5475
config.parameters.PFA_Cum_Prob_Selection_Threshold = 0.2
config.parameters.Population_Density_Infectivity_Correction = "CONSTANT_INFECTIVITY"
config.parameters.Population_Scale_Type = "FIXED_SCALING"
config.parameters.Run_Number = 1
config.parameters.STI_Coinfection_Acquisition_Multiplier = 5.5
config.parameters.STI_Coinfection_Transmission_Multiplier = 5.5
config.parameters.Serialized_Population_Reading_Type = "NONE"
config.parameters.Serialized_Population_Writing_Type = "NONE"
config.parameters.Sexual_Debut_Age_Setting_Type = "WEIBULL"
config.parameters.Sexual_Debut_Age_Female_Weibull_Heterogeneity = 0.22108015674142
config.parameters.Sexual_Debut_Age_Female_Weibull_Scale = 19.9609856656853
config.parameters.Sexual_Debut_Age_Male_Weibull_Heterogeneity = 0.165108885953777
config.parameters.Sexual_Debut_Age_Male_Weibull_Scale = 16.8039276633964
config.parameters.Sexual_Debut_Age_Min = 13
config.parameters["logLevel_InfectionHIV"] = "ERROR"
config.parameters["logLevel_Instrumentation"] = "INFO"
config.parameters["logLevel_Memory"] = "INFO"
config.parameters["logLevel_OutbreakIndividual"] = "ERROR"
config.parameters["logLevel_Simulation"] = "INFO"
config.parameters["logLevel_SusceptibilityHIV"] = "ERROR"
config.parameters["logLevel_AntiretroviralTherapy"] = "ERROR"
config.parameters["logLevel_default"] = "WARNING"
return config
[docs] @classmethod
def initialize_campaign(cls, schema_path: Union[str, Path]) -> emod_api.campaign:
campaign = super().initialize_campaign(schema_path=schema_path)
return campaign
#
# Campaign building below
#
[docs] @classmethod
def add_male_circumcision_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
# ---- Add historical vmmc NChooser events to campaign ----
non_hp = {'historical_vmmc_data_filepath': cls._historical_vmmc_data_file}
hp = {'historical_vmmc_reduced_acquire': None}
pc = ParameterizedCall(func=cls.add_historical_vmmc_nchooser, non_hyperparameters=non_hp, hyperparameters=hp)
parameterized_calls.append(pc)
# ---- Add traditional male circumcision to campaign ----
traditional_male_circumcision_coverage_by_node = {
1: 0.054978651,
2: 0.139462861,
3: 0.028676043,
4: 0.091349358,
5: 0.12318707,
6: 0.039308099,
7: 0.727917322,
8: 0.041105263,
9: 0.044388102,
10: 0.398239794
}
for node_id, coverage in traditional_male_circumcision_coverage_by_node.items():
hp = {
'traditional_male_circumcision_coverage': coverage,
'traditional_male_circumcision_reduced_acquire': None
}
pc = ParameterizedCall(func=cls.add_traditional_male_circumcision,
non_hyperparameters={'node_ids': [node_id]},
hyperparameters=hp,
label=NODE_ID_TO_NODE_NAME[node_id])
parameterized_calls.append(pc)
# ---- Add (VMMC) reference tracking to campaign ----
pc = ParameterizedCall(func=cls.add_vmmc_reference_tracking,
hyperparameters={'tracking_vmmc_reduced_acquire': None})
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_pmtct_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
pc = ParameterizedCall(func=cls.add_pmtct)
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_seed_infections_parameterized_calls(cls) -> List[ParameterizedCall]:
seeding_data = []
seeding_data.append({"node_id": 1, "start_year": 1977.96490840667, "coverage": 0.00994069137813979}) # noqa: E241
seeding_data.append({"node_id": 2, "start_year": 1981.21526571588, "coverage": 0.0290879518815339}) # noqa: E241, E202
seeding_data.append({"node_id": 3, "start_year": 1987.89402149164, "coverage": 0.11194470109949}) # noqa: E241
seeding_data.append({"node_id": 4, "start_year": 1970.14036061384, "coverage": 0.0222773645719442}) # noqa: E241
seeding_data.append({"node_id": 5, "start_year": 1970.36530180447, "coverage": 0.35030772723087}) # noqa: E241, E202
seeding_data.append({"node_id": 6, "start_year": 1984.22541975358, "coverage": 0.0167949011180618}) # noqa: E241, E202
seeding_data.append({"node_id": 7, "start_year": 1980.54040493867, "coverage": 0.0930776686478119}) # noqa: E241
seeding_data.append({"node_id": 8, "start_year": 1979.51682308393, "coverage": 0.0099968647453273}) # noqa: E241
seeding_data.append({"node_id": 9, "start_year": 1984.36462983067, "coverage": 0.0731563905879651}) # noqa: E241
seeding_data.append({"node_id": 10, "start_year": 1982.02918538299, "coverage": 0.267106173241476})
parameterized_calls = []
for data in seeding_data:
hp = {'seeding_start_year': data["start_year"], 'seeding_coverage': data["coverage"]}
nhp = {'node_ids': [data["node_id"]]}
pc = ParameterizedCall(func=cls.seed_infections,
non_hyperparameters=nhp,
hyperparameters=hp,
label=NODE_ID_TO_NODE_NAME[data["node_id"]])
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_csw_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
pc = ParameterizedCall(func=cls.add_csw,
non_hyperparameters={'node_ids': [1, 2, 3, 7, 9]},
hyperparameters={'csw_male_uptake_coverage': 0.195, 'csw_female_uptake_coverage': 0.125},
label='nodeset1')
parameterized_calls.append(pc)
pc = ParameterizedCall(func=cls.add_csw,
non_hyperparameters={'node_ids': [4, 5, 6, 8, 10]},
hyperparameters={'csw_male_uptake_coverage': 0.0781, 'csw_female_uptake_coverage': 0.05},
label='nodeset2')
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_post_debut_coinfection_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
hp = {'coinfection_coverage_HIGH': None, 'coinfection_coverage_MEDIUM': None, 'coinfection_coverage_LOW': None}
pc = ParameterizedCall(func=cls.add_post_debut_coinfection, hyperparameters=hp)
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_health_care_testing_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
non_hyperparameters = {
'hct_delay_to_next_test': [730, 365, 1100],
'hct_delay_to_next_test_node_ids': [[1, 2, 3, 4, 6, 7], [5, 9, 10], [8]],
'hct_delay_to_next_test_node_names': ['Default', 'Lusaka, Southern, Western', 'Northern']
}
pc = ParameterizedCall(func=cls.add_health_care_testing,
non_hyperparameters=non_hyperparameters,
hyperparameters={'hct_start_year': None})
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def add_ART_cascade_parameterized_calls(cls) -> List[ParameterizedCall]:
parameterized_calls = []
hp = {
'art_cascade_start_year': None,
'cd4_retention_rate': None,
'linking_to_pre_art_sigmoid_min': 0.824382156,
'linking_to_pre_art_sigmoid_max': 0.959334475,
'linking_to_pre_art_sigmoid_midyear': 2008.506768,
'linking_to_pre_art_sigmoid_rate': None,
'pre_staging_retention': None,
'pre_art_retention': None,
'linking_to_art_sigmoid_min': None,
'linking_to_art_sigmoid_max': 0.90041509,
'linking_to_art_sigmoid_midyear': 2001.053678,
'linking_to_art_sigmoid_rate': None,
'art_reenrollment_willingness': None,
'immediate_art_rate': None
}
pc = ParameterizedCall(func=cls.add_ART_cascade, hyperparameters=hp)
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def get_campaign_parameterized_calls(cls, campaign: emod_api.campaign) -> List[ParameterizedCall]:
parameterized_calls = super().get_campaign_parameterized_calls(campaign=campaign)
# ---- Add male circumcision to campaign -----
# This block contains 3 chunk of events: historical vmmc NChooser, traditional male circumcision and VMMC with
# reference tracking.
parameterized_calls.extend(cls.add_male_circumcision_parameterized_calls())
# ---- Add prevention of mother-to-child transmission (PMTCT) to campaign ----
# This pmtct block contains 2 Cascade states: TestingOnANC and TestingOnChild6w
parameterized_calls.extend(cls.add_pmtct_parameterized_calls())
# ---- Add OutbreakIndividual interventions to campaign ----
parameterized_calls.extend(cls.add_seed_infections_parameterized_calls())
# ---- Add commercial sex worker (CSW) uptake and dropout (with delays) for men and women to campaign ----
parameterized_calls.extend(cls.add_csw_parameterized_calls())
# ---- Add co-infections post sexual debut to campaign ----
parameterized_calls.extend(cls.add_post_debut_coinfection_parameterized_calls())
# ---- Add health care testing loops to campaign ----
# This health care testing block contains 3 Cascade states: HCTUptakeAtDebut, HCTUptakePostDebut and
# HCTTestingLoop
parameterized_calls.extend(cls.add_health_care_testing_parameterized_calls())
# ---- Add Antiretroviral Therapy (ART) Cascade to campaign ----
# This ART Cascade block contains 8 Cascade states: TestingOnSymptomatic, ARTStagingDiagnosticTest, ARTStaging
# LinkingToPreART, OnPreART, LinkingToART, OnART and LostForever
parameterized_calls.extend(cls.add_ART_cascade_parameterized_calls())
return parameterized_calls
#
# Demographics building below
#
[docs] @classmethod
def initialize_demographics(cls) -> HIVDemographics:
import emodpy_hiv.demographics.un_world_pop as unwp
# ------------------------------------------------------------------------------------
# --- The spreadsheets are large and reading/loading them can take many seconds.
# --- Hence, we cache the initial instance of the demographics so that new versions
# --- don't have re-read the spreadsheets.
# ------------------------------------------------------------------------------------
if cls._inital_demog_cache:
return copy.deepcopy(cls._inital_demog_cache)
# -------------------------------------------------------
# --- Initial population has data for different nodes so
# --- we can get use data from UN World Pop
# -------------------------------------------------------
pop_df = pd.DataFrame(NODE_DATA)
uwp_country = "Zambia"
uwp_version = "2015"
total_pop, age_distribution_yar = unwp.extract_population_by_age_and_distribution(country =uwp_country, # noqa: E221, E251
version =uwp_version, # noqa: E221, E251
year =1960) # noqa: E221, E251
fertility_yar = unwp.extract_fertility(country=uwp_country, version=uwp_version) # noqa: E241, E221
male_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="male" ) # noqa: E241, E221
female_mortality_yar = unwp.extract_mortality(country=uwp_country, version=uwp_version, gender="female")
# -----------------------------------------------------------------------------------------
# --- Need a longer fitting interval to flatten natural deaths under the peak in HIV deaths
# -----------------------------------------------------------------------------------------
male_mortality_yar = infer.infer_natural_mortality(male_mortality_yar, interval_fit=(1950, 1975)) # noqa: E241, E221
female_mortality_yar = infer.infer_natural_mortality(female_mortality_yar, interval_fit=(1950, 1975))
demog = HIVDemographics.from_year_age_rate_data(pop_df = pop_df, # noqa: E251, E221
age_distribution_yar = age_distribution_yar, # noqa: E251
fertility_yar = fertility_yar, # noqa: E251, E221
male_mortality_yar = male_mortality_yar, # noqa: E251, E221
female_mortality_yar = female_mortality_yar, # noqa: E251
society = None) # noqa: E251, E221, E202
cls._inital_demog_cache = copy.deepcopy(demog)
return demog
@classmethod
def _get_concurrency_parameterized_calls(cls,
relationship_type: str,
risk_group: str,
max_simul_rels_male: float,
max_simul_rels_female: float,
prob_xtra_rel_male: float,
prob_xtra_rel_female: float,
node_ids: Union[List[int], None] = None,
label: str = None) -> List[ParameterizedCall]:
from emodpy_hiv.demographics.library import set_concurrency_parameters
parameterized_calls = []
nhp = {'relationship_type': relationship_type, 'risk_group': risk_group, 'node_ids': node_ids}
hp = {'max_simul_rels_male': max_simul_rels_male, 'max_simul_rels_female': max_simul_rels_female,
'prob_xtra_rel_male': prob_xtra_rel_male, 'prob_xtra_rel_female': prob_xtra_rel_female}
if label is None:
label = cls.generate_label(relationship_type=relationship_type, risk_group=risk_group, node_ids=node_ids)
pc = ParameterizedCall(func=set_concurrency_parameters, non_hyperparameters=nhp,
hyperparameters=hp,
label=label)
parameterized_calls.append(pc)
return parameterized_calls
@classmethod
def _get_pair_formation_parameterized_calls(cls,
relationship_type: str,
risk_assortivity: float,
formation_rate: float,
node_ids: Union[List[int], None] = None,
label: str = None) -> List[ParameterizedCall]:
from emodpy_hiv.demographics.library import set_pair_formation_parameters
parameterized_calls = []
nhp = {'relationship_type': relationship_type, 'node_ids': node_ids}
hp = {'risk_assortivity': risk_assortivity, 'formation_rate': formation_rate}
if label is None:
label = cls.generate_label(relationship_type=relationship_type, node_ids=node_ids)
pc = ParameterizedCall(func=set_pair_formation_parameters,
non_hyperparameters=nhp,
hyperparameters=hp,
label=label)
parameterized_calls.append(pc)
return parameterized_calls
@classmethod
def _get_relationship_parameterized_calls(cls, relationship_type: str,
coital_act_rate: float = None,
condom_usage_min: float = None,
condom_usage_mid: float = None,
condom_usage_max: float = None,
condom_usage_rate: float = None,
duration_scale: float = None,
duration_heterogeneity: float = None,
node_ids: Union[List[int], None] = None,
label: str = None) -> List[ParameterizedCall]:
from emodpy_hiv.demographics.library import set_relationship_parameters
parameterized_calls = []
nhp = {'relationship_type': relationship_type, 'node_ids': node_ids}
hp = {
'coital_act_rate': coital_act_rate,
'condom_usage_min': condom_usage_min,
'condom_usage_mid': condom_usage_mid,
'condom_usage_max': condom_usage_max,
'condom_usage_rate': condom_usage_rate,
'duration_scale': duration_scale,
'duration_heterogeneity': duration_heterogeneity
}
if label is None:
label = cls.generate_label(relationship_type=relationship_type, node_ids=node_ids)
pc = ParameterizedCall(func=set_relationship_parameters,
non_hyperparameters=nhp,
hyperparameters=hp,
label=label)
parameterized_calls.append(pc)
return parameterized_calls
@classmethod
def _get_individual_property_parameterized_calls(cls) -> List[ParameterizedCall]:
from emodpy_hiv.demographics.library import set_initial_cascade_state_distribution, \
set_initial_health_care_accessibility_distribution, set_initial_risk_distribution
risk_data = []
risk_data.append({"node_id": 1, "low": 0.255498673 }) # noqa: E202, E241
risk_data.append({"node_id": 2, "low": 0.330078204 }) # noqa: E202, E241
risk_data.append({"node_id": 3, "low": 0.121868492 }) # noqa: E202, E241
risk_data.append({"node_id": 4, "low": 0.221650376 }) # noqa: E202, E241
risk_data.append({"node_id": 5, "low": 0.395151764 }) # noqa: E202, E241
risk_data.append({"node_id": 6, "low": 0.095060159 }) # noqa: E202, E241
risk_data.append({"node_id": 7, "low": 0.288686141 }) # noqa: E202, E241
risk_data.append({"node_id": 8, "low": 0.446287507 }) # noqa: E202, E241
risk_data.append({"node_id": 9, "low": 0.378176731 }) # noqa: E202, E241
risk_data.append({"node_id": 10, "low": 0.283319058 }) # noqa: E202
parameterized_calls = []
for data in risk_data:
hp = {'initial_risk_distribution_low': data["low"]}
nhp = {'node_ids': [data["node_id"]]}
pc = ParameterizedCall(func=set_initial_risk_distribution,
non_hyperparameters=nhp,
hyperparameters=hp,
label=NODE_ID_TO_NODE_NAME[data["node_id"]])
parameterized_calls.append(pc)
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
# Since we need to set separate Risk distributions per node, the following
# is working around a JSON overlay challene where the list of IP's in a node
# overrides the the list of IPs in Defaults. EMOD does not allow you to override
# specific IPs.
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
distribution = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
pc = ParameterizedCall(set_initial_cascade_state_distribution,
non_hyperparameters={
'cascade_state_distribution': distribution,
'node_ids': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
})
parameterized_calls.append(pc)
pc = ParameterizedCall(set_initial_health_care_accessibility_distribution,
hyperparameters={'initial_accessibility': 0.8},
non_hyperparameters={'node_ids': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]})
parameterized_calls.append(pc)
return parameterized_calls
[docs] @classmethod
def get_demographics_parameterized_calls(cls, demographics: HIVDemographics) -> List[ParameterizedCall]:
parameterized_calls = super().get_demographics_parameterized_calls(demographics=demographics)
concurrency_parameters = {
COMMERCIAL: {
LOW: {'max_simul_rels_male': 0, 'max_simul_rels_female': 0, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
MEDIUM: {'max_simul_rels_male': 0, 'max_simul_rels_female': 0, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
HIGH: {'max_simul_rels_male': 59, 'max_simul_rels_female': 59, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
},
TRANSITORY: {
LOW: {'max_simul_rels_male': 1.861181867682, 'max_simul_rels_female': 1.95751180447129, # noqa: E241
'prob_xtra_rel_male': 0.184386490919862, 'prob_xtra_rel_female': 0.0608225887738051}, # noqa: E241
MEDIUM: {'max_simul_rels_male': 2.48697611966592, 'max_simul_rels_female': 2.38937717475767, # noqa: E241
'prob_xtra_rel_male': 0.287814625075045, 'prob_xtra_rel_female': 0.3116864843045}, # noqa: E241
HIGH: {'max_simul_rels_male': 1, 'max_simul_rels_female': 1, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
},
INFORMAL: {
LOW: {'max_simul_rels_male': 1.34356628685348, 'max_simul_rels_female': 1.21853602956715, # noqa: E241
'prob_xtra_rel_male': 0.286213622743763, 'prob_xtra_rel_female': 0.289761028337281}, # noqa: E241
MEDIUM: {'max_simul_rels_male': 2.70230847725607, 'max_simul_rels_female': 2.76579821196633, # noqa: E241
'prob_xtra_rel_male': 0.396354197417793, 'prob_xtra_rel_female': 0.404566634121599}, # noqa: E241
HIGH: {'max_simul_rels_male': 1, 'max_simul_rels_female': 1, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
},
MARITAL: {
LOW: {'max_simul_rels_male': 1, 'max_simul_rels_female': 1, # noqa: E241
'prob_xtra_rel_male': 0, 'prob_xtra_rel_female': 0}, # noqa: E241
MEDIUM: {'max_simul_rels_male': 1.86152532283732, 'max_simul_rels_female': 1.7602276432905, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
HIGH: {'max_simul_rels_male': 1, 'max_simul_rels_female': 1, # noqa: E241
'prob_xtra_rel_male': 1, 'prob_xtra_rel_female': 1}, # noqa: E241
},
}
for relationship_type, by_risk_group in concurrency_parameters.items():
for risk_group, parameters in by_risk_group.items():
pcs = cls._get_concurrency_parameterized_calls(relationship_type=relationship_type,
risk_group=risk_group,
**parameters)
parameterized_calls.extend(pcs)
pair_formation = {
# assortivity -1 means a matrix of all 1s
COMMERCIAL: {'risk_assortivity': -1, 'formation_rate': 0.15 }, # noqa: E241, E202
TRANSITORY: {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.00135026850353989 }, # noqa: E241, E202
INFORMAL: {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.00032670785736624 }, # noqa: E241, E202
MARITAL: {'risk_assortivity': 0.159566721712443, 'formation_rate': 0.000151940868159564}, # noqa: E241
}
for relationship_type, parameters in pair_formation.items():
pcs = cls._get_pair_formation_parameterized_calls(relationship_type=relationship_type, **parameters)
parameterized_calls.extend(pcs)
# This value is by relationship type and node
condom_usage_max_by_rel_type_by_node = {
1: {TRANSITORY: 0.819762848239337, INFORMAL: 0.203364778711639, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241, E126- Central
2: {TRANSITORY: 0.397075298252557, INFORMAL: 0.398597987534942, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Copperbelt
3: {TRANSITORY: 0.999759873647185, INFORMAL: 1.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Eastern
4: {TRANSITORY: 0.929201760347435, INFORMAL: 0.773399898218325, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Luapula
5: {TRANSITORY: 0.403033833521129, INFORMAL: 0.285059471718292, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Lusaka
6: {TRANSITORY: 1.0000000000000000, INFORMAL: 0.88811421915765, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Muchinga
7: {TRANSITORY: 0.66571668093682, INFORMAL: 0.367634610613126, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Northwestern
8: {TRANSITORY: 0.275068086940464, INFORMAL: 1.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Northern
9: {TRANSITORY: 0.636776909282425, INFORMAL: 0.0000000000000000, MARITAL: 0.247035322003042, COMMERCIAL: 0.85}, # noqa: E241 - Southern
10: {TRANSITORY: 0.401805672308032, INFORMAL: 0.387841572157711, MARITAL: 0.247035322003042, COMMERCIAL: 0.85} # noqa: E241, E131- Western
}
# Other relationship parameters are only by relationship type
relationship_parameters = {
TRANSITORY: {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 2001.29656235789,
'condom_usage_rate': 2.36008597080353, 'duration_scale': 0.956774771214,
'duration_heterogeneity': 0.833333333},
INFORMAL: {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 2002.789368, # noqa: E241
'condom_usage_rate': 0.288440457896589, 'duration_scale': 2.03104913138,
'duration_heterogeneity': 0.75},
MARITAL: {'coital_act_rate': 0.33, 'condom_usage_min': 0, 'condom_usage_mid': 1994.323885, # noqa: E241
'condom_usage_rate': 1.66784081403387, 'duration_scale': 22.154455184937,
'duration_heterogeneity': 0.666666667},
COMMERCIAL: {'coital_act_rate': 0.0027397260273972603, 'condom_usage_min': 0.5, 'condom_usage_mid': 1999.5,
'condom_usage_rate': 1, 'duration_scale': 0.01917808219,
'duration_heterogeneity': 1}
}
# NOTE: The all-node versions of hyperparameters defined here override the per-node-id versions. The reason is
# that the all-node hyperparameters are added to the returned list LAST (last defined hyperparameter setting
# wins in parameterized call application when >1 hyperparameter affects the same model setting).
# NOTE: due to how demographics sub-objects are built, if we set 1+ items on e.g. relationship parameters,
# we need to set them all to prevent bad things. So we apply all-node values directly to individual nodes,
# making the parameters all-node by using an across-node-shared label (multi-use, shared parameters)
for node_id, condom_usage_max_by_rel_type in condom_usage_max_by_rel_type_by_node.items():
for relationship_type, condom_usage_max in condom_usage_max_by_rel_type.items():
node_label = cls.generate_label(relationship_type=relationship_type,
node_name=NODE_ID_TO_NODE_NAME[node_id])
pcs = cls._get_relationship_parameterized_calls(relationship_type=relationship_type,
condom_usage_max=condom_usage_max,
node_ids=[node_id],
label=node_label)
parameterized_calls.extend(pcs)
parameters = relationship_parameters[relationship_type]
all_nodes_label = cls.generate_label(relationship_type=relationship_type)
pcs = cls._get_relationship_parameterized_calls(relationship_type=relationship_type,
**parameters,
node_ids=[node_id],
label=all_nodes_label)
parameterized_calls.extend(pcs)
# setting initial properties
pcs = cls._get_individual_property_parameterized_calls()
parameterized_calls.extend(pcs)
return parameterized_calls
[docs] @classmethod
def build_reports(cls, reporters: Reporters) -> Reporters:
"""
Function that creates reports to be used in the experiment. The function must have Reporters
object as the parameter and return that object. It is assumed that all the reporters come from the
reporters that are part of EMOD main code. EMOD also supports reporters as custom plug-in dlls,
however, not through the current emodpy system.
Args:
reporters: The Reporters object to add reports to
Returns:
Reporters object
"""
reporters = super().build_reports(reporters=reporters)
reporters.add(ReportHIVART(reporters_object=reporters))
reporters.add(ReportHIVInfection(reporters_object=reporters,
report_filter=ReportFilter(start_year=1980,
end_year=2050)))
reporters.add(ReportHIVMortality(reporters_object=reporters))
reporters.add(ReportTransmission(reporters_object=reporters))
reporters.add(ReportRelationshipStart(reporters_object=reporters,
include_hiv_disease_statistics=True, # default
include_other_relationship_statistics=True, # default
individual_properties=None, # default
report_filter=ReportFilter(start_year=1980,
end_year=2050,
node_ids=None, # default
min_age_years=None, # default
max_age_years=None, # default
must_have_ip_key_value="", # default
must_have_intervention=""))) # default
reporters.add(InsetChart(reporters_object=reporters,
has_ip=None, # default
has_interventions=None, # default
include_pregnancies=False, # default
include_coital_acts=True, # default
event_channels_list=["NewInfectionEvent"]))
# reporting_period of 182.5 is required for calibration because this will cause EMOD to output a mid-year
# data timepoint. This reported data is INSTANTANEOUS (current state). It is the current-best-approximation to
# the year-averaged real-world reference data provided used for calibration (in calibration, we compare the
# instantaneous, mid-year model output to annual-averaged reference data).
reporters.add(ReportHIVByAgeAndGender(reporters_object=reporters,
collect_gender_data=True,
collect_age_bins_data=[15, 20, 25, 30, 35, 40, 45, 50],
collect_circumcision_data=True,
collect_hiv_data=True,
collect_hiv_stage_data=False, # default
collect_on_art_data=True,
collect_ip_data=None, # default
collect_intervention_data=["Traditional_MC"],
collect_targeting_config_data=None, # default
add_transmitters=False, # default
stratify_infected_by_cd4=False, # default
event_counter_list=["NewInfectionEvent"],
add_relationships=True,
add_concordant_relationships=False, # default
reporting_period=182.5,
report_filter=ReportFilter(start_year=1980,
end_year=2050)))
return reporters
[docs]class ZambiaForTraining(Zambia):
"""
A version of Zambia that is used for training purposes.
It has a smaller population and shorter simulation time.
"""
[docs] @classmethod
def initialize_config(cls, schema_path: Union[str, Path]) -> ReadOnlyDict:
config = super().initialize_config(schema_path=schema_path)
# Set the base population to a small value for training purposes
# This is to avoid the model running for too long during training.
config.parameters.x_Base_Population = 0.002
# Reduce the simulation duration by 10 years to decrease runtime.
config.parameters.Simulation_Duration = config.parameters.Simulation_Duration - (10 * 365)
return config