interventions.hiv_interventions

interventions.hiv_interventions

Define HIV interventions for STIsim By default, these all have units of a year and timesteps of 1/12

Classes

Name Description
ART Antiretroviral therapy intervention.
HIVDx HIV diagnostic product used by :class:HIVTest.
HIVTest HIV-specific testing intervention.
InfantHIVTest HIV test for infants born to mothers diagnosed during pregnancy.
Prep Pre-exposure prophylaxis (PrEP).
VMMC Voluntary medical male circumcision.

ART

interventions.hiv_interventions.ART(
    pars=None,
    coverage=None,
    vls_coverage=None,
    smoothness=0,
    format_priority='n',
    **kwargs,
)

Antiretroviral therapy intervention.

Requires HIVTest (or equivalent) to diagnose agents first — ART only initiates agents who have hiv.diagnosed=True. A warning is raised if no HIVTest is found in the sim.

Processing flow each timestep

  1. Agents scheduled to stop ART are removed
  2. Agents whose scheduled ART start has arrived (ti_art == this timestep and not on_art) are filtered by art_initiation probability
  3. If coverage is specified: agents are added/removed to match the target number, prioritized by CD4 count and care-seeking propensity
  4. If no coverage is specified: all who pass art_initiation go on ART (no capacity constraint)
  5. Mothers on ART reduce infant susceptibility (prenatal via MaternalNet, postnatal via BreastfeedingNet) by pmtct_efficacy

The scheduling (setting ti_art) is owned by the testing intervention that diagnoses the agent (HIVTest, ANCTest). ART here is passive: it picks up whoever is ready to start.

Coverage is parsed by :func:parse_coverage and accepts: - None: no coverage target; treat all who initiate (default) - Scalar (e.g. 0.8): constant proportion of infected on ART - Dict: {'year': [...], 'value': [...]} — interpolated to yearvec - Dict with mixed format: {'year': [...], 'value': [...], 'format': ['n','n','p','p']} - Single-column DataFrame: index=years, column n_art or p_art - Dual-column DataFrame: both n_art and p_art columns — uses format_priority to resolve (default: n_art when non-NaN) - Stratified DataFrame: columns Year, AgeBin (e.g. [15,25)), and optionally Gender/Sex, plus a numeric value column

Intervention ordering: HIVTest must appear before ART in the interventions list so that agents diagnosed this timestep can initiate ART in the same step.

Parameters

Name Type Description Default
coverage coverage target in any format above (default None) None
art_initiation probability a newly diagnosed person initiates ART (default: ss.bernoulli(p=0.9)). Set to 1 to treat all diagnosed. required
pmtct_efficacy efficacy of maternal ART in reducing infant susceptibility to HIV (default 0.96). Applied to both prenatal (MaternalNet) and postnatal (BreastfeedingNet) transmission. Set to 1.0 for complete protection (previous default behavior). required
vls_coverage fraction of newly-initiated agents who achieve viral suppression (effective ART) rather than non-suppressive ART. Accepts the same formats as coverage (scalar, time-varying dict, DataFrame, or age/sex-stratified DataFrame) — see :func:parse_coverage, though values must be proportions (0-1), not absolute counts. Default None means 100% of initiators achieve viral suppression. Any stratum not covered by a stratified vls_coverage also defaults to 100%. Forwarded to HIV.start_art() at initiation. None
smoothness interpolation smoothness (0=linear, default) 0
format_priority when both n_art and p_art are non-NaN, prefer this format (‘n’ or ‘p’, default ‘n’) 'n'

Examples::

# Simple: 80% of infected on ART
art = sti.ART(coverage=0.8)

# 70% of newly-initiated agents achieve viral suppression
art = sti.ART(coverage=0.8, vls_coverage=0.7)

# Time-varying VLS coverage
art = sti.ART(coverage=0.8, vls_coverage={'year': [2000, 2010, 2025], 'value': [0.5, 0.7, 0.9]})

# Age/sex-stratified VLS coverage — e.g. lower suppression in young men
vls_df = pd.DataFrame({
    'Year':    [2020, 2020, 2020, 2020],
    'AgeBin':  ['[15,25)', '[15,25)', '[25,100)', '[25,100)'],
    'Gender':  ['m', 'f', 'm', 'f'],
    'p_vls':   [0.5, 0.7, 0.75, 0.85],
})
art = sti.ART(coverage=0.8, vls_coverage=vls_df)

# Time-varying coverage
art = sti.ART(coverage={'year': [2000, 2010, 2025], 'value': [0, 0.5, 0.9]})

# No coverage target — 90% of newly diagnosed initiate (default art_initiation)
art = sti.ART()

# From a CSV file
art = sti.ART(coverage=pd.read_csv('art_coverage.csv').set_index('year'))

# Historical n_art then projected p_art
df = pd.read_csv('n_art.csv').set_index('year')
df['p_art'] = np.nan
df.loc[2023:, 'p_art'] = 0.90
art = sti.ART(coverage=df)

Methods

