diff --git a/src/hivpy/column_names.py b/src/hivpy/column_names.py index 43a5de6..f372486 100644 --- a/src/hivpy/column_names.py +++ b/src/hivpy/column_names.py @@ -115,9 +115,11 @@ FIRST_CAB_START_DATE = "first_cab_start_date" # None | date: start date of first ever injectable Cab PrEP usage FIRST_LEN_START_DATE = "first_len_start_date" # None | date: start date of first ever injectable Len PrEP usage FIRST_VR_START_DATE = "first_vr_start_date" # None | date: start date of first ever vaginal ring PrEP usage -LAST_PREP_START_DATE = "last_prep_start_date" # None | date: start date of most recent PrEP usage -LAST_PREP_STOP_DATE = "last_prep_stop_date" # None | date: stop date of most recent PrEP usage +LAST_PREP_START_DATE = "last_prep_start_date" # None | date: start date of most recent PrEP usage PREP_JUST_STARTED = "prep_just_started" # Bool: True if PrEP usage began this time step +LAST_PREP_USE_DATE = "last_prep_use_date" # None | date: most recent date of PrEP usage +LAST_PREP_STOP_DATE = "last_prep_stop_date" # None | date: stop date of most recent PrEP usage +PREP_PAUSED = "prep_paused" # Bool: True if PrEP usage is paused this time step due to ineligibility CONT_ON_PREP = "cont_on_prep" # None | timedelta: total length of continuous usage of current PrEP based on user intention (breaks due to ineligibility do not count against continuity) CONT_ACTIVE_ON_PREP = "cont_active_on_prep" # None | timedelta: actual total length of continuous usage of current PrEP CUMULATIVE_PREP_ORAL = "cumulative_prep_oral" # None | timedelta: total length of cumulative oral PrEP usage diff --git a/src/hivpy/data/prep.yaml b/src/hivpy/data/prep.yaml index 4a4c18b..95384e9 100644 --- a/src/hivpy/data/prep.yaml +++ b/src/hivpy/data/prep.yaml @@ -13,21 +13,22 @@ prob_suspect_risk_prep: 0.5 # prep preference beta prep_oral_pref_beta: Value: [1.1, 1.3, 1.5] +prep_willing_threshold: 0.2 # effect of low vl on prep willingness vl_prevalence_prep_threshold: Value: [0.005, 0.01] - -# starting prep +# prep-related testing rate_test_onprep_any: 1 -prep_willing_threshold: 0.2 prob_test_prep_start: Value: [0.25, 0.50, 0.75] + +# starting prep prob_base_prep_start: Value: [0.10, 0.30] -prob_prep_restart: - Value: [0.05, 0.10, 0.20] prob_base_prep_stop: Value: [0.05, 0.15, 0.30] prob_base_prep_stop_nonuniform: Value: [0.05, 0.15, 0.30] Probability: [0.8, 0.1, 0.1] +prob_prep_restart: + Value: [0.05, 0.10, 0.20] diff --git a/src/hivpy/prep.py b/src/hivpy/prep.py index dac38f3..96f42cf 100644 --- a/src/hivpy/prep.py +++ b/src/hivpy/prep.py @@ -46,12 +46,13 @@ def __init__(self, **kwargs): self.prep_cab_pref_beta = self.prep_oral_pref_beta + 0.3 self.prep_len_pref_beta = self.prep_cab_pref_beta self.prep_vr_pref_beta = self.prep_oral_pref_beta - 0.1 + self.prep_willing_threshold = self.p_data.prep_willing_threshold + self.vl_prevalence_affects_prep = rng.choice([True, False], p=[1/3, 2/3]) self.vl_prevalence_prep_threshold = self.p_data.vl_prevalence_prep_threshold.sample() - self.rate_test_onprep_any = self.p_data.rate_test_onprep_any - self.prep_willing_threshold = self.p_data.prep_willing_threshold self.prob_test_prep_start = self.p_data.prob_test_prep_start.sample() + # probability of starting prep in people who are eligible, willing, # and tested for HIV according to base rate of testing self.prob_base_prep_start = self.p_data.prob_base_prep_start.sample() @@ -60,12 +61,12 @@ def __init__(self, **kwargs): self.prob_cab_prep_start = self.prob_base_prep_start self.prob_len_prep_start = self.prob_base_prep_start self.prob_vr_prep_start = self.prob_base_prep_start - self.prob_prep_restart = self.p_data.prob_prep_restart.sample() # FIXME: stop probabilities dependent on time step length self.prob_oral_prep_stop = self.p_data.prob_base_prep_stop.sample() self.prob_cab_prep_stop = self.p_data.prob_base_prep_stop.sample() self.prob_len_prep_stop = self.prob_cab_prep_stop self.prob_vr_prep_stop = self.p_data.prob_base_prep_stop_nonuniform.sample() + self.prob_prep_restart = self.p_data.prob_prep_restart.sample() def init_prep_variables(self, pop: Population): pop.init_variable(col.PREP_ORAL_PREF, 0) @@ -95,8 +96,10 @@ def init_prep_variables(self, pop: Population): pop.init_variable(col.FIRST_LEN_START_DATE, None) pop.init_variable(col.FIRST_VR_START_DATE, None) pop.init_variable(col.LAST_PREP_START_DATE, None) - pop.init_variable(col.LAST_PREP_STOP_DATE, None) pop.init_variable(col.PREP_JUST_STARTED, False) + pop.init_variable(col.LAST_PREP_USE_DATE, None) + pop.init_variable(col.LAST_PREP_STOP_DATE, None) + pop.init_variable(col.PREP_PAUSED, False) pop.init_variable(col.CONT_ON_PREP, timedelta(months=0)) pop.init_variable(col.CONT_ACTIVE_ON_PREP, timedelta(months=0)) pop.init_variable(col.CUMULATIVE_PREP_ORAL, timedelta(months=0)) @@ -191,6 +194,20 @@ def get_presumed_hiv_neg_pop(self, pop: Population): return pop.apply_bool_mask(mask, false_neg_pop) + def get_prep_cont_choice_pop(self, pop: Population): + """ + Return the sub-population that have reached a point in their PrEP usage + where they can choose whether to stop or switch. For people on oral + or vaginal ring PrEP this happens every time step, but injectable PrEP + takes some time to wear off (~3 months for cab and ~6 months for len). + """ + return pop.get_sub_pop(OR(COND(col.PREP_TYPE, op.eq, PrEPType.Oral), + COND(col.PREP_TYPE, op.eq, PrEPType.VaginalRing), + AND(COND(col.PREP_TYPE, op.eq, PrEPType.Cabotegravir), + COND(col.LAST_PREP_USE_DATE, op.le, pop.date - timedelta(months=3))), + AND(COND(col.PREP_TYPE, op.eq, PrEPType.Lenacapavir), + COND(col.LAST_PREP_USE_DATE, op.le, pop.date - timedelta(months=6))))) + def prep_preference(self, pop: Population): """ Determine PrEP preferences for all PrEP types. @@ -529,6 +546,7 @@ def tested_start_prep(self, pop: Population, prep_eligible_pop, prep_type, pop.set_present_variable(col.CONT_ACTIVE_ON_PREP, time_step, starting_prep_pop) # increment cumulative use self.set_all_prep_cumulative(pop, starting_prep_pop, time_step) + pop.set_present_variable(col.LAST_PREP_USE_DATE, pop.date, starting_prep_pop) def general_start_prep(self, pop: Population, prep_eligible_pop, time_step): """ @@ -555,6 +573,7 @@ def general_start_prep(self, pop: Population, prep_eligible_pop, time_step): pop.set_present_variable(col.CONT_ACTIVE_ON_PREP, time_step, starting_prep_pop) # increment cumulative use self.set_all_prep_cumulative(pop, starting_prep_pop, time_step) + pop.set_present_variable(col.LAST_PREP_USE_DATE, pop.date, starting_prep_pop) def set_all_prep_start_dates(self, pop: Population, starting_prep_pop): """ @@ -658,22 +677,32 @@ def continue_prep(self, pop: Population, time_step): OR(COND(col.LAST_TEST_DATE, op.ne, pop.date), AND(COND(col.LAST_TEST_DATE, op.eq, pop.date), COND(col.HIV_DIAGNOSED, op.eq, False))))) + # people who can choose to stop or switch this time step + prep_choice_pop = pop.get_sub_pop_intersection(eligible, self.get_prep_cont_choice_pop(pop)) if len(eligible) > 0: # continuous prep outcomes prep_types = pop.transform_group([col.PREP_TYPE, col.FAVOURED_PREP_TYPE], - self.calc_current_prep, sub_pop=eligible, dropna=True) + self.calc_current_prep, sub_pop=prep_choice_pop, dropna=True) # find various sub-populations # people who are continuing current prep - continuing_prep_mask = pop.get_variable(col.PREP_TYPE, eligible) == prep_types - continuing_prep_pop = pop.apply_bool_mask(continuing_prep_mask, eligible) + continuing_prep_mask = pop.get_variable(col.PREP_TYPE, prep_choice_pop) == prep_types + continuing_prep_choice_pop = pop.apply_bool_mask(continuing_prep_mask, prep_choice_pop) + # need to differentiate between people who had the choice to stop but continued instead and + # people on injectable prep that continued automatically without making a choice this time step + # (all prep users need to have cumulative prep use updated but only people who chose to + # continue/switch should have their last prep use date updated) + continuing_prep_no_choice_pop = pop.get_variable(col.PREP_TYPE, eligible).index.difference(prep_types.index) + continuing_prep_pop = pop.get_sub_pop_union(continuing_prep_choice_pop, continuing_prep_no_choice_pop) # people who are switching prep - switching_prep_mask = (pop.get_variable(col.PREP_TYPE, eligible) != prep_types) & prep_types.notnull() - switching_prep_pop = pop.apply_bool_mask(switching_prep_mask, eligible) + switching_prep_mask = ((pop.get_variable(col.PREP_TYPE, prep_choice_pop) != prep_types) & + prep_types.notnull()) + switching_prep_pop = pop.apply_bool_mask(switching_prep_mask, prep_choice_pop) # people who are either continuing or switching prep - using_prep_pop = pop.apply_bool_mask(prep_types.notnull(), eligible) + using_prep_choice_pop = pop.apply_bool_mask(prep_types.notnull(), prep_choice_pop) + using_prep_pop = pop.get_sub_pop_union(using_prep_choice_pop, continuing_prep_no_choice_pop) # people who are stopping prep - stopping_prep_pop = pop.apply_bool_mask(prep_types.isnull(), eligible) + stopping_prep_pop = pop.apply_bool_mask(prep_types.isnull(), prep_choice_pop) if len(continuing_prep_pop) > 0: prep_cont = pop.get_variable(col.CONT_ON_PREP, eligible) + time_step @@ -691,6 +720,10 @@ def continue_prep(self, pop: Population, time_step): pop.set_present_variable(col.CONT_ON_PREP, time_step, switching_prep_pop) pop.set_present_variable(col.CONT_ACTIVE_ON_PREP, time_step, switching_prep_pop) + if len(using_prep_choice_pop) > 0: + # set last use date only for people who made a choice this time step + pop.set_present_variable(col.LAST_PREP_USE_DATE, pop.date, using_prep_choice_pop) + if len(using_prep_pop) > 0: # increment cumulative use self.set_all_prep_cumulative(pop, using_prep_pop, time_step) @@ -734,8 +767,8 @@ def restart_prep(self, pop: Population, time_step): if len(eligible) > 0: # starting prep outcomes - prep_types = pop.transform_group([col.FAVOURED_PREP_TYPE], self.calc_restarting_prep, - sub_pop=eligible, dropna=True) + prep_types = pop.transform_group([col.FAVOURED_PREP_TYPE, col.PREP_PAUSED], + self.calc_restarting_prep, sub_pop=eligible, dropna=True) # people who are restarting prep restarting_prep_pop = pop.apply_bool_mask(prep_types.notnull(), eligible) @@ -750,21 +783,47 @@ def restart_prep(self, pop: Population, time_step): pop.set_present_variable(col.CONT_ACTIVE_ON_PREP, time_step, restarting_prep_pop) # increment cumulative use self.set_all_prep_cumulative(pop, restarting_prep_pop, time_step) + pop.set_present_variable(col.LAST_PREP_USE_DATE, pop.date, restarting_prep_pop) # unset stop date pop.set_present_variable(col.LAST_PREP_STOP_DATE, None, restarting_prep_pop) + # unpause prep + pop.set_present_variable(col.PREP_PAUSED, False, restarting_prep_pop) - def calc_restarting_prep(self, favoured_prep, size): + def calc_restarting_prep(self, favoured_prep, prep_paused, size): """ Returns PrEP types for people restarting PrEP. Individual preferences and availability are taken into account. """ # outcomes r = rng.uniform(size=size) - restarting = r < self.prob_prep_restart + # those who stopped due to ineligibility are guaranteed to restart + if prep_paused: + restarting = r < 1 + else: + restarting = r < self.prob_prep_restart prep = [favoured_prep if r else None for r in restarting] return prep + def stop_prep(self, pop: Population): + """ + Update PrEP usage for people stopping PrEP due to lack of eligibility or positive test. + """ + # people who are using prep and are now ineligible + ineligible = pop.get_sub_pop(AND(OR(COND(col.HIV_DIAGNOSED, op.eq, True), + COND(col.PREP_ELIGIBLE, op.eq, False)), + COND(col.EVER_PREP, op.eq, True), + COND(col.LAST_PREP_STOP_DATE, op.eq, None), + COND(col.LAST_TEST_DATE, op.eq, pop.date))) + + if len(ineligible) > 0: + # reset active continuous use + pop.set_present_variable(col.CONT_ACTIVE_ON_PREP, timedelta(months=0), ineligible) + # set stop date + pop.set_present_variable(col.LAST_PREP_STOP_DATE, pop.date, ineligible) + # pause prep + pop.set_present_variable(col.PREP_PAUSED, True, ineligible) + def prep_usage(self, pop: Population, time_step): """ Update PrEP usage for people starting, continuing, switching, restarting, and stopping PrEP. @@ -775,3 +834,5 @@ def prep_usage(self, pop: Population, time_step): self.continue_prep(pop, time_step) # restarting prep self.restart_prep(pop, time_step) + # stopping prep + self.stop_prep(pop) diff --git a/src/hivpy/prep_data.py b/src/hivpy/prep_data.py index 4699e5c..90362b4 100644 --- a/src/hivpy/prep_data.py +++ b/src/hivpy/prep_data.py @@ -22,15 +22,15 @@ def __init__(self, filename): self.prob_suspect_risk_prep = self.data["prob_suspect_risk_prep"] self.prep_oral_pref_beta = self._get_discrete_dist("prep_oral_pref_beta") + self.prep_willing_threshold = self.data["prep_willing_threshold"] self.vl_prevalence_prep_threshold = self._get_discrete_dist("vl_prevalence_prep_threshold") - self.rate_test_onprep_any = self.data["rate_test_onprep_any"] - self.prep_willing_threshold = self.data["prep_willing_threshold"] self.prob_test_prep_start = self._get_discrete_dist("prob_test_prep_start") + self.prob_base_prep_start = self._get_discrete_dist("prob_base_prep_start") - self.prob_prep_restart = self._get_discrete_dist("prob_prep_restart") self.prob_base_prep_stop = self._get_discrete_dist("prob_base_prep_stop") self.prob_base_prep_stop_nonuniform = self._get_discrete_dist("prob_base_prep_stop_nonuniform") + self.prob_prep_restart = self._get_discrete_dist("prob_prep_restart") except KeyError as ke: print(ke.args) diff --git a/src/tests/test_prep.py b/src/tests/test_prep.py index b8a2d7f..eda55c2 100644 --- a/src/tests/test_prep.py +++ b/src/tests/test_prep.py @@ -758,6 +758,8 @@ def test_continuing_prep(): pop.data[col.CUMULATIVE_PREP_LEN] = time_step pop.data[col.CUMULATIVE_PREP_VR] = time_step pop.data[col.LAST_TEST_DATE] = pop.date - timedelta(months=3) + pop.data[col.LAST_PREP_USE_DATE] = [pop.date - time_step, pop.date - timedelta(months=3), + pop.date - timedelta(months=6), pop.date - time_step] * (N // 4) # prep types spread evenly among population pop.data[col.PREP_TYPE] = [PrEPType.Oral, PrEPType.Cabotegravir, PrEPType.Lenacapavir, PrEPType.VaginalRing] * (N // 4) @@ -801,14 +803,18 @@ def test_continuing_prep(): ((pop.data[col.CUMULATIVE_PREP_VR] == time_step) == (pop.data[col.LAST_PREP_STOP_DATE] == pop.date))) + pop.data[col.CONT_ON_PREP] = timedelta(months=3) + pop.data[col.CONT_ACTIVE_ON_PREP] = timedelta(months=2) pop.data[col.LAST_PREP_STOP_DATE] = None + pop.data[col.LAST_PREP_USE_DATE] = [pop.date - time_step, pop.date - timedelta(months=3), + pop.date - timedelta(months=6), pop.date - time_step] * (N // 4) # nobody is taking their favoured prep type pop.data[col.FAVOURED_PREP_TYPE] = [PrEPType.VaginalRing, PrEPType.Lenacapavir, PrEPType.Cabotegravir, PrEPType.Oral] * (N // 4) pop.prep.continue_prep(pop, time_step) # expecting 90% of people to switch prep - no_on_prep = sum(pop.data[col.CONT_ACTIVE_ON_PREP] == timedelta(months=1)) + no_on_prep = sum(pop.data[col.CONT_ACTIVE_ON_PREP] == time_step) mean = N * (1 - prob_base_prep_stop) stdev = sqrt(mean * prob_base_prep_stop) assert mean - 3 * stdev <= no_on_prep <= mean + 3 * stdev @@ -836,6 +842,50 @@ def test_continuing_prep(): ((pop.data[col.CUMULATIVE_PREP_VR] == time_step) == (pop.data[col.LAST_PREP_STOP_DATE] == pop.date))) + pop.data[col.LAST_PREP_STOP_DATE] = None + pop.data[col.LAST_PREP_USE_DATE] = pop.date - timedelta(months=3) + pop.data[col.PREP_TYPE] = [PrEPType.Cabotegravir, PrEPType.Lenacapavir] * (N // 2) + pop.data[col.FAVOURED_PREP_TYPE] = PrEPType.Oral + # nobody stops prep + prob_base_prep_stop = 0 + pop.prep.prob_oral_prep_stop = prob_base_prep_stop + pop.prep.prob_cab_prep_stop = prob_base_prep_stop + pop.prep.prob_len_prep_stop = prob_base_prep_stop + pop.prep.prob_vr_prep_stop = prob_base_prep_stop + + pop.prep.continue_prep(pop, time_step) + # len prep not updated because last prep usage is too recent + assert all((pop.data[col.PREP_TYPE] == PrEPType.Lenacapavir) == + (pop.data[col.LAST_PREP_USE_DATE] == pop.date - timedelta(months=3))) + assert all((pop.data[col.PREP_TYPE] == PrEPType.Oral) == + (pop.data[col.LAST_PREP_USE_DATE] == pop.date)) + assert all(pop.data[col.LAST_PREP_STOP_DATE].isnull()) + + pop.data[col.LAST_PREP_USE_DATE] = pop.date - time_step + pop.data[col.PREP_TYPE] = [PrEPType.Oral, PrEPType.Cabotegravir, + PrEPType.Lenacapavir, PrEPType.VaginalRing] * (N // 4) + + pop.prep.continue_prep(pop, time_step) + # no injectable prep updated because last prep usage is too recent + assert all((pop.data[col.PREP_TYPE] != PrEPType.Oral) == + (pop.data[col.LAST_PREP_USE_DATE] == pop.date - time_step)) + assert all((pop.data[col.PREP_TYPE] == PrEPType.Oral) == + (pop.data[col.LAST_PREP_USE_DATE] == pop.date)) + assert sum(pop.data[col.PREP_TYPE] == PrEPType.Oral) == N/2 + assert all(pop.data[col.LAST_PREP_STOP_DATE].isnull()) + + pop.data[col.LAST_PREP_USE_DATE] = pop.date - time_step + pop.data[col.PREP_TYPE] = PrEPType.Lenacapavir + pop.data[col.CONT_ON_PREP] = time_step + pop.data[col.CUMULATIVE_PREP_LEN] = timedelta(months=2) + + pop.prep.continue_prep(pop, time_step) + # everyone continues prep without choice + assert all(pop.data[col.PREP_TYPE] == PrEPType.Lenacapavir) + assert all(pop.data[col.LAST_PREP_USE_DATE] == pop.date - time_step) + assert all((pop.data[col.CONT_ON_PREP] == timedelta(months=2))) + assert all((pop.data[col.CUMULATIVE_PREP_LEN] == timedelta(months=3))) + def test_restarting_prep(): N = 100 @@ -846,6 +896,7 @@ def test_restarting_prep(): pop.data[col.PREP_ELIGIBLE] = True pop.data[col.EVER_PREP] = True pop.data[col.LAST_PREP_STOP_DATE] = pop.date - time_step + pop.data[col.PREP_PAUSED] = False pop.data[col.PREP_JUST_STARTED] = False pop.data[col.CONT_ON_PREP] = timedelta(months=0) pop.data[col.CONT_ACTIVE_ON_PREP] = timedelta(months=0) @@ -858,7 +909,7 @@ def test_restarting_prep(): pop.prep.prob_prep_restart = 0.5 pop.prep.restart_prep(pop, time_step) - # expecting 90% of people to restart prep + # expecting 50% of people to restart prep no_on_prep = sum(pop.data[col.LAST_PREP_STOP_DATE].isnull()) mean = N * pop.prep.prob_prep_restart stdev = sqrt(mean * (1 - pop.prep.prob_prep_restart)) @@ -868,3 +919,40 @@ def test_restarting_prep(): (pop.data[col.LAST_PREP_STOP_DATE].isnull())) assert all((pop.data[col.CONT_ACTIVE_ON_PREP] == time_step) == (pop.data[col.LAST_PREP_STOP_DATE].isnull())) + + pop.data[col.LAST_PREP_STOP_DATE] = pop.date - time_step + pop.data[col.PREP_PAUSED] = True + pop.prep.restart_prep(pop, time_step) + # expecting everyone to restart prep + assert sum(pop.data[col.LAST_PREP_STOP_DATE].isnull()) == N + + +def test_stopping_prep(): + N = 100 + pop = Population(size=N, start_date=date(5000, 1, 1)) + pop.prep.date_prep_intro = [date(2000), date(3000), date(4000), date(5000)] + pop.data[col.HIV_DIAGNOSED] = False + pop.data[col.PREP_ELIGIBLE] = False + pop.data[col.EVER_PREP] = True + pop.data[col.LAST_PREP_STOP_DATE] = None + pop.data[col.PREP_PAUSED] = False + pop.data[col.LAST_TEST_DATE] = pop.date + pop.data[col.CONT_ON_PREP] = timedelta(months=2) + pop.data[col.CONT_ACTIVE_ON_PREP] = timedelta(months=2) + + pop.prep.stop_prep(pop) + # expecting everyone to stop prep due to ineligibility + assert sum(pop.data[col.LAST_PREP_STOP_DATE] == pop.date) == N + assert sum(pop.data[col.PREP_PAUSED]) == N + # check continuous prep usage + assert sum(pop.data[col.CONT_ON_PREP] == timedelta(months=2)) == N + assert sum(pop.data[col.CONT_ACTIVE_ON_PREP] == timedelta(months=0)) == N + + pop.data[col.HIV_DIAGNOSED] = True + pop.data[col.PREP_ELIGIBLE] = True + pop.data[col.LAST_PREP_STOP_DATE] = None + pop.data[col.PREP_PAUSED] = False + pop.prep.stop_prep(pop) + # expecting everyone to stop prep due to positive diagnosis + assert sum(pop.data[col.LAST_PREP_STOP_DATE] == pop.date) == N + assert sum(pop.data[col.PREP_PAUSED]) == N diff --git a/tutorials/hiv_diagnosis.md b/tutorials/hiv_diagnosis.md index 15dbdfa..04c9d6e 100644 --- a/tutorials/hiv_diagnosis.md +++ b/tutorials/hiv_diagnosis.md @@ -7,7 +7,7 @@ The HIV diagnosis module tracks the current HIV test type, test sensitivities, a - `src/hivpy/data/hiv_diagnosis.yaml` - HIV diagnosis data and variables. - `src/hivpy/hiv_diagnosis_data.py` - A class for storing data loaded from `hiv_diagnosis.yaml`. -If there are any testing-related variables you would like to change before running your simulation, please change them in `hiv_diagnosis.yaml`. +If there are any diagnosis-related variables you would like to change before running your simulation, please change them in `hiv_diagnosis.yaml`. ### Module Overview diff --git a/tutorials/intervention.md b/tutorials/intervention.md index c64328e..f4a8a78 100644 --- a/tutorials/intervention.md +++ b/tutorials/intervention.md @@ -1,6 +1,6 @@ ## Intervention Tutorial -Intervention is an optional part of the simulation which is triggered at the _year of intervention_; this allows the simulation to fork at this point into two independent simulations which differ in some properties. The changes to the properties of the simulation are determined by the _intervention function_, what can modify properties of the population or any of the simulation modules. After the year on intervention, one simulation will continue with the intervention function applied, and the other will continue with no changes. The intervention function may be applied once at the year of intervention, or every timestep following that point. +Intervention is an optional part of the simulation which is triggered at the _year of intervention_; this allows the simulation to fork at this point into two independent simulations which differ in some properties. The changes to the properties of the simulation are determined by the _intervention function_, what can modify properties of the population or any of the simulation modules. After the year on intervention, one simulation will continue with the intervention function applied, and the other will continue with no changes. The intervention function may be applied once at the year of intervention, or every timestep following that point. The most relevant files are listed below: @@ -9,14 +9,14 @@ The most relevant files are listed below: - `src/hivpy/hivpy_intervention.yaml` - Sample file for running with intervention-related variables - `src/tests/test_population.py` - Test for understanding how the population class is modified -If there are any testing-related variables you would like to change before running your simulation, please change them in `hivpy_intervention.yaml` and `simulation.py`. +If there are any intervention-related variables you would like to change before running your simulation, please change them in `hivpy_intervention.yaml` and `simulation.py`. To make use of the intervention-related option there are two steps that need to be followed: -1) to add/modify the relevent intervention-related variables (intervention year, intervention option etc.) at the configuration file that is used to launch the simulation, -2) to ensure that the relevent intervention option is implemented in the intervention function in `simulation.py`. +1) to add/modify the relevant intervention-related variables (intervention year, intervention option etc.) at the configuration file that is used to launch the simulation, +2) to ensure that the relevant intervention option is implemented in the intervention function in `simulation.py`. -These steps are described in more detail below: +These steps are described in more detail below: ### Modifying the configuration file @@ -34,25 +34,26 @@ run_model hivpy_intervention.yaml ### Modifying the simulation module -If an intervention year is set in the configuration file the `intervention` function is called in the simulation module. A synopsis of the process is: if the intervention year exists the simulation runs until the intervention year; a deep copy of the population object is created (this includes all of the modules that it owns such as sexual behaviour, HIV status, etc.); the simulation advances in two seperate outcomes with and without the intervention being implemented. +If an intervention year is set in the configuration file the `intervention` function is called in the simulation module. A synopsis of the process is: if the intervention year exists the simulation runs until the intervention year; a deep copy of the population object is created (this includes all of the modules that it owns such as sexual behaviour, HIV status, etc.); the simulation advances in two separate outcomes with and without the intervention being implemented. -The 'intervention' function takes as input the 'intervention_option' provided as a numeric value in the configuration file and updates the population / modules accordingly. +The 'intervention' function takes as input the 'intervention_option' provided as a numeric value in the configuration file and updates the population / modules accordingly. #### Adding an intervention option in the simulation -To add a new intervention option into the simulation, the 'intervention' function in the simulation module needs to be modified. To do so a condition with the number of the option should be added and the corresponding population sub-module should be accessed. +To add a new intervention option into the simulation, the 'intervention' function in the simulation module needs to be modified. To do so a condition with the number of the option should be added and the corresponding population sub-module should be accessed. **Options with negative numbers (-1, -2) have been reserved for the purposes of testing and providing example code in the intervention function.** -For example, option no. `-1` changes the starting date of the sex worker program setting as starting date the intervention date: +For example, option no. `-1` changes the starting date of the sex worker program setting as starting date the intervention date: ```bash if option == -1: pop.sexual_behaviour.sw_program_start_date = pop.date - self.simulation_config.time_step - + ``` - `if` statements can be more complex if desired, e.g. `if option in [1, 2, 3, 4]:` can be used to define code which applies to all the options in that list. - Option code can modify any of the modules or the population itself through the population object (`pop`); in this case we are changing the `sw_program_start_date` by accessing the sexual behaviour module (`sexual_behaviour`). -- Alternative options can be added in a similar way. +- Alternative options can be added in a similar way. + +#### Recurrent intervention -#### Recurrent intervention -If the `repeat_intervention` is set to True in the configuration file, the intervention is set to repeat for every timestep after the intervention year. This can be useful if there is reason to believe that changes made in the option code may be overridden by other parts of the simulation. \ No newline at end of file +If the `repeat_intervention` is set to True in the configuration file, the intervention is set to repeat for every timestep after the intervention year. This can be useful if there is reason to believe that changes made in the option code may be overridden by other parts of the simulation. diff --git a/tutorials/post_processing.md b/tutorials/post_processing.md index 170860c..66158d2 100644 --- a/tutorials/post_processing.md +++ b/tutorials/post_processing.md @@ -5,7 +5,7 @@ Each time a HIVpy model run is completed, some basic post-processing is run at t ### Usage ```bash -python src/hivpy/post_processing.py [-hi hipvy_input] [-si sas_input] [-eec] +python src/hivpy/post_processing.py [-hi hipvy_input] [-si sas_input] [-eec early_epidemic_comparison] ``` - `config` - Path to configuration file containing names of expected graph output columns. diff --git a/tutorials/prep.md b/tutorials/prep.md index 8695211..ebf9bc4 100644 --- a/tutorials/prep.md +++ b/tutorials/prep.md @@ -1,10 +1,87 @@ ## PrEP Tutorial -The PrEP module tracks PrEP eligibility, willingness, and use, including starting, stopping, and restarting PrEP. The most relevant files are listed below: +The PrEP module tracks PrEP preferences, willingness, eligibility, and use, including starting, stopping, and restarting PrEP. The most relevant files are listed below: - `src/hivpy/prep.py` - The PrEP module. - `src/tests/test_prep.py` - Tests for the PrEP module. - `src/hivpy/data/prep.yaml` - PrEP data and variables. - `src/hivpy/prep_data.py` - A class for storing data loaded from `prep.yaml`. -If there are any testing-related variables you would like to change before running your simulation, please change them in `prep.yaml`. +If there are any PrEP-related variables you would like to change before running your simulation, please change them in `prep.yaml`. + +### Module Overview + +When PrEP is updated, the first thing to be calculated is PrEP propensity for the population aged 15 and over. For each individual, this includes: setting preference values for each type of PrEP, determining which types of PrEP they are willing to take, setting preference ranks for each type of PrEP, and calculating their 'favoured PrEP' – the type of PrEP with the highest preference value someone is willing to take that is also currently available. + +Next, PrEP eligibility is determined. Based on the current PrEP strategy, a sub-population of people is selected to be eligible for PrEP usage this time step. Eligible people typically either have short-term partners or have a long-term partner who is not on ART. + +Finally, PrEP usage is updated for anyone starting, continuing, stopping, or restarting PrEP. (`Note`: PrEP usage relies on the assumption that HIV diagnosis has already taken place in order to identify people that are HIV positive but have falsely not been diagnosed.) + +Individuals can be specifically tested to start PrEP for the first time, but tested people in the general population can also decide to start PrEP. When people continue PrEP usage, they can either continue with their current PrEP or switch to a different type if their favoured PrEP has changed. PrEP usage can be stopped for two reasons – an individual can choose to stop, or they can become ineligible for PrEP. People who have chosen to stop taking PrEP but are still eligible can also choose to restart, but anyone who stopped taking PrEP due to a break in eligibility automatically restarts. + +### PrEP Columns + +- *`R_PREP`* - A semi-permanent random float variable that determines whether an individual is risk informed or suspects they are at risk enough to take PrEP. Rerolled only for ineligible people when determining PrEP eligibility each time step. +- *`PREP_ORAL_PREF`* - A float value between [0, 1] drawn from a beta distribution that determines an individual's preference for oral PrEP. +- *`PREP_CAB_PREF`* - A float value between [0, 1] drawn from a beta distribution that determines an individual's preference for cabotegravir PrEP. +- *`PREP_LEN_PREF`* - A float value between [0, 1] drawn from a beta distribution that determines an individual's preference for lenacapavir PrEP. +- *`PREP_VR_PREF`* - A float value between [0, 1] drawn from a beta distribution that determines an individual's preference for vaginal ring PrEP. +- *`PREP_ORAL_RANK`* - An integer value between [1, 4] representing an individual's ranked PrEP preference for oral PrEP. +- *`PREP_CAB_RANK`* - An integer value between [1, 4] representing an individual's ranked PrEP preference for cabotegravir PrEP. +- *`PREP_LEN_RANK`* - An integer value between [1, 4] representing an individual's ranked PrEP preference for lenacapavir PrEP. +- *`PREP_VR_RANK`* - An integer value between [1, 4] representing an individual's ranked PrEP preference for vaginal ring PrEP. +- *`PREP_ORAL_WILLING`* - A boolean flag signifying whether an individual is willing to use oral PrEP. True if their oral preference value clears a willingness threshold. +- *`PREP_CAB_WILLING`* - A boolean flag signifying whether an individual is willing to use cabotegravir PrEP. True if their cab preference value clears a willingness threshold. +- *`PREP_LEN_WILLING`* - A boolean flag signifying whether an individual is willing to use lenacapavir PrEP. True if their len preference value clears a willingness threshold. +- *`PREP_VR_WILLING`* - A boolean flag signifying whether an individual is willing to use vaginal ring PrEP. True if their vr preference value clears a willingness threshold. +- *`FAVOURED_PREP_TYPE`* - The `PrEPType` with the highest preference value an individual is willing to take that is also currently available. If there is no PrEP available that they are willing to take, this value is set to None. +- *`PREP_ELIGIBLE`* - A boolean flag signifying whether an individual is eligible for PrEP this time step. +- *`PREP_ORAL_TESTED`* - A boolean flag signifying whether an individual has tested for HIV specifically for the purpose of starting oral PrEP. `Note`: Currently dummied. +- *`PREP_CAB_TESTED`* - A boolean flag signifying whether an individual has tested for HIV specifically for the purpose of starting cabotegravir PrEP. `Note`: Currently dummied. +- *`PREP_LEN_TESTED`* - A boolean flag signifying whether an individual has tested for HIV specifically for the purpose of starting lenacapavir PrEP. `Note`: Currently dummied. +- *`PREP_VR_TESTED`* - A boolean flag signifying whether an individual has tested for HIV specifically for the purpose of starting vaginal ring PrEP. `Note`: Currently dummied. +- *`PREP_TYPE`* - The most recent `PrEPType` an individual has used, otherwise None if they have never used PrEP. This column is kept intact upon stopping PrEP usage. +- *`EVER_PREP`* - A boolean flag signifying whether an individual has ever been on PrEP. +- *`FIRST_ORAL_START_DATE`* - The start date of an individual's first ever usage of oral PrEP, otherwise None if they have never used oral PrEP. +- *`FIRST_CAB_START_DATE`* - The start date of an individual's first ever usage of cabotegravir PrEP, otherwise None if they have never used cab PrEP. +- *`FIRST_LEN_START_DATE`* - The start date of an individual's first ever usage of lenacapavir PrEP, otherwise None if they have never used len PrEP. +- *`FIRST_VR_START_DATE`* - The start date of an individual's first ever usage of vaginal ring PrEP, otherwise None if they have never used vr PrEP. +- *`LAST_PREP_START_DATE`* - The start date of an individual's most recent period of PrEP usage. +- *`PREP_JUST_STARTED`* - A boolean flag signifying whether an individual started using PrEP this time step. +- *`LAST_PREP_USE_DATE`* - The date of an individual's most recent PrEP usage (at a time step granularity). +- *`LAST_PREP_STOP_DATE`* - The stop date of an individual's most recent period of PrEP usage. Reset to None if they restart PrEP. +- *`PREP_PAUSED`* - A boolean flag signifying whether an individual has paused their PrEP usage this time step due to ineligibility. +- *`CONT_ON_PREP`* - A timedelta tracking the total length of continuous PrEP usage (at a time step granularity) of the current type of PrEP based on user intention. Breaks due to ineligibility do not count against continuity, but choosing to stop using PrEP does. +- *`CONT_ACTIVE_ON_PREP`* - A timedelta tracking the actual total length of continuous PrEP usage (at a time step granularity) of the current type of PrEP. Both choosing to stop using PrEP and dropping out due to ineligibility will reset continuity. +- *`CUMULATIVE_PREP_ORAL`* - A timedelta tracking the total length of cumulative oral PrEP usage. +- *`CUMULATIVE_PREP_CAB`* - A timedelta tracking the total length of cumulative cabotegravir PrEP usage. +- *`CUMULATIVE_PREP_LEN`* - A timedelta tracking the total length of cumulative lenacapavir PrEP usage. +- *`CUMULATIVE_PREP_VR`* - A timedelta tracking the total length of cumulative vaginal ring PrEP usage. + +### PrEP Data Variables + +- *`prep_strategy`* - An integer that determines which sub-population of people is marked as eligible for PrEP. +- *`date_prep_intro`* - An array containing the introduction dates for each type of PrEP. Intended to be accessed through the use of `PrEPType`s as indices (e.g. `date_prep_intro[PrEPType.Oral]`). +- *`cab_available`* - The boolean flag that determines the availability of cabotegravir PrEP. Cab is only available if the current date has reached the cab introduction date and this flag is True. +- *`prob_risk_informed_prep`* - The probability of an individual with an *uninfected* long-term partner who is not on ART to meet the risk-informed PrEP eligibility criteria. +- *`prob_greater_risk_informed_prep`* - As `prob_risk_informed_prep`, but with a higher probability value. Used with certain `prep_strategy` values. +- *`prob_suspect_risk_prep`* - The probability of an individual with an *infected* long-term partner who is not on ART to meet the risk-informed PrEP eligibility criteria. +- *`prep_oral_pref_beta`* - The alpha value used to draw random oral PrEP preference values for the population from a beta distribution. +- *`prep_cab_pref_beta`* - The alpha value used to draw random cabotegravir PrEP preference values for the population from a beta distribution. +- *`prep_len_pref_beta`* - The alpha value used to draw random lenacapavir PrEP preference values for the population from a beta distribution. +- *`prep_vr_pref_beta`* - The alpha value used to draw random vaginal ring PrEP preference values for the population from a beta distribution. +- *`prep_willing_threshold`* - A threshold value that a PrEP preference value must exceed in order for an individual to be willing to take the respective type of PrEP. +- *`vl_prevalence_affects_prep`* - A boolean flag that dictates whether willingness to take PrEP is affected by low unsuppressed viral load prevalence in the population. +- *`vl_prevalence_prep_threshold`* - The threshold at which low unsuppressed viral load prevalence affects PrEP willingness. +- *`rate_test_onprep_any`* - The rate of being tested for HIV while on PrEP. +- *`prob_test_prep_start`* - The probability of being tested for HIV with the intent to start PrEP. +- *`prob_base_prep_start`* - The base probability of starting any type of PrEP for the first time. +- *`prob_oral_prep_start`* - The probability of starting oral PrEP for the first time. +- *`prob_cab_prep_start`* - The probability of starting cabotegravir PrEP for the first time. +- *`prob_len_prep_start`* - The probability of starting lenacapavir PrEP for the first time. +- *`prob_vr_prep_start`* - The probability of starting vaginal ring PrEP for the first time. +- *`prob_oral_prep_stop`* - The probability of choosing to stop taking oral PrEP despite being eligible. +- *`prob_cab_prep_stop`* - The probability of choosing to stop taking cabotegravir PrEP despite being eligible. +- *`prob_len_prep_stop`* - The probability of choosing to stop taking lenacapavir PrEP despite being eligible. +- *`prob_vr_prep_stop`* - The probability of choosing to stop taking vaginal ring PrEP despite being eligible. +- *`prob_prep_restart`* - The probability of restarting any type of PrEP after choosing to stop taking PrEP.