From ceb4d8fddb1ab4c64cdcc6d97f83f307feac5982 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Apr 2024 19:57:34 +0100 Subject: [PATCH 01/53] Added symptomatic test probabilities. --- src/hivpy/data/hiv_testing.yaml | 9 +++++++++ src/hivpy/hiv_testing.py | 4 ++++ src/hivpy/hiv_testing_data.py | 4 ++++ 3 files changed, 17 insertions(+) diff --git a/src/hivpy/data/hiv_testing.yaml b/src/hivpy/data/hiv_testing.yaml index 27462313..5c4d0125 100644 --- a/src/hivpy/data/hiv_testing.yaml +++ b/src/hivpy/data/hiv_testing.yaml @@ -24,6 +24,11 @@ prob_anc_test_trim3: 0.1 # probability of being tested after delivery prob_test_postdel: 0.95 +# probability of being tested based on symptoms +prob_test_who4: 0.1 +prob_test_tb: 0.1 +prob_test_non_tb_who3: 0.05 + # affects testing probability based on number of partners test_targeting: Value: [1, 1.25, 1.5] @@ -38,3 +43,7 @@ date_test_rate_plateau: an_lin_incr_test: Value: [0.0001, 0.0005, 0.003, 0.01, 0.02, 0.03] Probability: [0.2, 0.25, 0.35, 0.1, 0.05, 0.05] + +# multiplier for symptomatic testing probability +self.incr_test_rate_sympt: + Value: [1.05, 1.10, 1.15, 1.20, 1.25] diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 3fc7e0af..d49af685 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -27,9 +27,13 @@ def __init__(self, **kwargs): self.prob_anc_test_trim3 = self.ht_data.prob_anc_test_trim3 self.prob_test_postdel = self.ht_data.prob_test_postdel + self.prob_test_who4 = self.ht_data.prob_test_who4 + self.prob_test_tb = self.ht_data.prob_test_tb + self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 self.test_targeting = self.ht_data.test_targeting.sample() self.date_test_rate_plateau = self.ht_data.date_test_rate_plateau.sample() self.an_lin_incr_test = self.ht_data.an_lin_incr_test.sample() + self.incr_test_rate_sympt = self.ht_data.incr_test_rate_sympt.sample() # eff_max_freq_testing is used as an index to pick the correct # minimum number of days to wait between tests from this list diff --git a/src/hivpy/hiv_testing_data.py b/src/hivpy/hiv_testing_data.py index 65f326aa..ba08e897 100644 --- a/src/hivpy/hiv_testing_data.py +++ b/src/hivpy/hiv_testing_data.py @@ -24,9 +24,13 @@ def __init__(self, filename): self.prob_anc_test_trim3 = self.data["prob_anc_test_trim3"] self.prob_test_postdel = self.data["prob_test_postdel"] + self.prob_test_who4 = self.data["prob_test_who4"] + self.prob_test_tb = self.data["prob_test_tb"] + self.prob_test_non_tb_who3 = self.data["prob_test_non_tb_who3"] self.test_targeting = self._get_discrete_dist("test_targeting") self.date_test_rate_plateau = self._get_discrete_dist("date_test_rate_plateau") self.an_lin_incr_test = self._get_discrete_dist("an_lin_incr_test") + self.incr_test_rate_sympt = self._get_discrete_dist("incr_test_rate_sympt") except KeyError as ke: print(ke.args) From 9c5134f6e157e2ed03e8b85deb7df9f50a364ed6 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Apr 2024 20:18:30 +0100 Subject: [PATCH 02/53] Updated relevant symptomatic testing columns. --- src/hivpy/column_names.py | 1 + src/hivpy/hiv_status.py | 6 +++--- src/hivpy/population.py | 1 + 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/hivpy/column_names.py b/src/hivpy/column_names.py index 6fcf1512..e4cf2bfb 100644 --- a/src/hivpy/column_names.py +++ b/src/hivpy/column_names.py @@ -51,6 +51,7 @@ STI = "sti" # bool: True if has sexually transmitted infection (non HIV), false o/w (TODO: DUMMIED) HARD_REACH = "hard_reach" # bool: True if person is reluctant to test for HIV (also affects PrEP and VMMC), but will still test if symptomatic or in antenatal care +TEST_MARK = "test_mark" # bool: True if a person has been marked for testing this time step EVER_TESTED = "ever_tested" # bool: True if person has ever been tested for HIV LAST_TEST_DATE = "last_test_date" # None | datetime.date: date of last HIV test NSTP_LAST_TEST = "nstp_last_test" # int: number of short term condomless sex partners since last test (DUMMY) diff --git a/src/hivpy/hiv_status.py b/src/hivpy/hiv_status.py index 83cfe429..fc7431d9 100644 --- a/src/hivpy/hiv_status.py +++ b/src/hivpy/hiv_status.py @@ -95,10 +95,10 @@ def init_HIV_variables(self, population: Population): population.init_variable(col.X4_VIRUS, False) population.init_variable(col.WHO3_EVENT, False) - population.init_variable(col.NON_TB_WHO3, False) - population.init_variable(col.TB, False) + population.init_variable(col.NON_TB_WHO3, False, n_prev_steps=1) + population.init_variable(col.TB, False, n_prev_steps=2) population.init_variable(col.TB_DIAGNOSED, False) - population.init_variable(col.ADC, False) + population.init_variable(col.ADC, False, n_prev_steps=1) population.init_variable(col.C_MENINGITIS, False) population.init_variable(col.C_MENINGITIS_DIAGNOSED, False) population.init_variable(col.SBI, False) diff --git a/src/hivpy/population.py b/src/hivpy/population.py index c603334e..51a922f0 100644 --- a/src/hivpy/population.py +++ b/src/hivpy/population.py @@ -74,6 +74,7 @@ def _create_population_data(self): self.init_variable(col.AGE_GROUP, 0) self.hiv_status.init_HIV_variables(self) + self.init_variable(col.TEST_MARK, False) self.init_variable(col.EVER_TESTED, False) self.init_variable(col.LAST_TEST_DATE, None) self.init_variable(col.NSTP_LAST_TEST, 0) From 05492efe560de19ff39aa388f920d68225eee287 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Apr 2024 20:24:39 +0100 Subject: [PATCH 03/53] Added calculations for symptomatic test probabilities and outcomes. --- src/hivpy/hiv_testing.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index d49af685..95ca7d1f 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,6 +102,41 @@ def update_hiv_testing(self, pop): sub_pop=prev_tested_population) self.apply_test_outcomes_to_sub_pop(pop, tested, prev_tested_population) + def calc_symptomatic_testing_outcomes(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1, size): + """ + Uses the symptomatic test probability for a given group + of symptoms to select individuals marked to be tested. + """ + prob_test = self.calc_symptomatic_prob_test(adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1) + # outcomes + r = rng.uniform(size=size) + marked = r < prob_test + + return marked + + def calc_symptomatic_prob_test(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1): + """ + Calculates the probability of being tested for a group + with specific symptoms and returns it. Presence of + an AIDS defining condition (ADC; any WHO4) in the previous time step, + tuberculosis (TB) in the previous two time steps, + and a non-TB WHO3 disease in the previous time step + all affect groupings and test probability. + """ + # assume asymptomatic by default + prob_test = 0 + # presence of ADC last time step + if adc_tm1: + prob_test = self.prob_test_who4 + # presence of TB last time step but not the time step before, no ADC last time step + elif tb_tm1 and not tb_tm2: + prob_test = self.prob_test_tb + # presence of a non-TB WHO3 disease last time step, no ADC or TB last time step + elif non_tb_who3_tm1 and not tb_tm1: + prob_test = self.prob_test_non_tb_who3 + + return prob_test + def apply_test_outcomes_to_sub_pop(self, pop, tested, sub_pop): """ Uses HIV testing outcomes for a given sub-population to From c290e2166667ca541642fbaeb95fc2432f9db1fc Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Apr 2024 20:30:45 +0100 Subject: [PATCH 04/53] Fixed typo. --- src/hivpy/data/hiv_testing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hivpy/data/hiv_testing.yaml b/src/hivpy/data/hiv_testing.yaml index 5c4d0125..545ac507 100644 --- a/src/hivpy/data/hiv_testing.yaml +++ b/src/hivpy/data/hiv_testing.yaml @@ -45,5 +45,5 @@ an_lin_incr_test: Probability: [0.2, 0.25, 0.35, 0.1, 0.05, 0.05] # multiplier for symptomatic testing probability -self.incr_test_rate_sympt: +incr_test_rate_sympt: Value: [1.05, 1.10, 1.15, 1.20, 1.25] From 08031e520fd2c64c53e951ab45ab346bbe259b8b Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 15 Apr 2024 20:32:57 +0100 Subject: [PATCH 05/53] Added function to mark symptomatic people for testing. --- src/hivpy/hiv_testing.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 95ca7d1f..5aaff855 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,6 +102,31 @@ def update_hiv_testing(self, pop): sub_pop=prev_tested_population) self.apply_test_outcomes_to_sub_pop(pop, tested, prev_tested_population) + def test_mark_symptomatic(self, pop): + """ + Mark symptomatic individuals to undergo testing at a later stage. + """ + # testing occurs after a certain year + if (pop.date.year >= self.date_start_testing): + + # update symptomatic test probabilities + if pop.date.year <= 2015: + self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) + self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) + self.prob_test_non_tb_who3 = min(0.7, self.prob_test_non_tb_who3 * self.incr_test_rate_sympt) + + # undiagnosed and untested population + not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), + (col.EVER_TESTED, op.eq, False)]) + # mark symptomatic people for testing + marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), + pop.get_variable(col.TB, dt=1), pop.get_variable(col.TB, dt=2), + pop.get_variable(col.NON_TB_WHO3, dt=1)], + self.calc_symptomatic_testing_outcomes, + sub_pop=not_diag_tested_pop) + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) + def calc_symptomatic_testing_outcomes(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1, size): """ Uses the symptomatic test probability for a given group From bdb02ff4bbc47ddd1adf90a67046db21c0782b89 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 7 May 2024 17:48:53 +0100 Subject: [PATCH 06/53] Added function to mark non-HIV symptomatic people for testing. --- src/hivpy/hiv_testing.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 5aaff855..68cccaff 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,9 +102,26 @@ def update_hiv_testing(self, pop): sub_pop=prev_tested_population) self.apply_test_outcomes_to_sub_pop(pop, tested, prev_tested_population) - def test_mark_symptomatic(self, pop): + def test_mark_non_hiv_symptomatic(self, pop): """ - Mark symptomatic individuals to undergo testing at a later stage. + Mark non-HIV symptomatic individuals to undergo testing this time step. + """ + # testing occurs after a certain year if there is no covid disruption + if ((pop.date.year >= self.date_start_testing) + & (not (self.covid_disrup_affected | self.testing_disrup_covid))): + + # undiagnosed (last time step) and untested population + not_diag_tested_pop = pop.get_sub_pop([(pop.get_variable(col.HIV_DIAGNOSED, dt=1), op.eq, False), + (col.EVER_TESTED, op.eq, False)]) + # mark people for testing + r = rng.uniform(size=len(not_diag_tested_pop)) + marked = r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2 + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) + + def test_mark_hiv_symptomatic(self, pop): + """ + Mark HIV symptomatic individuals to undergo testing this time step. """ # testing occurs after a certain year if (pop.date.year >= self.date_start_testing): @@ -118,7 +135,7 @@ def test_mark_symptomatic(self, pop): # undiagnosed and untested population not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.EVER_TESTED, op.eq, False)]) - # mark symptomatic people for testing + # mark people for testing marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), pop.get_variable(col.TB, dt=1), pop.get_variable(col.TB, dt=2), pop.get_variable(col.NON_TB_WHO3, dt=1)], From 81886522fc53eddec5f3c300e9c267931beabac7 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 7 May 2024 18:30:27 +0100 Subject: [PATCH 07/53] Extracted general population test marking into its own function. --- src/hivpy/hiv_testing.py | 60 +++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 68cccaff..be9c7939 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,6 +102,36 @@ def update_hiv_testing(self, pop): sub_pop=prev_tested_population) self.apply_test_outcomes_to_sub_pop(pop, tested, prev_tested_population) + def test_mark_general_pop(self, pop): + """ + Mark general population to undergo testing this time step. + """ + # testing occurs after a certain year if there is no covid disruption + if ((pop.date.year >= self.date_start_testing) + & (not (self.covid_disrup_affected | self.testing_disrup_covid))): + + # update testing probabilities + self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) + - self.date_start_testing) \ + * self.an_lin_incr_test + self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) + - self.date_start_testing) * self.an_lin_incr_test + + # general population ready for testing + testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False), + (col.AGE, op.ge, 15), + (col.HIV_STATUS, op.eq, False), + [(col.LAST_TEST_DATE, op.le, pop.date - + timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), + (col.LAST_TEST_DATE, op.eq, None)]]) + + if len(testing_population) > 0: + # mark people for testing + marked = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], + self.calc_testing_outcomes, sub_pop=testing_population) + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, testing_population) + def test_mark_non_hiv_symptomatic(self, pop): """ Mark non-HIV symptomatic individuals to undergo testing this time step. @@ -113,11 +143,13 @@ def test_mark_non_hiv_symptomatic(self, pop): # undiagnosed (last time step) and untested population not_diag_tested_pop = pop.get_sub_pop([(pop.get_variable(col.HIV_DIAGNOSED, dt=1), op.eq, False), (col.EVER_TESTED, op.eq, False)]) - # mark people for testing - r = rng.uniform(size=len(not_diag_tested_pop)) - marked = r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2 - # set outcomes - pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) + + if len(not_diag_tested_pop) > 0: + # mark people for testing + r = rng.uniform(size=len(not_diag_tested_pop)) + marked = r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2 + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) def test_mark_hiv_symptomatic(self, pop): """ @@ -135,14 +167,16 @@ def test_mark_hiv_symptomatic(self, pop): # undiagnosed and untested population not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.EVER_TESTED, op.eq, False)]) - # mark people for testing - marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), - pop.get_variable(col.TB, dt=1), pop.get_variable(col.TB, dt=2), - pop.get_variable(col.NON_TB_WHO3, dt=1)], - self.calc_symptomatic_testing_outcomes, - sub_pop=not_diag_tested_pop) - # set outcomes - pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) + + if len(not_diag_tested_pop) > 0: + # mark people for testing + marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), + pop.get_variable(col.TB, dt=1), pop.get_variable(col.TB, dt=2), + pop.get_variable(col.NON_TB_WHO3, dt=1)], + self.calc_symptomatic_testing_outcomes, + sub_pop=not_diag_tested_pop) + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) def calc_symptomatic_testing_outcomes(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1, size): """ From 4a845c86b07df935318dcf053c79a7af03260151 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 8 May 2024 16:20:36 +0100 Subject: [PATCH 08/53] Updated main HIV testing function to make use of test marks. --- src/hivpy/hiv_testing.py | 59 ++++++++++------------------------------ 1 file changed, 15 insertions(+), 44 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index be9c7939..463c32b8 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -59,48 +59,14 @@ def update_hiv_testing(self, pop): Update which individuals in the population have been tested. COVID disruption is factored in. """ - # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_start_testing) - & (not (self.covid_disrup_affected | self.testing_disrup_covid))): + # mark people for testing + self.test_mark_general_pop(pop) + self.test_mark_non_hiv_symptomatic(pop) + self.test_mark_hiv_symptomatic(pop) - # update testing probabilities - self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) \ - * self.an_lin_incr_test - self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) * self.an_lin_incr_test - - # get population ready for testing - testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False), - (col.AGE, op.ge, 15), - (col.HIV_STATUS, op.eq, False), - [(col.LAST_TEST_DATE, op.le, pop.date - - timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), - (col.LAST_TEST_DATE, op.eq, None)] - ]) - - # first time testers - untested_population = pop.apply_bool_mask(~pop.get_variable(col.EVER_TESTED, testing_population), - testing_population) - # repeat testers - prev_tested_population = pop.apply_bool_mask(pop.get_variable(col.EVER_TESTED, testing_population), - testing_population) - - if len(untested_population) > 0: - # test first time testers - tested = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], - self.calc_testing_outcomes, - sub_pop=untested_population) - # set outcomes - pop.set_present_variable(col.EVER_TESTED, tested, untested_population) - self.apply_test_outcomes_to_sub_pop(pop, tested, untested_population) - - if len(prev_tested_population) > 0: - # test repeat testers - tested = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], - self.calc_testing_outcomes, - sub_pop=prev_tested_population) - self.apply_test_outcomes_to_sub_pop(pop, tested, prev_tested_population) + # apply testing to marked population + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + self.apply_test_outcomes_to_sub_pop(pop, True, marked_population) def test_mark_general_pop(self, pop): """ @@ -170,9 +136,8 @@ def test_mark_hiv_symptomatic(self, pop): if len(not_diag_tested_pop) > 0: # mark people for testing - marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), - pop.get_variable(col.TB, dt=1), pop.get_variable(col.TB, dt=2), - pop.get_variable(col.NON_TB_WHO3, dt=1)], + marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), pop.get_variable(col.TB, dt=1), + pop.get_variable(col.TB, dt=2), pop.get_variable(col.NON_TB_WHO3, dt=1)], self.calc_symptomatic_testing_outcomes, sub_pop=not_diag_tested_pop) # set outcomes @@ -218,6 +183,9 @@ def apply_test_outcomes_to_sub_pop(self, pop, tested, sub_pop): Uses HIV testing outcomes for a given sub-population to set last test date and reset number of partners since last test. """ + # set ever tested + pop.set_present_variable(col.EVER_TESTED, True, + sub_pop=pop.apply_bool_mask(tested, sub_pop)) # set last test date pop.set_present_variable(col.LAST_TEST_DATE, pop.date, sub_pop=pop.apply_bool_mask(tested, sub_pop)) @@ -226,6 +194,9 @@ def apply_test_outcomes_to_sub_pop(self, pop, tested, sub_pop): sub_pop=pop.apply_bool_mask(tested, sub_pop)) pop.set_present_variable(col.NP_LAST_TEST, 0, sub_pop=pop.apply_bool_mask(tested, sub_pop)) + # exhaust test marks + pop.set_present_variable(col.TEST_MARK, False, + sub_pop=pop.apply_bool_mask(tested, sub_pop)) def update_sub_pop_test_date(self, pop, sub_pop, prob_test): """ From 9c59325dd29bccd6b414f70dd1a4316be39668f5 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 8 May 2024 16:27:35 +0100 Subject: [PATCH 09/53] Added probability of testing with non-HIV symptoms. --- src/hivpy/data/hiv_testing.yaml | 1 + src/hivpy/hiv_testing.py | 5 ++++- src/hivpy/hiv_testing_data.py | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hivpy/data/hiv_testing.yaml b/src/hivpy/data/hiv_testing.yaml index 545ac507..968b7635 100644 --- a/src/hivpy/data/hiv_testing.yaml +++ b/src/hivpy/data/hiv_testing.yaml @@ -25,6 +25,7 @@ prob_anc_test_trim3: 0.1 prob_test_postdel: 0.95 # probability of being tested based on symptoms +prob_test_non_hiv_symptoms: 0.01 prob_test_who4: 0.1 prob_test_tb: 0.1 prob_test_non_tb_who3: 0.05 diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 463c32b8..0ffac376 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -27,6 +27,7 @@ def __init__(self, **kwargs): self.prob_anc_test_trim3 = self.ht_data.prob_anc_test_trim3 self.prob_test_postdel = self.ht_data.prob_test_postdel + self.prob_test_non_hiv_symptoms = self.ht_data.prob_test_non_hiv_symptoms self.prob_test_who4 = self.ht_data.prob_test_who4 self.prob_test_tb = self.ht_data.prob_test_tb self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 @@ -113,7 +114,9 @@ def test_mark_non_hiv_symptomatic(self, pop): if len(not_diag_tested_pop) > 0: # mark people for testing r = rng.uniform(size=len(not_diag_tested_pop)) - marked = r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2 + s = rng.uniform(size=len(not_diag_tested_pop)) + marked = ((r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2) & + (s < self.prob_test_non_hiv_symptoms)) # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) diff --git a/src/hivpy/hiv_testing_data.py b/src/hivpy/hiv_testing_data.py index ba08e897..f086e576 100644 --- a/src/hivpy/hiv_testing_data.py +++ b/src/hivpy/hiv_testing_data.py @@ -24,6 +24,7 @@ def __init__(self, filename): self.prob_anc_test_trim3 = self.data["prob_anc_test_trim3"] self.prob_test_postdel = self.data["prob_test_postdel"] + self.prob_test_non_hiv_symptoms = self.data["prob_test_non_hiv_symptoms"] self.prob_test_who4 = self.data["prob_test_who4"] self.prob_test_tb = self.data["prob_test_tb"] self.prob_test_non_tb_who3 = self.data["prob_test_non_tb_who3"] From b4a639f8a22d1fee9dfdc8ed20732a8aba03233a Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 8 May 2024 17:49:00 +0100 Subject: [PATCH 10/53] Bugfixes for unit tests (transform_group issue left unsolved; awaiting further discussion). --- src/hivpy/hiv_testing.py | 42 +++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 0ffac376..b7de6cfe 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -67,7 +67,7 @@ def update_hiv_testing(self, pop): # apply testing to marked population marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) - self.apply_test_outcomes_to_sub_pop(pop, True, marked_population) + self.apply_test_outcomes_to_sub_pop(pop, marked_population) def test_mark_general_pop(self, pop): """ @@ -90,7 +90,8 @@ def test_mark_general_pop(self, pop): (col.HIV_STATUS, op.eq, False), [(col.LAST_TEST_DATE, op.le, pop.date - timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), - (col.LAST_TEST_DATE, op.eq, None)]]) + (col.LAST_TEST_DATE, op.eq, None)], + (col.TEST_MARK, op.eq, False)]) if len(testing_population) > 0: # mark people for testing @@ -108,8 +109,9 @@ def test_mark_non_hiv_symptomatic(self, pop): & (not (self.covid_disrup_affected | self.testing_disrup_covid))): # undiagnosed (last time step) and untested population - not_diag_tested_pop = pop.get_sub_pop([(pop.get_variable(col.HIV_DIAGNOSED, dt=1), op.eq, False), - (col.EVER_TESTED, op.eq, False)]) + not_diag_tested_pop = pop.get_sub_pop([(pop.get_correct_column(col.HIV_DIAGNOSED, dt=1), op.eq, False), + (col.EVER_TESTED, op.eq, False), + (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: # mark people for testing @@ -135,12 +137,17 @@ def test_mark_hiv_symptomatic(self, pop): # undiagnosed and untested population not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), - (col.EVER_TESTED, op.eq, False)]) + (col.EVER_TESTED, op.eq, False), + (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: + # FIXME: can we bypass the param_list = list(map(lambda x: self.get_correct_column(x), param_list)) + # line in transform_group? this doesn't work with dt values other than 0 # mark people for testing - marked = pop.transform_group([pop.get_variable(col.ADC, dt=1), pop.get_variable(col.TB, dt=1), - pop.get_variable(col.TB, dt=2), pop.get_variable(col.NON_TB_WHO3, dt=1)], + marked = pop.transform_group([pop.get_correct_column(col.ADC, dt=1), + pop.get_correct_column(col.TB, dt=1), + pop.get_correct_column(col.TB, dt=2), + pop.get_correct_column(col.NON_TB_WHO3, dt=1)], self.calc_symptomatic_testing_outcomes, sub_pop=not_diag_tested_pop) # set outcomes @@ -181,25 +188,20 @@ def calc_symptomatic_prob_test(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1): return prob_test - def apply_test_outcomes_to_sub_pop(self, pop, tested, sub_pop): + def apply_test_outcomes_to_sub_pop(self, pop, sub_pop): """ - Uses HIV testing outcomes for a given sub-population to - set last test date and reset number of partners since last test. + Sets HIV testing outcomes for a given sub-population + and resets number of partners since last test. """ # set ever tested - pop.set_present_variable(col.EVER_TESTED, True, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + pop.set_present_variable(col.EVER_TESTED, True, sub_pop) # set last test date - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + pop.set_present_variable(col.LAST_TEST_DATE, pop.date, sub_pop) # "reset" dummy partner columns - pop.set_present_variable(col.NSTP_LAST_TEST, 0, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) - pop.set_present_variable(col.NP_LAST_TEST, 0, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + pop.set_present_variable(col.NSTP_LAST_TEST, 0, sub_pop) + pop.set_present_variable(col.NP_LAST_TEST, 0, sub_pop) # exhaust test marks - pop.set_present_variable(col.TEST_MARK, False, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + pop.set_present_variable(col.TEST_MARK, False, sub_pop) def update_sub_pop_test_date(self, pop, sub_pop, prob_test): """ From 0e33c594fe5ffd4e110d197af8126a74dcc01954 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 9 May 2024 11:30:02 +0100 Subject: [PATCH 11/53] Fixed transform_group column name issues for past time steps. --- src/hivpy/hiv_testing.py | 3 +-- src/hivpy/population.py | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index b7de6cfe..8d7d42c8 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -64,6 +64,7 @@ def update_hiv_testing(self, pop): self.test_mark_general_pop(pop) self.test_mark_non_hiv_symptomatic(pop) self.test_mark_hiv_symptomatic(pop) + # FIXME: add anc + vmmc test marking here too # apply testing to marked population marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) @@ -141,8 +142,6 @@ def test_mark_hiv_symptomatic(self, pop): (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: - # FIXME: can we bypass the param_list = list(map(lambda x: self.get_correct_column(x), param_list)) - # line in transform_group? this doesn't work with dt values other than 0 # mark people for testing marked = pop.transform_group([pop.get_correct_column(col.ADC, dt=1), pop.get_correct_column(col.TB, dt=1), diff --git a/src/hivpy/population.py b/src/hivpy/population.py index 51a922f0..033faa3f 100644 --- a/src/hivpy/population.py +++ b/src/hivpy/population.py @@ -241,9 +241,6 @@ def transform_group(self, param_list, func, use_size=True, sub_pop=None): If `sub_pop` is defined, then it acts only on the part of the dataframe defined by `data.loc[sub_pop]`. """ - # Use Dummy column to in order to enable transform method and avoid any risks to data - param_list = list(map(lambda x: self.get_correct_column(x), param_list)) - def general_func(g): if len(param_list) == 1: args = [g.name] @@ -258,6 +255,7 @@ def general_func(g): df = self.data.loc[sub_pop] else: df = self.data + # Use Dummy column to in order to enable transform method and avoid any risks to data return df.groupby(param_list)["Dummy"].transform(general_func) def evolve(self, time_step: timedelta): From e6b23abf12344d4a71064f33b66f0f9497bfce6e Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 9 May 2024 14:30:23 +0100 Subject: [PATCH 12/53] Function reordering for better clarity. --- src/hivpy/hiv_testing.py | 233 ++++++++++++++++++++------------------- 1 file changed, 118 insertions(+), 115 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 8d7d42c8..f5667a94 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -61,68 +61,16 @@ def update_hiv_testing(self, pop): COVID disruption is factored in. """ # mark people for testing - self.test_mark_general_pop(pop) - self.test_mark_non_hiv_symptomatic(pop) + # hiv symptomatic > non hiv symptomatic > vmmc > anc > self testing > general > prep self.test_mark_hiv_symptomatic(pop) - # FIXME: add anc + vmmc test marking here too + self.test_mark_non_hiv_symptomatic(pop) + # vmmc + anc + self.test_mark_general_pop(pop) # apply testing to marked population marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) self.apply_test_outcomes_to_sub_pop(pop, marked_population) - def test_mark_general_pop(self, pop): - """ - Mark general population to undergo testing this time step. - """ - # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_start_testing) - & (not (self.covid_disrup_affected | self.testing_disrup_covid))): - - # update testing probabilities - self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) \ - * self.an_lin_incr_test - self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) * self.an_lin_incr_test - - # general population ready for testing - testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False), - (col.AGE, op.ge, 15), - (col.HIV_STATUS, op.eq, False), - [(col.LAST_TEST_DATE, op.le, pop.date - - timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), - (col.LAST_TEST_DATE, op.eq, None)], - (col.TEST_MARK, op.eq, False)]) - - if len(testing_population) > 0: - # mark people for testing - marked = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], - self.calc_testing_outcomes, sub_pop=testing_population) - # set outcomes - pop.set_present_variable(col.TEST_MARK, marked, testing_population) - - def test_mark_non_hiv_symptomatic(self, pop): - """ - Mark non-HIV symptomatic individuals to undergo testing this time step. - """ - # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_start_testing) - & (not (self.covid_disrup_affected | self.testing_disrup_covid))): - - # undiagnosed (last time step) and untested population - not_diag_tested_pop = pop.get_sub_pop([(pop.get_correct_column(col.HIV_DIAGNOSED, dt=1), op.eq, False), - (col.EVER_TESTED, op.eq, False), - (col.TEST_MARK, op.eq, False)]) - - if len(not_diag_tested_pop) > 0: - # mark people for testing - r = rng.uniform(size=len(not_diag_tested_pop)) - s = rng.uniform(size=len(not_diag_tested_pop)) - marked = ((r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2) & - (s < self.prob_test_non_hiv_symptoms)) - # set outcomes - pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def test_mark_hiv_symptomatic(self, pop): """ Mark HIV symptomatic individuals to undergo testing this time step. @@ -187,30 +135,65 @@ def calc_symptomatic_prob_test(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1): return prob_test - def apply_test_outcomes_to_sub_pop(self, pop, sub_pop): + def test_mark_non_hiv_symptomatic(self, pop): """ - Sets HIV testing outcomes for a given sub-population - and resets number of partners since last test. + Mark non-HIV symptomatic individuals to undergo testing this time step. """ - # set ever tested - pop.set_present_variable(col.EVER_TESTED, True, sub_pop) - # set last test date - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, sub_pop) - # "reset" dummy partner columns - pop.set_present_variable(col.NSTP_LAST_TEST, 0, sub_pop) - pop.set_present_variable(col.NP_LAST_TEST, 0, sub_pop) - # exhaust test marks - pop.set_present_variable(col.TEST_MARK, False, sub_pop) + # testing occurs after a certain year if there is no covid disruption + if ((pop.date.year >= self.date_start_testing) + & (not (self.covid_disrup_affected | self.testing_disrup_covid))): - def update_sub_pop_test_date(self, pop, sub_pop, prob_test): + # undiagnosed (last time step) and untested population + not_diag_tested_pop = pop.get_sub_pop([(pop.get_correct_column(col.HIV_DIAGNOSED, dt=1), op.eq, False), + (col.EVER_TESTED, op.eq, False), + (col.TEST_MARK, op.eq, False)]) + + if len(not_diag_tested_pop) > 0: + # mark people for testing + r = rng.uniform(size=len(not_diag_tested_pop)) + s = rng.uniform(size=len(not_diag_tested_pop)) + marked = ((r < (self.prob_test_non_tb_who3 + self.prob_test_who4)/2) & + (s < self.prob_test_non_hiv_symptoms)) + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) + + # FIXME: perhaps this should be in the circumcision module + def update_vmmc_after_test(self, pop, time_step): """ - Update the last test date of a sub-population based on a given probability. + Update VMMC in individuals that tested HIV negative last time step. """ - if len(sub_pop) > 0: - r = rng.uniform(size=len(sub_pop)) - tested = r < prob_test - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + if pop.circumcision.circ_after_test: + # select uncircumcised men tested last timestep + tested_uncirc_male_pop = pop.get_sub_pop([(col.SEX, op.eq, SexType.Male), + (col.CIRCUMCISED, op.eq, False), + (col.HIV_DIAGNOSED, op.eq, False), + (col.LAST_TEST_DATE, op.eq, pop.date - time_step), + (col.HARD_REACH, op.eq, False), + (col.AGE, op.le, pop.circumcision.max_vmmc_age)]) + # continue if eligible men are present this timestep + if len(tested_uncirc_male_pop) > 0: + # calculate post-test vmmc outcomes + r = rng.uniform(size=len(tested_uncirc_male_pop)) + circumcision = r < pop.circumcision.prob_circ_after_test + # assign outcomes + pop.set_present_variable(col.CIRCUMCISED, circumcision, tested_uncirc_male_pop) + pop.set_present_variable(col.VMMC, circumcision, tested_uncirc_male_pop) + + def update_post_vmmc_testing(self, pop): + """ + Update HIV testing status after VMMC. + """ + # those that just got circumcised and weren't tested last time step get tested now + just_tested = pop.get_sub_pop(AND(COND(col.CIRCUMCISION_DATE, op.eq, pop.date), + OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - timedelta(days=90)), + COND(col.LAST_TEST_DATE, op.eq, None)))) + # correctly set up related columns + if len(just_tested) > 0: + pop.set_present_variable(col.EVER_TESTED, True, just_tested) + pop.set_present_variable(col.LAST_TEST_DATE, pop.date, just_tested) + # "reset" dummy partner columns + pop.set_present_variable(col.NSTP_LAST_TEST, 0, just_tested) + pop.set_present_variable(col.NP_LAST_TEST, 0, just_tested) def update_anc_hiv_testing(self, pop, time_step): """ @@ -260,42 +243,59 @@ def update_anc_hiv_testing(self, pop, time_step): pop.set_present_variable(col.NSTP_LAST_TEST, 0, just_tested) pop.set_present_variable(col.NP_LAST_TEST, 0, just_tested) - def update_vmmc_after_test(self, pop, time_step): + # FIXME: this function should likely be retired + def update_sub_pop_test_date(self, pop, sub_pop, prob_test): """ - Update VMMC in individuals that tested HIV negative last time step. + Update the last test date of a sub-population based on a given probability. """ - if pop.circumcision.circ_after_test: - # select uncircumcised men tested last timestep - tested_uncirc_male_pop = pop.get_sub_pop([(col.SEX, op.eq, SexType.Male), - (col.CIRCUMCISED, op.eq, False), - (col.HIV_DIAGNOSED, op.eq, False), - (col.LAST_TEST_DATE, op.eq, pop.date - time_step), - (col.HARD_REACH, op.eq, False), - (col.AGE, op.le, pop.circumcision.max_vmmc_age)]) - # continue if eligible men are present this timestep - if len(tested_uncirc_male_pop) > 0: - # calculate post-test vmmc outcomes - r = rng.uniform(size=len(tested_uncirc_male_pop)) - circumcision = r < pop.circumcision.prob_circ_after_test - # assign outcomes - pop.set_present_variable(col.CIRCUMCISED, circumcision, tested_uncirc_male_pop) - pop.set_present_variable(col.VMMC, circumcision, tested_uncirc_male_pop) + if len(sub_pop) > 0: + r = rng.uniform(size=len(sub_pop)) + tested = r < prob_test + pop.set_present_variable(col.LAST_TEST_DATE, pop.date, + sub_pop=pop.apply_bool_mask(tested, sub_pop)) - def update_post_vmmc_testing(self, pop): + def test_mark_general_pop(self, pop): """ - Update HIV testing status after VMMC. + Mark general population to undergo testing this time step. """ - # those that just got circumcised and weren't tested last time step get tested now - just_tested = pop.get_sub_pop(AND(COND(col.CIRCUMCISION_DATE, op.eq, pop.date), - OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - timedelta(days=90)), - COND(col.LAST_TEST_DATE, op.eq, None)))) - # correctly set up related columns - if len(just_tested) > 0: - pop.set_present_variable(col.EVER_TESTED, True, just_tested) - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, just_tested) - # "reset" dummy partner columns - pop.set_present_variable(col.NSTP_LAST_TEST, 0, just_tested) - pop.set_present_variable(col.NP_LAST_TEST, 0, just_tested) + # testing occurs after a certain year if there is no covid disruption + if ((pop.date.year >= self.date_start_testing) + & (not (self.covid_disrup_affected | self.testing_disrup_covid))): + + # update testing probabilities + self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) + - self.date_start_testing) \ + * self.an_lin_incr_test + self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) + - self.date_start_testing) * self.an_lin_incr_test + + # general population ready for testing + testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False), + (col.AGE, op.ge, 15), + (col.HIV_STATUS, op.eq, False), + [(col.LAST_TEST_DATE, op.le, pop.date - + timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), + (col.LAST_TEST_DATE, op.eq, None)], + (col.TEST_MARK, op.eq, False)]) + + if len(testing_population) > 0: + # mark people for testing + marked = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], + self.calc_testing_outcomes, sub_pop=testing_population) + # set outcomes + pop.set_present_variable(col.TEST_MARK, marked, testing_population) + + def calc_testing_outcomes(self, repeat_tester, np_last_test, nstp_last_test, size): + """ + Uses the HIV test probability for either + first-time or repeat testers to return testing outcomes. + """ + prob_test = self.calc_prob_test(repeat_tester, np_last_test, nstp_last_test) + # outcomes + r = rng.uniform(size=size) + tested = r < prob_test + + return tested def calc_prob_test(self, repeat_tester, np_last_test, nstp_last_test): """ @@ -322,14 +322,17 @@ def calc_prob_test(self, repeat_tester, np_last_test, nstp_last_test): return min(prob_test, 1) - def calc_testing_outcomes(self, repeat_tester, np_last_test, nstp_last_test, size): + def apply_test_outcomes_to_sub_pop(self, pop, sub_pop): """ - Uses the HIV test probability for either - first-time or repeat testers to return testing outcomes. + Sets HIV testing outcomes for a given sub-population + and resets number of partners since last test. """ - prob_test = self.calc_prob_test(repeat_tester, np_last_test, nstp_last_test) - # outcomes - r = rng.uniform(size=size) - tested = r < prob_test - - return tested + # set ever tested + pop.set_present_variable(col.EVER_TESTED, True, sub_pop) + # set last test date + pop.set_present_variable(col.LAST_TEST_DATE, pop.date, sub_pop) + # "reset" dummy partner columns + pop.set_present_variable(col.NSTP_LAST_TEST, 0, sub_pop) + pop.set_present_variable(col.NP_LAST_TEST, 0, sub_pop) + # exhaust test marks + pop.set_present_variable(col.TEST_MARK, False, sub_pop) From 6de1d5daec9950d347c0c41e35c1d7dca3a5cf31 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 May 2024 20:32:24 +0100 Subject: [PATCH 13/53] Added unit test for HIV symptomatic testing. --- src/tests/test_hiv_testing.py | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 1edf59d3..f023b567 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -23,6 +23,75 @@ def test_hiv_testing_covid(): assert sum(pop.get_variable(col.EVER_TESTED)) == 0 +def test_hiv_symptomatic_testing(): + + # build population + N = 100000 + pop = Population(size=N, start_date=date(2016, 1, 1)) + pop.data[col.HIV_DIAGNOSED] = False + pop.data[col.EVER_TESTED] = False + pop.data[col.LAST_TEST_DATE] = None + pop.data[pop.get_correct_column(col.ADC, dt=1)] = True + # fixing some values + pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.prob_test_who4 = 0.6 + pop.hiv_testing.prob_test_tb = 0.5 + pop.hiv_testing.prob_test_non_tb_who3 = 0.4 + pop.hiv_testing.covid_disrup_affected = False + pop.hiv_testing.testing_disrup_covid = False + + # evolve population + pop.hiv_testing.test_mark_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # get stats + tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) + prob_test = pop.hiv_testing.prob_test_who4 + mean = len(pop.data) * prob_test + stdev = sqrt(mean * (1 - prob_test)) + # check tested value is within 3 standard deviations + assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev + + # reset some columns + pop.data[col.EVER_TESTED] = False + pop.data[col.LAST_TEST_DATE] = None + pop.data[pop.get_correct_column(col.ADC, dt=1)] = False + pop.data[pop.get_correct_column(col.TB, dt=1)] = True + + # re-evolve population + pop.hiv_testing.test_mark_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # get stats + tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) + prob_test = pop.hiv_testing.prob_test_tb + mean = len(pop.data) * prob_test + stdev = sqrt(mean * (1 - prob_test)) + # check tested value is within 3 standard deviations + assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev + + # reset some columns + pop.data[col.EVER_TESTED] = False + pop.data[col.LAST_TEST_DATE] = None + pop.data[pop.get_correct_column(col.TB, dt=1)] = False + pop.data[pop.get_correct_column(col.NON_TB_WHO3, dt=1)] = True + + # re-evolve population + pop.hiv_testing.test_mark_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # get stats + tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) + prob_test = pop.hiv_testing.prob_test_non_tb_who3 + mean = len(pop.data) * prob_test + stdev = sqrt(mean * (1 - prob_test)) + # check tested value is within 3 standard deviations + assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev + + def test_general_testing_conditions(): # build population From 10e098bbaefef9b12077558067a10eb1f454146a Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 May 2024 20:42:52 +0100 Subject: [PATCH 14/53] Added unit test for non-HIV symptomatic testing. --- src/tests/test_hiv_testing.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index f023b567..99319120 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -92,6 +92,36 @@ def test_hiv_symptomatic_testing(): assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev +def test_non_hiv_symptomatic_testing(): + + # build population + N = 100000 + pop = Population(size=N, start_date=date(2010, 1, 1)) + pop.data[col.EVER_TESTED] = False + pop.data[col.LAST_TEST_DATE] = None + pop.data[pop.get_correct_column(col.HIV_DIAGNOSED, dt=1)] = False + # fixing some values + pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.prob_test_non_hiv_symptoms = 1 + pop.hiv_testing.prob_test_who4 = 0.6 + pop.hiv_testing.prob_test_non_tb_who3 = 0.4 + pop.hiv_testing.covid_disrup_affected = False + pop.hiv_testing.testing_disrup_covid = False + + # evolve population + pop.hiv_testing.test_mark_non_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # get stats + tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) + prob_test = (pop.hiv_testing.prob_test_non_tb_who3 + pop.hiv_testing.prob_test_who4)/2 + mean = len(pop.data) * prob_test + stdev = sqrt(mean * (1 - prob_test)) + # check tested value is within 3 standard deviations + assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev + + def test_general_testing_conditions(): # build population From 5666ccbece1e4e79ce62dde0b0ed00615cdca0e2 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 16 May 2024 21:12:32 +0100 Subject: [PATCH 15/53] Updated general HIV testing unit test. --- src/tests/test_hiv_testing.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 99319120..953333e1 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -128,12 +128,13 @@ def test_general_testing_conditions(): N = 100000 pop = Population(size=N, start_date=date(2010, 1, 1)) pop.data[col.AGE] = 20 + pop.data[col.HARD_REACH] = False pop.data[col.EVER_TESTED] = True pop.data[col.LAST_TEST_DATE] = date(2008, 1, 1) + pop.data[col.NP_LAST_TEST] = 1 # fixing some values pop.hiv_testing.date_start_testing = 2009 pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.init_rate_first_test = 0.1 pop.hiv_testing.date_test_rate_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False @@ -146,13 +147,23 @@ def test_general_testing_conditions(): pop.set_present_variable(col.HIV_STATUS, diagnosed) # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get people that were just tested tested_population = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) # check that no people just tested were already diagnosed with HIV assert (~pop.get_variable(col.HIV_STATUS, sub_pop=tested_population)).all() + # get stats + testing_population = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False)]) + prob_test = pop.hiv_testing.calc_prob_test(True, 1, 0) + mean = len(testing_population) * prob_test + stdev = sqrt(mean * (1 - prob_test)) + # check tested value is within 3 standard deviations + assert mean - 3 * stdev <= len(tested_population) <= mean + 3 * stdev + def test_first_time_testers(): From 207550e9c1b5d972b23e66f2db8ba8a875f46bc1 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 May 2024 17:32:51 +0100 Subject: [PATCH 16/53] Fixed ADC risk unit test. --- src/tests/test_sexual_behaviour.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/test_sexual_behaviour.py b/src/tests/test_sexual_behaviour.py index edf9fa63..67a0a176 100644 --- a/src/tests/test_sexual_behaviour.py +++ b/src/tests/test_sexual_behaviour.py @@ -234,7 +234,7 @@ def test_risk_adc(): init_HIV_idx = rng.integers(0, N, size=5) init_ADC_idx = init_HIV_idx[[0, 2, 4]] pop.data.loc[init_HIV_idx, col.HIV_STATUS] = True - pop.data.loc[init_ADC_idx, col.ADC] = True + pop.data.loc[init_ADC_idx, pop.get_correct_column(col.ADC, dt=0)] = True expected_risk = np.ones(N) expected_risk[init_ADC_idx] = 0.2 SBM = SexualBehaviourModule() @@ -246,7 +246,7 @@ def test_risk_adc(): add_HIV_idx = rng.integers(0, N, size=5) add_ADC_idx = add_HIV_idx[[0, 1, 2]] pop.data.loc[add_HIV_idx, col.HIV_STATUS] = True - pop.data.loc[add_ADC_idx, col.ADC] = True + pop.data.loc[add_ADC_idx, pop.get_correct_column(col.ADC, dt=0)] = True # Update risk factors SBM.update_sex_behaviour(pop) expected_risk[add_ADC_idx] = 0.2 From 6b579c45963ec0cabe60f55c2985596f36a2d971 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 May 2024 21:55:51 +0100 Subject: [PATCH 17/53] Moved post-test VMMC update function to circumcision module. --- src/hivpy/circumcision.py | 41 +++++++++++++++++++++++++++++---------- src/hivpy/hiv_testing.py | 24 +---------------------- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/hivpy/circumcision.py b/src/hivpy/circumcision.py index df3ee113..2558732b 100644 --- a/src/hivpy/circumcision.py +++ b/src/hivpy/circumcision.py @@ -159,7 +159,7 @@ def update_vmmc(self, pop, time_step): pop.data.loc[uncirc_male_population, col.VMMC] = circumcision # chance to get vmmc after a negative HIV test - pop.hiv_testing.update_vmmc_after_test(pop, time_step) + self.update_vmmc_after_test(pop, time_step) # newly circumcised males get the current date set as their circumcision date new_circ_males = pop.get_sub_pop([(col.CIRCUMCISED, op.eq, True), @@ -169,6 +169,18 @@ def update_vmmc(self, pop, time_step): # standard HIV testing after circumcision pop.hiv_testing.update_post_vmmc_testing(pop) + def calc_circ_outcomes(self, age_group, size): + """ + Uses the circumcision probability for a given + age group to return VMMC outcomes. + """ + prob_circ = self.calc_prob_circ(age_group) + # outcomes + r = rng.uniform(size=size) + circumcision = r < prob_circ + + return circumcision + def calc_prob_circ(self, age_group): """ Calculates the probability of individuals putting themselves forward @@ -207,14 +219,23 @@ def calc_prob_circ(self, age_group): return min(prob_circ, 1) - def calc_circ_outcomes(self, age_group, size): + def update_vmmc_after_test(self, pop, time_step): """ - Uses the circumcision probability for a given - age group to return VMMC outcomes. + Update VMMC in individuals that tested HIV negative last time step. """ - prob_circ = self.calc_prob_circ(age_group) - # outcomes - r = rng.uniform(size=size) - circumcision = r < prob_circ - - return circumcision + if self.circ_after_test: + # select uncircumcised men tested last timestep + tested_uncirc_male_pop = pop.get_sub_pop([(col.SEX, op.eq, SexType.Male), + (col.CIRCUMCISED, op.eq, False), + (col.HIV_DIAGNOSED, op.eq, False), + (col.LAST_TEST_DATE, op.eq, pop.date - time_step), + (col.HARD_REACH, op.eq, False), + (col.AGE, op.le, self.max_vmmc_age)]) + # continue if eligible men are present this timestep + if len(tested_uncirc_male_pop) > 0: + # calculate post-test vmmc outcomes + r = rng.uniform(size=len(tested_uncirc_male_pop)) + circumcision = r < self.prob_circ_after_test + # assign outcomes + pop.set_present_variable(col.CIRCUMCISED, circumcision, tested_uncirc_male_pop) + pop.set_present_variable(col.VMMC, circumcision, tested_uncirc_male_pop) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index f5667a94..b88367c1 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -3,7 +3,7 @@ import hivpy.column_names as col -from .common import AND, COND, OR, SexType, rng, timedelta +from .common import AND, COND, OR, rng, timedelta from .hiv_testing_data import HIVTestingData @@ -157,28 +157,6 @@ def test_mark_non_hiv_symptomatic(self, pop): # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - # FIXME: perhaps this should be in the circumcision module - def update_vmmc_after_test(self, pop, time_step): - """ - Update VMMC in individuals that tested HIV negative last time step. - """ - if pop.circumcision.circ_after_test: - # select uncircumcised men tested last timestep - tested_uncirc_male_pop = pop.get_sub_pop([(col.SEX, op.eq, SexType.Male), - (col.CIRCUMCISED, op.eq, False), - (col.HIV_DIAGNOSED, op.eq, False), - (col.LAST_TEST_DATE, op.eq, pop.date - time_step), - (col.HARD_REACH, op.eq, False), - (col.AGE, op.le, pop.circumcision.max_vmmc_age)]) - # continue if eligible men are present this timestep - if len(tested_uncirc_male_pop) > 0: - # calculate post-test vmmc outcomes - r = rng.uniform(size=len(tested_uncirc_male_pop)) - circumcision = r < pop.circumcision.prob_circ_after_test - # assign outcomes - pop.set_present_variable(col.CIRCUMCISED, circumcision, tested_uncirc_male_pop) - pop.set_present_variable(col.VMMC, circumcision, tested_uncirc_male_pop) - def update_post_vmmc_testing(self, pop): """ Update HIV testing status after VMMC. From 7a089dc96a8f743cc30c18ea240da566a85a590f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 May 2024 22:33:55 +0100 Subject: [PATCH 18/53] Updated post-VMMC HIV test marks. --- src/hivpy/circumcision.py | 3 --- src/hivpy/hiv_testing.py | 24 +++++++++++------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/hivpy/circumcision.py b/src/hivpy/circumcision.py index 2558732b..c65df306 100644 --- a/src/hivpy/circumcision.py +++ b/src/hivpy/circumcision.py @@ -166,9 +166,6 @@ def update_vmmc(self, pop, time_step): (col.CIRCUMCISION_DATE, op.eq, None)]) pop.data.loc[new_circ_males, col.CIRCUMCISION_DATE] = self.date - # standard HIV testing after circumcision - pop.hiv_testing.update_post_vmmc_testing(pop) - def calc_circ_outcomes(self, age_group, size): """ Uses the circumcision probability for a given diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index b88367c1..343d4724 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -64,7 +64,8 @@ def update_hiv_testing(self, pop): # hiv symptomatic > non hiv symptomatic > vmmc > anc > self testing > general > prep self.test_mark_hiv_symptomatic(pop) self.test_mark_non_hiv_symptomatic(pop) - # vmmc + anc + self.test_mark_vmmc(pop) + # self.test_mark_anc(pop) self.test_mark_general_pop(pop) # apply testing to marked population @@ -157,21 +158,18 @@ def test_mark_non_hiv_symptomatic(self, pop): # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def update_post_vmmc_testing(self, pop): + def test_mark_vmmc(self, pop): """ - Update HIV testing status after VMMC. + Mark recently circumcised individuals to undergo testing this time step. """ # those that just got circumcised and weren't tested last time step get tested now - just_tested = pop.get_sub_pop(AND(COND(col.CIRCUMCISION_DATE, op.eq, pop.date), - OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - timedelta(days=90)), - COND(col.LAST_TEST_DATE, op.eq, None)))) - # correctly set up related columns - if len(just_tested) > 0: - pop.set_present_variable(col.EVER_TESTED, True, just_tested) - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, just_tested) - # "reset" dummy partner columns - pop.set_present_variable(col.NSTP_LAST_TEST, 0, just_tested) - pop.set_present_variable(col.NP_LAST_TEST, 0, just_tested) + # FIXME: should the actual time step be used here instead of a flat 90 days? + testing_population = pop.get_sub_pop(AND(COND(col.CIRCUMCISION_DATE, op.eq, pop.date), + OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - timedelta(days=90)), + COND(col.LAST_TEST_DATE, op.eq, None)))) + # mark people for testing + if len(testing_population) > 0: + pop.set_present_variable(col.TEST_MARK, True, testing_population) def update_anc_hiv_testing(self, pop, time_step): """ From 71208f92d263312ed8fbaa47190a81e3e4c9b5df Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 17 May 2024 22:34:58 +0100 Subject: [PATCH 19/53] Updated unit tests to account for post-VMMC testing. --- src/tests/test_circumcision.py | 3 +++ src/tests/test_hiv_testing.py | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/src/tests/test_circumcision.py b/src/tests/test_circumcision.py index 53554132..033d7467 100644 --- a/src/tests/test_circumcision.py +++ b/src/tests/test_circumcision.py @@ -451,6 +451,9 @@ def test_vmmc_testing(): # evolve population pop.circumcision.update_vmmc(pop, time_step) + pop.hiv_testing.test_mark_vmmc(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get stats no_tested = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 953333e1..b1071b3c 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -12,6 +12,8 @@ def test_hiv_testing_covid(): N = 100000 pop = Population(size=N, start_date=date(2010, 1, 1)) pop.data[col.AGE] = 20 + pop.data[col.CIRCUMCISED] = False + pop.data[col.CIRCUMCISION_DATE] = None # covid disruption is in place pop.hiv_testing.covid_disrup_affected = True pop.hiv_testing.testing_disrup_covid = True @@ -171,6 +173,10 @@ def test_first_time_testers(): N = 100000 pop = Population(size=N, start_date=date(2010, 1, 1)) pop.data[col.AGE] = 20 + pop.data[col.EVER_TESTED] = False + pop.data[col.LAST_TEST_DATE] = None + pop.data[col.CIRCUMCISED] = False + pop.data[col.CIRCUMCISION_DATE] = None # fixing some values pop.hiv_testing.date_start_testing = 2009 pop.hiv_testing.init_rate_first_test = 0.1 @@ -202,6 +208,8 @@ def test_repeat_testers(): pop.data[col.AGE] = 20 pop.data[col.EVER_TESTED] = True pop.data[col.LAST_TEST_DATE] = date(2008, 1, 1) + pop.data[col.CIRCUMCISED] = False + pop.data[col.CIRCUMCISION_DATE] = None # fixing some values pop.hiv_testing.date_start_testing = 2009 pop.hiv_testing.eff_max_freq_testing = 1 @@ -274,6 +282,8 @@ def test_max_frequency_testing(): pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=pop.hiv_testing.days_to_wait[index]-30) pop.data[col.NP_LAST_TEST] = 1 pop.data[col.NSTP_LAST_TEST] = 1 + pop.data[col.CIRCUMCISED] = False + pop.data[col.CIRCUMCISION_DATE] = None # fixing some values pop.hiv_testing.date_start_testing = 2009 pop.hiv_testing.eff_max_freq_testing = index From d12439563ec8236e6274e3e35c889330e18cf58d Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 20 May 2024 13:34:53 +0100 Subject: [PATCH 20/53] Updated call ordering of ANC HIV test marks. --- src/hivpy/hiv_testing.py | 13 +++++-------- src/hivpy/population.py | 4 ++-- src/hivpy/pregnancy.py | 13 ++++--------- 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 343d4724..8568da89 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -55,7 +55,7 @@ def __init__(self, **kwargs): self.covid_disrup_affected = False self.testing_disrup_covid = False - def update_hiv_testing(self, pop): + def update_hiv_testing(self, pop, time_step: timedelta): """ Update which individuals in the population have been tested. COVID disruption is factored in. @@ -65,7 +65,7 @@ def update_hiv_testing(self, pop): self.test_mark_hiv_symptomatic(pop) self.test_mark_non_hiv_symptomatic(pop) self.test_mark_vmmc(pop) - # self.test_mark_anc(pop) + self.test_mark_anc(pop, time_step) self.test_mark_general_pop(pop) # apply testing to marked population @@ -171,7 +171,7 @@ def test_mark_vmmc(self, pop): if len(testing_population) > 0: pop.set_present_variable(col.TEST_MARK, True, testing_population) - def update_anc_hiv_testing(self, pop, time_step): + def test_mark_anc(self, pop, time_step): """ Update which pregnant women are tested while in antenatal care. COVID disruption is factored in. @@ -212,12 +212,9 @@ def update_anc_hiv_testing(self, pop, time_step): # get population tested this time step just_tested = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) - # correctly set up related columns + # mark people for testing if len(just_tested) > 0: - pop.set_present_variable(col.EVER_TESTED, True, just_tested) - # "reset" dummy partner columns - pop.set_present_variable(col.NSTP_LAST_TEST, 0, just_tested) - pop.set_present_variable(col.NP_LAST_TEST, 0, just_tested) + pop.set_present_variable(col.TEST_MARK, True, just_tested) # FIXME: this function should likely be retired def update_sub_pop_test_date(self, pop, sub_pop, prob_test): diff --git a/src/hivpy/population.py b/src/hivpy/population.py index 033faa3f..60e3578a 100644 --- a/src/hivpy/population.py +++ b/src/hivpy/population.py @@ -278,12 +278,12 @@ def evolve(self, time_step: timedelta): self.circumcision.update_vmmc(self, time_step) # Get the number of sexual partners this time step self.sexual_behaviour.update_sex_behaviour(self) - self.pregnancy.update_pregnancy(self, time_step) + self.pregnancy.update_pregnancy(self) # If HIV has been introduced, then run HIV relevant code if self.HIV_introduced: self.hiv_status.update_HIV_status(self) - self.hiv_testing.update_hiv_testing(self) + self.hiv_testing.update_hiv_testing(self, time_step) HIV_deaths = self.hiv_status.HIV_related_disease_risk(self, time_step) n_deaths = n_deaths + sum(HIV_deaths) if (n_deaths and self.apply_death): diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 8efd411f..18908752 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -104,7 +104,7 @@ def calc_init_num_children_outcomes(self, age_group, size): outcomes = self.init_num_children_distributions[index].sample(size) return outcomes - def update_pregnancy(self, pop: Population, time_step: timedelta): + def update_pregnancy(self, pop: Population): """ Monitor pregnancies and model childbirth. """ @@ -121,9 +121,7 @@ def update_pregnancy(self, pop: Population, time_step: timedelta): (col.NUM_CHILDREN, op.lt, self.max_children), [(col.NUM_PARTNERS, op.gt, 0), (col.LONG_TERM_PARTNER, op.eq, True)], [(col.LAST_PREGNANCY_DATE, op.eq, None), - (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=450)) - ] - ]) + (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=450))]]) # continue if there are women who can become pregnant in this time step if len(can_get_pregnant) > 0: @@ -144,11 +142,11 @@ def update_pregnancy(self, pop: Population, time_step: timedelta): pop.date, pop.apply_bool_mask(pregnancy, can_get_pregnant)) - self.update_antenatal_care(pop, time_step) + self.update_antenatal_care(pop) self.update_births(pop) self.update_want_no_children(pop) - def update_antenatal_care(self, pop: Population, time_step: timedelta): + def update_antenatal_care(self, pop: Population): """ Determine who is in antenatal care and receiving prevention of mother to child transmission care. @@ -164,9 +162,6 @@ def update_antenatal_care(self, pop: Population, time_step: timedelta): anc = r < self.prob_anc pop.set_present_variable(col.ANC, anc, pregnant_population) - # anc testing - pop.hiv_testing.update_anc_hiv_testing(pop, time_step) - # FIXME: this should probably only be applied to HIV diagnosed individuals? # If date is after introduction of prevention of mother to child transmission if pop.date.year >= self.date_pmtct: From 48a1c2285c12ba378d75b250e2802ffe36b14f5e Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 20 May 2024 13:48:07 +0100 Subject: [PATCH 21/53] Updated unit tests to account for new ANC testing. --- src/tests/test_hiv_status.py | 4 +-- src/tests/test_hiv_testing.py | 12 ++++----- src/tests/test_pregnancy.py | 48 +++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 27 deletions(-) diff --git a/src/tests/test_hiv_status.py b/src/tests/test_hiv_status.py index 69b87ce3..1f233a59 100644 --- a/src/tests/test_hiv_status.py +++ b/src/tests/test_hiv_status.py @@ -356,10 +356,10 @@ def check_init_cd4_by_sex_age(pop, hivpos_subpop, hivneg_subpop, hiv_module, sex def test_naive_cd4_progression(): - N = 1000 + N = 10000 pop = Population(size=N, start_date=date(1989, 1, 1)) pop.data[col.AGE] = 35 - pop.data[col.HIV_STATUS] = [True, False] * 500 + pop.data[col.HIV_STATUS] = [True, False] * 5000 # Reset viral load for testing (original values affected by intro of HIV) pop.data[col.ART_NAIVE] = True hiv_module = pop.hiv_status diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index b1071b3c..0eec434d 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -19,7 +19,7 @@ def test_hiv_testing_covid(): pop.hiv_testing.testing_disrup_covid = True # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # check that nobody was tested assert sum(pop.get_variable(col.EVER_TESTED)) == 0 @@ -187,7 +187,7 @@ def test_first_time_testers(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # get stats testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False)]) @@ -220,7 +220,7 @@ def test_repeat_testers(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # get stats testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False)]) @@ -257,7 +257,7 @@ def test_partner_reset_after_test(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # get people that were just tested tested_population = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) @@ -295,14 +295,14 @@ def test_max_frequency_testing(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # check that nobody has just been tested assert (pop.get_variable(col.LAST_TEST_DATE) != pop.date).all() # move date forward and evolve again pop.date += timedelta(days=30) - pop.hiv_testing.update_hiv_testing(pop) + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # check that everyone has just been tested assert (pop.get_variable(col.LAST_TEST_DATE) == pop.date).all() diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index 83a341bf..6fe32636 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -74,7 +74,7 @@ def test_ltp_preg(): pop.data[col.NUM_CHILDREN] = 0 pop.pregnancy.prob_pregnancy_base = 0.1 pop.pregnancy.init_fertility(pop) - pop.pregnancy.update_pregnancy(pop, timedelta(days=90)) + pop.pregnancy.update_pregnancy(pop) # age restrictions on those who can get pregnant if age < 15: @@ -119,7 +119,7 @@ def test_stp_preg(): pop.data[col.WANT_NO_CHILDREN] = False pop.pregnancy.prob_pregnancy_base = 0.1 pop.pregnancy.init_fertility(pop) - pop.pregnancy.update_pregnancy(pop, timedelta(days=90)) + pop.pregnancy.update_pregnancy(pop) # get stats no_active_female = sum((~pop.data[col.LOW_FERTILITY]) @@ -153,11 +153,11 @@ def test_childbirth(): # evolve population for _ in range(0, ceil(timedelta(days=270)/time_step)): # advance pregnancy - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # final advancement into childbirth - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # check that nobody is pregnant anymore assert (~pop.data[col.PREGNANT]).all() @@ -165,12 +165,12 @@ def test_childbirth(): assert (pop.data[col.NUM_CHILDREN] == 1).all() # test the pregnancy pause period - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # check that there are still no pregnancies assert (~pop.data[col.PREGNANT]).all() - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # check that everyone is pregnant again assert pop.data[col.PREGNANT].all() @@ -199,11 +199,11 @@ def test_child_cap(): # get through pregnancy, childbirth, and pregnancy pause period for _ in range(0, ceil(timedelta(days=450)/time_step)): # advance pregnancy - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # past pregnancy pause period - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # check that there are no pregnancies due to reaching child cap assert (~pop.data[col.PREGNANT]).all() @@ -229,7 +229,7 @@ def test_want_no_children(): pop.data[col.WANT_NO_CHILDREN] = want_no_children_outcomes # advance pregnancy - pop.pregnancy.update_pregnancy(pop, timedelta(days=90)) + pop.pregnancy.update_pregnancy(pop) # get stats want_no_children = sum(pop.data[col.WANT_NO_CHILDREN]) @@ -270,7 +270,7 @@ def test_anc_and_pmtct(): pop.pregnancy.pmtct_inc_rate = 1 # advance pregnancy - pop.pregnancy.update_pregnancy(pop, timedelta(days=90)) + pop.pregnancy.update_pregnancy(pop) # get stats no_anc = sum(pop.data[col.ANC]) @@ -314,11 +314,18 @@ def test_anc_testing(): pop.pregnancy.prob_anc = 1 pop.pregnancy.rate_test_anc_inc = 1 + # get test outcomes + def update_anc_testing_outcomes(pop, time_step): + pop.hiv_testing.test_mark_anc(pop, time_step) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + # advance pregnancy to start of second trimester - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) for _ in range(0, ceil(timedelta(days=90)/time_step)): pop.date += time_step - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) + update_anc_testing_outcomes(pop, time_step) # store people not in anc not_inc_anc = pop.get_sub_pop([(col.ANC, op.eq, False)]) @@ -336,7 +343,8 @@ def test_anc_testing(): # advance pregnancy to start of third trimester for _ in range(0, ceil(timedelta(days=90)/time_step)): pop.date += time_step - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) + update_anc_testing_outcomes(pop, time_step) # get stats no_tested = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) @@ -349,7 +357,8 @@ def test_anc_testing(): # final advancement into childbirth for _ in range(0, ceil(timedelta(days=90)/time_step)): pop.date += time_step - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) + update_anc_testing_outcomes(pop, time_step) # get stats no_tested = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) @@ -361,7 +370,8 @@ def test_anc_testing(): # advance to post-delivery pop.date += time_step - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) + update_anc_testing_outcomes(pop, time_step) # get stats no_tested = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) @@ -372,8 +382,8 @@ def test_anc_testing(): assert mean - 3 * stdev <= no_tested <= mean + 3 * stdev # check that nobody not in ANC has been tested - assert len(pop.get_sub_pop_intersection(not_inc_anc, - pop.get_sub_pop([(col.EVER_TESTED, op.eq, True)]))) == 0 + assert len(pop.get_sub_pop_intersection( + not_inc_anc, pop.get_sub_pop([(col.EVER_TESTED, op.eq, True)]))) == 0 def test_infected_births(): @@ -399,11 +409,11 @@ def test_infected_births(): # evolve population for _ in range(0, ceil(timedelta(days=270)/time_step)): # advance pregnancy - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # final advancement into childbirth - pop.pregnancy.update_pregnancy(pop, time_step) + pop.pregnancy.update_pregnancy(pop) pop.date += time_step # get stats From 7109ddf15eb903f543f931725c4af23439309dd2 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 20 May 2024 13:53:46 +0100 Subject: [PATCH 22/53] Moved timing of ANC removal from people giving birth this time step. --- src/hivpy/hiv_testing.py | 2 ++ src/hivpy/pregnancy.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 8568da89..417886f3 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -202,6 +202,8 @@ def test_mark_anc(self, pop, time_step): (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=270))]) self.update_sub_pop_test_date(pop, third_trimester_pop, self.prob_anc_test_trim3) + # remove from antenatal care + pop.set_present_variable(col.ANC, False, third_trimester_pop) # get post-delivery population tested during the previous time step post_delivery_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 18908752..52005d1d 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -189,8 +189,6 @@ def update_births(self, pop: Population): if len(birthing_population) > 0: # remove pregnancy status pop.set_present_variable(col.PREGNANT, False, birthing_population) - # remove from antenatal care - pop.set_present_variable(col.ANC, False, birthing_population) # add to children pop.set_present_variable(col.NUM_CHILDREN, pop.get_variable(col.NUM_CHILDREN)+1, birthing_population) # birth with infected child From a40185027582d03de96ebef535c4ed0652d40e12 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 20 May 2024 14:24:14 +0100 Subject: [PATCH 23/53] Updated ANC test mark code for brevity. --- src/hivpy/hiv_testing.py | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 417886f3..1a4af065 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -173,7 +173,7 @@ def test_mark_vmmc(self, pop): def test_mark_anc(self, pop, time_step): """ - Update which pregnant women are tested while in antenatal care. + Mark which pregnant women are tested while in antenatal care. COVID disruption is factored in. """ # conduct up to three tests in anc during pregnancy if there is no covid disruption @@ -185,7 +185,7 @@ def test_mark_anc(self, pop, time_step): - timedelta(days=90)), (col.LAST_PREGNANCY_DATE, op.gt, pop.date - (timedelta(days=90) + time_step))]) - self.update_sub_pop_test_date(pop, first_trimester_pop, self.prob_anc_test_trim1) + self.update_sub_pop_test_mark(pop, first_trimester_pop, self.prob_anc_test_trim1) # get population at the end of the second trimester second_trimester_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), @@ -194,14 +194,14 @@ def test_mark_anc(self, pop, time_step): - timedelta(days=180)), (col.LAST_PREGNANCY_DATE, op.gt, pop.date - (timedelta(days=180) + time_step))]) - self.update_sub_pop_test_date(pop, second_trimester_pop, self.prob_anc_test_trim2) + self.update_sub_pop_test_mark(pop, second_trimester_pop, self.prob_anc_test_trim2) # get population at the end of the third trimester third_trimester_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), (col.ANC, op.eq, True), (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=270))]) - self.update_sub_pop_test_date(pop, third_trimester_pop, self.prob_anc_test_trim3) + self.update_sub_pop_test_mark(pop, third_trimester_pop, self.prob_anc_test_trim3) # remove from antenatal care pop.set_present_variable(col.ANC, False, third_trimester_pop) @@ -210,24 +210,18 @@ def test_mark_anc(self, pop, time_step): (col.LAST_TEST_DATE, op.eq, pop.date - time_step), (col.LAST_PREGNANCY_DATE, op.eq, pop.date - (timedelta(days=270) + time_step))]) - self.update_sub_pop_test_date(pop, post_delivery_pop, self.prob_test_postdel) + self.update_sub_pop_test_mark(pop, post_delivery_pop, self.prob_test_postdel) - # get population tested this time step - just_tested = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) - # mark people for testing - if len(just_tested) > 0: - pop.set_present_variable(col.TEST_MARK, True, just_tested) - - # FIXME: this function should likely be retired - def update_sub_pop_test_date(self, pop, sub_pop, prob_test): + def update_sub_pop_test_mark(self, pop, sub_pop, prob_test): """ - Update the last test date of a sub-population based on a given probability. + Update the test mark of a sub-population based on a given probability. """ if len(sub_pop) > 0: + # mark people for testing r = rng.uniform(size=len(sub_pop)) - tested = r < prob_test - pop.set_present_variable(col.LAST_TEST_DATE, pop.date, - sub_pop=pop.apply_bool_mask(tested, sub_pop)) + marked = r < prob_test + # set outcomes + pop.set_present_variable(col.TEST_MARK, True, sub_pop=pop.apply_bool_mask(marked, sub_pop)) def test_mark_general_pop(self, pop): """ From fb807d9ffcec528a25ac21e1c457b254cc652627 Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 22 May 2024 13:46:59 +0100 Subject: [PATCH 24/53] Updated VMMC test marking to work with time step. --- src/hivpy/hiv_testing.py | 10 +++++----- src/tests/test_circumcision.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 1a4af065..fd5f7564 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -64,7 +64,7 @@ def update_hiv_testing(self, pop, time_step: timedelta): # hiv symptomatic > non hiv symptomatic > vmmc > anc > self testing > general > prep self.test_mark_hiv_symptomatic(pop) self.test_mark_non_hiv_symptomatic(pop) - self.test_mark_vmmc(pop) + self.test_mark_vmmc(pop, time_step) self.test_mark_anc(pop, time_step) self.test_mark_general_pop(pop) @@ -158,14 +158,14 @@ def test_mark_non_hiv_symptomatic(self, pop): # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def test_mark_vmmc(self, pop): + def test_mark_vmmc(self, pop, time_step): """ Mark recently circumcised individuals to undergo testing this time step. """ # those that just got circumcised and weren't tested last time step get tested now - # FIXME: should the actual time step be used here instead of a flat 90 days? - testing_population = pop.get_sub_pop(AND(COND(col.CIRCUMCISION_DATE, op.eq, pop.date), - OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - timedelta(days=90)), + testing_population = pop.get_sub_pop(AND(COND(col.VMMC, op.eq, True), + COND(col.CIRCUMCISION_DATE, op.eq, pop.date), + OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - time_step), COND(col.LAST_TEST_DATE, op.eq, None)))) # mark people for testing if len(testing_population) > 0: diff --git a/src/tests/test_circumcision.py b/src/tests/test_circumcision.py index 033d7467..848cb409 100644 --- a/src/tests/test_circumcision.py +++ b/src/tests/test_circumcision.py @@ -451,7 +451,7 @@ def test_vmmc_testing(): # evolve population pop.circumcision.update_vmmc(pop, time_step) - pop.hiv_testing.test_mark_vmmc(pop) + pop.hiv_testing.test_mark_vmmc(pop, time_step) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) From 3b1df5c66068a9dcb86aad7880de046cf9335981 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 May 2024 21:46:47 +0100 Subject: [PATCH 25/53] Added HIV testing tutorial. --- tutorials/hiv_testing.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tutorials/hiv_testing.md diff --git a/tutorials/hiv_testing.md b/tutorials/hiv_testing.md new file mode 100644 index 00000000..37627971 --- /dev/null +++ b/tutorials/hiv_testing.md @@ -0,0 +1,38 @@ +## HIV Testing Tutorial + +The HIV testing module tracks HIV symptomatic, non-HIV symptomatic, voluntary medical male circumcision (VMMC), antenatal care (ANC), and general testing in the population. The most relevant files are listed below: + +- `src/hivpy/hiv_testing.py` - The HIV testing module. +- `src/tests/test_hiv_testing.py` - Tests for the HIV testing module. +- `src/hivpy/data/hiv_testing.yaml` - HIV testing data and variables. +- `src/hivpy/hiv_testing_data.py` - A class for storing data loaded from `hiv_testing.yaml`. + +If there are any testing-related variables you would like to change before running your simulation, please change them in `hiv_testing.yaml`. + +### Module Overview + +When HIV testing is updated, people are marked for testing each time step, starting with the symptomatic. First, those exhibiting HIV symptoms have a chance to be scheduled for testing. New testing probabilities are calculated for each class of symptoms (WHO4, TB, non-TB WHO3) each time step until 2015. Note: If the simulation is started after 2015 (or if `date_start_testing` is set to be after 2015) these testing probabilities will not be updated and the code may not work as expected. Second, people with non-HIV symptoms have a chance to be scheduled for testing, borrowing WHO4 and non-TB WHO3 testing probabilities from the previous step, if their symptoms are concerning enough to warrant a test. + +Next, men undergoing VMMC this time step are tested and women in ANC this time step have a chance to be tested depending on which trimester they are in or whether they have given birth. + +Finally, the general population has a chance to be tested (assuming no COVID disruption). General testing probabilities change based on whether someone is a first time or repeat tester and these probabilities increase each time step. + +At the end, everyone scheduled to be tested has their testing information updated, and their test mark is removed. + +### HIV Testing Data Variables + +- *`date_start_testing`* - The year during which HIV testing begins (2009 by default). +- *`init_rate_first_test`* - The initial testing probability for first time testers. +- *`eff_max_freq_testing`* - An integer between 0-2 used to select the minimum number of days to wait between tests (365, 180, or 90). +- *`test_scenario`* - An integer that affects test targeting. Scenario 0 is the default behaviour, while scenario 1 increases effective test targeting. +- *`no_test_if_np0`* - A boolean that, if True, means people with no partners don't participate in general population testing. +- *`prob_anc_test_trim1`, `prob_anc_test_trim2`, `prob_anc_test_trim3`* - The probabilities of getting tested in antenatal care at the end of each trimester. +- *`prob_test_postdel`* - The probability of being tested after labour and delivery. +- *`prob_test_non_hiv_symptoms`* - The probability of being tested when exhibiting non-HIV symptoms. +- *`prob_test_who4`* - The probability of being tested when exhibiting WHO4 symptoms. +- *`prob_test_tb`* - The probability of being tested when exhibiting tuberculosis symptoms. +- *`prob_test_non_tb_who3`* - The probability of being tested when exhibiting WHO3 symptoms (excluding tuberculosis). +- *`test_targeting`* - A factor that affects an individual's general population testing probability based on their number of partners (multiplicative). +- *`date_test_rate_plateau`* - The year during which general population testing probability plateaus. +- *`an_lin_incr_test`* - The rate at which general population testing probability increases each time step (multiplicative). +- *`incr_test_rate_sympt`* - The rate at which HIV symptomatic testing probability increases each time step before 2015 (multiplicative). From 5d5514598ef960d6c680459ef8db5fef6ce1d2cd Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 May 2024 21:48:12 +0100 Subject: [PATCH 26/53] Added new tutorial to general and updated other tutorials for better consistency. --- tutorials/circumcision.md | 2 +- tutorials/general.md | 1 + tutorials/pregnancy.md | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tutorials/circumcision.md b/tutorials/circumcision.md index d04db4de..41a7a1a5 100644 --- a/tutorials/circumcision.md +++ b/tutorials/circumcision.md @@ -1,6 +1,6 @@ ## Circumcision Tutorial -Welcome to the circumcision module. This code deals with circumcision at birth and voluntary medical male circumcision (VMMC) intervention. The most relevant files are listed below: +The circumcision module tracks circumcision at birth and voluntary medical male circumcision (VMMC) intervention. The most relevant files are listed below: - `src/hivpy/circumcision.py` - The circumcision module. - `src/tests/test_circumcision.py` - Tests for the circumcision module. diff --git a/tutorials/general.md b/tutorials/general.md index 24a9ef94..b3b1bdd3 100644 --- a/tutorials/general.md +++ b/tutorials/general.md @@ -11,6 +11,7 @@ Welcome to the HIVpy package. Below is an overview of the repository contents: ### Module-Specific Tutorials - [Circumcision Tutorial](circumcision.md) +- [HIV Testing Tutorial](hiv_testing.md) - [Pregnancy Tutorial](pregnancy.md) - [Sexual Behaviour Tutorial](sexual_behaviour.md) diff --git a/tutorials/pregnancy.md b/tutorials/pregnancy.md index 75fc72a6..405d12aa 100644 --- a/tutorials/pregnancy.md +++ b/tutorials/pregnancy.md @@ -1,6 +1,6 @@ ## Pregnancy Tutorial -Welcome to the pregnancy module. This code deals with fertility, pregnancy, antenatal care (ANC), prevention of mother to child transmission care (PMTCT), birth, and number of children. The most relevant files are listed below: +The pregnancy module tracks fertility, pregnancy, antenatal care (ANC), prevention of mother to child transmission care (PMTCT), birth, and number of children. The most relevant files are listed below: - `src/hivpy/pregnancy.py` - The pregnancy module. - `src/tests/test_pregnancy.py` - Tests for the pregnancy module. From ec544522f631e009b1a67d46bc9ed96165536531 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 May 2024 21:53:31 +0100 Subject: [PATCH 27/53] Added some notes and documented a warning about testing HIV symptomatic people depending on simulation start date and the date at which testing begins. --- src/hivpy/hiv_testing.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index fd5f7564..4dcac3d1 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -15,7 +15,7 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "hiv_testing.yaml") as data_path: self.ht_data = HIVTestingData(data_path) - self.date_start_anc_testing = self.ht_data.date_start_anc_testing + self.date_start_anc_testing = self.ht_data.date_start_anc_testing # FIXME: currently unused, do we need this for anything? self.date_start_testing = self.ht_data.date_start_testing self.init_rate_first_test = self.ht_data.init_rate_first_test self.eff_max_freq_testing = self.ht_data.eff_max_freq_testing @@ -75,11 +75,16 @@ def update_hiv_testing(self, pop, time_step: timedelta): def test_mark_hiv_symptomatic(self, pop): """ Mark HIV symptomatic individuals to undergo testing this time step. + + Note: If the simulation is started after 2015 (or if date_start_testing + is set to be after 2015) the HIV symptomatic testing probabilities + will not be updated and this function may not work as expected. """ # testing occurs after a certain year if (pop.date.year >= self.date_start_testing): # update symptomatic test probabilities + # FIXME: where does this year come from? move to yaml later if pop.date.year <= 2015: self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) From 180e9070f0015ce867c967e60c632332aac80c22 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 May 2024 21:55:01 +0100 Subject: [PATCH 28/53] Comment style fix. --- src/hivpy/hiv_testing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 4dcac3d1..15cf1ae1 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -15,7 +15,8 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "hiv_testing.yaml") as data_path: self.ht_data = HIVTestingData(data_path) - self.date_start_anc_testing = self.ht_data.date_start_anc_testing # FIXME: currently unused, do we need this for anything? + # FIXME: date_start_anc_testing currently unused, do we need this for anything? + self.date_start_anc_testing = self.ht_data.date_start_anc_testing self.date_start_testing = self.ht_data.date_start_testing self.init_rate_first_test = self.ht_data.init_rate_first_test self.eff_max_freq_testing = self.ht_data.eff_max_freq_testing From be5ed07491e607208b6c26dc4bc971725f2cba04 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 May 2024 22:56:09 +0100 Subject: [PATCH 29/53] Fixed testing population selection criteria + updated unit tests. --- src/hivpy/hiv_testing.py | 31 ++++++++++++++++--------------- src/tests/test_hiv_testing.py | 18 ++++++++++-------- src/tests/test_pregnancy.py | 2 +- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 15cf1ae1..ed790aef 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -91,9 +91,8 @@ def test_mark_hiv_symptomatic(self, pop): self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) self.prob_test_non_tb_who3 = min(0.7, self.prob_test_non_tb_who3 * self.incr_test_rate_sympt) - # undiagnosed and untested population + # undiagnosed population not scheduled for testing not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), - (col.EVER_TESTED, op.eq, False), (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: @@ -150,9 +149,8 @@ def test_mark_non_hiv_symptomatic(self, pop): if ((pop.date.year >= self.date_start_testing) & (not (self.covid_disrup_affected | self.testing_disrup_covid))): - # undiagnosed (last time step) and untested population + # undiagnosed (last time step) population not scheduled for testing not_diag_tested_pop = pop.get_sub_pop([(pop.get_correct_column(col.HIV_DIAGNOSED, dt=1), op.eq, False), - (col.EVER_TESTED, op.eq, False), (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: @@ -185,7 +183,7 @@ def test_mark_anc(self, pop, time_step): # conduct up to three tests in anc during pregnancy if there is no covid disruption if not (self.covid_disrup_affected | self.testing_disrup_covid): # get population at the end of the first trimester - first_trimester_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), + first_trimester_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.ANC, op.eq, True), (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=90)), @@ -194,7 +192,7 @@ def test_mark_anc(self, pop, time_step): self.update_sub_pop_test_mark(pop, first_trimester_pop, self.prob_anc_test_trim1) # get population at the end of the second trimester - second_trimester_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), + second_trimester_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.ANC, op.eq, True), (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=180)), @@ -203,7 +201,7 @@ def test_mark_anc(self, pop, time_step): self.update_sub_pop_test_mark(pop, second_trimester_pop, self.prob_anc_test_trim2) # get population at the end of the third trimester - third_trimester_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), + third_trimester_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.ANC, op.eq, True), (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=270))]) @@ -212,7 +210,7 @@ def test_mark_anc(self, pop, time_step): pop.set_present_variable(col.ANC, False, third_trimester_pop) # get post-delivery population tested during the previous time step - post_delivery_pop = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False), + post_delivery_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.LAST_TEST_DATE, op.eq, pop.date - time_step), (col.LAST_PREGNANCY_DATE, op.eq, pop.date - (timedelta(days=270) + time_step))]) @@ -245,13 +243,16 @@ def test_mark_general_pop(self, pop): - self.date_start_testing) * self.an_lin_incr_test # general population ready for testing - testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False), - (col.AGE, op.ge, 15), - (col.HIV_STATUS, op.eq, False), - [(col.LAST_TEST_DATE, op.le, pop.date - - timedelta(days=self.days_to_wait[self.eff_max_freq_testing])), - (col.LAST_TEST_DATE, op.eq, None)], - (col.TEST_MARK, op.eq, False)]) + testing_population = pop.get_sub_pop(AND(COND(col.HARD_REACH, op.eq, False), + COND(col.AGE, op.ge, 15), + COND(col.HIV_DIAGNOSED, op.eq, False), + OR(COND(col.LAST_TEST_DATE, op.le, pop.date - + timedelta(days=self.days_to_wait[ + self.eff_max_freq_testing])), + COND(col.LAST_TEST_DATE, op.eq, None)), + COND(col.TEST_MARK, op.eq, False))) + + # FIXME: add sex workers to general population testing if len(testing_population) > 0: # mark people for testing diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 0eec434d..bc774100 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -146,7 +146,7 @@ def test_general_testing_conditions(): # diagnose roughly 20% of the population with HIV r = rng.uniform(size=len(pop.data)) diagnosed = r < 0.2 - pop.set_present_variable(col.HIV_STATUS, diagnosed) + pop.set_present_variable(col.HIV_DIAGNOSED, diagnosed) # evolve population pop.hiv_testing.test_mark_general_pop(pop) @@ -156,10 +156,10 @@ def test_general_testing_conditions(): # get people that were just tested tested_population = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) # check that no people just tested were already diagnosed with HIV - assert (~pop.get_variable(col.HIV_STATUS, sub_pop=tested_population)).all() + assert (~pop.get_variable(col.HIV_DIAGNOSED, sub_pop=tested_population)).all() # get stats - testing_population = pop.get_sub_pop([(col.HIV_STATUS, op.eq, False)]) + testing_population = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False)]) prob_test = pop.hiv_testing.calc_prob_test(True, 1, 0) mean = len(testing_population) * prob_test stdev = sqrt(mean * (1 - prob_test)) @@ -276,14 +276,12 @@ def test_max_frequency_testing(): N = 100 pop = Population(size=N, start_date=start_date) pop.data[col.AGE] = 20 - pop.data[col.HIV_STATUS] = False + pop.data[col.HIV_DIAGNOSED] = False pop.data[col.HARD_REACH] = False pop.data[col.EVER_TESTED] = True pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=pop.hiv_testing.days_to_wait[index]-30) pop.data[col.NP_LAST_TEST] = 1 pop.data[col.NSTP_LAST_TEST] = 1 - pop.data[col.CIRCUMCISED] = False - pop.data[col.CIRCUMCISION_DATE] = None # fixing some values pop.hiv_testing.date_start_testing = 2009 pop.hiv_testing.eff_max_freq_testing = index @@ -295,14 +293,18 @@ def test_max_frequency_testing(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # check that nobody has just been tested assert (pop.get_variable(col.LAST_TEST_DATE) != pop.date).all() # move date forward and evolve again pop.date += timedelta(days=30) - pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # check that everyone has just been tested assert (pop.get_variable(col.LAST_TEST_DATE) == pop.date).all() diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index 6fe32636..d4e363cb 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -302,7 +302,7 @@ def test_anc_testing(): pop.data[col.LONG_TERM_PARTNER] = True pop.data[col.LAST_PREGNANCY_DATE] = None pop.data[col.NUM_CHILDREN] = 0 - pop.data[col.HIV_STATUS] = False + pop.data[col.HIV_DIAGNOSED] = False pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.hiv_testing.covid_disrup_affected = False From a95d5ac6e2046b716c3687e6a45340f939cdb88f Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 May 2024 17:40:20 +0100 Subject: [PATCH 30/53] Added regular sex worker testing to general population testing. --- src/hivpy/hiv_testing.py | 30 +++++++++++++++++++++++++----- tutorials/hiv_testing.md | 2 +- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index ed790aef..fff73eb7 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -55,6 +55,8 @@ def __init__(self, **kwargs): # FIXME: move this to a yaml later self.covid_disrup_affected = False self.testing_disrup_covid = False + # sex workers regularly test every 6 months + self.sw_test_regularly = False def update_hiv_testing(self, pop, time_step: timedelta): """ @@ -170,7 +172,8 @@ def test_mark_vmmc(self, pop, time_step): testing_population = pop.get_sub_pop(AND(COND(col.VMMC, op.eq, True), COND(col.CIRCUMCISION_DATE, op.eq, pop.date), OR(COND(col.LAST_TEST_DATE, op.lt, pop.date - time_step), - COND(col.LAST_TEST_DATE, op.eq, None)))) + COND(col.LAST_TEST_DATE, op.eq, None)), + COND(col.TEST_MARK, op.eq, False))) # mark people for testing if len(testing_population) > 0: pop.set_present_variable(col.TEST_MARK, True, testing_population) @@ -235,10 +238,12 @@ def test_mark_general_pop(self, pop): if ((pop.date.year >= self.date_start_testing) & (not (self.covid_disrup_affected | self.testing_disrup_covid))): + # mark sex workers for testing first + self.test_mark_sex_workers(pop) + # update testing probabilities self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) \ - * self.an_lin_incr_test + - self.date_start_testing) * self.an_lin_incr_test self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) - self.date_start_testing) * self.an_lin_incr_test @@ -252,8 +257,6 @@ def test_mark_general_pop(self, pop): COND(col.LAST_TEST_DATE, op.eq, None)), COND(col.TEST_MARK, op.eq, False))) - # FIXME: add sex workers to general population testing - if len(testing_population) > 0: # mark people for testing marked = pop.transform_group([col.EVER_TESTED, col.NP_LAST_TEST, col.NSTP_LAST_TEST], @@ -261,6 +264,23 @@ def test_mark_general_pop(self, pop): # set outcomes pop.set_present_variable(col.TEST_MARK, marked, testing_population) + def test_mark_sex_workers(self, pop): + """ + Mark sex workers to undergo testing this time step. + """ + # testing occurs if sex workers regularly test every 6 months + if self.sw_test_regularly: + # sex workers ready for testing + testing_population = pop.get_sub_pop(AND(COND(col.SEX_WORKER, op.eq, True), + COND(col.HIV_DIAGNOSED, op.eq, False), + OR(COND(col.LAST_TEST_DATE, op.le, pop.date - + timedelta(days=180)), + COND(col.LAST_TEST_DATE, op.eq, None)), + COND(col.TEST_MARK, op.eq, False))) + # mark people for testing + if len(testing_population) > 0: + pop.set_present_variable(col.TEST_MARK, True, testing_population) + def calc_testing_outcomes(self, repeat_tester, np_last_test, nstp_last_test, size): """ Uses the HIV test probability for either diff --git a/tutorials/hiv_testing.md b/tutorials/hiv_testing.md index 37627971..f85d1cdd 100644 --- a/tutorials/hiv_testing.md +++ b/tutorials/hiv_testing.md @@ -15,7 +15,7 @@ When HIV testing is updated, people are marked for testing each time step, start Next, men undergoing VMMC this time step are tested and women in ANC this time step have a chance to be tested depending on which trimester they are in or whether they have given birth. -Finally, the general population has a chance to be tested (assuming no COVID disruption). General testing probabilities change based on whether someone is a first time or repeat tester and these probabilities increase each time step. +Finally, the general population has a chance to be tested (assuming no COVID disruption). As part of this, sex workers are also scheduled for testing if sex workers regularly test every 6 months. General testing probabilities change based on whether someone is a first time or repeat tester and these probabilities increase each time step. At the end, everyone scheduled to be tested has their testing information updated, and their test mark is removed. From 0d2c7aee4c15dd4b7e0f41507e162e7bc62a9167 Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 31 May 2024 18:25:07 +0100 Subject: [PATCH 31/53] Added regular sex worker testing unit test + minor adjustments to other tests. --- src/tests/test_hiv_testing.py | 43 ++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index bc774100..6ccd8239 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -11,9 +11,6 @@ def test_hiv_testing_covid(): # build population N = 100000 pop = Population(size=N, start_date=date(2010, 1, 1)) - pop.data[col.AGE] = 20 - pop.data[col.CIRCUMCISED] = False - pop.data[col.CIRCUMCISION_DATE] = None # covid disruption is in place pop.hiv_testing.covid_disrup_affected = True pop.hiv_testing.testing_disrup_covid = True @@ -124,6 +121,41 @@ def test_non_hiv_symptomatic_testing(): assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev +def test_general_sex_worker_testing(): + + # build population + N = 100 + start_date = date(2010, 1, 1) + pop = Population(size=N, start_date=start_date) + pop.data[col.AGE] = 20 + pop.data[col.SEX_WORKER] = True + pop.data[col.HIV_DIAGNOSED] = False + pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=150) + # fixing some values + pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.eff_max_freq_testing = 0 + pop.hiv_testing.sw_test_regularly = True + pop.hiv_testing.covid_disrup_affected = False + pop.hiv_testing.testing_disrup_covid = False + + # evolve population + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # check that nobody has just been tested + assert (pop.get_variable(col.LAST_TEST_DATE) != pop.date).all() + + # move date forward and evolve again + pop.date += timedelta(days=30) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + + # check that everyone has just been tested + assert (pop.get_variable(col.LAST_TEST_DATE) == pop.date).all() + + def test_general_testing_conditions(): # build population @@ -140,6 +172,7 @@ def test_general_testing_conditions(): pop.hiv_testing.date_test_rate_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False + pop.hiv_testing.sw_test_regularly = False pop.hiv_testing.covid_disrup_affected = False pop.hiv_testing.testing_disrup_covid = False @@ -183,6 +216,7 @@ def test_first_time_testers(): pop.hiv_testing.date_test_rate_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False + pop.hiv_testing.sw_test_regularly = False pop.hiv_testing.covid_disrup_affected = False pop.hiv_testing.testing_disrup_covid = False @@ -216,6 +250,7 @@ def test_repeat_testers(): pop.hiv_testing.date_test_rate_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False + pop.hiv_testing.sw_test_regularly = False pop.hiv_testing.covid_disrup_affected = False pop.hiv_testing.testing_disrup_covid = False @@ -253,6 +288,7 @@ def test_partner_reset_after_test(): pop.hiv_testing.date_test_rate_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False + pop.hiv_testing.sw_test_regularly = False pop.hiv_testing.covid_disrup_affected = False pop.hiv_testing.testing_disrup_covid = False @@ -289,6 +325,7 @@ def test_max_frequency_testing(): # guaranteed testing pop.hiv_testing.an_lin_incr_test = 1 pop.hiv_testing.no_test_if_np0 = False + pop.hiv_testing.sw_test_regularly = False pop.hiv_testing.covid_disrup_affected = False pop.hiv_testing.testing_disrup_covid = False From 964f2b42e43f0a18955ab11ba93522916b1aeafc Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 10 Jun 2024 17:22:22 +0100 Subject: [PATCH 32/53] Adding suggested ANC-related changes. --- src/hivpy/hiv_testing.py | 8 +++----- src/hivpy/population.py | 3 +++ src/hivpy/pregnancy.py | 13 +++++++++++++ src/tests/test_pregnancy.py | 1 + 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index fff73eb7..aa916aa3 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -64,11 +64,11 @@ def update_hiv_testing(self, pop, time_step: timedelta): COVID disruption is factored in. """ # mark people for testing - # hiv symptomatic > non hiv symptomatic > vmmc > anc > self testing > general > prep + # anc > hiv symptomatic > non hiv symptomatic > vmmc > self testing > general > prep + self.test_mark_anc(pop, time_step) self.test_mark_hiv_symptomatic(pop) self.test_mark_non_hiv_symptomatic(pop) self.test_mark_vmmc(pop, time_step) - self.test_mark_anc(pop, time_step) self.test_mark_general_pop(pop) # apply testing to marked population @@ -152,7 +152,7 @@ def test_mark_non_hiv_symptomatic(self, pop): & (not (self.covid_disrup_affected | self.testing_disrup_covid))): # undiagnosed (last time step) population not scheduled for testing - not_diag_tested_pop = pop.get_sub_pop([(pop.get_correct_column(col.HIV_DIAGNOSED, dt=1), op.eq, False), + not_diag_tested_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), (col.TEST_MARK, op.eq, False)]) if len(not_diag_tested_pop) > 0: @@ -209,8 +209,6 @@ def test_mark_anc(self, pop, time_step): (col.LAST_PREGNANCY_DATE, op.le, pop.date - timedelta(days=270))]) self.update_sub_pop_test_mark(pop, third_trimester_pop, self.prob_anc_test_trim3) - # remove from antenatal care - pop.set_present_variable(col.ANC, False, third_trimester_pop) # get post-delivery population tested during the previous time step post_delivery_pop = pop.get_sub_pop([(col.HIV_DIAGNOSED, op.eq, False), diff --git a/src/hivpy/population.py b/src/hivpy/population.py index 60e3578a..a76941d4 100644 --- a/src/hivpy/population.py +++ b/src/hivpy/population.py @@ -289,6 +289,9 @@ def evolve(self, time_step: timedelta): if (n_deaths and self.apply_death): self.drop_from_population(HIV_deaths) + # Some population cleanup + self.pregnancy.reset_anc_at_birth(self) + # Apply non-hiv deaths non_HIV_deaths = self.demographics.determine_deaths(self, time_step) n_deaths = n_deaths + sum(non_HIV_deaths) diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 52005d1d..9e7972bd 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -227,6 +227,19 @@ def update_want_no_children(self, pop: Population): # assign outcomes pop.set_present_variable(col.WANT_NO_CHILDREN, want_no_children, want_children_population) + def reset_anc_at_birth(self, pop: Population): + """ + Reset the ANC status of everyone giving birth this time step + if they are currently in ANC. + """ + # get population at the end of the third trimester + third_trimester_pop = pop.get_sub_pop([(col.ANC, op.eq, True), + (col.LAST_PREGNANCY_DATE, op.le, pop.date + - timedelta(days=270))]) + if len(third_trimester_pop) > 0: + # remove from antenatal care + pop.set_present_variable(col.ANC, False, third_trimester_pop) + def calc_prob_preg(self, age_group, ltp, stp, want_no_children): """ Calculates the probability of getting pregnant for a group diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index d4e363cb..9e8a1b6b 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -359,6 +359,7 @@ def update_anc_testing_outcomes(pop, time_step): pop.date += time_step pop.pregnancy.update_pregnancy(pop) update_anc_testing_outcomes(pop, time_step) + pop.pregnancy.reset_anc_at_birth(pop) # get stats no_tested = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) From 4d310ce6799b5187032ee9db77d16cd8c1bec692 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 10 Jun 2024 19:06:41 +0100 Subject: [PATCH 33/53] Updated testing date variables for better clarity. --- src/hivpy/data/hiv_testing.yaml | 12 +++++++----- src/hivpy/hiv_testing.py | 25 ++++++++++++------------ src/hivpy/hiv_testing_data.py | 5 +++-- src/hivpy/pregnancy.py | 1 + src/tests/test_hiv_testing.py | 34 ++++++++++++++++++++------------- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/src/hivpy/data/hiv_testing.yaml b/src/hivpy/data/hiv_testing.yaml index 968b7635..a9f9bbf6 100644 --- a/src/hivpy/data/hiv_testing.yaml +++ b/src/hivpy/data/hiv_testing.yaml @@ -1,6 +1,7 @@ -# years during which HIV testing begins -date_start_anc_testing: 2003.5 -date_start_testing: 2009 +# year during which HIV testing begins +date_start_testing: 2003.5 +# year after which rate of testing starts increasing +date_rate_testing_incr: 2009 # starting probability for first tests init_rate_first_test: 0 @@ -35,10 +36,11 @@ test_targeting: Value: [1, 1.25, 1.5] Probability: [0.2, 0.6, 0.2] -# year during which testing probability plateaus -date_test_rate_plateau: +# years during which testing probability plateaus +date_general_testing_plateau: Value: [2011.5, 2013.5, 2015.5, 2017.5, 2019.5] Probability: [0.1, 0.1, 0.2, 0.3, 0.3] +date_specific_testing_plateau: 2015 # multiplier for testing probability an_lin_incr_test: diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index aa916aa3..6948039c 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -15,9 +15,8 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "hiv_testing.yaml") as data_path: self.ht_data = HIVTestingData(data_path) - # FIXME: date_start_anc_testing currently unused, do we need this for anything? - self.date_start_anc_testing = self.ht_data.date_start_anc_testing self.date_start_testing = self.ht_data.date_start_testing + self.date_rate_testing_incr = self.ht_data.date_rate_testing_incr self.init_rate_first_test = self.ht_data.init_rate_first_test self.eff_max_freq_testing = self.ht_data.eff_max_freq_testing self.test_scenario = self.ht_data.test_scenario @@ -33,7 +32,8 @@ def __init__(self, **kwargs): self.prob_test_tb = self.ht_data.prob_test_tb self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 self.test_targeting = self.ht_data.test_targeting.sample() - self.date_test_rate_plateau = self.ht_data.date_test_rate_plateau.sample() + self.date_general_testing_plateau = self.ht_data.date_general_testing_plateau.sample() + self.date_specific_testing_plateau = self.ht_data.date_specific_testing_plateau self.an_lin_incr_test = self.ht_data.an_lin_incr_test.sample() self.incr_test_rate_sympt = self.ht_data.incr_test_rate_sympt.sample() @@ -79,16 +79,16 @@ def test_mark_hiv_symptomatic(self, pop): """ Mark HIV symptomatic individuals to undergo testing this time step. - Note: If the simulation is started after 2015 (or if date_start_testing - is set to be after 2015) the HIV symptomatic testing probabilities - will not be updated and this function may not work as expected. + Note: If the simulation is started after date_specific_testing_plateau + (or if date_start_testing is set to be after date_specific_testing_plateau) + the HIV symptomatic testing probabilities will not be updated + and this function may not work as expected. """ # testing occurs after a certain year - if (pop.date.year >= self.date_start_testing): + if (pop.date.year > self.date_start_testing): # update symptomatic test probabilities - # FIXME: where does this year come from? move to yaml later - if pop.date.year <= 2015: + if pop.date.year <= self.date_specific_testing_plateau: self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) self.prob_test_non_tb_who3 = min(0.7, self.prob_test_non_tb_who3 * self.incr_test_rate_sympt) @@ -240,10 +240,9 @@ def test_mark_general_pop(self, pop): self.test_mark_sex_workers(pop) # update testing probabilities - self.rate_first_test = self.init_rate_first_test + (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) * self.an_lin_incr_test - self.rate_rep_test = (min(pop.date.year, self.date_test_rate_plateau) - - self.date_start_testing) * self.an_lin_incr_test + self.rate_rep_test = (min(pop.date.year, self.date_general_testing_plateau) + - self.date_rate_testing_incr) * self.an_lin_incr_test + self.rate_first_test = self.init_rate_first_test + self.rate_rep_test # general population ready for testing testing_population = pop.get_sub_pop(AND(COND(col.HARD_REACH, op.eq, False), diff --git a/src/hivpy/hiv_testing_data.py b/src/hivpy/hiv_testing_data.py index f086e576..183191dc 100644 --- a/src/hivpy/hiv_testing_data.py +++ b/src/hivpy/hiv_testing_data.py @@ -12,8 +12,8 @@ def __init__(self, filename): super().__init__(filename) try: - self.date_start_anc_testing = self.data["date_start_anc_testing"] self.date_start_testing = self.data["date_start_testing"] + self.date_rate_testing_incr = self.data["date_rate_testing_incr"] self.init_rate_first_test = self.data["init_rate_first_test"] self.eff_max_freq_testing = self.data["eff_max_freq_testing"] self.test_scenario = self.data["test_scenario"] @@ -29,7 +29,8 @@ def __init__(self, filename): self.prob_test_tb = self.data["prob_test_tb"] self.prob_test_non_tb_who3 = self.data["prob_test_non_tb_who3"] self.test_targeting = self._get_discrete_dist("test_targeting") - self.date_test_rate_plateau = self._get_discrete_dist("date_test_rate_plateau") + self.date_general_testing_plateau = self._get_discrete_dist("date_general_testing_plateau") + self.date_specific_testing_plateau = self.data["date_specific_testing_plateau"] self.an_lin_incr_test = self._get_discrete_dist("an_lin_incr_test") self.incr_test_rate_sympt = self._get_discrete_dist("incr_test_rate_sympt") diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 9e7972bd..e8a16430 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -154,6 +154,7 @@ def update_antenatal_care(self, pop: Population): # get population that became pregnant this time step pregnant_population = pop.get_sub_pop([(col.PREGNANT, op.eq, True), (col.LAST_PREGNANCY_DATE, op.eq, pop.date)]) + # FIXME: should be affected by date_start_testing and date_specific_testing_plateau # update probability of antenatal care attendance self.prob_anc = min(max(self.prob_anc, 0.1) + self.rate_test_anc_inc, 0.975) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 6ccd8239..60b515f4 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -32,7 +32,8 @@ def test_hiv_symptomatic_testing(): pop.data[col.LAST_TEST_DATE] = None pop.data[pop.get_correct_column(col.ADC, dt=1)] = True # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_tb = 0.5 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -100,7 +101,8 @@ def test_non_hiv_symptomatic_testing(): pop.data[col.LAST_TEST_DATE] = None pop.data[pop.get_correct_column(col.HIV_DIAGNOSED, dt=1)] = False # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.prob_test_non_hiv_symptoms = 1 pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -132,7 +134,8 @@ def test_general_sex_worker_testing(): pop.data[col.HIV_DIAGNOSED] = False pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=150) # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.eff_max_freq_testing = 0 pop.hiv_testing.sw_test_regularly = True pop.hiv_testing.covid_disrup_affected = False @@ -167,9 +170,10 @@ def test_general_testing_conditions(): pop.data[col.LAST_TEST_DATE] = date(2008, 1, 1) pop.data[col.NP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_test_rate_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -211,9 +215,10 @@ def test_first_time_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_test_rate_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -245,9 +250,10 @@ def test_repeat_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_test_rate_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -282,10 +288,11 @@ def test_partner_reset_after_test(): pop.data[col.NP_LAST_TEST] = 2 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.eff_max_freq_testing = 1 pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_test_rate_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = 2015.5 pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -319,9 +326,10 @@ def test_max_frequency_testing(): pop.data[col.NP_LAST_TEST] = 1 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2009 + pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_rate_testing_incr = 2009 pop.hiv_testing.eff_max_freq_testing = index - pop.hiv_testing.date_test_rate_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = 2015.5 # guaranteed testing pop.hiv_testing.an_lin_incr_test = 1 pop.hiv_testing.no_test_if_np0 = False From be212d85a9b2f2fdff2618ff5f37ff63d3011e3a Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Thu, 13 Jun 2024 13:36:54 +0100 Subject: [PATCH 34/53] Use "targeted" instead of "specific" --- src/hivpy/data/hiv_testing.yaml | 2 +- src/hivpy/hiv_testing.py | 8 ++++---- src/hivpy/hiv_testing_data.py | 2 +- src/hivpy/pregnancy.py | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hivpy/data/hiv_testing.yaml b/src/hivpy/data/hiv_testing.yaml index a9f9bbf6..652b4819 100644 --- a/src/hivpy/data/hiv_testing.yaml +++ b/src/hivpy/data/hiv_testing.yaml @@ -40,7 +40,7 @@ test_targeting: date_general_testing_plateau: Value: [2011.5, 2013.5, 2015.5, 2017.5, 2019.5] Probability: [0.1, 0.1, 0.2, 0.3, 0.3] -date_specific_testing_plateau: 2015 +date_targeted_testing_plateau: 2015 # multiplier for testing probability an_lin_incr_test: diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 6948039c..f4c81688 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -33,7 +33,7 @@ def __init__(self, **kwargs): self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 self.test_targeting = self.ht_data.test_targeting.sample() self.date_general_testing_plateau = self.ht_data.date_general_testing_plateau.sample() - self.date_specific_testing_plateau = self.ht_data.date_specific_testing_plateau + self.date_targeted_testing_plateau = self.ht_data.date_targeted_testing_plateau self.an_lin_incr_test = self.ht_data.an_lin_incr_test.sample() self.incr_test_rate_sympt = self.ht_data.incr_test_rate_sympt.sample() @@ -79,8 +79,8 @@ def test_mark_hiv_symptomatic(self, pop): """ Mark HIV symptomatic individuals to undergo testing this time step. - Note: If the simulation is started after date_specific_testing_plateau - (or if date_start_testing is set to be after date_specific_testing_plateau) + Note: If the simulation is started after date_targeted_testing_plateau + (or if date_start_testing is set to be after date_targeted_testing_plateau) the HIV symptomatic testing probabilities will not be updated and this function may not work as expected. """ @@ -88,7 +88,7 @@ def test_mark_hiv_symptomatic(self, pop): if (pop.date.year > self.date_start_testing): # update symptomatic test probabilities - if pop.date.year <= self.date_specific_testing_plateau: + if pop.date.year <= self.date_targeted_testing_plateau: self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) self.prob_test_non_tb_who3 = min(0.7, self.prob_test_non_tb_who3 * self.incr_test_rate_sympt) diff --git a/src/hivpy/hiv_testing_data.py b/src/hivpy/hiv_testing_data.py index 183191dc..bffa4f66 100644 --- a/src/hivpy/hiv_testing_data.py +++ b/src/hivpy/hiv_testing_data.py @@ -30,7 +30,7 @@ def __init__(self, filename): self.prob_test_non_tb_who3 = self.data["prob_test_non_tb_who3"] self.test_targeting = self._get_discrete_dist("test_targeting") self.date_general_testing_plateau = self._get_discrete_dist("date_general_testing_plateau") - self.date_specific_testing_plateau = self.data["date_specific_testing_plateau"] + self.date_targeted_testing_plateau = self.data["date_targeted_testing_plateau"] self.an_lin_incr_test = self._get_discrete_dist("an_lin_incr_test") self.incr_test_rate_sympt = self._get_discrete_dist("incr_test_rate_sympt") diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index e8a16430..c48294ab 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -154,7 +154,7 @@ def update_antenatal_care(self, pop: Population): # get population that became pregnant this time step pregnant_population = pop.get_sub_pop([(col.PREGNANT, op.eq, True), (col.LAST_PREGNANCY_DATE, op.eq, pop.date)]) - # FIXME: should be affected by date_start_testing and date_specific_testing_plateau + # FIXME: should be affected by date_start_testing and date_targeted_testing_plateau # update probability of antenatal care attendance self.prob_anc = min(max(self.prob_anc, 0.1) + self.rate_test_anc_inc, 0.975) From 11e8e76821c843edbb8d3e69d3a6672b13244c7d Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Thu, 13 Jun 2024 13:37:18 +0100 Subject: [PATCH 35/53] Fix pregnancy test for want no children --- src/tests/test_pregnancy.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index 9e8a1b6b..7f3092ee 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -223,10 +223,8 @@ def test_want_no_children(): pop.data[col.NUM_CHILDREN] = 0 pop.pregnancy.prob_pregnancy_base = 0.1 - # make roughly half the population not want children - r = rng.uniform(size=len(pop.data)) - want_no_children_outcomes = r < 0.5 - pop.data[col.WANT_NO_CHILDREN] = want_no_children_outcomes + # make half the population not want children + pop.data[col.WANT_NO_CHILDREN] = [True, False] * (N//2) # advance pregnancy pop.pregnancy.update_pregnancy(pop) @@ -235,7 +233,7 @@ def test_want_no_children(): want_no_children = sum(pop.data[col.WANT_NO_CHILDREN]) no_pregnant = sum(pop.data[col.WANT_NO_CHILDREN] & pop.data[col.PREGNANT]) prob_preg = pop.pregnancy.prob_pregnancy_base * 2 * 0.2 - mean = want_no_children * prob_preg + mean = (N - want_no_children) * prob_preg stdev = sqrt(mean * (1 - prob_preg)) # check pregnancy value is within 3 standard deviations assert mean - 3 * stdev <= no_pregnant <= mean + 3 * stdev From 4df66588403044334db1a7a271f7138aee6b956d Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 19 Jun 2024 20:04:06 +0100 Subject: [PATCH 36/53] Updated symptomatic testing outcomes to use TB infection date. --- src/hivpy/hiv_status.py | 4 ++-- src/hivpy/hiv_testing.py | 34 ++++++++++++++++------------------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/hivpy/hiv_status.py b/src/hivpy/hiv_status.py index cb6f0f78..210c5317 100644 --- a/src/hivpy/hiv_status.py +++ b/src/hivpy/hiv_status.py @@ -95,8 +95,8 @@ def init_HIV_variables(self, population: Population): population.init_variable(col.X4_VIRUS, False) population.init_variable(col.WHO3_EVENT, False) - population.init_variable(col.NON_TB_WHO3, False, n_prev_steps=1) - population.init_variable(col.TB, False, n_prev_steps=2) + population.init_variable(col.NON_TB_WHO3, False) + population.init_variable(col.TB, False) population.init_variable(col.TB_DIAGNOSED, False) population.init_variable(col.TB_INFECTION_DATE, None) population.init_variable(col.ADC, False) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index f4c81688..ec82784b 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -87,6 +87,9 @@ def test_mark_hiv_symptomatic(self, pop): # testing occurs after a certain year if (pop.date.year > self.date_start_testing): + # date needed for a function passed to transform_group + self.date = pop.date + # update symptomatic test probabilities if pop.date.year <= self.date_targeted_testing_plateau: self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) @@ -99,46 +102,41 @@ def test_mark_hiv_symptomatic(self, pop): if len(not_diag_tested_pop) > 0: # mark people for testing - marked = pop.transform_group([pop.get_correct_column(col.ADC, dt=1), - pop.get_correct_column(col.TB, dt=1), - pop.get_correct_column(col.TB, dt=2), - pop.get_correct_column(col.NON_TB_WHO3, dt=1)], + marked = pop.transform_group([col.ADC, col.TB_INFECTION_DATE, col.NON_TB_WHO3], self.calc_symptomatic_testing_outcomes, sub_pop=not_diag_tested_pop) # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def calc_symptomatic_testing_outcomes(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1, size): + def calc_symptomatic_testing_outcomes(self, adc, tb_infection_date, non_tb_who3, size): """ Uses the symptomatic test probability for a given group of symptoms to select individuals marked to be tested. """ - prob_test = self.calc_symptomatic_prob_test(adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1) + prob_test = self.calc_symptomatic_prob_test(adc, tb_infection_date, non_tb_who3) # outcomes r = rng.uniform(size=size) marked = r < prob_test return marked - def calc_symptomatic_prob_test(self, adc_tm1, tb_tm1, tb_tm2, non_tb_who3_tm1): + def calc_symptomatic_prob_test(self, adc, tb_infection_date, non_tb_who3): """ Calculates the probability of being tested for a group - with specific symptoms and returns it. Presence of - an AIDS defining condition (ADC; any WHO4) in the previous time step, - tuberculosis (TB) in the previous two time steps, - and a non-TB WHO3 disease in the previous time step - all affect groupings and test probability. + with specific symptoms and returns it. Presence of an AIDS + defining condition (ADC; any WHO4), tuberculosis (TB), and a + non-TB WHO3 disease all affect groupings and test probability. """ # assume asymptomatic by default prob_test = 0 - # presence of ADC last time step - if adc_tm1: + # presence of ADC + if adc: prob_test = self.prob_test_who4 - # presence of TB last time step but not the time step before, no ADC last time step - elif tb_tm1 and not tb_tm2: + # presence of TB, no ADC + elif tb_infection_date == self.date: prob_test = self.prob_test_tb - # presence of a non-TB WHO3 disease last time step, no ADC or TB last time step - elif non_tb_who3_tm1 and not tb_tm1: + # presence of a non-TB WHO3 disease, no ADC or TB + elif non_tb_who3 and tb_infection_date is None: prob_test = self.prob_test_non_tb_who3 return prob_test From f6a06d82ffcaff1c90b3b1ef90befbef41b267ed Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 19 Jun 2024 20:08:03 +0100 Subject: [PATCH 37/53] Updated HIV testing unit tests to account for use of TB infection date. --- src/tests/test_hiv_testing.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 60b515f4..ce9886c8 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -30,7 +30,9 @@ def test_hiv_symptomatic_testing(): pop.data[col.HIV_DIAGNOSED] = False pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None - pop.data[pop.get_correct_column(col.ADC, dt=1)] = True + pop.data[col.ADC] = True + pop.data[col.TB_INFECTION_DATE] = None + pop.data[col.NON_TB_WHO3] = False # fixing some values pop.hiv_testing.date_start_testing = 2003.5 pop.hiv_testing.date_rate_testing_incr = 2009 @@ -56,8 +58,8 @@ def test_hiv_symptomatic_testing(): # reset some columns pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None - pop.data[pop.get_correct_column(col.ADC, dt=1)] = False - pop.data[pop.get_correct_column(col.TB, dt=1)] = True + pop.data[col.ADC] = False + pop.data[col.TB_INFECTION_DATE] = pop.date # re-evolve population pop.hiv_testing.test_mark_hiv_symptomatic(pop) @@ -72,11 +74,11 @@ def test_hiv_symptomatic_testing(): # check tested value is within 3 standard deviations assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev + pop.date += timedelta(days=30) # reset some columns pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None - pop.data[pop.get_correct_column(col.TB, dt=1)] = False - pop.data[pop.get_correct_column(col.NON_TB_WHO3, dt=1)] = True + pop.data[col.NON_TB_WHO3] = True # re-evolve population pop.hiv_testing.test_mark_hiv_symptomatic(pop) @@ -99,7 +101,7 @@ def test_non_hiv_symptomatic_testing(): pop = Population(size=N, start_date=date(2010, 1, 1)) pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None - pop.data[pop.get_correct_column(col.HIV_DIAGNOSED, dt=1)] = False + pop.data[col.HIV_DIAGNOSED] = False # fixing some values pop.hiv_testing.date_start_testing = 2003.5 pop.hiv_testing.date_rate_testing_incr = 2009 @@ -226,7 +228,9 @@ def test_first_time_testers(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get stats testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False)]) @@ -261,7 +265,9 @@ def test_repeat_testers(): pop.hiv_testing.testing_disrup_covid = False # evolve population - pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get stats testing_population = pop.get_sub_pop([(col.HARD_REACH, op.eq, False)]) From 5e8bc5154779735b2c642433ad8df4e02e904e77 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 20 Jun 2024 18:35:59 +0100 Subject: [PATCH 38/53] Fixed HIV symptomatic test marking with new TB primary infection column. --- src/hivpy/column_names.py | 1 + src/hivpy/hiv_status.py | 3 +++ src/hivpy/hiv_testing.py | 12 ++++++------ src/tests/test_hiv_testing.py | 6 +++--- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/hivpy/column_names.py b/src/hivpy/column_names.py index c2832ffa..f0905bb4 100644 --- a/src/hivpy/column_names.py +++ b/src/hivpy/column_names.py @@ -73,6 +73,7 @@ TB = "tb" # Bool: True if tb occurs this timestep in HIV positive person TB_DIAGNOSED = "tb_diagnosed" # Bool: True if TB is diagnosed this timestep TB_INFECTION_DATE = "tb_infection_date" # date: Date of start of most recent TB infection +TB_PRIMARY_INFECTION = "tb_primary_infection" # Bool: True if TB infection began this timestep C_MENINGITIS = "c_meningitis" # Bool: True if cryptococcal meningitis occurs this timestep C_MENINGITIS_DIAGNOSED = "c_meningitis_diagnosed" # Bool: True if cryptococcal meningitis diagnosed this timestep SBI = "serious_bacterial_infection" # Bool: True if serious bacterial infection this time step diff --git a/src/hivpy/hiv_status.py b/src/hivpy/hiv_status.py index 210c5317..4b09e936 100644 --- a/src/hivpy/hiv_status.py +++ b/src/hivpy/hiv_status.py @@ -99,6 +99,7 @@ def init_HIV_variables(self, population: Population): population.init_variable(col.TB, False) population.init_variable(col.TB_DIAGNOSED, False) population.init_variable(col.TB_INFECTION_DATE, None) + population.init_variable(col.TB_PRIMARY_INFECTION, False) population.init_variable(col.ADC, False) population.init_variable(col.C_MENINGITIS, False) population.init_variable(col.C_MENINGITIS_DIAGNOSED, False) @@ -390,6 +391,8 @@ def disease_and_diagnosis(disease_col, diagnosis_col, disease_rate, diagnosis_pr pop.set_present_variable(col.WHO3_EVENT, (who3_disease | tb), HIV_pos) new_tb = pop.get_sub_pop_from_array(tb, HIV_pos) pop.set_present_variable(col.TB_INFECTION_DATE, pop.date, new_tb) + pop.set_present_variable(col.TB_PRIMARY_INFECTION, False) # FIXME: move this reset elsewhere? + pop.set_present_variable(col.TB_PRIMARY_INFECTION, True, new_tb) # cryptococcal meningitis (cm, _) = disease_and_diagnosis(col.C_MENINGITIS, diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index ec82784b..334beb95 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,25 +102,25 @@ def test_mark_hiv_symptomatic(self, pop): if len(not_diag_tested_pop) > 0: # mark people for testing - marked = pop.transform_group([col.ADC, col.TB_INFECTION_DATE, col.NON_TB_WHO3], + marked = pop.transform_group([col.ADC, col.TB_PRIMARY_INFECTION, col.NON_TB_WHO3], self.calc_symptomatic_testing_outcomes, sub_pop=not_diag_tested_pop) # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def calc_symptomatic_testing_outcomes(self, adc, tb_infection_date, non_tb_who3, size): + def calc_symptomatic_testing_outcomes(self, adc, tb_primary_infection, non_tb_who3, size): """ Uses the symptomatic test probability for a given group of symptoms to select individuals marked to be tested. """ - prob_test = self.calc_symptomatic_prob_test(adc, tb_infection_date, non_tb_who3) + prob_test = self.calc_symptomatic_prob_test(adc, tb_primary_infection, non_tb_who3) # outcomes r = rng.uniform(size=size) marked = r < prob_test return marked - def calc_symptomatic_prob_test(self, adc, tb_infection_date, non_tb_who3): + def calc_symptomatic_prob_test(self, adc, tb_primary_infection, non_tb_who3): """ Calculates the probability of being tested for a group with specific symptoms and returns it. Presence of an AIDS @@ -133,10 +133,10 @@ def calc_symptomatic_prob_test(self, adc, tb_infection_date, non_tb_who3): if adc: prob_test = self.prob_test_who4 # presence of TB, no ADC - elif tb_infection_date == self.date: + elif tb_primary_infection: prob_test = self.prob_test_tb # presence of a non-TB WHO3 disease, no ADC or TB - elif non_tb_who3 and tb_infection_date is None: + elif non_tb_who3 and not tb_primary_infection: prob_test = self.prob_test_non_tb_who3 return prob_test diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index ce9886c8..75ca7c6d 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -31,7 +31,7 @@ def test_hiv_symptomatic_testing(): pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.ADC] = True - pop.data[col.TB_INFECTION_DATE] = None + pop.data[col.TB_PRIMARY_INFECTION] = False pop.data[col.NON_TB_WHO3] = False # fixing some values pop.hiv_testing.date_start_testing = 2003.5 @@ -59,7 +59,7 @@ def test_hiv_symptomatic_testing(): pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.ADC] = False - pop.data[col.TB_INFECTION_DATE] = pop.date + pop.data[col.TB_PRIMARY_INFECTION] = True # re-evolve population pop.hiv_testing.test_mark_hiv_symptomatic(pop) @@ -74,10 +74,10 @@ def test_hiv_symptomatic_testing(): # check tested value is within 3 standard deviations assert mean - 3 * stdev <= tested_population <= mean + 3 * stdev - pop.date += timedelta(days=30) # reset some columns pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None + pop.data[col.TB_PRIMARY_INFECTION] = False pop.data[col.NON_TB_WHO3] = True # re-evolve population From a45a8b29999342741b41c0429bfaca64a147dcdf Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 24 Jun 2024 12:12:57 +0100 Subject: [PATCH 39/53] Update disease status before testing --- src/hivpy/hiv_status.py | 3 ++- src/hivpy/population.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hivpy/hiv_status.py b/src/hivpy/hiv_status.py index 4b09e936..2215e7a6 100644 --- a/src/hivpy/hiv_status.py +++ b/src/hivpy/hiv_status.py @@ -391,7 +391,8 @@ def disease_and_diagnosis(disease_col, diagnosis_col, disease_rate, diagnosis_pr pop.set_present_variable(col.WHO3_EVENT, (who3_disease | tb), HIV_pos) new_tb = pop.get_sub_pop_from_array(tb, HIV_pos) pop.set_present_variable(col.TB_INFECTION_DATE, pop.date, new_tb) - pop.set_present_variable(col.TB_PRIMARY_INFECTION, False) # FIXME: move this reset elsewhere? + # FIXME: may need to update this reset for non-HIV related TB + pop.set_present_variable(col.TB_PRIMARY_INFECTION, False) pop.set_present_variable(col.TB_PRIMARY_INFECTION, True, new_tb) # cryptococcal meningitis diff --git a/src/hivpy/population.py b/src/hivpy/population.py index a03a0fb2..fa78f4d5 100644 --- a/src/hivpy/population.py +++ b/src/hivpy/population.py @@ -285,8 +285,8 @@ def evolve(self, time_step: timedelta): # If HIV has been introduced, then run HIV relevant code if self.HIV_introduced: self.hiv_status.update_HIV_status(self) - self.hiv_testing.update_hiv_testing(self, time_step) HIV_deaths = self.hiv_status.HIV_related_disease_risk(self, time_step) + self.hiv_testing.update_hiv_testing(self, time_step) n_deaths = n_deaths + sum(HIV_deaths) if (n_deaths and self.apply_death): self.drop_from_population(HIV_deaths) From 21c9bd1de2f0fc1014eb22002f9d13241bcc538f Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 24 Jun 2024 12:22:03 +0100 Subject: [PATCH 40/53] Change name to TB_INITIAL_INFECTION --- src/hivpy/column_names.py | 2 +- src/hivpy/hiv_status.py | 6 +++--- src/hivpy/hiv_testing.py | 12 ++++++------ src/tests/test_hiv_testing.py | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/hivpy/column_names.py b/src/hivpy/column_names.py index f0905bb4..72aa66df 100644 --- a/src/hivpy/column_names.py +++ b/src/hivpy/column_names.py @@ -73,7 +73,7 @@ TB = "tb" # Bool: True if tb occurs this timestep in HIV positive person TB_DIAGNOSED = "tb_diagnosed" # Bool: True if TB is diagnosed this timestep TB_INFECTION_DATE = "tb_infection_date" # date: Date of start of most recent TB infection -TB_PRIMARY_INFECTION = "tb_primary_infection" # Bool: True if TB infection began this timestep +TB_INITIAL_INFECTION = "tb_initial_infection" # Bool: True if TB infection began this timestep C_MENINGITIS = "c_meningitis" # Bool: True if cryptococcal meningitis occurs this timestep C_MENINGITIS_DIAGNOSED = "c_meningitis_diagnosed" # Bool: True if cryptococcal meningitis diagnosed this timestep SBI = "serious_bacterial_infection" # Bool: True if serious bacterial infection this time step diff --git a/src/hivpy/hiv_status.py b/src/hivpy/hiv_status.py index 2215e7a6..cee99c7e 100644 --- a/src/hivpy/hiv_status.py +++ b/src/hivpy/hiv_status.py @@ -99,7 +99,7 @@ def init_HIV_variables(self, population: Population): population.init_variable(col.TB, False) population.init_variable(col.TB_DIAGNOSED, False) population.init_variable(col.TB_INFECTION_DATE, None) - population.init_variable(col.TB_PRIMARY_INFECTION, False) + population.init_variable(col.TB_INITIAL_INFECTION, False) population.init_variable(col.ADC, False) population.init_variable(col.C_MENINGITIS, False) population.init_variable(col.C_MENINGITIS_DIAGNOSED, False) @@ -392,8 +392,8 @@ def disease_and_diagnosis(disease_col, diagnosis_col, disease_rate, diagnosis_pr new_tb = pop.get_sub_pop_from_array(tb, HIV_pos) pop.set_present_variable(col.TB_INFECTION_DATE, pop.date, new_tb) # FIXME: may need to update this reset for non-HIV related TB - pop.set_present_variable(col.TB_PRIMARY_INFECTION, False) - pop.set_present_variable(col.TB_PRIMARY_INFECTION, True, new_tb) + pop.set_present_variable(col.TB_INITIAL_INFECTION, False) + pop.set_present_variable(col.TB_INITIAL_INFECTION, True, new_tb) # cryptococcal meningitis (cm, _) = disease_and_diagnosis(col.C_MENINGITIS, diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 334beb95..15afd590 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -102,25 +102,25 @@ def test_mark_hiv_symptomatic(self, pop): if len(not_diag_tested_pop) > 0: # mark people for testing - marked = pop.transform_group([col.ADC, col.TB_PRIMARY_INFECTION, col.NON_TB_WHO3], + marked = pop.transform_group([col.ADC, col.TB_INITIAL_INFECTION, col.NON_TB_WHO3], self.calc_symptomatic_testing_outcomes, sub_pop=not_diag_tested_pop) # set outcomes pop.set_present_variable(col.TEST_MARK, marked, not_diag_tested_pop) - def calc_symptomatic_testing_outcomes(self, adc, tb_primary_infection, non_tb_who3, size): + def calc_symptomatic_testing_outcomes(self, adc, tb_initial_infection, non_tb_who3, size): """ Uses the symptomatic test probability for a given group of symptoms to select individuals marked to be tested. """ - prob_test = self.calc_symptomatic_prob_test(adc, tb_primary_infection, non_tb_who3) + prob_test = self.calc_symptomatic_prob_test(adc, tb_initial_infection, non_tb_who3) # outcomes r = rng.uniform(size=size) marked = r < prob_test return marked - def calc_symptomatic_prob_test(self, adc, tb_primary_infection, non_tb_who3): + def calc_symptomatic_prob_test(self, adc, tb_initial_infection, non_tb_who3): """ Calculates the probability of being tested for a group with specific symptoms and returns it. Presence of an AIDS @@ -133,10 +133,10 @@ def calc_symptomatic_prob_test(self, adc, tb_primary_infection, non_tb_who3): if adc: prob_test = self.prob_test_who4 # presence of TB, no ADC - elif tb_primary_infection: + elif tb_initial_infection: prob_test = self.prob_test_tb # presence of a non-TB WHO3 disease, no ADC or TB - elif non_tb_who3 and not tb_primary_infection: + elif non_tb_who3 and not tb_initial_infection: prob_test = self.prob_test_non_tb_who3 return prob_test diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 75ca7c6d..7bb645aa 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -31,7 +31,7 @@ def test_hiv_symptomatic_testing(): pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.ADC] = True - pop.data[col.TB_PRIMARY_INFECTION] = False + pop.data[col.TB_INITIAL_INFECTION] = False pop.data[col.NON_TB_WHO3] = False # fixing some values pop.hiv_testing.date_start_testing = 2003.5 @@ -59,7 +59,7 @@ def test_hiv_symptomatic_testing(): pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.ADC] = False - pop.data[col.TB_PRIMARY_INFECTION] = True + pop.data[col.TB_INITIAL_INFECTION] = True # re-evolve population pop.hiv_testing.test_mark_hiv_symptomatic(pop) @@ -77,7 +77,7 @@ def test_hiv_symptomatic_testing(): # reset some columns pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None - pop.data[col.TB_PRIMARY_INFECTION] = False + pop.data[col.TB_INITIAL_INFECTION] = False pop.data[col.NON_TB_WHO3] = True # re-evolve population From 7d6ad09c5c49402da607978d8890e555be577827 Mon Sep 17 00:00:00 2001 From: mmcleod89 Date: Tue, 25 Jun 2024 11:08:21 +0100 Subject: [PATCH 41/53] Update src/hivpy/hiv_testing.py --- src/hivpy/hiv_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 15afd590..84a659ab 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -231,7 +231,7 @@ def test_mark_general_pop(self, pop): Mark general population to undergo testing this time step. """ # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_start_testing) + if ((pop.date.year >= self.date_rate_testing_incr) & (not (self.covid_disrup_affected | self.testing_disrup_covid))): # mark sex workers for testing first From c9493e1cf5723f608392811a7ae642ea09d7d7c9 Mon Sep 17 00:00:00 2001 From: mmcleod89 Date: Tue, 25 Jun 2024 11:08:44 +0100 Subject: [PATCH 42/53] Update src/tests/test_sexual_behaviour.py --- src/tests/test_sexual_behaviour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test_sexual_behaviour.py b/src/tests/test_sexual_behaviour.py index 9d97b5ef..69a6add5 100644 --- a/src/tests/test_sexual_behaviour.py +++ b/src/tests/test_sexual_behaviour.py @@ -235,7 +235,7 @@ def test_risk_adc(): init_HIV_idx = rng.integers(0, N, size=5) init_ADC_idx = init_HIV_idx[[0, 2, 4]] pop.data.loc[init_HIV_idx, col.HIV_STATUS] = True - pop.data.loc[init_ADC_idx, pop.get_correct_column(col.ADC, dt=0)] = True + pop.data.loc[init_ADC_idx, col.ADC] = True expected_risk = np.ones(N) expected_risk[init_ADC_idx] = 0.2 SBM = SexualBehaviourModule() From 3eb1d780cc6b3cc00ef6b639e0eaa9c506eb4ad3 Mon Sep 17 00:00:00 2001 From: mmcleod89 Date: Tue, 25 Jun 2024 11:08:56 +0100 Subject: [PATCH 43/53] Update src/tests/test_sexual_behaviour.py --- src/tests/test_sexual_behaviour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/test_sexual_behaviour.py b/src/tests/test_sexual_behaviour.py index 69a6add5..29db25b1 100644 --- a/src/tests/test_sexual_behaviour.py +++ b/src/tests/test_sexual_behaviour.py @@ -247,7 +247,7 @@ def test_risk_adc(): add_HIV_idx = rng.integers(0, N, size=5) add_ADC_idx = add_HIV_idx[[0, 1, 2]] pop.data.loc[add_HIV_idx, col.HIV_STATUS] = True - pop.data.loc[add_ADC_idx, pop.get_correct_column(col.ADC, dt=0)] = True + pop.data.loc[add_ADC_idx, col.ADC] = True # Update risk factors SBM.update_sex_behaviour(pop) expected_risk[add_ADC_idx] = 0.2 From 9b32d429f9f91b0668f571b5b60e8165ddf76535 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 25 Jun 2024 15:02:15 +0100 Subject: [PATCH 44/53] Added negative unit test to ensure nobody was being tested before the testing start date. --- src/tests/test_hiv_testing.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 7bb645aa..595ffc42 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -9,7 +9,7 @@ def test_hiv_testing_covid(): # build population - N = 100000 + N = 100 pop = Population(size=N, start_date=date(2010, 1, 1)) # covid disruption is in place pop.hiv_testing.covid_disrup_affected = True @@ -17,7 +17,21 @@ def test_hiv_testing_covid(): # evolve population pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) + # check that nobody was tested + assert sum(pop.get_variable(col.EVER_TESTED)) == 0 + +def test_hiv_testing_before_start(): + + # build population + N = 100 + pop = Population(size=N, start_date=date(2003, 1, 1)) + pop.data[col.ADC] = True + # start date is before testing begins + pop.hiv_testing.date_start_testing = 2003.5 + + # evolve population + pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) # check that nobody was tested assert sum(pop.get_variable(col.EVER_TESTED)) == 0 From cd90a9b14e878743060e1af32285a3d7e22aa039 Mon Sep 17 00:00:00 2001 From: unknown Date: Mon, 1 Jul 2024 18:38:09 +0100 Subject: [PATCH 45/53] Added more date-related checks to unit tests. --- src/tests/test_hiv_testing.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 595ffc42..3bbd125a 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -40,7 +40,7 @@ def test_hiv_symptomatic_testing(): # build population N = 100000 - pop = Population(size=N, start_date=date(2016, 1, 1)) + pop = Population(size=N, start_date=date(2003, 1, 1)) pop.data[col.HIV_DIAGNOSED] = False pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None @@ -60,6 +60,14 @@ def test_hiv_symptomatic_testing(): pop.hiv_testing.test_mark_hiv_symptomatic(pop) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + # check that nobody was tested before date_start_testing + assert sum(pop.get_variable(col.EVER_TESTED)) == 0 + + pop.date = date(2008, 1, 1) + # re-evolve population + pop.hiv_testing.test_mark_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get stats tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) @@ -112,7 +120,7 @@ def test_non_hiv_symptomatic_testing(): # build population N = 100000 - pop = Population(size=N, start_date=date(2010, 1, 1)) + pop = Population(size=N, start_date=date(2003, 1, 1)) pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.HIV_DIAGNOSED] = False @@ -129,6 +137,14 @@ def test_non_hiv_symptomatic_testing(): pop.hiv_testing.test_mark_non_hiv_symptomatic(pop) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + # check that nobody was tested before date_start_testing + assert sum(pop.get_variable(col.EVER_TESTED)) == 0 + + pop.date = date(2008, 1, 1) + # re-evolve population + pop.hiv_testing.test_mark_non_hiv_symptomatic(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get stats tested_population = len(pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)])) @@ -179,11 +195,11 @@ def test_general_testing_conditions(): # build population N = 100000 - pop = Population(size=N, start_date=date(2010, 1, 1)) + pop = Population(size=N, start_date=date(2008, 1, 1)) pop.data[col.AGE] = 20 pop.data[col.HARD_REACH] = False pop.data[col.EVER_TESTED] = True - pop.data[col.LAST_TEST_DATE] = date(2008, 1, 1) + pop.data[col.LAST_TEST_DATE] = date(2009, 1, 1) pop.data[col.NP_LAST_TEST] = 1 # fixing some values pop.hiv_testing.date_start_testing = 2003.5 @@ -205,6 +221,14 @@ def test_general_testing_conditions(): pop.hiv_testing.test_mark_general_pop(pop) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) + # check that nobody was tested before date_rate_testing_incr + assert all(pop.get_variable(col.LAST_TEST_DATE) > pop.date) + + pop.date = date(2010, 1, 1) + # re-evolve population + pop.hiv_testing.test_mark_general_pop(pop) + marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) + pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # get people that were just tested tested_population = pop.get_sub_pop([(col.LAST_TEST_DATE, op.eq, pop.date)]) From 895aa547bc78db2e803cd85071da1eaf0c7c210a Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 11:03:02 +0100 Subject: [PATCH 46/53] Remove floating point dates --- src/hivpy/circumcision.py | 36 ++++++++++++++++---------------- src/hivpy/common.py | 16 ++++++++++++-- src/hivpy/hiv_testing.py | 26 +++++++++++------------ src/hivpy/pregnancy.py | 8 +++---- src/hivpy/sexual_behaviour.py | 6 +++--- src/tests/test_circumcision.py | 8 +++---- src/tests/test_hiv_testing.py | 38 +++++++++++++++++----------------- 7 files changed, 75 insertions(+), 63 deletions(-) diff --git a/src/hivpy/circumcision.py b/src/hivpy/circumcision.py index c65df306..77f24374 100644 --- a/src/hivpy/circumcision.py +++ b/src/hivpy/circumcision.py @@ -6,7 +6,7 @@ import hivpy.column_names as col from .circumcision_data import CircumcisionData -from .common import SexType, rng, timedelta +from .common import SexType, rng, timedelta, date, diff_years class CircumcisionModule: @@ -17,12 +17,12 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "circumcision.yaml") as data_path: self.c_data = CircumcisionData(data_path) - self.vmmc_start_year = self.c_data.vmmc_start_year - self.circ_rate_change_year = self.c_data.circ_rate_change_year - self.prob_circ_calc_cutoff_year = self.c_data.prob_circ_calc_cutoff_year + self.vmmc_start_year = date(self.c_data.vmmc_start_year) + self.circ_rate_change_year = date(self.c_data.circ_rate_change_year) + self.prob_circ_calc_cutoff_year = date(self.c_data.prob_circ_calc_cutoff_year) self.circ_after_test = self.c_data.circ_after_test self.prob_circ_after_test = self.c_data.prob_circ_after_test - self.policy_intervention_year = self.c_data.policy_intervention_year + self.policy_intervention_year = date(self.c_data.policy_intervention_year) self.circ_policy_scenario = self.c_data.circ_policy_scenario # NOTE: the covid disrup field may not belong here self.covid_disrup_affected = self.c_data.covid_disrup_affected @@ -114,18 +114,18 @@ def update_vmmc(self, pop, time_step): # only apply VMMC after a specific year # unless a scenario allows no further circumcision - if ((self.vmmc_start_year <= self.date.year) + if ((self.vmmc_start_year <= self.date) & (not (self.vmmc_disrup_covid - | ((self.policy_intervention_year <= self.date.year) + | ((self.policy_intervention_year <= self.date) & (self.circ_policy_scenario == 2)) - | ((self.policy_intervention_year + 5 <= self.date.year) + | (((self.policy_intervention_year + timedelta(5)) <= self.date) & (self.circ_policy_scenario == 4))))): # circumcision stops in 10-14 year olds if ((self.circ_policy_scenario == 1) | (self.circ_policy_scenario == 3) | (self.circ_policy_scenario == 4)) \ - & (self.policy_intervention_year <= self.date.year): + & (self.policy_intervention_year <= self.date): uncirc_male_population = pop.get_sub_pop([(col.SEX, op.eq, SexType.Male), (col.CIRCUMCISED, op.eq, False), (col.HIV_DIAGNOSED, op.eq, False), @@ -190,29 +190,29 @@ def calc_prob_circ(self, age_group): elif age_group == 3: age_mod = self.circ_rate_change_30_49 - calc_date = self.date.year + calc_date = self.date # cap date at prob_circ_calc_cutoff_year (2019 by default) for calculations - if self.prob_circ_calc_cutoff_year < self.date.year: + if self.prob_circ_calc_cutoff_year < self.date: calc_date = self.prob_circ_calc_cutoff_year # circumcision probability for a given age group # year is after circ_rate_change_year (2013 by default) - if self.circ_rate_change_year < self.date.year: + if self.circ_rate_change_year < self.date: # case where age group 1 has a modifier ag1_has_mod = (age_group == 1) & (self.circ_policy_scenario == 1) \ - & (self.policy_intervention_year <= self.date.year) + & (self.policy_intervention_year <= self.date) if ag1_has_mod: - prob_circ = ((self.circ_rate_change_year - self.vmmc_start_year) - + (calc_date - self.circ_rate_change_year) + prob_circ = (diff_years(self.circ_rate_change_year, self.vmmc_start_year) + + diff_years(calc_date, self.circ_rate_change_year) * self.circ_rate_change_post_2013 * self.circ_rate_change_15_19) \ * self.circ_increase_rate else: - prob_circ = ((self.circ_rate_change_year - self.vmmc_start_year) - + (calc_date - self.circ_rate_change_year) + prob_circ = (diff_years(self.circ_rate_change_year, self.vmmc_start_year) + + diff_years(calc_date, self.circ_rate_change_year) * self.circ_rate_change_post_2013) * self.circ_increase_rate * age_mod # year is before circ_rate_change_year (2013 by default) else: - prob_circ = (calc_date - self.vmmc_start_year) * self.circ_increase_rate * age_mod + prob_circ = diff_years(calc_date, self.vmmc_start_year) * self.circ_increase_rate * age_mod return min(prob_circ, 1) diff --git a/src/hivpy/common.py b/src/hivpy/common.py index 989c732c..b958d278 100644 --- a/src/hivpy/common.py +++ b/src/hivpy/common.py @@ -72,7 +72,12 @@ def __add__(self, delta): return date(year, month, self.day) def __sub__(self, delta): - return self.__add__(timedelta(years=-delta.year, months=-delta.month)) + if(type(delta) == timedelta): + return self.__add__(timedelta(years=-delta.year, months=-delta.month)) + elif(type(delta) == date): + month = (self.month - delta.month) % 12 + year = (self.year - delta.year) + (self.month - delta.month)//12 + return timedelta(year, month) def __repr__(self): return f"({self.year}, {self.month}, {self.day})" @@ -110,6 +115,10 @@ def __truediv__(self, dt): months2 = dt.year * 12 + dt.month return months1 / months2 +def floatToDate(fp_year): + int_year = int(fp_year) + int_month = int((fp_year - int_year) * 12) + return date(int_year, int_month) class timedelta: def __init__(self, years=0, months=0, days=0): @@ -164,9 +173,12 @@ def __truediv__(self, x): months1 = self.year * 12 + self.month months2 = x.year * 12 + x.month return months1 / months2 + + def years(self): + return self.year + (self.month / 12) -def diff_years(date_begin: date, date_end: date): +def diff_years(date_end: date, date_begin: date): return (date_end.year - date_begin.year) + (date_end.month - date_begin.month) / 12 diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 84a659ab..dad72242 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -3,7 +3,7 @@ import hivpy.column_names as col -from .common import AND, COND, OR, rng, timedelta +from .common import AND, COND, OR, rng, timedelta, floatToDate, diff_years from .hiv_testing_data import HIVTestingData @@ -15,8 +15,8 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "hiv_testing.yaml") as data_path: self.ht_data = HIVTestingData(data_path) - self.date_start_testing = self.ht_data.date_start_testing - self.date_rate_testing_incr = self.ht_data.date_rate_testing_incr + self.date_start_testing = floatToDate(self.ht_data.date_start_testing) + self.date_rate_testing_incr = floatToDate(self.ht_data.date_rate_testing_incr) self.init_rate_first_test = self.ht_data.init_rate_first_test self.eff_max_freq_testing = self.ht_data.eff_max_freq_testing self.test_scenario = self.ht_data.test_scenario @@ -32,14 +32,14 @@ def __init__(self, **kwargs): self.prob_test_tb = self.ht_data.prob_test_tb self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 self.test_targeting = self.ht_data.test_targeting.sample() - self.date_general_testing_plateau = self.ht_data.date_general_testing_plateau.sample() - self.date_targeted_testing_plateau = self.ht_data.date_targeted_testing_plateau + self.date_general_testing_plateau = floatToDate(self.ht_data.date_general_testing_plateau.sample()) + self.date_targeted_testing_plateau = floatToDate(self.ht_data.date_targeted_testing_plateau) self.an_lin_incr_test = self.ht_data.an_lin_incr_test.sample() self.incr_test_rate_sympt = self.ht_data.incr_test_rate_sympt.sample() # eff_max_freq_testing is used as an index to pick the correct # minimum number of days to wait between tests from this list - self.days_to_wait = [365, 180, 90] # 12 months, 6 months, 3 months + self.months_to_wait = [12, 6, 3] # 12 months, 6 months, 3 months self.rate_first_test = 0 self.rate_rep_test = 0 @@ -85,13 +85,13 @@ def test_mark_hiv_symptomatic(self, pop): and this function may not work as expected. """ # testing occurs after a certain year - if (pop.date.year > self.date_start_testing): + if (pop.date > self.date_start_testing): # date needed for a function passed to transform_group self.date = pop.date # update symptomatic test probabilities - if pop.date.year <= self.date_targeted_testing_plateau: + if pop.date <= self.date_targeted_testing_plateau: self.prob_test_who4 = min(0.9, self.prob_test_who4 * self.incr_test_rate_sympt) self.prob_test_tb = min(0.8, self.prob_test_tb * self.incr_test_rate_sympt) self.prob_test_non_tb_who3 = min(0.7, self.prob_test_non_tb_who3 * self.incr_test_rate_sympt) @@ -146,7 +146,7 @@ def test_mark_non_hiv_symptomatic(self, pop): Mark non-HIV symptomatic individuals to undergo testing this time step. """ # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_start_testing) + if ((pop.date >= self.date_start_testing) & (not (self.covid_disrup_affected | self.testing_disrup_covid))): # undiagnosed (last time step) population not scheduled for testing @@ -231,15 +231,15 @@ def test_mark_general_pop(self, pop): Mark general population to undergo testing this time step. """ # testing occurs after a certain year if there is no covid disruption - if ((pop.date.year >= self.date_rate_testing_incr) + if ((pop.date >= self.date_rate_testing_incr) & (not (self.covid_disrup_affected | self.testing_disrup_covid))): # mark sex workers for testing first self.test_mark_sex_workers(pop) # update testing probabilities - self.rate_rep_test = (min(pop.date.year, self.date_general_testing_plateau) - - self.date_rate_testing_incr) * self.an_lin_incr_test + self.rate_rep_test = (min(pop.date, self.date_general_testing_plateau) - + self.date_rate_testing_incr).years() * self.an_lin_incr_test self.rate_first_test = self.init_rate_first_test + self.rate_rep_test # general population ready for testing @@ -247,7 +247,7 @@ def test_mark_general_pop(self, pop): COND(col.AGE, op.ge, 15), COND(col.HIV_DIAGNOSED, op.eq, False), OR(COND(col.LAST_TEST_DATE, op.le, pop.date - - timedelta(days=self.days_to_wait[ + timedelta(months=self.months_to_wait[ self.eff_max_freq_testing])), COND(col.LAST_TEST_DATE, op.eq, None)), COND(col.TEST_MARK, op.eq, False))) diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index c48294ab..08839697 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -10,7 +10,7 @@ import hivpy.column_names as col from . import output -from .common import SexType, rng, timedelta +from .common import SexType, rng, timedelta, floatToDate, diff_years from .pregnancy_data import PregnancyData if TYPE_CHECKING: @@ -28,7 +28,7 @@ def __init__(self, **kwargs): self.can_be_pregnant = self.p_data.can_be_pregnant self.rate_want_no_children = self.p_data.rate_want_no_children # dependent on time step length - self.date_pmtct = self.p_data.date_pmtct + self.date_pmtct = floatToDate(self.p_data.date_pmtct) self.pmtct_inc_rate = self.p_data.pmtct_inc_rate self.fertility_factor = self.p_data.fertility_factor self.inc_cat = self.p_data.inc_cat.sample() @@ -165,9 +165,9 @@ def update_antenatal_care(self, pop: Population): # FIXME: this should probably only be applied to HIV diagnosed individuals? # If date is after introduction of prevention of mother to child transmission - if pop.date.year >= self.date_pmtct: + if pop.date >= self.date_pmtct: # probability of prevention of mother to child transmission care - self.prob_pmtct = min((pop.date.year - self.date_pmtct) * self.pmtct_inc_rate, 0.975) + self.prob_pmtct = min(diff_years(pop.date, self.date_pmtct) * self.pmtct_inc_rate, 0.975) # FIXME: NVP use hasn't been modelled yet and neither has drug resistance # this expression assumed ANC can only be true if pregnant in_anc = pop.get_sub_pop([(col.ART_NAIVE, op.eq, True), diff --git a/src/hivpy/sexual_behaviour.py b/src/hivpy/sexual_behaviour.py index d4a87995..fcdbc4cf 100644 --- a/src/hivpy/sexual_behaviour.py +++ b/src/hivpy/sexual_behaviour.py @@ -515,12 +515,12 @@ def update_risk_population(self, date): yearly_change_90s = self.yearly_risk_change["1990s"] yearly_change_10s = self.yearly_risk_change["2010s"] if (date1995 < date <= date2000): - dt = diff_years(date1995, date) + dt = diff_years(date, date1995) self.risk_population = yearly_change_90s**dt elif (date2000 < date < date2010): self.risk_population = yearly_change_90s**5 elif (date2010 < date < date2021): - dt = diff_years(date2010, date) + dt = diff_years(date, date2010) self.risk_population = yearly_change_90s**5 * yearly_change_10s**dt elif (date2021 < date): self.risk_population = yearly_change_90s**5 * yearly_change_10s**11 @@ -613,7 +613,7 @@ def get_ratio(sex, age): def update_ltp_rate_change(self, date): if date1995 < date < date2000: - dt = diff_years(date1995, date) + dt = diff_years(date, date1995) self.ltp_rate_change = self.annual_ltp_rate_change**(dt) elif date >= date2000: self.ltp_rate_change = self.annual_ltp_rate_change**5 diff --git a/src/tests/test_circumcision.py b/src/tests/test_circumcision.py index 848cb409..cbc7d504 100644 --- a/src/tests/test_circumcision.py +++ b/src/tests/test_circumcision.py @@ -18,10 +18,10 @@ def set_covid(circ_module, truth_val): def set_vmmc_default_dates(circ_module): - circ_module.vmmc_start_year = 2008 - circ_module.circ_rate_change_year = 2013 - circ_module.prob_circ_calc_cutoff_year = 2019 - circ_module.policy_intervention_year = 2022 + circ_module.vmmc_start_year = date(2008) + circ_module.circ_rate_change_year = date(2013) + circ_module.prob_circ_calc_cutoff_year = date(2019) + circ_module.policy_intervention_year = date(2022) def general_circumcision_checks(mean, stdev, no_circumcised, data): diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 7bb645aa..dd7349ea 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -2,7 +2,7 @@ from math import isclose, sqrt import hivpy.column_names as col -from hivpy.common import date, rng, timedelta +from hivpy.common import date, rng, timedelta, floatToDate from hivpy.population import Population @@ -34,8 +34,8 @@ def test_hiv_symptomatic_testing(): pop.data[col.TB_INITIAL_INFECTION] = False pop.data[col.NON_TB_WHO3] = False # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_tb = 0.5 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -103,8 +103,8 @@ def test_non_hiv_symptomatic_testing(): pop.data[col.LAST_TEST_DATE] = None pop.data[col.HIV_DIAGNOSED] = False # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.prob_test_non_hiv_symptoms = 1 pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -136,8 +136,8 @@ def test_general_sex_worker_testing(): pop.data[col.HIV_DIAGNOSED] = False pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=150) # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.eff_max_freq_testing = 0 pop.hiv_testing.sw_test_regularly = True pop.hiv_testing.covid_disrup_affected = False @@ -172,10 +172,10 @@ def test_general_testing_conditions(): pop.data[col.LAST_TEST_DATE] = date(2008, 1, 1) pop.data[col.NP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_general_testing_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -217,10 +217,10 @@ def test_first_time_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_general_testing_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -254,10 +254,10 @@ def test_repeat_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_general_testing_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -294,11 +294,11 @@ def test_partner_reset_after_test(): pop.data[col.NP_LAST_TEST] = 2 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.eff_max_freq_testing = 1 pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_general_testing_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False From d45b8949967eb59f01e0ffca677abf76884a4066 Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 11:09:51 +0100 Subject: [PATCH 47/53] Fix newer tests; use months for testing periods --- src/tests/test_hiv_testing.py | 10 +++++----- src/tests/test_pregnancy.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 9afd364e..0c7d8f2a 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -28,7 +28,7 @@ def test_hiv_testing_before_start(): pop = Population(size=N, start_date=date(2003, 1, 1)) pop.data[col.ADC] = True # start date is before testing begins - pop.hiv_testing.date_start_testing = 2003.5 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) # evolve population pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) @@ -366,14 +366,14 @@ def test_max_frequency_testing(): pop.data[col.HIV_DIAGNOSED] = False pop.data[col.HARD_REACH] = False pop.data[col.EVER_TESTED] = True - pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=pop.hiv_testing.days_to_wait[index]-30) + pop.data[col.LAST_TEST_DATE] = start_date - timedelta(months=pop.hiv_testing.months_to_wait[index]-1) pop.data[col.NP_LAST_TEST] = 1 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = 2003.5 - pop.hiv_testing.date_rate_testing_incr = 2009 + pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) pop.hiv_testing.eff_max_freq_testing = index - pop.hiv_testing.date_general_testing_plateau = 2015.5 + pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) # guaranteed testing pop.hiv_testing.an_lin_incr_test = 1 pop.hiv_testing.no_test_if_np0 = False diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index 7f3092ee..bc5454e3 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -2,7 +2,7 @@ from math import ceil, isclose, sqrt import hivpy.column_names as col -from hivpy.common import SexType, date, rng, timedelta +from hivpy.common import SexType, date, rng, timedelta, diff_years from hivpy.population import Population @@ -264,7 +264,7 @@ def test_anc_and_pmtct(): # guaranteed pregnancy pop.pregnancy.prob_pregnancy_base = 1 pop.pregnancy.rate_test_anc_inc = 1 - pop.pregnancy.date_pmtct = 2004 + pop.pregnancy.date_pmtct = date(2004) pop.pregnancy.pmtct_inc_rate = 1 # advance pregnancy @@ -279,7 +279,7 @@ def test_anc_and_pmtct(): # get stats no_pmtct = sum(pop.data[col.PMTCT]) - prob_pmtct = min((pop.date.year - pop.pregnancy.date_pmtct) * pop.pregnancy.pmtct_inc_rate, 0.975) + prob_pmtct = min(diff_years(pop.date, pop.pregnancy.date_pmtct) * pop.pregnancy.pmtct_inc_rate, 0.975) mean = no_anc * prob_pmtct stdev = sqrt(mean * (1 - prob_pmtct)) # check pmtct value is within 3 standard deviations From 1aa5357ca7f158ba8cf7443decded5787a9328a3 Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 11:19:48 +0100 Subject: [PATCH 48/53] style fixes --- src/hivpy/circumcision.py | 2 +- src/hivpy/common.py | 8 +++++--- src/hivpy/hiv_testing.py | 6 +++--- src/hivpy/pregnancy.py | 2 +- src/tests/test_hiv_testing.py | 2 +- src/tests/test_pregnancy.py | 2 +- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/hivpy/circumcision.py b/src/hivpy/circumcision.py index 77f24374..c3b3be59 100644 --- a/src/hivpy/circumcision.py +++ b/src/hivpy/circumcision.py @@ -6,7 +6,7 @@ import hivpy.column_names as col from .circumcision_data import CircumcisionData -from .common import SexType, rng, timedelta, date, diff_years +from .common import SexType, date, diff_years, rng, timedelta class CircumcisionModule: diff --git a/src/hivpy/common.py b/src/hivpy/common.py index b958d278..41a83dfa 100644 --- a/src/hivpy/common.py +++ b/src/hivpy/common.py @@ -72,9 +72,9 @@ def __add__(self, delta): return date(year, month, self.day) def __sub__(self, delta): - if(type(delta) == timedelta): + if (type(delta) is timedelta): return self.__add__(timedelta(years=-delta.year, months=-delta.month)) - elif(type(delta) == date): + elif (type(delta) is date): month = (self.month - delta.month) % 12 year = (self.year - delta.year) + (self.month - delta.month)//12 return timedelta(year, month) @@ -115,11 +115,13 @@ def __truediv__(self, dt): months2 = dt.year * 12 + dt.month return months1 / months2 + def floatToDate(fp_year): int_year = int(fp_year) int_month = int((fp_year - int_year) * 12) return date(int_year, int_month) + class timedelta: def __init__(self, years=0, months=0, days=0): self.year = years @@ -173,7 +175,7 @@ def __truediv__(self, x): months1 = self.year * 12 + self.month months2 = x.year * 12 + x.month return months1 / months2 - + def years(self): return self.year + (self.month / 12) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index dad72242..d2910009 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -3,7 +3,7 @@ import hivpy.column_names as col -from .common import AND, COND, OR, rng, timedelta, floatToDate, diff_years +from .common import AND, COND, OR, floatToDate, rng, timedelta from .hiv_testing_data import HIVTestingData @@ -238,8 +238,8 @@ def test_mark_general_pop(self, pop): self.test_mark_sex_workers(pop) # update testing probabilities - self.rate_rep_test = (min(pop.date, self.date_general_testing_plateau) - - self.date_rate_testing_incr).years() * self.an_lin_incr_test + self.rate_rep_test = (min(pop.date, self.date_general_testing_plateau) - + self.date_rate_testing_incr).years() * self.an_lin_incr_test self.rate_first_test = self.init_rate_first_test + self.rate_rep_test # general population ready for testing diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 08839697..33bb9d44 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -10,7 +10,7 @@ import hivpy.column_names as col from . import output -from .common import SexType, rng, timedelta, floatToDate, diff_years +from .common import SexType, diff_years, floatToDate, rng, timedelta from .pregnancy_data import PregnancyData if TYPE_CHECKING: diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 0c7d8f2a..7ddfde6e 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -2,7 +2,7 @@ from math import isclose, sqrt import hivpy.column_names as col -from hivpy.common import date, rng, timedelta, floatToDate +from hivpy.common import date, floatToDate, rng, timedelta from hivpy.population import Population diff --git a/src/tests/test_pregnancy.py b/src/tests/test_pregnancy.py index bc5454e3..4ddb764f 100644 --- a/src/tests/test_pregnancy.py +++ b/src/tests/test_pregnancy.py @@ -2,7 +2,7 @@ from math import ceil, isclose, sqrt import hivpy.column_names as col -from hivpy.common import SexType, date, rng, timedelta, diff_years +from hivpy.common import SexType, date, diff_years, rng, timedelta from hivpy.population import Population From ac3bd53f6a71e4afe6c3132b79bad821d2e8f4ca Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 11:36:10 +0100 Subject: [PATCH 49/53] Don't have last date date > current date --- src/tests/test_hiv_testing.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 7ddfde6e..d1d60c8e 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -199,7 +199,7 @@ def test_general_testing_conditions(): pop.data[col.AGE] = 20 pop.data[col.HARD_REACH] = False pop.data[col.EVER_TESTED] = True - pop.data[col.LAST_TEST_DATE] = date(2009, 1, 1) + pop.data[col.LAST_TEST_DATE] = None pop.data[col.NP_LAST_TEST] = 1 # fixing some values pop.hiv_testing.date_start_testing = floatToDate(2003.5) @@ -222,9 +222,11 @@ def test_general_testing_conditions(): marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) pop.hiv_testing.apply_test_outcomes_to_sub_pop(pop, marked_population) # check that nobody was tested before date_rate_testing_incr - assert all(pop.get_variable(col.LAST_TEST_DATE) > pop.date) + for d in pop.get_variable(col.LAST_TEST_DATE): + assert (d is None) pop.date = date(2010, 1, 1) + pop.data[col.LAST_TEST_DATE] = date(2009) # re-evolve population pop.hiv_testing.test_mark_general_pop(pop) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) From 99516ed8ded01594bb592721a3ea9c0e402b0877 Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 12:16:46 +0100 Subject: [PATCH 50/53] Evertested should be false if date test is none --- src/tests/test_hiv_testing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index d1d60c8e..58475e7e 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -198,7 +198,7 @@ def test_general_testing_conditions(): pop = Population(size=N, start_date=date(2008, 1, 1)) pop.data[col.AGE] = 20 pop.data[col.HARD_REACH] = False - pop.data[col.EVER_TESTED] = True + pop.data[col.EVER_TESTED] = False pop.data[col.LAST_TEST_DATE] = None pop.data[col.NP_LAST_TEST] = 1 # fixing some values @@ -227,6 +227,7 @@ def test_general_testing_conditions(): pop.date = date(2010, 1, 1) pop.data[col.LAST_TEST_DATE] = date(2009) + pop.data[col.EVER_TESTED] = True # re-evolve population pop.hiv_testing.test_mark_general_pop(pop) marked_population = pop.get_sub_pop([(col.TEST_MARK, op.eq, True)]) From 7c53bd339adda44292d7d2be3c67a517d2a55b3d Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 12:20:36 +0100 Subject: [PATCH 51/53] Oops camelCase --- src/hivpy/common.py | 2 +- src/hivpy/hiv_testing.py | 10 +++++----- src/hivpy/pregnancy.py | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hivpy/common.py b/src/hivpy/common.py index 41a83dfa..d5191e05 100644 --- a/src/hivpy/common.py +++ b/src/hivpy/common.py @@ -116,7 +116,7 @@ def __truediv__(self, dt): return months1 / months2 -def floatToDate(fp_year): +def float_to_date(fp_year): int_year = int(fp_year) int_month = int((fp_year - int_year) * 12) return date(int_year, int_month) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index d2910009..5fdc5384 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -3,7 +3,7 @@ import hivpy.column_names as col -from .common import AND, COND, OR, floatToDate, rng, timedelta +from .common import AND, COND, OR, float_to_date, rng, timedelta from .hiv_testing_data import HIVTestingData @@ -15,8 +15,8 @@ def __init__(self, **kwargs): with importlib.resources.path("hivpy.data", "hiv_testing.yaml") as data_path: self.ht_data = HIVTestingData(data_path) - self.date_start_testing = floatToDate(self.ht_data.date_start_testing) - self.date_rate_testing_incr = floatToDate(self.ht_data.date_rate_testing_incr) + self.date_start_testing = float_to_date(self.ht_data.date_start_testing) + self.date_rate_testing_incr = float_to_date(self.ht_data.date_rate_testing_incr) self.init_rate_first_test = self.ht_data.init_rate_first_test self.eff_max_freq_testing = self.ht_data.eff_max_freq_testing self.test_scenario = self.ht_data.test_scenario @@ -32,8 +32,8 @@ def __init__(self, **kwargs): self.prob_test_tb = self.ht_data.prob_test_tb self.prob_test_non_tb_who3 = self.ht_data.prob_test_non_tb_who3 self.test_targeting = self.ht_data.test_targeting.sample() - self.date_general_testing_plateau = floatToDate(self.ht_data.date_general_testing_plateau.sample()) - self.date_targeted_testing_plateau = floatToDate(self.ht_data.date_targeted_testing_plateau) + self.date_general_testing_plateau = float_to_date(self.ht_data.date_general_testing_plateau.sample()) + self.date_targeted_testing_plateau = float_to_date(self.ht_data.date_targeted_testing_plateau) self.an_lin_incr_test = self.ht_data.an_lin_incr_test.sample() self.incr_test_rate_sympt = self.ht_data.incr_test_rate_sympt.sample() diff --git a/src/hivpy/pregnancy.py b/src/hivpy/pregnancy.py index 33bb9d44..7cafffc8 100644 --- a/src/hivpy/pregnancy.py +++ b/src/hivpy/pregnancy.py @@ -10,7 +10,7 @@ import hivpy.column_names as col from . import output -from .common import SexType, diff_years, floatToDate, rng, timedelta +from .common import SexType, diff_years, float_to_date, rng, timedelta from .pregnancy_data import PregnancyData if TYPE_CHECKING: @@ -28,7 +28,7 @@ def __init__(self, **kwargs): self.can_be_pregnant = self.p_data.can_be_pregnant self.rate_want_no_children = self.p_data.rate_want_no_children # dependent on time step length - self.date_pmtct = floatToDate(self.p_data.date_pmtct) + self.date_pmtct = float_to_date(self.p_data.date_pmtct) self.pmtct_inc_rate = self.p_data.pmtct_inc_rate self.fertility_factor = self.p_data.fertility_factor self.inc_cat = self.p_data.inc_cat.sample() From c17035ba002e2388df03b5e2dc24a87c839f7a1f Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 12:21:10 +0100 Subject: [PATCH 52/53] Comment made redundant --- src/hivpy/hiv_testing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hivpy/hiv_testing.py b/src/hivpy/hiv_testing.py index 5fdc5384..ae9bb186 100644 --- a/src/hivpy/hiv_testing.py +++ b/src/hivpy/hiv_testing.py @@ -39,7 +39,7 @@ def __init__(self, **kwargs): # eff_max_freq_testing is used as an index to pick the correct # minimum number of days to wait between tests from this list - self.months_to_wait = [12, 6, 3] # 12 months, 6 months, 3 months + self.months_to_wait = [12, 6, 3] self.rate_first_test = 0 self.rate_rep_test = 0 From fc445b506e483ff3659830a443f8915ff536bb9f Mon Sep 17 00:00:00 2001 From: "Michael A. McLeod" Date: Mon, 8 Jul 2024 12:23:49 +0100 Subject: [PATCH 53/53] Fix import --- src/tests/test_hiv_testing.py | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/tests/test_hiv_testing.py b/src/tests/test_hiv_testing.py index 58475e7e..39b2eb26 100644 --- a/src/tests/test_hiv_testing.py +++ b/src/tests/test_hiv_testing.py @@ -2,7 +2,7 @@ from math import isclose, sqrt import hivpy.column_names as col -from hivpy.common import date, floatToDate, rng, timedelta +from hivpy.common import date, float_to_date, rng, timedelta from hivpy.population import Population @@ -28,7 +28,7 @@ def test_hiv_testing_before_start(): pop = Population(size=N, start_date=date(2003, 1, 1)) pop.data[col.ADC] = True # start date is before testing begins - pop.hiv_testing.date_start_testing = floatToDate(2003.5) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) # evolve population pop.hiv_testing.update_hiv_testing(pop, timedelta(days=30)) @@ -48,8 +48,8 @@ def test_hiv_symptomatic_testing(): pop.data[col.TB_INITIAL_INFECTION] = False pop.data[col.NON_TB_WHO3] = False # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_tb = 0.5 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -125,8 +125,8 @@ def test_non_hiv_symptomatic_testing(): pop.data[col.LAST_TEST_DATE] = None pop.data[col.HIV_DIAGNOSED] = False # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.prob_test_non_hiv_symptoms = 1 pop.hiv_testing.prob_test_who4 = 0.6 pop.hiv_testing.prob_test_non_tb_who3 = 0.4 @@ -166,8 +166,8 @@ def test_general_sex_worker_testing(): pop.data[col.HIV_DIAGNOSED] = False pop.data[col.LAST_TEST_DATE] = start_date - timedelta(days=150) # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.eff_max_freq_testing = 0 pop.hiv_testing.sw_test_regularly = True pop.hiv_testing.covid_disrup_affected = False @@ -202,10 +202,10 @@ def test_general_testing_conditions(): pop.data[col.LAST_TEST_DATE] = None pop.data[col.NP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) + pop.hiv_testing.date_general_testing_plateau = float_to_date(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -258,10 +258,10 @@ def test_first_time_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) + pop.hiv_testing.date_general_testing_plateau = float_to_date(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -295,10 +295,10 @@ def test_repeat_testers(): pop.data[col.CIRCUMCISED] = False pop.data[col.CIRCUMCISION_DATE] = None # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.eff_max_freq_testing = 1 - pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) + pop.hiv_testing.date_general_testing_plateau = float_to_date(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -335,11 +335,11 @@ def test_partner_reset_after_test(): pop.data[col.NP_LAST_TEST] = 2 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.eff_max_freq_testing = 1 pop.hiv_testing.init_rate_first_test = 0.1 - pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) + pop.hiv_testing.date_general_testing_plateau = float_to_date(2015.5) pop.hiv_testing.an_lin_incr_test = 0.8 pop.hiv_testing.no_test_if_np0 = False pop.hiv_testing.sw_test_regularly = False @@ -373,10 +373,10 @@ def test_max_frequency_testing(): pop.data[col.NP_LAST_TEST] = 1 pop.data[col.NSTP_LAST_TEST] = 1 # fixing some values - pop.hiv_testing.date_start_testing = floatToDate(2003.5) - pop.hiv_testing.date_rate_testing_incr = floatToDate(2009) + pop.hiv_testing.date_start_testing = float_to_date(2003.5) + pop.hiv_testing.date_rate_testing_incr = float_to_date(2009) pop.hiv_testing.eff_max_freq_testing = index - pop.hiv_testing.date_general_testing_plateau = floatToDate(2015.5) + pop.hiv_testing.date_general_testing_plateau = float_to_date(2015.5) # guaranteed testing pop.hiv_testing.an_lin_incr_test = 1 pop.hiv_testing.no_test_if_np0 = False