Name Description
art_coverage_correction Adjust ART coverage to match data.
make_vls_prob_fn Per-agent probability of achieving viral suppression (effective ART)
prioritize_art Prioritize ART to n agents among those awaiting treatment
step Apply ART at each timestep: stop ART for those scheduled, initiate for newly diagnosed,
art_coverage_correction
interventions.hiv_interventions.ART.art_coverage_correction(
    sim,
    target_coverage=None,
    stratum_targets=None,
)

Adjust ART coverage to match data.

If stratum_targets is supplied, correction is done independently within each (age_bin, sex) stratum so that age/sex differentials in the input data are preserved. Otherwise, correction is done globally against target_coverage.

make_vls_prob_fn
interventions.hiv_interventions.ART.make_vls_prob_fn(sim, uids)

Per-agent probability of achieving viral suppression (effective ART) at initiation, derived from vls_coverage.

Any agent not covered by an explicit vls_coverage stratum (or when vls_coverage is unset entirely) defaults to 100% — i.e. always effective, matching the previous behavior.

prioritize_art
interventions.hiv_interventions.ART.prioritize_art(
    sim,
    n=None,
    awaiting_art_uids=None,
)

Prioritize ART to n agents among those awaiting treatment

step
interventions.hiv_interventions.ART.step()

Apply ART at each timestep: stop ART for those scheduled, initiate for newly diagnosed, and correct coverage to match targets (per-stratum when stratified data is supplied, otherwise against the aggregate total).

HIVDx

interventions.hiv_interventions.HIVDx(*args, **kwargs)

HIV diagnostic product used by :class:HIVTest.

A simple perfect-sensitivity test that classifies agents as positive (infected) or negative (susceptible) based on their current HIV state.

Parameters

Name Type Description Default
*args Positional arguments forwarded to ss.Product. ()
**kwargs Keyword arguments forwarded to ss.Product. {}

HIVTest

interventions.hiv_interventions.HIVTest(
    product=None,
    pars=None,
    test_prob_data=None,
    years=None,
    start=None,
    eligibility=None,
    name=None,
    label=None,
    newborn_test=None,
    **kwargs,
)

HIV-specific testing intervention.

Tests eligible agents for HIV; positive results set hiv.diagnosed=True, which is a prerequisite for ART initiation. By default, only undiagnosed agents are eligible.

The testing → diagnosis → ART pipeline works as follows:

1. HIVTest tests eligible agents each timestep (annual probability, converted via ss.probperyear)
2. Positive results set hiv.diagnosed=True and hiv.ti_diagnosed
3. HIVTest samples dur_dx2tx and sets hiv.ti_art = ti + delay (scheduled start)
4. ART picks up agents whose ti_art == current ti (and not yet on_art),
   filters by art_initiation, and puts them on ART
5. If coverage data is provided, ART corrects to match targets

Parameters

Name Type Description Default
product diagnostic product (default: :class:HIVDx). None
pars dict override default pars (rel_test, dt_scale, dur_dx2tx). None
test_prob_data annual testing probability (if dt_scale=True, the default). A value of 0.1 means ~10% of eligible agents tested per year. To specify a per-timestep probability instead, set dt_scale=False. None
years array years over which testing is active (mutually exclusive with start). None
start float calendar year when testing begins. None
eligibility func who can be tested. Default: undiagnosed agents. None
name, label standard module identifiers. required
newborn_test ( class:InfantHIVTest): if supplied, schedules an infant HIV test at delivery for unborn children of mothers who test positive (requires ss.MaternalNet and ss.Pregnancy in the sim). required
dur_dx2tx ss.Dist delay from diagnosis to scheduled ART start (default: ss.constant(0), no delay). May be passed directly or via the pars dict. Example: ss.constant(ss.years(0.5)) for a 6-month delay. required
dt_scale bool if True (default), test_prob_data is an annual probability. Set to False for per-timestep probability. required

Example::

# Test 20% of undiagnosed agents per year starting in 2000
test = sti.HIVTest(test_prob_data=0.2, start=2000, name='hiv_test')

# Test everyone every timestep (per-timestep probability)
test = sti.HIVTest(test_prob_data=1.0, dt_scale=False, name='hiv_test')

# FSW-targeted testing at higher rate
fsw_test = sti.HIVTest(
    test_prob_data=0.5,
    name='fsw_test',
    eligibility=lambda sim: sim.networks.structuredsexual.fsw & ~sim.diseases.hiv.diagnosed,
)

# ANC testing: test undiagnosed pregnant women in first trimester
anc_test = sti.HIVTest(
    test_prob_data=0.9,
    dt_scale=False,
    name='anc_test',
    eligibility=lambda sim: sim.demographics.pregnancy.tri1_uids[
        ~sim.diseases.hiv.diagnosed[sim.demographics.pregnancy.tri1_uids]
    ],
)

InfantHIVTest

interventions.hiv_interventions.InfantHIVTest(
    test_prob=1.0,
    name=None,
    label=None,
    **kwargs,
)

HIV test for infants born to mothers diagnosed during pregnancy.

Scheduled by :class:HIVTest (via newborn_test=) or :class:ANCTest when the mother tests positive. Fires only at the scheduled timestep. A positive result sets hiv.diagnosed on the infant, enabling ART linkage.

