"""
Defines classes and methods for HIV natural history
"""
import numpy as np
import sciris as sc
import pandas as pd
from scipy.stats import weibull_min
from . import utils as hpu
from . import defaults as hpd
from . import base as hpb
[docs]
class HIVsim(hpb.ParsObj):
"""
A class based around performing operations on a self.pars dict.
"""
def __init__(self, sim, art_datafile, hiv_datafile, hiv_pars):
"""
Initialization
"""
# Load in the parameters from provided datafiles
pars = self.load_data(hiv_datafile=hiv_datafile, art_datafile=art_datafile)
# Define default parameters, can be overwritten by hiv_pars
pars["hiv_pars"] = {
"cd4states": ["lt200", "gt200"], # code names for HIV states
"cd4statesfull": ["CD4<200", "200<CD4<500"], # full names for HIV states
"cd4_lb": [0, 200], # Lower bound for CD4 states
"cd4_ub": [200, 500], # Lower bound for CD4 states
"rel_sus": { # Increased risk of acquiring HPV
"lt200": 2.2,
"gt200": 2.2,
},
"rel_sev": { # Increased risk of disease severity
"lt200": 1.5,
"gt200": 1.2,
},
"rel_imm": { # Reduction in neutralizing/t-cell immunity acquired after infection/vaccination
"lt200": 0.36,
"gt200": 0.76,
},
"rel_reactivation_prob": 3, # Unused for now
"model_hiv_death": True, # whether or not to model HIV mortality. Typically only set to False for testing purposes
"time_to_hiv_death_shape": 2, # shape parameter for weibull distribution, based on https://royalsocietypublishing.org/action/downloadSupplement?doi=10.1098%2Frsif.2013.0613&file=rsif20130613supp1.pdf
"time_to_hiv_death_scale": lambda a: 21.182
- 0.2717
* a, # scale parameter for weibull distribution, based on https://royalsocietypublishing.org/action/downloadSupplement?doi=10.1098%2Frsif.2013.0613&file=rsif20130613supp1.pdf
"hiv_death_adj": 1,
"cd4_start": dict(dist="normal", par1=594, par2=20),
"cd4_trajectory": lambda f: (24.363 - 16.672 * f)
** 2, # based on https://docs.idmod.org/projects/emod-hiv/en/latest/hiv-model-healthcare-systems.html?highlight=art#art-s-impact-on-cd4-count
"cd4_reconstitution": lambda m: 15.584 * m
- 0.2113 * m**2, # growth in CD4 count following ART initiation
"art_failure_prob": 0.0, # Percentage of people on ART who will fail treatment
"dt_art": 1.0, # Timestep for art updates (in years)
}
self.ncd4 = len(pars["hiv_pars"]["cd4states"])
self.update_pars(old_pars=pars, new_pars=hiv_pars, create=True)
self.init_results(sim)
y = np.linspace(0, 1, 101)
cd4_decline = self["hiv_pars"]["cd4_trajectory"](y)
self.cd4_decline_diff = np.diff(cd4_decline)
sim.pars["hiv_pars"]["mortality_rates"] = self.pars["mortality_rates"]
return
def update_pars(self, old_pars=None, new_pars=None, create=True):
if len(new_pars):
for parkey, parval in new_pars.items():
if isinstance(parval, dict):
for parvalkey, parvalval in parval.items():
if isinstance(parvalval, dict):
for parvalkeyval, parvalvalval in parvalval.items():
old_pars["hiv_pars"][parkey][parvalkey][
parvalkeyval
] = parvalvalval
else:
old_pars["hiv_pars"][parkey][parvalkey] = parvalval
else:
old_pars["hiv_pars"][parkey] = parval
# Call update_pars() for ParsObj
super().update_pars(pars=old_pars, create=create)
[docs]
@staticmethod
def init_states(people):
"""Add HIV-related states to the people states"""
hiv_states = [
hpd.State("hiv", bool, False),
hpd.State("art", bool, False),
hpd.State("art_failure", bool, False),
]
people.meta.other_stock_states += hiv_states
people.meta.durs += [hpd.State("dur_hiv", hpd.default_float, np.nan)]
people.meta.person += [hpd.State("cd4", hpd.default_float, np.nan)]
people.meta.alive_states += (hpd.State("dead_hiv", bool, False),)
return
def init_results(self, sim):
def init_res(*args, **kwargs):
"""Initialize a single result object"""
output = hpb.Result(*args, **kwargs, npts=sim.res_npts)
return output
self.resfreq = sim.resfreq
# Initialize storage
results = sc.objdict()
na = len(sim["age_bin_edges"]) - 1 # Number of age bins
stock_colors = [i for i in set(sim.people.meta.stock_colors) if i is not None]
results["hiv_infections"] = init_res("New HIV infections")
results["hiv_infections_by_age"] = init_res(
"New HIV infections by age", n_rows=na, color=stock_colors[0]
)
results["female_hiv_infections_by_age"] = init_res(
"New Female HIV infections by age", n_rows=na, color=stock_colors[0]
)
results["male_hiv_infections_by_age"] = init_res(
"New Male HIV infections by age", n_rows=na, color=stock_colors[0]
)
results["n_hiv"] = init_res("Number living with HIV", color=stock_colors[0])
results["n_hiv_by_age"] = init_res(
"Number living with HIV by age", n_rows=na, color=stock_colors[0]
)
results["hiv_prevalence"] = init_res("HIV prevalence", color=stock_colors[0])
results["female_hiv_prevalence"] = init_res(
"Female HIV prevalence", color=stock_colors[1]
)
results["male_hiv_prevalence"] = init_res(
"Male HIV prevalence", color=stock_colors[2]
)
results["hiv_prevalence_by_age"] = init_res(
"HIV prevalence by age", n_rows=na, color=stock_colors[0]
)
results["female_hiv_prevalence_by_age"] = init_res(
"Female HIV prevalence by age", n_rows=na, color=stock_colors[1]
)
results["male_hiv_prevalence_by_age"] = init_res(
"Male HIV prevalence by age", n_rows=na, color=stock_colors[2]
)
results["hiv_deaths"] = init_res("New HIV deaths")
results["hiv_deaths_by_age"] = init_res(
"New HIV deaths by age", n_rows=na, color=stock_colors[0]
)
results["hiv_mortality"] = init_res("HIV mortality rate")
results["hiv_mortality_by_age"] = init_res(
"HIV mortality rate by age", n_rows=na, color=stock_colors[0]
)
results["excess_hiv_mortality"] = init_res("Excess HIV mortality rate")
results["excess_hiv_mortality_by_age"] = init_res(
"Excess HIV mortality rate by age", n_rows=na, color=stock_colors[0]
)
results["hiv_incidence_15"] = init_res(
"HIV incidence among >15 year olds", color=stock_colors[0]
)
results["hiv_incidence"] = init_res("HIV incidence", color=stock_colors[0])
results["hiv_incidence_by_age"] = init_res(
"HIV incidence by age", n_rows=na, color=stock_colors[0]
)
results["n_hpv_by_age_with_hiv"] = init_res(
"Number HPV infections by age among HIV+", n_rows=na, color=stock_colors[0]
)
results["n_hpv_by_age_no_hiv"] = init_res(
"Number HPV infections by age among HIV-", n_rows=na, color=stock_colors[0]
)
results["hpv_prevalence_by_age_with_hiv"] = init_res(
"HPV prevalence by age among HIV+", n_rows=na, color=stock_colors[0]
)
results["hpv_prevalence_by_age_no_hiv"] = init_res(
"HPV prevalence by age among HIV-", n_rows=na, color=stock_colors[1]
)
results["cancers_by_age_with_hiv"] = init_res(
"Cancers by age among HIV+", n_rows=na, color=stock_colors[0]
)
results["cancers_by_age_no_hiv"] = init_res(
"Cancers by age among HIV-", n_rows=na, color=stock_colors[1]
)
results["cancers_with_hiv"] = init_res(
"Cancers among HIV+", color=stock_colors[1]
)
results["cancers_no_hiv"] = init_res(
"Cancers among HIV-", color=stock_colors[2]
)
results["cancer_incidence_with_hiv"] = init_res(
"Cancer incidence among HIV+", color=stock_colors[0]
)
results["cancer_incidence_no_hiv"] = init_res(
"Cancer incidence among HIV-", color=stock_colors[1]
)
results["cancer_incidence_by_age_with_hiv"] = init_res(
"Cancer incidence by age among HIV+", n_rows=na, color=stock_colors[0]
)
results["cancer_incidence_by_age_no_hiv"] = init_res(
"Cancer incidence by age among HIV-", n_rows=na, color=stock_colors[1]
)
results["n_females_with_hiv_alive_by_age"] = init_res(
"Number females with HIV alive by age", n_rows=na
)
results["n_females_no_hiv_alive_by_age"] = init_res(
"Number females without HIV alive by age", n_rows=na
)
results["n_females_with_hiv_alive"] = init_res("Number females with HIV alive")
results["n_males_with_hiv_alive"] = init_res("Number males with HIV alive")
results["n_males_with_hiv_alive_by_age"] = init_res(
"Number males with HIV alive by age", n_rows=na
)
results["n_males_no_hiv_alive_by_age"] = init_res(
"Number males without HIV alive by age", n_rows=na
)
results["n_females_no_hiv_alive"] = init_res("Number females without HIV alive")
results["n_males_no_hiv_alive"] = init_res("Number males without HIV alive")
results["n_art"] = init_res("Number on ART")
results["art_coverage"] = init_res("ART coverage")
self.results = results
return
# %% HIV methods
[docs]
def set_hiv_prognoses(self, people, inds):
"""Set HIV outcomes"""
shape = self["hiv_pars"]["time_to_hiv_death_shape"]
dt = people.pars["dt"]
if self["hiv_pars"]["model_hiv_death"]:
scale = self["hiv_pars"]["time_to_hiv_death_scale"](people.age[inds])
adjust = self["hiv_pars"]["hiv_death_adj"]
scale = np.maximum(scale, 0)
time_to_hiv_death = adjust * weibull_min.rvs(
c=shape, scale=scale, size=len(inds)
)
people.dur_hiv[inds] = time_to_hiv_death
people.date_dead_hiv[inds] = people.t + sc.randround(time_to_hiv_death / dt)
return
[docs]
def check_hiv_death(self, people):
"""
Check for new deaths from HIV
"""
filter_inds = people.true("hiv")
inds = people.check_inds(
people.dead_hiv, people.date_dead_hiv, filter_inds=filter_inds
)
# Remove people and update flows
people.remove_people(inds, cause="hiv")
idx = int(people.t / self.resfreq)
if len(inds):
deaths_by_age = np.histogram(
people.age[inds], bins=people.age_bin_edges, weights=people.scale[inds]
)[0]
self.results["hiv_deaths"][idx] += people.scale_flows(inds)
self.results["hiv_deaths_by_age"][:, idx] += deaths_by_age
return
[docs]
def update_cd4(self, people):
"""
Update CD4 counts
"""
dt = people.pars["dt"]
filter_inds = people.true("hiv")
if len(filter_inds):
art_success_inds = filter_inds[
hpu.true(people.art[filter_inds] & ~people.art_failure[filter_inds])
]
art_failure_inds = filter_inds[
hpu.true(people.art[filter_inds] & people.art_failure[filter_inds])
]
not_art_inds = filter_inds[hpu.false(people.art[filter_inds])]
cd4_decline_inds = np.concatenate((art_failure_inds, not_art_inds))
# First take care of people unsuccessfully on ART or not on ART (CD4 will decline)
cd4_remaining_inds = hpu.itrue(
((people.t - people.date_hiv[cd4_decline_inds]) * dt)
< people.dur_hiv[cd4_decline_inds],
cd4_decline_inds,
)
frac_prognosis = (
100
* ((people.t - people.date_hiv[cd4_remaining_inds]) * dt)
/ people.dur_hiv[cd4_remaining_inds]
)
cd4_change = self.cd4_decline_diff[frac_prognosis.astype(hpd.default_int)]
people.cd4[cd4_remaining_inds] += cd4_change
# Now take care of people successfully on ART (CD4 reconstitutes)
mpy = 12
months_on_ART = (people.t - people.date_art[art_success_inds]) * mpy
cd4_change = self["hiv_pars"]["cd4_reconstitution"](months_on_ART)
cd4_change[cd4_change < 0] = 0
people.cd4[art_success_inds] += cd4_change
return
[docs]
def step(self, people=None, year=None):
"""
Wrapper method that checks for new HIV infections, updates prognoses, etc.
"""
new_infection_inds = self.new_hiv_infections(
people, year
) # Newly acquired HIV infections
self.set_hiv_prognoses(people, new_infection_inds) # Set time to HIV-death
self.check_ART(people, year=year) # Set time to HIV-death
self.check_hiv_death(people)
self.update_cd4(people)
self.update_hpv_progs(people)
self.update_hiv_results(people, new_infection_inds)
return
[docs]
def new_hiv_infections(self, people, year=None):
"""Apply HIV infection rates to population"""
hiv_pars = self["infection_rates"]
all_years = np.array(list(hiv_pars.keys()))
year_ind = sc.findnearest(all_years, year)
nearest_year = all_years[year_ind]
hiv_year = hiv_pars[nearest_year]
dt = people.pars["dt"]
hiv_probs = np.zeros(len(people), dtype=hpd.default_float)
for sk in ["f", "m"]:
hiv_year_sex = hiv_year[sk]
age_bins = hiv_year_sex[:, 0]
hiv_rates = hiv_year_sex[:, 1] * dt
mf_inds = people.is_female if sk == "f" else people.is_male
mf_inds *= people.alive # Only include people alive
age_inds = np.digitize(people.age[mf_inds], age_bins) - 1
hiv_probs[mf_inds] = hiv_rates[age_inds]
hiv_probs[people.hiv] = 0 # not at risk if already infected
# Get indices of people who acquire HIV
hiv_inds = hpu.true(hpu.binomial_arr(hiv_probs))
people.hiv[hiv_inds] = True
people.cd4[hiv_inds] = hpu.sample(
**self["hiv_pars"]["cd4_start"], size=len(hiv_inds)
)
people.date_hiv[hiv_inds] = people.t
return hiv_inds
[docs]
def update_hpv_progs(self, people):
"""Update people's relative susceptibility, severity, and immunity"""
hiv_inds = sc.autolist()
for sn, cd4state in enumerate(self["hiv_pars"]["cd4states"]):
inds = sc.findinds(
(people.cd4 >= self["hiv_pars"]["cd4_lb"][sn])
& (people.cd4 < self["hiv_pars"]["cd4_ub"][sn])
)
hiv_inds += list(inds)
if len(inds):
for ir, rel_par in enumerate(["rel_sus", "rel_sev", "rel_imm"]):
people[rel_par][inds] = self["hiv_pars"][rel_par][cd4state]
# If anyone has HIV, update their HPV parameters
if len(hiv_inds):
hiv_inds = np.array(hiv_inds)
dt = people.pars["dt"]
for g in range(people.pars["n_genotypes"]):
gpars = people.pars["genotype_pars"][g]
hpv_inds = hpu.itruei(
(
people.is_female
& people.precin[g, :]
& (np.isnan(people.date_cin[g, :]))
),
hiv_inds,
) # Women with HIV who have pre-CIN and were not going to progress to CIN
hpv_cin_inds = hpu.itruei(
(
people.is_female
& people.precin[g, :]
& ~np.isnan(people.date_cin[g, :])
& (np.isnan(people.date_cancerous[g, :]))
),
hiv_inds,
) # Women with HIV who have PRECIN and were going to develop CIN but not going to progress to cancer
cin_inds = hpu.itruei(
(
people.is_female
& people.cin[g, :]
& (np.isnan(people.date_cancerous[g, :]))
),
hiv_inds,
) # Women with HIV who have CIN and were not going to progress to cancer
if len(hpv_inds): # Reevaluate these women's risk of developing CIN
people.set_prognoses(hpv_inds, g, gpars, dt)
cin_reevaluate_inds = np.concatenate((hpv_cin_inds, cin_inds))
if len(
cin_reevaluate_inds
): # Reevaluate these women's risk of developing cancer
people.set_severity(cin_reevaluate_inds, g, gpars, dt)
return
def calculate_stocks(self, people):
idx = int(people.t / self.resfreq)
hivinds = hpu.true(people["hiv"] * people.alive)
self.results["n_hiv"][idx] = people.scale_flows(hivinds)
self.results["n_hiv_by_age"][:, idx] = np.histogram(
people.age[hivinds],
bins=people.age_bin_edges,
weights=people.scale[hivinds],
)[0]
artinds = hpu.true(people.art * people.alive)
self.results["n_art"][idx] = people.scale_flows(artinds)
# Pull out those with HPV and HIV+
hpvhivinds = hpu.true((people["hiv"]) & people["infectious"])
self.results["n_hpv_by_age_with_hiv"][:, idx] = np.histogram(
people.age[hpvhivinds],
bins=people.age_bin_edges,
weights=people.scale[hpvhivinds],
)[0]
# Pull out those with HPV and HIV-
hpvnohivinds = hpu.true(~(people["hiv"]) & people["infectious"])
self.results["n_hpv_by_age_no_hiv"][:, idx] = np.histogram(
people.age[hpvnohivinds],
bins=people.age_bin_edges,
weights=people.scale[hpvnohivinds],
)[0]
alive_female_hiv_inds = hpu.true(people.alive * people.is_female * people.hiv)
self.results["n_females_with_hiv_alive"][idx] = people.scale_flows(
alive_female_hiv_inds
)
self.results["n_females_with_hiv_alive_by_age"][:, idx] = np.histogram(
people.age[alive_female_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[alive_female_hiv_inds],
)[0]
alive_female_no_hiv_inds = hpu.true(
people.alive * people.is_female * ~people.hiv
)
self.results["n_females_no_hiv_alive"][idx] = people.scale_flows(
alive_female_no_hiv_inds
)
self.results["n_females_no_hiv_alive_by_age"][:, idx] = np.histogram(
people.age[alive_female_no_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[alive_female_no_hiv_inds],
)[0]
alive_male_hiv_inds = hpu.true(people.alive * people.is_male * people.hiv)
self.results["n_males_with_hiv_alive"][idx] = people.scale_flows(
alive_male_hiv_inds
)
self.results["n_males_with_hiv_alive_by_age"][:, idx] = np.histogram(
people.age[alive_male_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[alive_male_hiv_inds],
)[0]
alive_male_no_hiv_inds = hpu.true(people.alive * people.is_male * ~people.hiv)
self.results["n_males_no_hiv_alive"][idx] = people.scale_flows(
alive_male_no_hiv_inds
)
self.results["n_males_no_hiv_alive_by_age"][:, idx] = np.histogram(
people.age[alive_male_no_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[alive_male_no_hiv_inds],
)[0]
[docs]
def update_hiv_results(self, people, hiv_inds):
"""Update the HIV results"""
idx = int(people.t / self.resfreq)
#### Calculate flows
# Flows get accumulated *every* time step
self.results["hiv_infections"][idx] += people.scale_flows(hiv_inds)
self.results["hiv_infections_by_age"][:, idx] += np.histogram(
people.age[hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[hiv_inds],
)[0]
female_hiv_inds = hiv_inds[hpu.true(people.is_female[hiv_inds])]
male_hiv_inds = hiv_inds[hpu.true(people.is_male[hiv_inds])]
self.results["female_hiv_infections_by_age"][:, idx] += np.histogram(
people.age[female_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[female_hiv_inds],
)[0]
self.results["male_hiv_infections_by_age"][:, idx] += np.histogram(
people.age[male_hiv_inds],
bins=people.age_bin_edges,
weights=people.scale[male_hiv_inds],
)[0]
#### Calculate stocks
# Stocks only get accumulated every nth time step, where n is the result frequency
if people.t % self.resfreq == self.resfreq - 1:
self.calculate_stocks(people)
return
[docs]
def get_hiv_data(self, hiv_datafile=None, art_datafile=None):
"""
Load HIV incidence and art coverage data, if provided
Args:
location (str): must be provided if you want to run with HIV dynamics
hiv_datafile (str): must be provided if you want to run with HIV dynamics
art_datafile (str): must be provided if you want to run with HIV dynamics
verbose (bool): whether to print progress
Returns:
hiv_inc (dict): dictionary keyed by sex, storing arrays of HIV incidence over time by age
art_cov (dict): dictionary keyed by sex, storing arrays of ART coverage over time by age
"""
if hiv_datafile is None and art_datafile is None:
hiv_incidence_rates, hiv_mortality, art_coverage = None, None, None
else:
hiv_datafile = sc.promotetolist(hiv_datafile)
art_datafile = sc.promotetolist(art_datafile)
hiv_incidence_rates = dict()
# Load data
dfs_hiv = []
for hiv_data in hiv_datafile:
df_hiv = pd.read_csv(hiv_data)
dfs_hiv.append(df_hiv)
# df_inc = pd.read_csv(hiv_datafile) # HIV incidence
dfs_art = []
for art_data in art_datafile:
df_art = pd.read_csv(art_data) # ART coverage
dfs_art.append(df_art)
# Process HIV and ART data
sex_keys = ["Male", "Female"]
sex_key_map = {"Male": "m", "Female": "f"}
hiv_mortality_dfs = []
for id, df in enumerate(dfs_hiv):
if "mortality" in hiv_datafile[id]:
if "female" in hiv_datafile[id]:
sex = "f"
else:
sex = "m"
df = df.set_index("age")
df = df.melt(ignore_index=False).reset_index()
df["Sex"] = sex
df.columns = ["Age", "Year", "Mortality", "Sex"]
hiv_mortality_dfs.append(df)
else:
## Start with incidence file
years = df["Year"].unique()
# Processing
for year in years:
hiv_incidence_rates[year] = dict()
for sk in sex_keys:
sk_out = sex_key_map[sk]
hiv_incidence_rates[year][sk_out] = np.concatenate(
[
np.array([[9, 0]]),
np.array(
df[
(df["Year"] == year) & (df["Sex"] == sk_out)
][["Age", "Incidence"]],
dtype=hpd.default_float,
),
np.array(
[[150, 0]]
), # Add another entry so that all older age groups are covered
]
)
hiv_mortality_df = pd.concat(hiv_mortality_dfs)
hiv_mortality = dict()
years = hiv_mortality_df["Year"].unique().astype(float)
for year in years:
year = round(year)
hiv_mortality[year] = dict()
for sk in sex_keys:
sk_out = sex_key_map[sk]
hiv_mortality[year][sk_out] = np.array(
hiv_mortality_df[
(hiv_mortality_df["Year"] == str(year))
& (hiv_mortality_df["Sex"] == sk_out)
][["Age", "Mortality"]],
dtype=hpd.default_float,
)
# Now compute ART adherence over time/age
if len(dfs_art) == 1:
art_coverage = dict()
df_art = dfs_art[0]
years = df_art["Year"].values
for i, year in enumerate(years):
art_coverage[year] = df_art.iloc[i]["ART Coverage"]
else:
art_dfs = []
for id, df in enumerate(dfs_art):
if "females" in art_datafile[id]:
sex = "f"
else:
sex = "m"
df = df.set_index("age")
df = df.melt(ignore_index=False).reset_index()
df["Sex"] = sex
df.columns = ["Age", "Year", "ART", "Sex"]
art_dfs.append(df)
art_df = pd.concat(art_dfs)
art_coverage = dict()
years = art_df["Year"].unique().astype(float)
for year in years:
year = round(year)
art_coverage[year] = dict()
for sk in sex_keys:
sk_out = sex_key_map[sk]
art_coverage[year][sk_out] = np.array(
art_df[
(art_df["Year"] == str(year))
& (art_df["Sex"] == sk_out)
][["Age", "ART"]],
dtype=hpd.default_float,
)
return hiv_incidence_rates, hiv_mortality, art_coverage
[docs]
def load_data(self, hiv_datafile=None, art_datafile=None):
"""Load any data files that are used to create additional parameters, if provided"""
hiv_data = sc.objdict()
hiv_data.infection_rates, hiv_data.mortality_rates, hiv_data.art_coverage = (
self.get_hiv_data(hiv_datafile=hiv_datafile, art_datafile=art_datafile)
)
return hiv_data
[docs]
def finalize(self, sim):
"""
Compute prevalence, incidence.
"""
res = self.results
simres = sim.results
# Compute HIV incidence and prevalence
def safedivide(num, denom):
"""Define a variation on sc.safedivide that respects shape of numerator"""
answer = np.zeros_like(num)
fill_inds = (denom != 0).nonzero()
if len(num.shape) == len(denom.shape):
answer[fill_inds] = num[fill_inds] / denom[fill_inds]
else:
answer[:, fill_inds] = num[:, fill_inds] / denom[fill_inds]
return answer
# Compute stocks for final day of sim:
if sim.people.t % self.resfreq == self.resfreq - 1:
self.calculate_stocks(sim.people)
alive_females_15 = np.sum(simres["n_females_alive_by_age"][3:, :], axis=0)
alive_males = np.sum(
(
res["n_males_with_hiv_alive_by_age"][2:, :]
+ res["n_males_no_hiv_alive_by_age"][2:, :]
),
axis=0,
)
hiv_alive_females_15 = np.sum(
res["n_females_with_hiv_alive_by_age"][3:, :], axis=0
)
incident_hiv_15 = np.sum(res["hiv_infections_by_age"][3:, :], axis=0)
susceptibile_hiv_15 = np.sum(
simres["n_females_alive_by_age"][3:, :], axis=0
) - np.sum(res["n_females_with_hiv_alive_by_age"][3:, :], axis=0)
ng = sim.pars["n_genotypes"]
no_hiv_by_age = simres["n_alive_by_age"][:] - res["n_hiv_by_age"][:]
self.results["hiv_prevalence_by_age"][:] = safedivide(
res["n_hiv_by_age"][:], simres["n_alive_by_age"][:]
)
self.results["female_hiv_prevalence_by_age"][:] = safedivide(
res["n_females_with_hiv_alive_by_age"][:],
(
res["n_females_with_hiv_alive_by_age"][:]
+ res["n_females_no_hiv_alive_by_age"][:]
),
)
self.results["hiv_incidence"][:] = sc.safedivide(
res["hiv_infections"][:], (simres["n_alive"][:] - res["n_hiv"][:])
)
self.results["hiv_incidence_15"][:] = sc.safedivide(
incident_hiv_15, susceptibile_hiv_15
)
self.results["hiv_incidence_by_age"][:] = sc.safedivide(
res["hiv_infections_by_age"][:],
(simres["n_alive_by_age"][:] - res["n_hiv_by_age"][:]),
)
self.results["hiv_prevalence"][:] = safedivide(
res["n_hiv"][:], simres["n_alive"][:]
)
self.results["female_hiv_prevalence"][:] = safedivide(
hiv_alive_females_15, alive_females_15
)
self.results["male_hiv_prevalence"][:] = safedivide(
res["n_males_with_hiv_alive"][:], alive_males
)
self.results["hpv_prevalence_by_age_with_hiv"][:] = safedivide(
res["n_hpv_by_age_with_hiv"][:], ng * res["n_hiv_by_age"][:]
)
self.results["hpv_prevalence_by_age_no_hiv"][:] = safedivide(
res["n_hpv_by_age_no_hiv"][:], ng * no_hiv_by_age
)
self.results["art_coverage"][:] = safedivide(res["n_art"][:], res["n_hiv"][:])
self.results["excess_hiv_mortality"][:] = safedivide(
res["hiv_deaths"][:], simres["n_alive"][:]
)
self.results["excess_hiv_mortality_by_age"][:] = safedivide(
res["hiv_deaths_by_age"][:], simres["n_alive_by_age"][:]
)
self.results["hiv_mortality"][:] = safedivide(
res["hiv_deaths"][:], res["n_hiv"][:]
)
self.results["hiv_mortality_by_age"][:] = safedivide(
res["hiv_deaths_by_age"][:], res["n_hiv_by_age"][:]
)
# Compute cancer incidence
scale_factor = 1e5 # Cancer incidence are displayed as rates per 100k women
self.results["cancer_incidence_with_hiv"][:] = (
safedivide(res["cancers_with_hiv"][:], res["n_females_with_hiv_alive"][:])
* scale_factor
)
self.results["cancer_incidence_no_hiv"][:] = (
safedivide(res["cancers_no_hiv"][:], res["n_females_no_hiv_alive"][:])
* scale_factor
)
self.results["cancer_incidence_by_age_with_hiv"][:] = (
safedivide(
res["cancers_by_age_with_hiv"][:],
res["n_females_with_hiv_alive_by_age"][:],
)
* scale_factor
)
self.results["cancer_incidence_by_age_no_hiv"][:] = (
safedivide(
res["cancers_by_age_no_hiv"][:], res["n_females_no_hiv_alive_by_age"][:]
)
* scale_factor
)
sim.results = sc.mergedicts(simres, self.results)
return
def check_ART(self, people, year):
update_freq = max(
1, int(self["hiv_pars"]["dt_art"] / people.pars["dt"])
) # Ensure it's an integer not smaller than 1
if people.t % update_freq == 0:
art_coverage = self["art_coverage"] # Shorten
all_inds = hpu.true(people.hiv * ~people.art * people.alive)
if len(all_inds):
# Extract index of current year
all_years = np.array(list(art_coverage.keys()))
year_ind = sc.findnearest(all_years, year)
nearest_year = round(all_years[year_ind])
# Apply ART coverage (being careful of level 0 and level 1 people!)
art_cov = art_coverage[nearest_year]
if isinstance(art_cov, dict):
for sk in ["f", "m"]:
art_year_sex = art_cov[sk]
age_bins = art_year_sex[:, 0]
this_art_cov = art_year_sex[:, 1]
mf_inds = people.is_female if sk == "f" else people.is_male
mf_inds *= people.alive # Only include people alive
mf_art_inds = mf_inds * people.art
mf_hiv_inds = mf_inds * people.hiv
age_art_inds = (
np.digitize(people.age[mf_art_inds], age_bins) - 1
)
age_hiv_inds = (
np.digitize(people.age[mf_hiv_inds], age_bins) - 1
)
cur_n_age_bin = np.zeros(len(age_bins))
cur_n_age_bin[age_art_inds] = people.scale_flows(
hpu.true(mf_art_inds)
)
desired_n_age_bin = np.zeros(len(age_bins))
desired_n_age_bin[age_hiv_inds] = sc.randround(
this_art_cov[age_hiv_inds]
* people.scale_flows(hpu.true(mf_hiv_inds))
)
num_art_age_bin = desired_n_age_bin - cur_n_age_bin
inds_age = np.digitize(people.age[all_inds], age_bins) - 1
age_bins_to_fill = np.where(num_art_age_bin > 0)[0]
for age_bin in age_bins_to_fill:
eligible_inds = all_inds[np.where(inds_age == age_bin)]
if len(eligible_inds):
art_probs = (
num_art_age_bin[age_bin]
* people.scale[eligible_inds]
/ np.sum(people.scale[eligible_inds] ** 2)
)
art_inds_this_age = eligible_inds[
hpu.true(hpu.binomial_arr(art_probs))
]
people.art[art_inds_this_age] = True
people.date_art[art_inds_this_age] = people.t
# print(f'{year}: I want {num_art_age_bin[age_bin]} more {age_bin}yo, put {people.scale_flows(art_inds_this_age)} on')
# Get indices of people who are on ART who will not be virologically suppressed
art_failure_prob = self["hiv_pars"]["art_failure_prob"]
art_failure_probs = np.full(
len(art_inds_this_age),
fill_value=art_failure_prob,
dtype=hpd.default_float,
)
art_failure_bools = hpu.binomial_arr(art_failure_probs)
art_failure_inds = art_inds_this_age[art_failure_bools]
people.art_failure[art_failure_inds] = True
# Remove date of HIV death for those successfully on ART
art_success_inds = np.setdiff1d(
art_inds_this_age, art_failure_inds
)
people.date_dead_hiv[art_success_inds] = np.nan
else:
cur_n = people.scale_flows(hpu.true(people.art & people.alive))
desired_n = sc.randround(
art_cov
* people.scale_flows(hpu.true(people.hiv & people.alive))
)
# Scaled number of people to get on ART (not equal to n_agents, will need to scale back)
num_art = desired_n - cur_n
num_art = np.maximum(num_art, 0)
num_art = np.minimum(num_art, people.scale_flows(all_inds))
if num_art > 0:
art_probs = (
num_art
* people.scale[all_inds]
/ np.sum(people.scale[all_inds] ** 2)
)
art_inds = all_inds[hpu.true(hpu.binomial_arr(art_probs))]
n_art = people.scale_flows(art_inds)
people.art[art_inds] = True
people.date_art[art_inds] = people.t
# Get indices of people who are on ART who will not be virologically suppressed
art_failure_prob = self["hiv_pars"]["art_failure_prob"]
art_failure_probs = np.full(
len(art_inds),
fill_value=art_failure_prob,
dtype=hpd.default_float,
)
art_failure_bools = hpu.binomial_arr(art_failure_probs)
art_failure_inds = art_inds[art_failure_bools]
people.art_failure[art_failure_inds] = True
# Remove date of HIV death for those successfully on ART
art_success_inds = np.setdiff1d(art_inds, art_failure_inds)
people.date_dead_hiv[art_success_inds] = np.nan
return