From 9717fa9683f58259efc36d3a4db94e17ff347431 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Tue, 8 Mar 2022 10:06:03 -0500 Subject: [PATCH 01/23] refactor(atlanta): add age assortative mixing to atlanta --- titan/settings/atlanta/assort_mix.yml | 6 ++ titan/settings/atlanta/demographics.yml | 84 ++++++++++++++++--------- titan/settings/atlanta/model.yml | 38 +++++++++++ 3 files changed, 100 insertions(+), 28 deletions(-) diff --git a/titan/settings/atlanta/assort_mix.yml b/titan/settings/atlanta/assort_mix.yml index 81e0dd10..cf1edd83 100644 --- a/titan/settings/atlanta/assort_mix.yml +++ b/titan/settings/atlanta/assort_mix.yml @@ -23,4 +23,10 @@ assort_mix: partner_values: insertive: 0.0 __other__: 1.00 + assort_age: + attribute: age_bin + agent_value: __any__ + partner_values: + __same__: 0.7 + __other__: 0.3 diff --git a/titan/settings/atlanta/demographics.yml b/titan/settings/atlanta/demographics.yml index 2e7a5bf0..79e1fcff 100644 --- a/titan/settings/atlanta/demographics.yml +++ b/titan/settings/atlanta/demographics.yml @@ -3,6 +3,23 @@ demographics: ppl: 0.389 hiv: transmission: 3.75 + age: &agent_age + 1: + prob: .25 + min: 18 + max: 24 + 2: + prob: .5 + min: 25 + max: 29 + 3: + prob: .75 + min: 30 + max: 34 + 4: + prob: 1. + min: 35 + max: 39 sex_type: MSM: ppl: 1.0 @@ -20,36 +37,45 @@ demographics: cap: 0.258 init: 0.258 drug_type: - NonInj: - ppl: 0.30 - num_partners: &black_num_partners - Sex: - dist_type: poisson - vars: - 1: - value: 7.0 - value_type: float - hiv: &black_hiv - init: 0.434 - dx: - init: 0.655 - prob: 0.02 - aids: - init: 0.232 - haart: &black_haart - init: 0.625 - cap: 0.35 - adherence: - init: 0.885 - prob: 0.885 - discontinue: 0.10 - None: - ppl: 0.70 - num_partners: *black_num_partners - hiv: *black_hiv - haart: *black_haart + NonInj: + ppl: 0.30 + num_partners: &black_num_partners + Sex: + dist_type: poisson + vars: + 1: + value: 7.0 + value_type: float + hiv: &black_hiv + init: 0.434 + dx: + init: 0.655 + prob: 0.02 + aids: + init: 0.232 + exit: &black_exit_rate + age_out: + prob: 1.0 + death: + base: 8.6 + hiv: 17.2 + aids: 34.4 + haart: &black_haart + init: 0.625 + cap: 0.35 + adherence: + init: 0.885 + prob: 0.885 + discontinue: 0.10 + None: + ppl: 0.70 + num_partners: *black_num_partners + exit: *black_exit_rate + hiv: *black_hiv + haart: *black_haart white: ppl: 0.611 + age: *agent_age sex_type: MSM: ppl: 1.0 @@ -77,6 +103,8 @@ demographics: aids: init: 0.07 exit: &white_exit_rate + age_out: + prob: 1.0 death: base: 8.6 hiv: 17.2 diff --git a/titan/settings/atlanta/model.yml b/titan/settings/atlanta/model.yml index 07735cfe..8037c612 100644 --- a/titan/settings/atlanta/model.yml +++ b/titan/settings/atlanta/model.yml @@ -45,3 +45,41 @@ classes: drug_types: - NonInj - None + exit: + age_out: + exit_type: age_out + ignore_incar: false + age: 40 + death: + exit_type: death + ignore_incar: false + enter: + age_in: + enter_type: replace + age_in: true + age: 18 + prob: 1.0 + replace: + enter_type: replace + prob: 1.0 + age_bins: + 0: + min_age: 18 + max_age: 24 + 1: + min_age: 25 + max_age: 29 + 2: + min_age: 30 + max_age: 34 + 3: + min_age: 35 + max_age: 39 + +exit_enter: + age_out_in: + exit_class: age_out + entry_class: age_in + die_and_replace: + exit_class: death + entry_class: replace From 89076c07a10db836b1aef69641f10599224605e5 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Tue, 8 Mar 2022 10:54:21 -0500 Subject: [PATCH 02/23] fix(atlanta): make age out date and max age the same --- titan/settings/atlanta/model.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titan/settings/atlanta/model.yml b/titan/settings/atlanta/model.yml index 8037c612..da061074 100644 --- a/titan/settings/atlanta/model.yml +++ b/titan/settings/atlanta/model.yml @@ -49,7 +49,7 @@ classes: age_out: exit_type: age_out ignore_incar: false - age: 40 + age: 39 death: exit_type: death ignore_incar: false From 4b6f25f356c03293f4fa08734ba0c7af864ef1d4 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Mon, 14 Mar 2022 14:43:48 -0400 Subject: [PATCH 03/23] fix(exit): add break statement so agents don't try to exit twice --- titan/model.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/titan/model.py b/titan/model.py index 7f7aeac7..57aa2c0d 100644 --- a/titan/model.py +++ b/titan/model.py @@ -387,6 +387,7 @@ def exit(self): # agent ages out of model if agent.age > exit.age: self.exits[strategy.exit_class].append(agent) + break elif case == "death": p = ( prob.get_death_rate( @@ -406,6 +407,7 @@ def exit(self): if self.run_random.random() < p: # agent dies self.exits[strategy.exit_class].append(agent) + break elif case == "drop_out": p = ( agent.location.params.demographics[agent.race] @@ -417,6 +419,7 @@ def exit(self): if self.run_random.random() < p: # agent leaves study pop self.exits[strategy.exit_class].append(agent) + break for exit_list in self.exits.values(): for agent in exit_list: From a33ee7c010f6ad2330f293a35771e8e290dd90ad Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 16 Mar 2022 11:09:26 -0400 Subject: [PATCH 04/23] refactor(output): add back printing of HIV statistics in component report --- titan/output.py | 15 +++++++++++++-- titan/settings/atlanta/calibration.yml | 6 +++--- titan/settings/atlanta/model.yml | 1 + titan/settings/atlanta/outputs.yml | 5 ++++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/titan/output.py b/titan/output.py index c0f4bf87..e8e10c82 100644 --- a/titan/output.py +++ b/titan/output.py @@ -296,7 +296,8 @@ def print_components( # if this is a new file, write the header info if f.tell() == 0: f.write( - "run_id\trunseed\tpopseed\tt\tcomponent" + "run_id\trunseed\tpopseed\tt\tcomponent\tagents" + "\tinfected\ttrt\ttrtinfected" "\tdensity\tEffectiveSize\tdeg_cent\n" ) @@ -307,9 +308,19 @@ def print_components( comp_density = nx.density(comp) deg_cent = mean(list(nx.degree_centrality(comp).values())) + nhiv = ntrthiv = ntrt = 0 + for agent in comp.nodes(): + if agent.hiv.active: + nhiv += 1 + if agent.random_trial.treated: + ntrthiv += 1 + + if agent.random_trial.treated: + ntrt += 1 f.write( - f"{run_id}\t{runseed}\t{popseed}\t{t}\t{id}\t" + f"{run_id}\t{runseed}\t{popseed}\t{t}\t{id}\t{num_nodes}" + f"\t{nhiv}\t{ntrt}\t{ntrthiv}" f"\t{comp_density:.4f}" f"\t{average_size:.4f}\t{deg_cent}\n" ) diff --git a/titan/settings/atlanta/calibration.yml b/titan/settings/atlanta/calibration.yml index 1da3504d..d15f4cb4 100644 --- a/titan/settings/atlanta/calibration.yml +++ b/titan/settings/atlanta/calibration.yml @@ -1,8 +1,8 @@ calibration: sex: - act: 1.0 + act: 0.718 partner: 1.0 test_frequency: 1.00 haart: - coverage: 0.50 - acquisition: 0.50 + coverage: 0.792 + acquisition: 0.523 diff --git a/titan/settings/atlanta/model.yml b/titan/settings/atlanta/model.yml index da061074..b137696b 100644 --- a/titan/settings/atlanta/model.yml +++ b/titan/settings/atlanta/model.yml @@ -9,6 +9,7 @@ model: burn_steps: 24 network: type: scale_free + enable: true features: incar: false diff --git a/titan/settings/atlanta/outputs.yml b/titan/settings/atlanta/outputs.yml index bdb4564d..45bd95a9 100644 --- a/titan/settings/atlanta/outputs.yml +++ b/titan/settings/atlanta/outputs.yml @@ -1,3 +1,6 @@ outputs: reports: - - basicReport \ No newline at end of file + - basicReport + network: + calc_network_stats: true + calc_component_stats: true \ No newline at end of file From 895f1f2fb23a2a43ccbec298aa859c9772839ea7 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Mon, 16 May 2022 12:19:13 -0400 Subject: [PATCH 05/23] refactor: update atlanta for calibration --- titan/output.py | 2 +- titan/population.py | 2 +- titan/settings/atlanta/calibration.yml | 7 ++++--- titan/settings/atlanta/demographics.yml | 4 ++-- titan/settings/atlanta/model.yml | 6 +++--- titan/settings/atlanta/partnership.yml | 5 ++++- 6 files changed, 15 insertions(+), 11 deletions(-) diff --git a/titan/output.py b/titan/output.py index e8e10c82..15f876a9 100644 --- a/titan/output.py +++ b/titan/output.py @@ -314,7 +314,7 @@ def print_components( nhiv += 1 if agent.random_trial.treated: ntrthiv += 1 - + if agent.random_trial.treated: ntrt += 1 diff --git a/titan/population.py b/titan/population.py index a392e244..b0cbeed0 100644 --- a/titan/population.py +++ b/titan/population.py @@ -503,7 +503,7 @@ def trim_component(component, max_size): comp.number_of_nodes() > self.params.model.network.component_size.max ): - logging.info("TOO BIG", comp, comp.number_of_nodes()) + logging.info(f"TOO BIG {comp} {comp.number_of_nodes()}") trim_component(comp, self.params.model.network.component_size.max) logging.info(f" Total agents in graph: {self.graph.number_of_nodes()}") diff --git a/titan/settings/atlanta/calibration.yml b/titan/settings/atlanta/calibration.yml index d15f4cb4..9c452331 100644 --- a/titan/settings/atlanta/calibration.yml +++ b/titan/settings/atlanta/calibration.yml @@ -1,8 +1,9 @@ calibration: sex: - act: 0.718 + act: 0.705 partner: 1.0 test_frequency: 1.00 haart: - coverage: 0.792 - acquisition: 0.523 + coverage: 0.700 + acquisition: 0.525 + diff --git a/titan/settings/atlanta/demographics.yml b/titan/settings/atlanta/demographics.yml index 79e1fcff..dd14e736 100644 --- a/titan/settings/atlanta/demographics.yml +++ b/titan/settings/atlanta/demographics.yml @@ -44,7 +44,7 @@ demographics: dist_type: poisson vars: 1: - value: 7.0 + value: 3.0 value_type: float hiv: &black_hiv init: 0.434 @@ -121,7 +121,7 @@ demographics: dist_type: poisson vars: 1: - value: 7.0 + value: 3.0 value_type: float None: ppl: 0.52 diff --git a/titan/settings/atlanta/model.yml b/titan/settings/atlanta/model.yml index b137696b..26c4ed88 100644 --- a/titan/settings/atlanta/model.yml +++ b/titan/settings/atlanta/model.yml @@ -4,11 +4,11 @@ model: run: 0 num_pop: 17440 time: - num_steps: 120 + num_steps: 12 steps_per_year: 12 - burn_steps: 24 + burn_steps: 12 network: - type: scale_free + type: comp_size enable: true features: diff --git a/titan/settings/atlanta/partnership.yml b/titan/settings/atlanta/partnership.yml index 5a479b6d..42a5cbda 100644 --- a/titan/settings/atlanta/partnership.yml +++ b/titan/settings/atlanta/partnership.yml @@ -41,4 +41,7 @@ partnership: MSM: insertive: 0.0011 receptive: 0.0138 - versatile: 0.00745 + versatile: 0. + network: + same_component: + prob: 0.7 From 261b292e38f855ad9cb0856182d1f72103e010e2 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Thu, 19 May 2022 09:20:37 -0400 Subject: [PATCH 06/23] refactor: add prep to agent str --- titan/agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titan/agent.py b/titan/agent.py index 7b8db7e4..10e38250 100644 --- a/titan/agent.py +++ b/titan/agent.py @@ -88,7 +88,7 @@ def __str__(self) -> str: """ return ( f"\t{self.id}\t{self.age}\t{self.sex_type}\t{self.drug_type}\t" # type: ignore[attr-defined] - f"{self.race}\t{self.hiv.active}" # type: ignore[attr-defined] + f"{self.race}\t{self.hiv.active}\t{self.prep.active}" # type: ignore[attr-defined] ) def __repr__(self) -> str: From 3a9a22979387e32a2a0b698a69a6747dd888dcd0 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Tue, 24 May 2022 14:44:11 -0400 Subject: [PATCH 07/23] fix: correct erroneously changed number --- titan/settings/atlanta/partnership.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titan/settings/atlanta/partnership.yml b/titan/settings/atlanta/partnership.yml index 42a5cbda..4eb78ba7 100644 --- a/titan/settings/atlanta/partnership.yml +++ b/titan/settings/atlanta/partnership.yml @@ -41,7 +41,7 @@ partnership: MSM: insertive: 0.0011 receptive: 0.0138 - versatile: 0. + versatile: 0.00745 network: same_component: prob: 0.7 From c97e33f4d358acf93e8660dde2c727469172b88e Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Tue, 24 May 2022 14:44:11 -0400 Subject: [PATCH 08/23] fix: correct erroneously changed number --- titan/settings/atlanta/demographics.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titan/settings/atlanta/demographics.yml b/titan/settings/atlanta/demographics.yml index dd14e736..eae2a93f 100644 --- a/titan/settings/atlanta/demographics.yml +++ b/titan/settings/atlanta/demographics.yml @@ -44,7 +44,7 @@ demographics: dist_type: poisson vars: 1: - value: 3.0 + value: 7.0 value_type: float hiv: &black_hiv init: 0.434 From 3df818f8ca372e7d38b9f2e8d349847823eb169a Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 10:39:55 -0400 Subject: [PATCH 09/23] feat(WIP): add monkeypox --- titan/exposures/monkeypox.py | 305 ++++++++++++++++++ titan/params/agent_zero.yml | 1 + titan/params/demographics.yml | 20 ++ titan/params/exposures.yml | 4 + titan/settings/nyc-monkeypox/agent_zero.yml | 5 + titan/settings/nyc-monkeypox/assort_mix.yml | 23 ++ titan/settings/nyc-monkeypox/classes.yml | 23 ++ titan/settings/nyc-monkeypox/demographics.yml | 151 +++++++++ titan/settings/nyc-monkeypox/exposures.yml | 3 + titan/settings/nyc-monkeypox/monkeypox.yml | 7 + titan/settings/nyc-monkeypox/partnership.yml | 104 ++++++ 11 files changed, 646 insertions(+) create mode 100644 titan/exposures/monkeypox.py create mode 100644 titan/settings/nyc-monkeypox/agent_zero.yml create mode 100644 titan/settings/nyc-monkeypox/assort_mix.yml create mode 100644 titan/settings/nyc-monkeypox/classes.yml create mode 100644 titan/settings/nyc-monkeypox/demographics.yml create mode 100644 titan/settings/nyc-monkeypox/exposures.yml create mode 100644 titan/settings/nyc-monkeypox/monkeypox.yml create mode 100644 titan/settings/nyc-monkeypox/partnership.yml diff --git a/titan/exposures/monkeypox.py b/titan/exposures/monkeypox.py new file mode 100644 index 00000000..1e1ff432 --- /dev/null +++ b/titan/exposures/monkeypox.py @@ -0,0 +1,305 @@ +from typing import List, Dict, Optional, Set + +from . import base_exposure +from .. import agent +from .. import population +from .. import model +from .. import utils + + +class MonkeyPox(base_exposure.BaseExposure): + + name: str = "monkeypox" + stats: List[str] = ["monkeypox", "monkeypox_dx", "monkeypox_new", "monkeypox_dx_new"] + """ + MonkeyPox collects the following stats: + + * monkeypox - number of agents with hx of monkeypox + * monkeypox_dx - number of agents ever diagnosed with monkeypox + * monkeypox_new - number of agents converted to monkeypox this timestep + * monkeypox_dx_new - number of agents with diagnosed with monkeypox this timestep + """ + + dx_counts: Dict[str, Dict[str, int]] = {} + """Counts of diagnosed agents by race and sex_type""" + + agents: Set["agent.Agent"] = set() + """Agents who have ever had monkeypox""" + + def __init__(self, agent: "agent.Agent"): + super().__init__(agent) + + self.active = False + self.time: Optional[int] = None + self.dx = False + self.dx_time: Optional[int] = None + + @classmethod + def init_class(cls, params): + """ + Initialize any diagnosis counts and the agents set. + + args: + params: parameters for this population + """ + cls.dx_counts = { + race: {so: 0 for so in params.classes.sex_types} + for race in params.classes.races + } + cls.agents = set() + + def init_agent(self, pop: "population.Population", time: int): + """ + Initialize the agent for this exposure during population initialization (`Population.create_agent`). Called on only exposures that are enabled per the params. + + Based on demographic params for the agent, stochastically determine if hiv is active, and if active, at what past time point was the agent converted, if the agent is diagnosed, and if the agent has aids. + + args: + pop: the population this agent is a part of + time: the current time step + """ + agent_params = ( + self.agent.location.params.demographics[self.agent.race] + .sex_type[self.agent.sex_type] + .drug_type[self.agent.drug_type] + ) + + # Monkeypox + if ( + pop.pop_random.random() < agent_params.monkeypox.init + and time >= pop.params.monkeypox.start_time + ): + self.active = True + + # if monkeypox, when did the agent convert? Random sample + self.time = utils.safe_random_int( + time - self.agent.location.params.monkeypox.max_init_time, + time, + pop.pop_random, + ) + + if pop.pop_random.random() < agent_params.monkeypox.dx.init: + self.dx = True + # agent was diagnosed at a random time between conversion and now + self.dx_time = utils.safe_random_int(self.time, time, pop.pop_random) + + # add agent to class + self.add_agent(self.agent) + + def update_agent(self, model: "model.TITAN"): + """ + Update the agent for this exposure for a time step. Called once per time step in `TITAN.update_all_agents`. Agent level updates are done after population level updates. Called on only exposures that are enabled per the params. + + If the agent is monkeypox+ and the model time is past the monkeypox start_time, determine if the agent becomes diagnosed if not yet diagnosed. + + args: + model: the instance of TITAN currently being run + """ + if self.active and model.time >= model.params.monkeypox.start_time: + if not self.dx: + test_prob = ( + self.agent.location.params.demographics[self.agent.race] + .sex_type[self.agent.sex_type] + .drug_type[self.agent.drug_type] + .monkeypox.dx.prob + ) + + # Rescale based on calibration param + test_prob *= model.calibration.test_frequency + + if model.run_random.random() < test_prob: + self.diagnose(model) + + @classmethod + def add_agent(cls, agent: "agent.Agent"): + """ + Add an agent to the class (not instance). This can be useful if tracking population level statistics or groups, such as counts or newly active agents. + + Add the agent to the `agents` set and if the agent is diagnosed, updated the `dx_counts` + + args: + agent: the agent to add to the class attributes + """ + cls.agents.add(agent) + + if agent.monkeypox.dx: # type: ignore[attr-defined] + cls.dx_counts[agent.race][agent.sex_type] += 1 + + @classmethod + def remove_agent(cls, agent: "agent.Agent"): + """ + Remove an agent from the class (not instance). This can be useful if tracking population level statistics or groups, such as counts. + + Remove the agent from the `agents` set and decrement the `dx_counts` if the agent was diagnosed. + + args: + agent: the agent to remove from the class attributes + """ + cls.agents.remove(agent) + + if agent.monkeypox.dx: # type: ignore[attr-defined] + cls.dx_counts[agent.race][agent.sex_type] -= 1 + + def set_stats(self, stats: Dict[str, int], time: int): + if self.active: + stats["monkeypox"] += 1 + if self.time == time: + stats["monkeypox_new"] += 1 + if self.dx: + stats["monkeypox_dx"] += 1 + if self.dx_time == time: + stats["monkeypox_dx_new"] += 1 + + @staticmethod + def expose( + model: "model.TITAN", + interaction: str, + rel: "agent.Relationship", + num_acts: int, + ): + """ + Expose a relationship to the exposure for a number of acts of a specific interaction type. Typically, this is determining if the exposure can cause conversion/change in one of the agents, then if so determining the probability of that and then converting the succeptible agent. + + For hiv, one agent must be active and the other not for an exposure to cause conversion. + + args: + model: The running model + interaction: The type of interaction (e.g. sex, injection) + rel: The relationship where the interaction is occuring + num_acts: The number of acts of that interaction + """ + # Agent 1 is monkeypox+, Agent 2 is not, Agent 2 is succept + if rel.agent1.monkeypox.active and not rel.agent2.monkeypox.active: # type: ignore[attr-defined] + agent = rel.agent1 + partner = rel.agent2 + # If Agent 2 is monkeypox+ and Agent 1 is not, Agent 1 is succept + elif not rel.agent1.monkeypox.active and rel.agent2.monkeypox.active: # type: ignore[attr-defined] + agent = rel.agent2 + partner = rel.agent1 + else: # neither agent is monkeypox+ or both are + return + + p = agent.monkeypox.get_transmission_probability( # type: ignore[attr-defined] + model, interaction, partner, num_acts + ) + + if model.run_random.random() < p: + # if agent HIV+ partner becomes HIV+ + partner.monkeypox.convert(model) # type: ignore[attr-defined] + + def get_transmission_probability( + self, + model: "model.TITAN", + interaction: str, + partner: "agent.Agent", + num_acts: int, + ) -> float: + """ + Determines the probability of an hiv transmission event from agent to partner based on + interaction type and numer of acts. For sex acts, transmission probability is a + function of the acquisition probability of the HIV- agent's sex role + and the HIV+ agent's haart adherence, acute status, and dx risk reduction + + args: + model: The running model + interaction : "injection" or "sex" + partner: HIV- Agent + num_acts: The number of exposure interactions the agents had this time step + + returns: + probability of transmission from agent to partner + """ + # if this isn't an interaction where hiv can transmit, return 0% prob + # TODO is this correct for monkeypox + if interaction not in ("sex"): + return 0.0 + + # Logic for if needle or sex type interaction + p: float + + # get baseline probabilities + # TODO only sex? + if interaction == "injection": + p = model.params.partnership.injection.transmission.base + elif interaction == "sex": + agent_sex_role = self.agent.sex_role + partner_sex_role = partner.sex_role + + # TODO sex roles don't matter + # get partner's sex role during acts + partner_sex_role = "versatile" + + # get probability of sex acquisition given HIV- partner's position + p = partner.location.params.partnership.sex.acquisition[partner.sex_type][ + partner_sex_role + ] + + # feature specific risk adjustment + for feature in model.features: + agent_feature = getattr(self.agent, feature.name) + p *= agent_feature.get_transmission_risk_multiplier(self.time, interaction) + + partner_feature = getattr(partner, feature.name) + p *= partner_feature.get_acquisition_risk_multiplier(self.time, interaction) + + # Scaling parameter for acute HIV infections + # TO_REVIEW do we need an acute period + if self.get_acute_status(model.time): + p *= self.agent.location.params.monkeypox.acute.infectivity + + # Scaling parameter for positively identified HIV agents + if self.dx: + p *= 1 - self.agent.location.params.monkeypox.dx.risk_reduction[interaction] + + # Racial calibration parameter to attain proper race incidence disparity + p *= partner.location.params.demographics[partner.race].monkeypox.transmission + + # Scaling parameter for per act transmission. + p *= model.calibration.acquisition + + return utils.total_probability(p, num_acts) + + def convert(self, model: "model.TITAN"): + """ + Agent becomes HIV agent. Update all appropriate attributes, sets and dictionaries. + + args: + model: The model being run + """ + if not self.active: + self.active = True + self.time = model.time + self.agent.vaccine.active = False # type: ignore[attr-defined] + self.add_agent(self.agent) + + def diagnose(self, model: "model.TITAN"): + """ + Mark the agent as diagnosed. + + args: + model: the running model + """ + self.dx = True + self.dx_time = model.time + self.add_agent(self.agent) + + # ============================ HELPER METHODS ============================== + + def get_acute_status(self, time: int) -> bool: + # TO_REVIEW do we need this + """ + Get acute status of agent at time + + args: + time: The current time step + + returns: + whether an agent is acute + """ + if self.active and self.time is not None: + hiv_duration = time - self.time + + if self.agent.location.params.hiv.acute.duration >= hiv_duration >= 0: + return True + + return False diff --git a/titan/params/agent_zero.yml b/titan/params/agent_zero.yml index 0b00d478..545be139 100644 --- a/titan/params/agent_zero.yml +++ b/titan/params/agent_zero.yml @@ -26,3 +26,4 @@ agent_zero: values: - hiv - knowledge + - monkeypox diff --git a/titan/params/demographics.yml b/titan/params/demographics.yml index 65dc2d10..0363aff7 100644 --- a/titan/params/demographics.yml +++ b/titan/params/demographics.yml @@ -295,6 +295,26 @@ demographics: type: float min: 0.0 max: 1.0 + monkeypox: + init: + default: 0.0 + description: "Probability that an agent in this class initially has monkeypox" + type: float + min: 0.0 + max: 1.0 + dx: + init: + default: 0.0 + description: "Probability that an agent with monkeypox in the initial ppl is initially diagnosed" + type: float + min: 0.0 + max: 1.0 + prob: + default: 0.0 + description: "Probability that an agent with HIV becomes diagnosed at a given time step and 'knows' their monkeypox status, therefore enabling enrollment in treatment" + type: float + min: 0.0 + max: 1.0 haart: init: default: 0.0 diff --git a/titan/params/exposures.yml b/titan/params/exposures.yml index 4924cc58..d79217e9 100644 --- a/titan/params/exposures.yml +++ b/titan/params/exposures.yml @@ -7,3 +7,7 @@ exposures: default: false description: Whether knowledge will be used as an exposure in the model type: boolean + monkeypox: + default: false + description: Whether monkeypox will be used as an exposure in the model + type: boolean diff --git a/titan/settings/nyc-monkeypox/agent_zero.yml b/titan/settings/nyc-monkeypox/agent_zero.yml new file mode 100644 index 00000000..ecd18050 --- /dev/null +++ b/titan/settings/nyc-monkeypox/agent_zero.yml @@ -0,0 +1,5 @@ +agent_zero: + num_partners: 4 + fallback: true + interaction_type: sex + exposure: monkeypox \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/assort_mix.yml b/titan/settings/nyc-monkeypox/assort_mix.yml new file mode 100644 index 00000000..3c7c4b68 --- /dev/null +++ b/titan/settings/nyc-monkeypox/assort_mix.yml @@ -0,0 +1,23 @@ +assort_mix: + assort_white: + attribute: race + agent_value: white + partner_values: + white: 0.649 + black: 0.099 + latino: 0.252 + assort_black: + attribute: race + agent_value: black + partner_values: + black: 0.588 + latino: 0.227 + white: 0.185 + assort_latino: + attribute: race + agent_value: latino + partner_values: + black: 0.227 + latino: 0.460 + white: 0.313 + \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/classes.yml b/titan/settings/nyc-monkeypox/classes.yml new file mode 100644 index 00000000..e36bb9d7 --- /dev/null +++ b/titan/settings/nyc-monkeypox/classes.yml @@ -0,0 +1,23 @@ +classes: + races: + white: + hispanic: false + black: + hispanic: false + latino: + hispanic: true + sex_types: + MSM: + gender: M + cis_trans: cis + sleeps_with: + - MSM + bond_types: + Main: + acts_allowed: + - sex + Casual: + acts_allowed: + - sex + drug_types: + - None \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/demographics.yml b/titan/settings/nyc-monkeypox/demographics.yml new file mode 100644 index 00000000..b3c0a4cd --- /dev/null +++ b/titan/settings/nyc-monkeypox/demographics.yml @@ -0,0 +1,151 @@ +demographics: + black: + ppl: 0.182 + sex_type: + MSM: + ppl: 1.0 + sex_role: + init: + versatile: 0.200 + insertive: 0.480 + receptive: 0.320 + safe_sex: + Main: + prob: 0.275 + Casual: + prob: 0.409 + prep: + discontinue: 0.034 # calibration + adherence: 0.565 + cap: 0.028 + init: 0.252 + drug_type: + None: + ppl: 1.0 + monkeypox: + init: 0.355 + dx: + init: 0.0 + prob: 0.198 + exit: + death: + base: 1.512 + num_partners: + Main: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 1.56 + 2: + value_type: float + value: 0.59 + Casual: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 0.67 + 2: + value_type: float + value: 0.06 + latino: + ppl: 0.261 + sex_type: + MSM: + ppl: 1.0 + sex_role: # calibration + init: + insertive: 0.220 + receptive: 0.195 + versatile: 0.585 + safe_sex: + Main: + prob: 0.183 + Casual: + prob: 0.341 + prep: + discontinue: 0.025 # calibration + adherence: 0.719 + cap: 0.026 + init: 0.234 + drug_type: + None: + ppl: 1.0 + monkeypox: + init: 0.20 + dx: + init: 0.920 + prob: 0.055 + exit: + death: + base: 0.792 + num_partners: + Main: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 1.37 + 2: + value_type: float + value: 0.58 + Casual: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 0.46 + 2: + value_type: float + value: 0.03 + white: + ppl: 0.557 + sex_type: + MSM: + ppl: 1.0 + safe_sex: + Main: + prob: 0.126 + Casual: + prob: 0.277 + sex_role: + init: + insertive: 0.439 + receptive: 0.286 + versatile: 0.275 + prep: + discontinue: 0.014 + adherence: 0.738 + cap: 0.031 + init: 0.279 + drug_type: + None: + ppl: 1.0 + monkeypox: + init: 0.111 + dx: + init: 0.920 + prob: 0.155 + exit: + death: + base: 0.1 + num_partners: + Main: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 1.02 + 2: + value_type: float + value: 0.48 + Casual: + dist_type: negative_binomial + vars: + 1: + value_type: float + value: 0.57 + 2: + value_type: float + value: 0.03 diff --git a/titan/settings/nyc-monkeypox/exposures.yml b/titan/settings/nyc-monkeypox/exposures.yml new file mode 100644 index 00000000..95635275 --- /dev/null +++ b/titan/settings/nyc-monkeypox/exposures.yml @@ -0,0 +1,3 @@ +exposures: + hiv: false + monkeypox: true \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/monkeypox.yml b/titan/settings/nyc-monkeypox/monkeypox.yml new file mode 100644 index 00000000..4107997f --- /dev/null +++ b/titan/settings/nyc-monkeypox/monkeypox.yml @@ -0,0 +1,7 @@ +monkeypox: + dx: + risk_reduction: + sex: 0.0 + start_time: 0 + max_init_time: 2 + \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/partnership.yml b/titan/settings/nyc-monkeypox/partnership.yml new file mode 100644 index 00000000..fc67f1bc --- /dev/null +++ b/titan/settings/nyc-monkeypox/partnership.yml @@ -0,0 +1,104 @@ +partnership: + sex: + frequency: + Main: + type: distribution + distribution: + dist_type: gamma + vars: + 1: + value: 0.439 + value_type: float + 2: + value: 15.3 + value_type: float + Casual: + type: distribution + distributions: + dist_type: gamma + vars: + 1: + value: 0.131 + value_type: float + 2: + value: 16.5 + value_type: float + acquisition: + MSM: + versatile: 0.0046 + + duration: + Main: + white: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 70.44 + value_type: float + 2: + value: 6.72 + value_type: float + mean: 70.44 + black: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 20.88 + value_type: float + 2: + value: 2.76 + value_type: float + mean: 20.88 + latino: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 35.52 + value_type: float + 2: + value: 3.84 + value_type: float + mean: 3.84 + Casual: + white: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 11.76 + value_type: float + 2: + value: 1.32 + value_type: float + mean: 11.76 + black: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 17.16 + value_type: float + 2: + value: 1.56 + value_type: float + mean: 17.16 + latino: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 16.32 + value_type: float + 2: + value: 1.20 + value_type: float + mean: 16.32 From 4efd0bbeb73b576fd873d95831b7b25776c549ce Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 11:12:37 -0400 Subject: [PATCH 10/23] update acute status for monkeypox --- titan/exposures/monkeypox.py | 35 ++++++++++++----------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/titan/exposures/monkeypox.py b/titan/exposures/monkeypox.py index 1e1ff432..ed473c37 100644 --- a/titan/exposures/monkeypox.py +++ b/titan/exposures/monkeypox.py @@ -209,9 +209,8 @@ def get_transmission_probability( returns: probability of transmission from agent to partner """ - # if this isn't an interaction where hiv can transmit, return 0% prob - # TODO is this correct for monkeypox - if interaction not in ("sex"): + # if this isn't an interaction where monkeypox can transmit, return 0% prob + if interaction not in ("sex") or not self.get_acute_status(model.time): return 0.0 # Logic for if needle or sex type interaction @@ -219,20 +218,16 @@ def get_transmission_probability( # get baseline probabilities # TODO only sex? - if interaction == "injection": - p = model.params.partnership.injection.transmission.base - elif interaction == "sex": - agent_sex_role = self.agent.sex_role - partner_sex_role = partner.sex_role + partner_sex_role = partner.sex_role - # TODO sex roles don't matter - # get partner's sex role during acts - partner_sex_role = "versatile" + # TODO sex roles don't matter + # get partner's sex role during acts + partner_sex_role = "versatile" - # get probability of sex acquisition given HIV- partner's position - p = partner.location.params.partnership.sex.acquisition[partner.sex_type][ - partner_sex_role - ] + # get probability of sex acquisition given HIV- partner's position + p = partner.location.params.partnership.sex.acquisition[partner.sex_type][ + partner_sex_role + ] # feature specific risk adjustment for feature in model.features: @@ -242,11 +237,6 @@ def get_transmission_probability( partner_feature = getattr(partner, feature.name) p *= partner_feature.get_acquisition_risk_multiplier(self.time, interaction) - # Scaling parameter for acute HIV infections - # TO_REVIEW do we need an acute period - if self.get_acute_status(model.time): - p *= self.agent.location.params.monkeypox.acute.infectivity - # Scaling parameter for positively identified HIV agents if self.dx: p *= 1 - self.agent.location.params.monkeypox.dx.risk_reduction[interaction] @@ -286,7 +276,6 @@ def diagnose(self, model: "model.TITAN"): # ============================ HELPER METHODS ============================== def get_acute_status(self, time: int) -> bool: - # TO_REVIEW do we need this """ Get acute status of agent at time @@ -297,9 +286,9 @@ def get_acute_status(self, time: int) -> bool: whether an agent is acute """ if self.active and self.time is not None: - hiv_duration = time - self.time + monkeypox_duration = time - self.time - if self.agent.location.params.hiv.acute.duration >= hiv_duration >= 0: + if self.agent.location.params.monkeypox.acute.duration >= monkeypox_duration >= 0: return True return False From 3411bdfb526f6210c5f0b9cfa060a272b180915a Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 11:43:20 -0400 Subject: [PATCH 11/23] feat(monkeypox): feat nominally working --- titan/exposures/__init__.py | 1 + titan/features/vaccine.py | 2 ++ titan/params/demographics.yml | 6 +++++ titan/params/hiv.yml | 2 +- titan/params/monkeypox.yml | 24 ++++++++++++++++++++ titan/params/vaccine.yml | 7 ++++++ titan/settings/nyc-monkeypox/model.yml | 16 +++++++++++++ titan/settings/nyc-monkeypox/partnership.yml | 8 +++---- 8 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 titan/params/monkeypox.yml create mode 100644 titan/settings/nyc-monkeypox/model.yml diff --git a/titan/exposures/__init__.py b/titan/exposures/__init__.py index 4b8e687b..0f7718e4 100644 --- a/titan/exposures/__init__.py +++ b/titan/exposures/__init__.py @@ -2,3 +2,4 @@ from .base_exposure import * from .hiv import * from .knowledge import * +from .monkeypox import MonkeyPox diff --git a/titan/features/vaccine.py b/titan/features/vaccine.py index c641c4f2..ee72d010 100644 --- a/titan/features/vaccine.py +++ b/titan/features/vaccine.py @@ -102,6 +102,8 @@ def get_acquisition_risk_multiplier(self, time: int, interaction_type: str): ) elif self.type == "RV144": return np.exp(-2.40 + 0.76 * (np.log(vaccine_time_months))) + elif self.type == "other": + return 1 - self.agent.location.params.vaccine.efficacy return 1.0 diff --git a/titan/params/demographics.yml b/titan/params/demographics.yml index 0363aff7..ab1de5b5 100644 --- a/titan/params/demographics.yml +++ b/titan/params/demographics.yml @@ -16,6 +16,12 @@ demographics: description: Scaling factor for transmission probability type: float min: 0 + monkeypox: + transmission: + default: 1.0 + description: Scaling factor for transmission probability + type: float + min: 0 age: type: bin description: Binned probabilities of ages by race, last bin should be 1 to have default diff --git a/titan/params/hiv.yml b/titan/params/hiv.yml index 4a358d14..de3f139a 100644 --- a/titan/params/hiv.yml +++ b/titan/params/hiv.yml @@ -3,7 +3,7 @@ hiv: risk_reduction: sex: default: 0 - description: risk redection in sex transmission probability for diagnosed agents + description: risk reduction in sex transmission probability for diagnosed agents type: float min: 0 max: 1 diff --git a/titan/params/monkeypox.yml b/titan/params/monkeypox.yml new file mode 100644 index 00000000..5c4ab2ca --- /dev/null +++ b/titan/params/monkeypox.yml @@ -0,0 +1,24 @@ +monkeypox: + dx: + risk_reduction: + sex: + default: 0 + description: risk reduction in sex transmission probability for diagnosed agents + type: float + min: 0 + max: 1 + acute: + duration: + default: 4 + description: number of timesteps agent is acutely infected and contagious + type: int + min: 0 + start_time: + default: -999 + description: "what timesteps to allow agents to init with monkeypox" + type: int + max_init_time: + default: 1 + description: On creation, agents are randomly assigned a monkeypox.time. this is the maximum time + type: int + min: 1 \ No newline at end of file diff --git a/titan/params/vaccine.yml b/titan/params/vaccine.yml index b8293a4e..8f718f88 100644 --- a/titan/params/vaccine.yml +++ b/titan/params/vaccine.yml @@ -6,6 +6,13 @@ vaccine: values: - RV144 - HVTN702 + - other + efficacy: + default: 0.0 + description: for "other" vaccine, how efficacious is the vaccine + type: float + min: 0.0 + max: 1.0 booster: default: true description: Whether to use booster vaccines diff --git a/titan/settings/nyc-monkeypox/model.yml b/titan/settings/nyc-monkeypox/model.yml new file mode 100644 index 00000000..141c20df --- /dev/null +++ b/titan/settings/nyc-monkeypox/model.yml @@ -0,0 +1,16 @@ +features: + exit_enter: true + agent_zero: true + assort_mix: true + vaccine: true + +outputs: + classes: + - races + +model: + num_pop: 1719 + time: + num_steps: 12 + steps_per_year: 52 + burn_steps: 0 diff --git a/titan/settings/nyc-monkeypox/partnership.yml b/titan/settings/nyc-monkeypox/partnership.yml index fc67f1bc..6c90a2ce 100644 --- a/titan/settings/nyc-monkeypox/partnership.yml +++ b/titan/settings/nyc-monkeypox/partnership.yml @@ -14,7 +14,7 @@ partnership: value_type: float Casual: type: distribution - distributions: + distribution: dist_type: gamma vars: 1: @@ -23,9 +23,9 @@ partnership: 2: value: 16.5 value_type: float - acquisition: - MSM: - versatile: 0.0046 + acquisition: + MSM: + versatile: 0.0046 duration: Main: From 07600b3418c6e9d5b86dc10e3f27706830de07b8 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 11:53:32 -0400 Subject: [PATCH 12/23] style: run black --- titan/exposures/monkeypox.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/titan/exposures/monkeypox.py b/titan/exposures/monkeypox.py index ed473c37..6581d66e 100644 --- a/titan/exposures/monkeypox.py +++ b/titan/exposures/monkeypox.py @@ -10,7 +10,12 @@ class MonkeyPox(base_exposure.BaseExposure): name: str = "monkeypox" - stats: List[str] = ["monkeypox", "monkeypox_dx", "monkeypox_new", "monkeypox_dx_new"] + stats: List[str] = [ + "monkeypox", + "monkeypox_dx", + "monkeypox_new", + "monkeypox_dx_new", + ] """ MonkeyPox collects the following stats: @@ -288,7 +293,11 @@ def get_acute_status(self, time: int) -> bool: if self.active and self.time is not None: monkeypox_duration = time - self.time - if self.agent.location.params.monkeypox.acute.duration >= monkeypox_duration >= 0: + if ( + self.agent.location.params.monkeypox.acute.duration + >= monkeypox_duration + >= 0 + ): return True return False From 63a73d483b709cd652aba256b366e8b444d1ff83 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 14:21:00 -0400 Subject: [PATCH 13/23] docs(monkeypox): remove stray references to HIV --- titan/exposures/monkeypox.py | 20 ++++++++++---------- titan/features/vaccine.py | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/titan/exposures/monkeypox.py b/titan/exposures/monkeypox.py index 6581d66e..b6430326 100644 --- a/titan/exposures/monkeypox.py +++ b/titan/exposures/monkeypox.py @@ -57,7 +57,7 @@ def init_agent(self, pop: "population.Population", time: int): """ Initialize the agent for this exposure during population initialization (`Population.create_agent`). Called on only exposures that are enabled per the params. - Based on demographic params for the agent, stochastically determine if hiv is active, and if active, at what past time point was the agent converted, if the agent is diagnosed, and if the agent has aids. + Based on demographic params for the agent, stochastically determine if monkeypox is active, and if active, at what past time point was the agent converted, if the agent is diagnosed, and if the agent has aids. args: pop: the population this agent is a part of @@ -165,7 +165,7 @@ def expose( """ Expose a relationship to the exposure for a number of acts of a specific interaction type. Typically, this is determining if the exposure can cause conversion/change in one of the agents, then if so determining the probability of that and then converting the succeptible agent. - For hiv, one agent must be active and the other not for an exposure to cause conversion. + For monkeypox, one agent must be active and the other not for an exposure to cause conversion. args: model: The running model @@ -189,7 +189,7 @@ def expose( ) if model.run_random.random() < p: - # if agent HIV+ partner becomes HIV+ + # if agent monkeypox+ partner becomes monkeypox+ partner.monkeypox.convert(model) # type: ignore[attr-defined] def get_transmission_probability( @@ -200,15 +200,15 @@ def get_transmission_probability( num_acts: int, ) -> float: """ - Determines the probability of an hiv transmission event from agent to partner based on + Determines the probability of an monkeypox transmission event from agent to partner based on interaction type and numer of acts. For sex acts, transmission probability is a - function of the acquisition probability of the HIV- agent's sex role - and the HIV+ agent's haart adherence, acute status, and dx risk reduction + function of the acquisition probability of the monkeypox- agent's sex role + and the monkeypox+ agent's haart adherence, acute status, and dx risk reduction args: model: The running model interaction : "injection" or "sex" - partner: HIV- Agent + partner: monkeypox- Agent num_acts: The number of exposure interactions the agents had this time step returns: @@ -229,7 +229,7 @@ def get_transmission_probability( # get partner's sex role during acts partner_sex_role = "versatile" - # get probability of sex acquisition given HIV- partner's position + # get probability of sex acquisition given monkeypox- partner's position p = partner.location.params.partnership.sex.acquisition[partner.sex_type][ partner_sex_role ] @@ -242,7 +242,7 @@ def get_transmission_probability( partner_feature = getattr(partner, feature.name) p *= partner_feature.get_acquisition_risk_multiplier(self.time, interaction) - # Scaling parameter for positively identified HIV agents + # Scaling parameter for positively identified monkeypox agents if self.dx: p *= 1 - self.agent.location.params.monkeypox.dx.risk_reduction[interaction] @@ -256,7 +256,7 @@ def get_transmission_probability( def convert(self, model: "model.TITAN"): """ - Agent becomes HIV agent. Update all appropriate attributes, sets and dictionaries. + Agent becomes monkeypox agent. Update all appropriate attributes, sets and dictionaries. args: model: The model being run diff --git a/titan/features/vaccine.py b/titan/features/vaccine.py index ee72d010..0a41c6a3 100644 --- a/titan/features/vaccine.py +++ b/titan/features/vaccine.py @@ -102,7 +102,7 @@ def get_acquisition_risk_multiplier(self, time: int, interaction_type: str): ) elif self.type == "RV144": return np.exp(-2.40 + 0.76 * (np.log(vaccine_time_months))) - elif self.type == "other": + else: return 1 - self.agent.location.params.vaccine.efficacy return 1.0 From cee0a018f750a09a52c34cc20c34d16ab070e8cf Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 14:21:42 -0400 Subject: [PATCH 14/23] test(monkeypox): add new tests for monkeypox --- tests/exposures/monkeypox_test.py | 101 ++++++++++++++++++++++++++++++ tests/features/vaccine_test.py | 26 ++++++++ 2 files changed, 127 insertions(+) create mode 100644 tests/exposures/monkeypox_test.py diff --git a/tests/exposures/monkeypox_test.py b/tests/exposures/monkeypox_test.py new file mode 100644 index 00000000..89fd05df --- /dev/null +++ b/tests/exposures/monkeypox_test.py @@ -0,0 +1,101 @@ +import pytest + +from conftest import FakeRandom + +from titan.exposures import MonkeyPox +from titan.agent import Relationship + + +@pytest.mark.unit +def test_monkeypox_expose(make_model, make_agent): + model = make_model() + model.run_random = FakeRandom(0.0) # always less than param + a = make_agent() + p = make_agent() + a.partners["Sex"] = set() + p.partners["Sex"] = set() + rel = Relationship(a, p, 10, bond_type="Sex") + + MonkeyPox.expose(model, "sex", rel, 1) + + assert a.monkeypox.active is False + assert p.monkeypox.active is False + + a.monkeypox.active = True + a.monkeypox.time = model.time + p.monkeypox.active = True + a.monkeypox.time = model.time + + # test nothing happens + MonkeyPox.expose(model, "sex", rel, 10) + + assert a.monkeypox.active + assert p.monkeypox.active + + p.monkeypox.active = False + + # test conversion happens + MonkeyPox.expose(model, "sex", rel, 10) + + assert a.monkeypox.active + assert p.monkeypox.active + + +@pytest.mark.unit +def test_monkeypox_convert(make_model, make_agent): + model = make_model() + a = make_agent() + + model.run_random = FakeRandom(-0.1) + + a.monkeypox.convert(model) + + assert a.monkeypox.active + assert a.monkeypox.time == model.time + assert a in MonkeyPox.agents + + +# TODO talk with Ellen about why this is failing? +# @pytest.mark.unit +# def test_diagnose_monkeypox(make_model, make_agent): +# model = make_model() +# model.params.partner_tracing.prob = 1.0 +# model.time = 1 +# a = make_agent() +# p = make_agent() +# p.monkeypox.active = True +# a.partners["Sex"].add(p) + +# model.run_random = FakeRandom(-0.1) # always less than param +# a.monkeypox.diagnose(model) + +# assert a.monkeypox.dx +# assert a.monkeypox.dx_time == model.time + + +@pytest.mark.unit +def test_get_transmission_probability(make_model, make_agent): + model = make_model() + a = make_agent(race="white", SO="MSM") + a.sex_role = "versatile" + + p = make_agent(race="white", SO="MSM") + p.sex_role = "versatile" + + p_sex = model.params.partnership.sex.acquisition.MSM.versatile + scale = model.params.calibration.acquisition + + # must be in acute phase + a.monkeypox.active = True + a.monkeypox.time = model.time + + assert a.monkeypox.get_transmission_probability(model, "sex", p, 1) == p_sex * scale + + +@pytest.mark.unit +def test_get_acute_status(make_agent, make_model): + model = make_model() + a = make_agent() + assert a.monkeypox.get_acute_status(model.time + 2) is False + a.monkeypox.convert(model) + assert a.monkeypox.get_acute_status(model.time + 2) is True diff --git a/tests/features/vaccine_test.py b/tests/features/vaccine_test.py index db8c1162..fcba300a 100644 --- a/tests/features/vaccine_test.py +++ b/tests/features/vaccine_test.py @@ -64,3 +64,29 @@ def test_vaccine_cquisition_risk_multiplier(make_agent): a.vaccine.vaccinate(0) assert a.vaccine.get_acquisition_risk_multiplier(1, "sex") < 1.0 + + +@pytest.mark.unit +def test_vaccine_HVTN(make_agent): + a = make_agent() + print(a.location.params.vaccine.type) + a.location.params.vaccine.type = "HVTN702" + assert a.vaccine.get_acquisition_risk_multiplier(0, "sex") == 1.0 + + a.vaccine.vaccinate(0) + assert a.vaccine.get_acquisition_risk_multiplier(1, "sex") < 1.0 + + +@pytest.mark.unit +def test_vaccine_other(make_agent): + a = make_agent() + print(a.location.params.vaccine.type) + a.location.params.vaccine.type = "other" + assert a.vaccine.get_acquisition_risk_multiplier(0, "sex") == 1.0 + + a.vaccine.vaccinate(0) + # no risk multiplier + assert a.vaccine.get_acquisition_risk_multiplier(1, "sex") == 1.0 + + a.location.params.vaccine.efficacy = 0.5 + assert a.vaccine.get_acquisition_risk_multiplier(1, "sex") == 0.5 From d0abf1849256a9fec50ccaacfac0b38461bb155e Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 Aug 2022 14:34:42 -0400 Subject: [PATCH 15/23] test(monkeypox): fix monkeypox dx test --- tests/exposures/monkeypox_test.py | 30 +++++++++++++++--------------- tests/params/basic.yml | 1 + 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/tests/exposures/monkeypox_test.py b/tests/exposures/monkeypox_test.py index 89fd05df..633f13cb 100644 --- a/tests/exposures/monkeypox_test.py +++ b/tests/exposures/monkeypox_test.py @@ -56,21 +56,21 @@ def test_monkeypox_convert(make_model, make_agent): # TODO talk with Ellen about why this is failing? -# @pytest.mark.unit -# def test_diagnose_monkeypox(make_model, make_agent): -# model = make_model() -# model.params.partner_tracing.prob = 1.0 -# model.time = 1 -# a = make_agent() -# p = make_agent() -# p.monkeypox.active = True -# a.partners["Sex"].add(p) - -# model.run_random = FakeRandom(-0.1) # always less than param -# a.monkeypox.diagnose(model) - -# assert a.monkeypox.dx -# assert a.monkeypox.dx_time == model.time +@pytest.mark.unit +def test_diagnose_monkeypox(make_model, make_agent): + model = make_model() + model.params.partner_tracing.prob = 1.0 + model.time = 1 + a = make_agent() + p = make_agent() + p.monkeypox.active = True + a.partners["Sex"].add(p) + + model.run_random = FakeRandom(-0.1) # always less than param + a.monkeypox.diagnose(model) + + assert a.monkeypox.dx + assert a.monkeypox.dx_time == model.time @pytest.mark.unit diff --git a/tests/params/basic.yml b/tests/params/basic.yml index 60443957..ddd03f1e 100644 --- a/tests/params/basic.yml +++ b/tests/params/basic.yml @@ -324,6 +324,7 @@ outputs: exposures: hiv: true knowledge: true + monkeypox: true haart: use_reinit: true From d97efe9378d967240b93b8efde37f0bfff7c84db Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Thu, 11 Aug 2022 09:29:11 -0400 Subject: [PATCH 16/23] test: add monkeypox to remove agent test New monkeypox's remove agent feature wasn't being tested. This should fix that issue. --- tests/population_test.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/population_test.py b/tests/population_test.py index dab36d8c..745e2bac 100644 --- a/tests/population_test.py +++ b/tests/population_test.py @@ -94,6 +94,9 @@ def test_add_remove_agent_to_pop(make_population): agent.hiv.aids = True agent.hiv.dx = True agent.hiv.add_agent(agent) + agent.monkeypox.active = True + agent.monkeypox.dx = True + agent.monkeypox.add_agent(agent) pop.add_agent(agent) From f4d514ba7055aa2191b5791f973216f10f425b0b Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 26 Aug 2022 13:34:27 -0400 Subject: [PATCH 17/23] add monkeypox params --- titan/settings/nyc-monkeypox/demographics.yml | 12 ++--- titan/settings/nyc-monkeypox/model.yml | 2 +- titan/settings/nyc-monkeypox/monkeypox.yml | 5 ++ titan/settings/nyc-monkeypox/partnership.yml | 51 ++++++++++--------- titan/settings/nyc-monkeypox/vaccine.yml | 6 +++ 5 files changed, 44 insertions(+), 32 deletions(-) create mode 100644 titan/settings/nyc-monkeypox/vaccine.yml diff --git a/titan/settings/nyc-monkeypox/demographics.yml b/titan/settings/nyc-monkeypox/demographics.yml index b3c0a4cd..80a883cc 100644 --- a/titan/settings/nyc-monkeypox/demographics.yml +++ b/titan/settings/nyc-monkeypox/demographics.yml @@ -39,7 +39,7 @@ demographics: value: 1.56 2: value_type: float - value: 0.59 + value: 0.767 Casual: dist_type: negative_binomial vars: @@ -48,7 +48,7 @@ demographics: value: 0.67 2: value_type: float - value: 0.06 + value: 0.39 latino: ppl: 0.261 sex_type: @@ -89,7 +89,7 @@ demographics: value: 1.37 2: value_type: float - value: 0.58 + value: 0.754 Casual: dist_type: negative_binomial vars: @@ -98,7 +98,7 @@ demographics: value: 0.46 2: value_type: float - value: 0.03 + value: 0.39 white: ppl: 0.557 sex_type: @@ -139,7 +139,7 @@ demographics: value: 1.02 2: value_type: float - value: 0.48 + value: 0.624 Casual: dist_type: negative_binomial vars: @@ -148,4 +148,4 @@ demographics: value: 0.57 2: value_type: float - value: 0.03 + value: 0.39 diff --git a/titan/settings/nyc-monkeypox/model.yml b/titan/settings/nyc-monkeypox/model.yml index 141c20df..7f0ad239 100644 --- a/titan/settings/nyc-monkeypox/model.yml +++ b/titan/settings/nyc-monkeypox/model.yml @@ -12,5 +12,5 @@ model: num_pop: 1719 time: num_steps: 12 - steps_per_year: 52 + steps_per_year: 365 burn_steps: 0 diff --git a/titan/settings/nyc-monkeypox/monkeypox.yml b/titan/settings/nyc-monkeypox/monkeypox.yml index 4107997f..b4d27147 100644 --- a/titan/settings/nyc-monkeypox/monkeypox.yml +++ b/titan/settings/nyc-monkeypox/monkeypox.yml @@ -4,4 +4,9 @@ monkeypox: sex: 0.0 start_time: 0 max_init_time: 2 + acute: + infectivity: 10000 + duration: 30 + aids: + prob: 0 \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/partnership.yml b/titan/settings/nyc-monkeypox/partnership.yml index 6c90a2ce..787e9238 100644 --- a/titan/settings/nyc-monkeypox/partnership.yml +++ b/titan/settings/nyc-monkeypox/partnership.yml @@ -1,5 +1,9 @@ partnership: sex: + acquisition: + versatile: .00001005 + insertive: .00001005 + receptive: .00001005 frequency: Main: type: distribution @@ -7,10 +11,10 @@ partnership: dist_type: gamma vars: 1: - value: 0.439 + value: 0.51 value_type: float 2: - value: 15.3 + value: .439 value_type: float Casual: type: distribution @@ -18,14 +22,11 @@ partnership: dist_type: gamma vars: 1: - value: 0.131 + value: 0.55 value_type: float 2: - value: 16.5 + value: 0.131 value_type: float - acquisition: - MSM: - versatile: 0.0046 duration: Main: @@ -35,36 +36,36 @@ partnership: dist_type: wald vars: 1: - value: 70.44 + value: 2113.2 value_type: float 2: - value: 6.72 + value: 201.6 value_type: float - mean: 70.44 + mean: 2113.2 black: type: distribution distribution: dist_type: wald vars: 1: - value: 20.88 + value: 626.4 value_type: float 2: - value: 2.76 + value: 82.8 value_type: float - mean: 20.88 + mean: 626.4 latino: type: distribution distribution: dist_type: wald vars: 1: - value: 35.52 + value: 1065.6 value_type: float 2: - value: 3.84 + value: 115.2 value_type: float - mean: 3.84 + mean: 1065.6 Casual: white: type: distribution @@ -72,33 +73,33 @@ partnership: dist_type: wald vars: 1: - value: 11.76 + value: 352.8 value_type: float 2: - value: 1.32 + value: 39.6 value_type: float - mean: 11.76 + mean: 352.8 black: type: distribution distribution: dist_type: wald vars: 1: - value: 17.16 + value: 518.4 value_type: float 2: - value: 1.56 + value: 46.8 value_type: float - mean: 17.16 + mean: 518.4 latino: type: distribution distribution: dist_type: wald vars: 1: - value: 16.32 + value: 489.6 value_type: float 2: - value: 1.20 + value: 36.0 value_type: float - mean: 16.32 + mean: 489.6 diff --git a/titan/settings/nyc-monkeypox/vaccine.yml b/titan/settings/nyc-monkeypox/vaccine.yml new file mode 100644 index 00000000..2ffc233f --- /dev/null +++ b/titan/settings/nyc-monkeypox/vaccine.yml @@ -0,0 +1,6 @@ +vaccine: + type: other + efficacy: 0.193 + booster: false + start_time: 1 + on_init: false \ No newline at end of file From fec6735cf309893752e513fe9d54b02974651343 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 26 Aug 2022 13:57:08 -0400 Subject: [PATCH 18/23] fix: update 'unused' params --- titan/params/monkeypox.yml | 7 ++++++- titan/settings/nyc-monkeypox/monkeypox.yml | 2 -- titan/settings/nyc-monkeypox/partnership.yml | 7 ++++--- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/titan/params/monkeypox.yml b/titan/params/monkeypox.yml index 5c4ab2ca..95e17b64 100644 --- a/titan/params/monkeypox.yml +++ b/titan/params/monkeypox.yml @@ -9,10 +9,15 @@ monkeypox: max: 1 acute: duration: - default: 4 + default: 30 description: number of timesteps agent is acutely infected and contagious type: int min: 0 + infectivity: + default: 1.0 + description: Infectivity multiplier ratio for acute status infections + type: float + min: 0 start_time: default: -999 description: "what timesteps to allow agents to init with monkeypox" diff --git a/titan/settings/nyc-monkeypox/monkeypox.yml b/titan/settings/nyc-monkeypox/monkeypox.yml index b4d27147..501a6b65 100644 --- a/titan/settings/nyc-monkeypox/monkeypox.yml +++ b/titan/settings/nyc-monkeypox/monkeypox.yml @@ -7,6 +7,4 @@ monkeypox: acute: infectivity: 10000 duration: 30 - aids: - prob: 0 \ No newline at end of file diff --git a/titan/settings/nyc-monkeypox/partnership.yml b/titan/settings/nyc-monkeypox/partnership.yml index 787e9238..a15d359e 100644 --- a/titan/settings/nyc-monkeypox/partnership.yml +++ b/titan/settings/nyc-monkeypox/partnership.yml @@ -1,9 +1,10 @@ partnership: sex: acquisition: - versatile: .00001005 - insertive: .00001005 - receptive: .00001005 + MSM: + versatile: .00001005 + insertive: .00001005 + receptive: .00001005 frequency: Main: type: distribution From dcf415a6a862d0ef95a3fa4dbc45826a0aba3002 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Thu, 1 Sep 2022 09:22:29 -0400 Subject: [PATCH 19/23] monkeypox param updates --- pyproject.toml | 2 +- titan/settings/nyc-monkeypox/agent_zero.yml | 5 +++-- titan/settings/nyc-monkeypox/demographics.yml | 11 +++-------- titan/settings/nyc-monkeypox/model.yml | 6 +++--- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d4cd32e4..49192653 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "titan-model" -version = "3.1.0" +version = "3.2.0" description = "TITAN Agent Based Model" license = "GPL-3.0-only" authors = ["Sam Bessey ", "Mary McGrath "] diff --git a/titan/settings/nyc-monkeypox/agent_zero.yml b/titan/settings/nyc-monkeypox/agent_zero.yml index ecd18050..e54041af 100644 --- a/titan/settings/nyc-monkeypox/agent_zero.yml +++ b/titan/settings/nyc-monkeypox/agent_zero.yml @@ -1,5 +1,6 @@ agent_zero: num_partners: 4 - fallback: true + fallback: false interaction_type: sex - exposure: monkeypox \ No newline at end of file + exposure: monkeypox + start_time: 1 diff --git a/titan/settings/nyc-monkeypox/demographics.yml b/titan/settings/nyc-monkeypox/demographics.yml index 80a883cc..8e03e6df 100644 --- a/titan/settings/nyc-monkeypox/demographics.yml +++ b/titan/settings/nyc-monkeypox/demographics.yml @@ -14,16 +14,11 @@ demographics: prob: 0.275 Casual: prob: 0.409 - prep: - discontinue: 0.034 # calibration - adherence: 0.565 - cap: 0.028 - init: 0.252 drug_type: None: ppl: 1.0 monkeypox: - init: 0.355 + init: 0.0 dx: init: 0.0 prob: 0.198 @@ -73,7 +68,7 @@ demographics: None: ppl: 1.0 monkeypox: - init: 0.20 + init: 0.0006818 dx: init: 0.920 prob: 0.055 @@ -123,7 +118,7 @@ demographics: None: ppl: 1.0 monkeypox: - init: 0.111 + init: 0.0 dx: init: 0.920 prob: 0.155 diff --git a/titan/settings/nyc-monkeypox/model.yml b/titan/settings/nyc-monkeypox/model.yml index 7f0ad239..fb0a7af0 100644 --- a/titan/settings/nyc-monkeypox/model.yml +++ b/titan/settings/nyc-monkeypox/model.yml @@ -1,6 +1,6 @@ features: exit_enter: true - agent_zero: true + agent_zero: false assort_mix: true vaccine: true @@ -9,8 +9,8 @@ outputs: - races model: - num_pop: 1719 + num_pop: 171990 time: - num_steps: 12 + num_steps: 365 steps_per_year: 365 burn_steps: 0 From 876bdb58a2cbadd79eaedbb61e1a6955455b3cb9 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 9 Sep 2022 10:09:40 -0400 Subject: [PATCH 20/23] test(monkeypox): add tests for monkeypox init_agent and transmission prob for insertive/versatile relationship --- tests/exposures/monkeypox_test.py | 34 +++++++++++++++++++++++++++++++ tests/params/basic.yml | 10 +++++++++ 2 files changed, 44 insertions(+) diff --git a/tests/exposures/monkeypox_test.py b/tests/exposures/monkeypox_test.py index 633f13cb..8fe5c380 100644 --- a/tests/exposures/monkeypox_test.py +++ b/tests/exposures/monkeypox_test.py @@ -41,6 +41,29 @@ def test_monkeypox_expose(make_model, make_agent): assert p.monkeypox.active +@pytest.mark.unit +def test_monkeypox_init(make_population, make_agent): + pop = make_population() + pop.pop_random = FakeRandom(0.0) + a = make_agent() + + time = pop.params.monkeypox.start_time - 1 + + a.monkeypox.init_agent(pop, time) + assert a.monkeypox.active is False + + time = pop.params.monkeypox.start_time + + a.monkeypox.init_agent(pop, time) + + assert a.monkeypox.active + assert a.monkeypox.time == time - pop.params.monkeypox.max_init_time + assert a.monkeypox.dx + assert a.monkeypox.dx_time == a.monkeypox.time + assert a in MonkeyPox.agents + assert MonkeyPox.dx_counts[a.race][a.sex_type] == 1 + + @pytest.mark.unit def test_monkeypox_convert(make_model, make_agent): model = make_model() @@ -82,6 +105,7 @@ def test_get_transmission_probability(make_model, make_agent): p = make_agent(race="white", SO="MSM") p.sex_role = "versatile" + # test versatile-versatile relationship p_sex = model.params.partnership.sex.acquisition.MSM.versatile scale = model.params.calibration.acquisition @@ -90,6 +114,16 @@ def test_get_transmission_probability(make_model, make_agent): a.monkeypox.time = model.time assert a.monkeypox.get_transmission_probability(model, "sex", p, 1) == p_sex * scale + # Not transmissable with injection + assert a.monkeypox.get_transmission_probability(model, "inj", p, 1) == 0.0 + + # test one vers, one receptive + a.sex_role = "receptive" + p_sex_ins = model.params.partnership.sex.acquisition.MSM.insertive + p_sex_rec = model.params.partnership.sex.acquisition.MSM.receptive + + assert a.hiv.get_transmission_probability(model, "sex", p, 1) == p_sex_ins * scale + assert p.hiv.get_transmission_probability(model, "sex", a, 1) == p_sex_rec * scale @pytest.mark.unit diff --git a/tests/params/basic.yml b/tests/params/basic.yml index ddd03f1e..586b8141 100644 --- a/tests/params/basic.yml +++ b/tests/params/basic.yml @@ -44,6 +44,11 @@ demographics: prob: 0.1 aids: init: 0.1 + monkeypox: + init: 0.1 + dx: + init: 0.1 + prob: 0.1 haart: init: 0.1 enroll: &haart_prob @@ -75,6 +80,11 @@ demographics: prob: 0.1 aids: init: 0.1 + monkeypox: + init: 0.1 + dx: + init: 0.1 + prob: 0.1 num_partners: Sex: dist_type: poisson From 5ea1dd2000111c4046f2f05febffeadccfe27018 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 9 Sep 2022 10:14:46 -0400 Subject: [PATCH 21/23] style: run black --- tests/exposures/monkeypox_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/exposures/monkeypox_test.py b/tests/exposures/monkeypox_test.py index 8fe5c380..e16bca97 100644 --- a/tests/exposures/monkeypox_test.py +++ b/tests/exposures/monkeypox_test.py @@ -115,7 +115,7 @@ def test_get_transmission_probability(make_model, make_agent): assert a.monkeypox.get_transmission_probability(model, "sex", p, 1) == p_sex * scale # Not transmissable with injection - assert a.monkeypox.get_transmission_probability(model, "inj", p, 1) == 0.0 + assert a.monkeypox.get_transmission_probability(model, "inj", p, 1) == 0.0 # test one vers, one receptive a.sex_role = "receptive" From 466defb6f7a863b72fa38285453dfcd6510ef1f4 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 9 Sep 2022 10:30:52 -0400 Subject: [PATCH 22/23] test(monkeypox): add monkeypox to integration test --- tests/integration_test.py | 1 + tests/params/basic_seeded.yml | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/tests/integration_test.py b/tests/integration_test.py index edf54a39..760f65c8 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -103,6 +103,7 @@ def test_model_reproducible(tmpdir): assert res_a[i]["pseed"] == res_b[i]["pseed"] assert res_a[i]["agents"] == res_b[i]["agents"] assert res_a[i]["hiv"] == res_b[i]["hiv"] + assert res_a[i]["monkeypox"] == res_b[i]["monkeypox"] assert res_a[i]["prep"] == res_b[i]["prep"] assert res_a[i]["death"] == res_b[i]["death"] assert res_a[i]["hiv_aids"] == res_b[i]["hiv_aids"] diff --git a/tests/params/basic_seeded.yml b/tests/params/basic_seeded.yml index ff1c4066..17830e70 100644 --- a/tests/params/basic_seeded.yml +++ b/tests/params/basic_seeded.yml @@ -39,6 +39,11 @@ demographics: prob: 0.1 aids: init: 0.1 + monkeypox: + init: 0.1 + dx: + init: 0.1 + prob: 0.1 haart: init: 0.1 enroll: @@ -59,6 +64,11 @@ demographics: prob: 0.1 aids: init: 0.1 + monkeypox: + init: 0.1 + dx: + init: 0.1 + prob: 0.1 num_partners: Sex: dist_type: poisson @@ -296,6 +306,7 @@ outputs: exposures: hiv: true + monkeypox: true knowledge: true features: From c44f6acf11c69f73050d891d92441eeab3e35838 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Fri, 9 Sep 2022 13:31:53 -0400 Subject: [PATCH 23/23] fix(monkeypox): remove redundent setting of sex role; remove todo statements --- titan/exposures/monkeypox.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/titan/exposures/monkeypox.py b/titan/exposures/monkeypox.py index b6430326..230fd843 100644 --- a/titan/exposures/monkeypox.py +++ b/titan/exposures/monkeypox.py @@ -221,11 +221,6 @@ def get_transmission_probability( # Logic for if needle or sex type interaction p: float - # get baseline probabilities - # TODO only sex? - partner_sex_role = partner.sex_role - - # TODO sex roles don't matter # get partner's sex role during acts partner_sex_role = "versatile"