Parameters

Name Type Description Default
test_prob float probability of the infant receiving the test once scheduled (default 1.0 — if scheduled, it happens) 1.0
name, label standard required

Prep

interventions.hiv_interventions.Prep(
    pars=None,
    prep_eff=0.8,
    prep_dur=None,
    prep_adh=1.0,
    coverage=None,
    eligibility=None,
    smoothness=0,
    format_priority='n',
    **kwargs,
)

Pre-exposure prophylaxis (PrEP).

Any PrEP product (oral or injectable) is specified as a combination of three parameters: efficacy (prep_eff), adherence (prep_adh), and duration (prep_dur). prep_adh is a continuous multiplier which reduces the base efficacy (prep_eff), ranges from [0-1], default value is 1.0.

The state of the intervention (on_prep/prep_eff/prep_source/ti_prep_start/ti_prep_stop`) lives on :class:~stisim.diseases.hiv.HIV`. This excludes people from enrolling on multiple PrEP products at once.

The eligibility callable defines the target population. It receives the Sim object and should return a boolean array over all agents. HIV-negative and not-already-on-PrEP filters are always applied on top, so eligibility only needs to express who to target, not the clinical preconditions.

Parameters

Name Type Description Default
prep_eff base efficacy (0-1) when fully adherent (default 0.8) 0.8
prep_dur course duration before renewal is needed, an ss.dur/Time value (default ss.months(3)) None
prep_adh adherence level (0-1), multiplied into prep_eff to get the realized efficacy (default 1.0 = fully adherent) 1.0
coverage coverage target; any format accepted by parse_coverage. Defaults to a modest historical ramp-up. None
eligibility callable (sim) -> BoolArr defining the target population; defaults to FSW (sim.networks.structuredsexual.fsw) None
smoothness interpolation smoothness (0=linear, default) 0
format_priority when both n_prep and p_prep are non-NaN, prefer this format 'n'

Examples::

# Default: oral-like PrEP for FSW, ramping up over time
prep = sti.Prep(coverage={'year': [2020, 2025], 'value': [0, 0.5]})

# AGYW targeting
prep = sti.Prep(
    coverage=0.4,
    eligibility=lambda sim: sim.people.female & (sim.people.age < 25),
)

# A long-acting product alongside oral PrEP (distinct names required)
interventions = [
    sti.Prep(prep_eff=0.75, prep_dur=ss.months(3), prep_adh=1.0,
             coverage=0.3, name='prep_oral'),
             # source: doi/full/10.1056/NEJMoa1108524
    sti.Prep(prep_eff=0.95, prep_dur=ss.months(12), prep_adh=1.0,
             coverage=0.1, name='prep_cab_la'),
             # source: doi.org/10.1016/S0140-6736(22)00538-4
]

VMMC

interventions.hiv_interventions.VMMC(
    pars=None,
    coverage=None,
    eligibility=None,
    smoothness=0,
    format_priority='n',
    **kwargs,
)

Voluntary medical male circumcision.

Reduces male susceptibility to HIV acquisition (see HIVPars.eff_circ). Unlike ART, VMMC does not require diagnosis — it circumcises males up to a coverage target, prioritized by willingness (a random per-agent score).

If no coverage is specified, VMMC does nothing. Coverage must be provided explicitly via the coverage parameter.

Coverage is parsed by :func:parse_coverage (same formats as ART, using n_vmmc/p_vmmc column names). Age-only stratification is supported (no Gender column required, since VMMC is males-only).

Circumcision status (circumcised/circ_traditional/ti_circumcised) and efficacy (eff_circ) live on :class:~stisim.diseases.hiv.HIV, not on this intervention — see HIV.circumcise() and HIVPars.eff_circ. This lets other circumcision sources (e.g. non-program/traditional circumcision) share the same state and be included in this intervention’s “already circumcised” checks without going through VMMC at all.

In addition to program VMMC (the coverage-target logic above), this class can optionally also model non-program/traditional circumcision.

Parameters

Name Type Description Default
coverage coverage target (default None; program VMMC does nothing without data). See :func:parse_coverage for supported formats. None
eligibility optional function to restrict who is eligible for PROGRAM VMMC (default: all males); does not affect traditional circumcision. None
smoothness interpolation smoothness (0=linear, default) 0
format_priority when both n_vmmc and p_vmmc are non-NaN, prefer this format 'n'
traditional_prob probability (float or ss.bernoulli) that a pre-sexual-debut male receives traditional circumcision once he reaches traditional_age (default 0 = off). required
traditional_age age (years) at which pre-debut males are assessed for traditional circumcision, once (default 15). required

Examples::

# Program VMMC
vmmc = sti.VMMC(coverage=0.3)
vmmc = sti.VMMC(coverage={'year': [2010, 2025], 'value': [0, 0.4]})

# Traditional circumcision only, no program VMMC
trad = sti.VMMC(traditional_prob=0.1, traditional_age=15)

# Program VMMC for adults + traditional circumcision for boys, combined
both = sti.VMMC(coverage=0.3, eligibility=lambda sim: sim.people.age >= 25,
                traditional_prob=0.1, traditional_age=15)