diff --git a/scripts/bs_Core.sh b/scripts/bs_Core.sh deleted file mode 100755 index fd156520..00000000 --- a/scripts/bs_Core.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -#SBATCH -J MODEL_NAME -#SBATCH -c NCORES -#SBATCH --time=WALL_TIME -#SBATCH --mail-type=FAIL -#SBATCH --mail-type=END -#SBATCH --mail-user=NoMail -#SBATCH --mem=MEMORYGB - -if [ -z "$SLURM_NPROCS" ] ; then - if [ -z "$SLURM_NTASKS_PER_NODE" ] ; then - SLURM_NTASKS_PER_NODE=1 - fi - SLURM_NPROCS=$(( $SLURM_JOB_NUM_NODES * $SLURM_NTASKS_PER_NODE )) -fi - -#!/bin/bash -module load graphviz - -setting="" -paramPath="" -nMC="" -forceFlag="" -sweepDefs="" -sweepfile="" -rows="" - -while getopts S:n:w:W:r:Fa:p:P: option -do - case "${option}" - in - S) setting=${OPTARG};; - n) nMC=${OPTARG};; - w) sweepDefs+="-w ${OPTARG} ";; - W) sweepfile="-W ${OPTARG}";; - r) rows="-r ${OPTARG}";; - F) forceFlag="-F";; - a) paramPath=${OPTARG};; - p) savePop="--savepop";; - P) popPath="--poppath ${OPTARG}";; - esac -done - -cd $PWD - -echo Master process running on `hostname` -echo Directory is `pwd` -echo PBS has allocated the following nodes: -echo `cat $PBS_NODEFILE` -echo Starting execution at `date` -NCPU=`wc -l < $PBS_NODEFILE` -echo This job has allocated $NCPU CPUs - -./run_titan.py -S $setting -n $nMC -p $paramPath $forceFlag $sweepDefs $sweepfile $rows $savePop $popPath diff --git a/settings/nyc-msm/partnership.yml b/settings/nyc-msm/partnership.yml deleted file mode 100644 index d76b0a3b..00000000 --- a/settings/nyc-msm/partnership.yml +++ /dev/null @@ -1,49 +0,0 @@ -partnership: - sex: - frequency: - Main: - type: distribution - distribution: - dist_type: gamma - vars: - 1: - value: 0.439 - value_type: float - 2: - value: 15.3 - value_type: float - Casual: - type: distribution - distribution: - dist_type: gamma - vars: - 1: - value: 0.131 - value_type: float - 2: - value: 16.5 - value_type: float - haart_scaling: - MSM: # calibration - non_adherent: 0.165 - adherent: 0.00 - acquisition: - MSM: - insertive: 0.0017 - receptive: 0.0075 - versatile: 0.0046 - - duration: - Main: - type: distribution - distribution: - dist_type: wald - vars: - 1: - value: 52.88 - value_type: float - 2: - value: 5.248 - value_type: float - mean: 52.88 - diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..972740e0 --- /dev/null +++ b/setup.py @@ -0,0 +1,73 @@ +import setuptools +import subprocess + +# ========= FROM https://github.com/vivin/better-setuptools-git-version/blob/master/better_setuptools_git_version.py with edits ========= +def get_latest_tag(): + return subprocess.getoutput( + "git describe --tags `git rev-list --tags --max-count=1`" + ) + + +def get_tag_commit_sha(tag): + """Return the commit that the tag is pointing to.""" + return subprocess.getoutput("git rev-list -n 1 {tag}".format(tag=tag)) + + +def get_head_sha(): + """Return the sha key of HEAD.""" + return subprocess.getoutput("git rev-parse HEAD") + + +def is_head_at_tag(tag): + """Return True or False depending on whether the given tag is pointing to HEAD""" + return get_head_sha() == get_tag_commit_sha(tag) + + +def get_version(): + """ + Return the full git version using the given template. If there are no annotated tags, the version specified by + starting_version will be used. If HEAD is at the tag, the version will be the tag itself. If there are commits ahead + of the tag, the first 8 characters of the sha of the HEAD commit will be included. + In all of the above cases, if the working tree is also dirty or contains untracked files, a "+dirty" suffix will be + appended to the version. + Returns: + the formatted version based on tags in the git repository. + """ + template = "{tag}.dev{sha}" + tag = get_latest_tag() + if is_head_at_tag(tag): + version = tag + else: + sha = get_head_sha()[:8] + version = template.format(tag=tag, sha=sha) + + return version + + +# add the readme as the description +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +# make the requirements.txt be installed (not good practice for general packaging, good for current oscar set up) +with open("requirements.txt") as f: + required = f.read().splitlines() + +setuptools.setup( + name="titan", # Replace with your own username, + version=get_version(), + # author="Example Author", + # author_email="author@example.com", + description="TITAN Agent Based Model", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/marshall-lab/TITAN", + packages=setuptools.find_packages(), + install_requires=required, + classifiers=[ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + ], + python_requires=">=3.6", + package_data={"titan": ["params/*.yml", "settings/*/*.yml"]}, + entry_points={"console_scripts": ["run_titan=titan:script_init"]}, +) diff --git a/subTitan.sh b/subTitan.sh deleted file mode 100755 index e49eb126..00000000 --- a/subTitan.sh +++ /dev/null @@ -1,189 +0,0 @@ -#!/bin/bash - -#Read in source code path, then shift for optargs -titanPath=$PWD -paramPath="$1" -shift - -setting="custom" -date=`date +%Y-%m-%d-T%H-%M-%S` -user=${USER} -walltime=12:00:00 -memory=12g -outfile="Jobname.o" -repeats=1 -nMC=1 -model=${PWD##*/} -basePath=$PWD -jobname="" -folderName="" -sweepDefs="" -sweepfile="" -rows="" -num_cores=1 -forceFlag="" -savePop="" -popPath="" - -while getopts m:j:T:S:r:n:f:w:W:R:Fc:t:p:P: option -do - case "${option}" - in - m) memory=${OPTARG};; - j) jobname=${OPTARG};; - T) walltime=${OPTARG};; - S) setting=${OPTARG};; - r) repeats=${OPTARG};; - n) nMC=${OPTARG};; - f) folderName=${OPTARG};; - w) sweepDefs+="-w ${OPTARG} ";; - W) sweepfile="-W ${OPTARG}";; - R) rows="-r ${OPTARG}";; - F) forceFlag="-F";; - c) num_cores=${OPTARG};; - t) titanPath=${OPTARG};; - p) savePop="-p";; - P) popPath="-P ${OPTARG}";; - esac -done - -if [[ $jobname == "" ]]; then - jobname="Analysis_$setting_$date" -fi - -if [[ $folderName == "" ]]; then - folderName="$setting/" -fi - -srcCode="${titanPath}/titan/" -outPath="$HOME/scratch/$folderName" - -usage() { -echo " -usage: subtitan {Parameter file or directory}[-T walltime] [-m memory] [-S setting] [-j jobname] [-r repeats] [-n iterations] [-b use_base] [-f folder_name] [-w sweep_defs] [-F force] [-c num_cores ] [-t titanPath ] - -Starts a TITAN simulation in ~/scratch/{SourceFolder}/{jobname} - -options: - -j jobname name of analysis for organization (default: {SourceFolder}_date) - -T walltime as hh:mm:ss, max compute time (default: $walltime) - -m memory per node, as #[k|m|g] (default: $memory) - -S setting name of setting for this model - -r repeats number of times to repeat the analysis (default: $repeats) - -n iterations number of mode iterations per job (default: $nMC) - -f folder_name What the parent folder for the model run outputs should be called (default: ) - -w sweep_defs Optionally, definitions of sweep parameters in the format param:start:stop[:step] - -W sweepfile Optionally, a csv file with sweep definitions (if used, -w flag is ignored) - -R rows Optionally, which data rows of the sweep file to use in format start:stop - -F force If the number of sweep combinations exceeds 100, run anyway - -c num_cores How many cores to request and run the job on (default: $num_cores) - -t titanPath where the code is - -p savePop save population, either 'all' or 'core' (default none) - -P popPath load population from the provide path (by default, creates a new population) -" -exit 0 -} - -updateParams() { -echo " - - Updating params: - savePath $PWD - sourceCode $srcCode - jobname $jobname - walltime $walltime - memory $memory - num_cores $num_cores -" - -#Submit script params -sed -i "s/MODEL_NAME/$jobname/g" scripts/bs_Core.sh -sed -i "s/WALL_TIME/$walltime/g" scripts/bs_Core.sh -sed -i "s/MEMORY/$memory/g" scripts/bs_Core.sh -sed -i "s/NCORES/$num_cores/g" scripts/bs_Core.sh - -} - -prepSubmit() { - #Copy source code into parent path - echo -e "\n\tCopying $srcCode to $finalPath" - mkdir -p $finalPath - cp $titanPath/run_titan.py $finalPath - cp -rT $titanPath/titan $finalPath/titan - cp -rT $titanPath/scripts $finalPath/scripts - cp -rT $titanPath/settings $finalPath/settings - #Move into new source code folder - echo -e "\n\tMoving to model folder directory" - cd $finalPath - echo -e "\t$PWD" - updateParams; - - #Submit job to cluster - sbatch scripts/bs_Core.sh -S $setting -a $paramPath -n $nMC $forceFlag $sweepDefs $sweepfile $rows $savePop $popPath - - #Move back to base directory - cd $basePath -} - - -# User and Date will be ignored if job ID is specified - -if [ ! $paramPath ]; then - usage; -fi - -if [[ ${paramPath:0:1} != "/" ]] || [[ ${paramPath:0:1} == "~" ]]; then - paramPath="${pwd}/$paramPath" -fi - -if [ ! -d $srcCode ]; then - echo -e "\n\n$srcCode is not a directory! Source code must be provided as a directory\n" - exit 0 -fi - -if [ -d $outPath$jobname ]; then - echo -e "\n\n!! WARNING !!\nThe folder $jobname already exists and will be OVERWRITTEN!\n" - read -p "Continue (y/n)?" choice - case "$choice" in - y|Y ) echo "Proceeding";; - n|N|* ) echo "Aborting" - exit 0;; - esac -fi - -if [ $srcCode ]; then - - echo " - jobname $jobname - outPath $outPath - paramPath $paramPath - user $user - date $date - walltime $walltime - memory $memory - outfile $outfile - model $model" - echo -e "\n" - - echo -e "\tMaking parent directory in scratch" - mkdir -p $outPath - echo -e "\t $outPath" - - if [ $repeats -gt 1 ]; then - # mkdir -p $outPath$jobname - basejobname=$jobname - for ((i=1; i<=repeats; i++)); do - echo -e "\n\nWorking on repeat $i" - jobname=$basejobname"_"$i - finalPath=$outPath"/"$basejobname"/"$jobname - prepSubmit; - done - else - finalPath=$outPath"/"$jobname - prepSubmit; - fi - -else - echo -e "\nSOMETHING WENT WRONG!!! Abort" - exit 1; -fi diff --git a/tests/features/haart_test.py b/tests/features/haart_test.py index fd535a8e..a9694dfd 100644 --- a/tests/features/haart_test.py +++ b/tests/features/haart_test.py @@ -18,6 +18,7 @@ def test_update_haart_t1(make_model, make_agent): # t0 agent initialized HAART a.hiv.dx = True + a.hiv.dx_time = model.time # go on haart model.run_random = FakeRandom( @@ -35,7 +36,7 @@ def test_update_haart_t1(make_model, make_agent): assert a.haart.active is False # Try haart cap with low cap, one agent on haart and one diagnosed agent. Nothing happens - a.location.params.hiv.haart_cap = True + a.location.params.haart.use_cap = True a.haart.counts[a.race][a.sex_type] = 1 a.hiv.dx_counts[a.race][a.sex_type] = 5 a.location.params.demographics[a.race].sex_type[a.sex_type].drug_type[ @@ -48,7 +49,52 @@ def test_update_haart_t1(make_model, make_agent): # Increase cap. Agent goes on haart a.location.params.demographics[a.race].sex_type[a.sex_type].drug_type[ a.drug_type - ].haart.prob = 2.0 + ].haart.cap = 2.0 a.haart.update_agent(model) assert a.haart.active assert a.haart.adherent is True + + # falls off adherence + model.run_random = FakeRandom(1.0) + a.location.params.demographics[a.race].sex_type[a.sex_type].drug_type[ + a.drug_type + ].haart.adherence.discontinue = 5.0 + a.haart.update_agent(model) + assert a.haart.active + assert a.haart.adherent is False + + +def test_update_haart_dx_time(make_model, make_agent): + model = make_model() + model.time = 1 + a = make_agent(race="white") + model.run_random = FakeRandom(0.5) + + a.hiv.active = True + a.hiv.dx = True + a.hiv.dx_time = model.time # agent dx duration is 0 + a.haart.update_agent(model) + assert not a.haart.active + + a.hiv.dx_time -= 10 + a.haart.update_agent(model) + assert a.haart.active + + +def test_update_haart_reinit(make_model, make_agent): + model = make_model() + model.time = 1 + a = make_agent(race="white") + + a.hiv.active = True + a.hiv.dx = True + a.hiv.dx_time = model.time - 1 # make duration 1, would pass if not reinit + a.haart.ever = True # uses reinit prob + a.haart.update_agent(model) + assert not a.haart.active + + a.location.params.demographics[a.race].sex_type[a.sex_type].drug_type[ + a.drug_type + ].haart.reinit.prob = 1.0 + a.haart.update_agent(model) + assert a.haart.active diff --git a/tests/features/prep_test.py b/tests/features/prep_test.py index aa1e632e..034b4bfb 100644 --- a/tests/features/prep_test.py +++ b/tests/features/prep_test.py @@ -199,7 +199,7 @@ def test_initiate_prep_eligible(make_model, make_agent): p.hiv.dx = True p.external_exposure.active = True model.time = 10 - a.location.params.prep.target = 1.0 + a.location.params.prep.cap = 1.0 a.location.params.prep.target_model = ["cdc_women"] rel = Relationship(a, p, 10, bond_type="Sex") # non-forcing, adherant, inj @@ -223,7 +223,7 @@ def test_initiate_prep_eligible_racial(make_model, make_agent): p.hiv.dx = True p.external_exposure.active = True model.time = 10 - a.location.params.prep.target = 1.0 + a.location.params.prep.cap = 1.0 a.location.params.prep.target_model = ["Racial"] rel = Relationship(a, p, 10, bond_type="Sex") # non-forcing, adherant, inj @@ -383,10 +383,10 @@ def test_progress_inj_prep(make_agent, params, make_model): @pytest.mark.unit -def test_target_as_prob(make_agent, make_model, params): +def test_cap_as_prob(make_agent, make_model, params): model = make_model(params) a = make_agent() - a.location.params.prep.target_as_prob = True + a.location.params.prep.cap_as_prob = True model.run_random = FakeRandom(0.2) a.prep.initiate(model) diff --git a/tests/features/random_trial_test.py b/tests/features/random_trial_test.py index 28a41e81..0bc2ffe5 100644 --- a/tests/features/random_trial_test.py +++ b/tests/features/random_trial_test.py @@ -10,7 +10,7 @@ def test_initialize_random_trial_prep_all(make_model, params): params.features.prep = True params.vaccine.on_init = False - params.prep.target = 0 + params.prep.cap = 0 params.random_trial.choice = "all" model = make_model(params) model.run_random = FakeRandom(-0.1) @@ -27,7 +27,7 @@ def test_initialize_random_trial_prep_all(make_model, params): def test_initialize_random_trial_prep_eigenvector(make_model, params): params.features.prep = True params.vaccine.on_init = False - params.prep.target = 0 + params.prep.cap = 0 params.hiv.start_time = 5 params.random_trial.choice = "eigenvector" model = make_model(params) @@ -53,7 +53,7 @@ def test_initialize_random_trial_prep_eigenvector(make_model, params): def test_initialize_random_trial_prep_random(make_model, params): params.features.prep = True params.vaccine.on_init = False - params.prep.target = 0 + params.prep.cap = 0 params.hiv.start_time = 5 params.random_trial.choice = "random" model = make_model(params) diff --git a/tests/integration_test.py b/tests/integration_test.py index 5f9431a4..9de11b30 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -30,7 +30,9 @@ def _make_model_integration(): @pytest.mark.integration_deterministic def test_model_runs(): - f = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "run_titan.py") + f = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "run_titan.py" + ) param_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "params", "basic.yml" ) @@ -39,11 +41,26 @@ def test_model_runs(): assert True +@pytest.mark.integration_deterministic +def test_model_runs_sweep(): + f = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "run_titan.py" + ) + param_file = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "params", "basic.yml" + ) + + subprocess.check_call([f, f"-p {param_file}", "-w model.seed.run:1:3"]) + assert True + + @pytest.mark.integration_deterministic def test_model_reproducible(tmpdir): path_a = tmpdir.mkdir("result_a") path_b = tmpdir.mkdir("result_b") - f = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "run_titan.py") + f = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "run_titan.py" + ) param_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "params", "basic_seeded.yml" ) @@ -80,7 +97,9 @@ def test_model_reproducible(tmpdir): @pytest.mark.integration_deterministic def test_model_pop_write_read(tmpdir): path_a = tmpdir.mkdir("a") - f = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "run_titan.py") + f = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "run_titan.py" + ) param_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "params", "basic.yml" ) @@ -99,13 +118,17 @@ def test_model_pop_write_read(tmpdir): @pytest.mark.integration_deterministic def test_model_settings_run(tmpdir): - f = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "run_titan.py") + f = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "run_titan.py" + ) param_file = os.path.join( os.path.dirname(os.path.abspath(__file__)), "params", "integration_base.yml" ) for item in os.listdir( - os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "settings") + os.path.join( + os.path.dirname(os.path.abspath(__file__)), "..", "titan", "settings" + ) ): if "__" not in item and item != "base": path = tmpdir.mkdir(item) @@ -189,7 +212,7 @@ def test_prep_coverage(make_model_integration, tmpdir): model_a.run(path_a) # change the coverage upward for creating model b, use same seeds - model_a.params.prep.target = 0.9 + model_a.params.prep.cap = 0.9 model_a.params.prep.init = 0.9 model_a.params.model.seed.run = model_a.run_seed model_a.params.model.seed.ppl = model_a.pop.pop_seed @@ -197,7 +220,7 @@ def test_prep_coverage(make_model_integration, tmpdir): model_b = TITAN(model_a.params) model_b.run(path_b) print( - f"model b prep world target: {model_b.pop.geography.locations['world'].params.prep.target}" + f"model b prep world cap: {model_b.pop.geography.locations['world'].params.prep.cap}" ) result_file_a = os.path.join(path_a, "basicReport.txt") @@ -232,6 +255,7 @@ def test_prep_coverage(make_model_integration, tmpdir): t10_hiv_b = res_b["10"]["hiv"] t10_diff = t10_hiv_a - t10_hiv_b # a should be higher assert res_a["10"]["prep"] < res_b["10"]["prep"] + assert t10_hiv_a > t0_hiv_a assert t10_diff > t0_diff @@ -242,11 +266,13 @@ def test_syringe_services(params_integration, tmpdir): """ params_integration.demographics.black.sex_type.MSM.drug_type.Inj.ppl = 1.0 params_integration.demographics.black.sex_type.MSM.drug_type["None"].ppl = 0.0 + params_integration.model.num_pop = 500 model_a = TITAN(params_integration) - model_a.params.partnership.sex.frequency.Sex = ObjMap( - {"type": "bins", "bins": {1: {"prob": 1.0, "min": 0, "max": 1}}} + model_a.params.partnership.sex.frequency.Sex = ( + ObjMap( # turn off sex to show only injection effects + {"type": "bins", "bins": {1: {"prob": 1.0, "min": 0, "max": 1}}} + ) ) - path_a = tmpdir.mkdir("a") path_a.mkdir("network") path_b = tmpdir.mkdir("b") @@ -254,6 +280,15 @@ def test_syringe_services(params_integration, tmpdir): # run with default bins (0-9) model_a.run(path_a) + num_bonds = 0 + num_ag = 0 + for ag in model_a.pop.all_agents: + if ag.hiv.active: + num_ag += 1 + for ptnr in ag.get_partners(["Inj", "SexInj"]): + if not ptnr.hiv.active: + num_bonds += 1 + assert num_bonds # make sure there are serodiscordant partnerships # change the coverage upward for creating model b, use same seeds model_a.params.features.syringe_services = True @@ -290,6 +325,8 @@ def test_syringe_services(params_integration, tmpdir): t10_hiv_a = res_a["10"] t10_hiv_b = res_b["10"] t10_diff = t10_hiv_a - t10_hiv_b # a should be higher + assert t10_hiv_a > t0_hiv_a + assert t10_hiv_b > t0_hiv_b assert t10_diff > t0_diff diff --git a/tests/model_test.py b/tests/model_test.py index 36b4bbf7..e9ba2e52 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -141,21 +141,21 @@ def test_timeline_scaling_prep_def(make_model): model.params.timeline_scaling.timeline = ObjMap( { "scale": { - "parameter": "prep|target", + "parameter": "prep|cap", "start_time": 1, "stop_time": 3, "scalar": scalar, } } ) - original_prep_target = model.params.prep.target + original_prep_target = model.params.prep.cap # scale the param model.time = 1 model.timeline_scaling() assert math.isclose( - original_prep_target * scalar, model.params.prep.target, abs_tol=0.001 + original_prep_target * scalar, model.params.prep.cap, abs_tol=0.001 ) # param still scaled @@ -163,17 +163,17 @@ def test_timeline_scaling_prep_def(make_model): model.timeline_scaling() assert math.isclose( - original_prep_target * scalar, model.params.prep.target, abs_tol=0.001 + original_prep_target * scalar, model.params.prep.cap, abs_tol=0.001 ) # revert to original model.time = 3 model.timeline_scaling() - assert math.isclose(original_prep_target, model.params.prep.target, abs_tol=0.001) + assert math.isclose(original_prep_target, model.params.prep.cap, abs_tol=0.001) # still original model.time = 4 model.timeline_scaling() - assert math.isclose(original_prep_target, model.params.prep.target, abs_tol=0.001) + assert math.isclose(original_prep_target, model.params.prep.cap, abs_tol=0.001) diff --git a/tests/params/basic.yml b/tests/params/basic.yml index dfda011e..de5970c0 100644 --- a/tests/params/basic.yml +++ b/tests/params/basic.yml @@ -41,7 +41,17 @@ demographics: init: 0.1 haart: init: 0.1 - prob: 0.1 + enroll: &haart_prob + 0: + prob: 0.1 + start: 0 + stop: 10 + 1: + prob: 1.0 + start: 10 + stop: 100 + reinit: &reinit_prob + prob: 0.0 adherence: init: 0.1 prob: 0.1 @@ -64,7 +74,8 @@ demographics: value_type: float haart: init: 0.1 - prob: 0.1 + enroll: *haart_prob + reinit: *reinit_prob adherence: init: 0.1 prob: 0.1 @@ -73,14 +84,20 @@ demographics: incar: &base_incar init: 0.02 prob: 0.02 - safe_sex: 0.1 + safe_sex: &safe_sex_default + Sex: + prob: 0.1 + Inj: + prob: 0.1 + SexInj: + prob: 0.1 injection: &base_injection unsafe_prob: 0.1 num_acts: 10 prep: &base_prep adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.3 high_risk: &base_high_risk init: 0.1 @@ -94,12 +111,12 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.5 high_risk: *base_high_risk vaccine: *base_vaccine @@ -107,7 +124,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -116,7 +133,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -125,7 +142,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -137,7 +154,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -146,7 +163,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -155,7 +172,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -164,7 +181,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -175,7 +192,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -217,40 +234,54 @@ partnership: receptive: 0.0138 versatile: 0.00745 duration: - Sex: - type: bins - bins: - 1: - prob: 1.0 - min: 1 - max: 2 - Inj: - type: distribution - distribution: - dist_type: randint - vars: + Sex: + white: + type: bins + bins: 1: - value: 1 - value_type: int - 2: - value: 3 - value_type: int - mean: 1 - SexInj: &poisson_one - type: distribution - distribution: - dist_type: poisson - vars: + prob: 1.0 + min: 1 + max: 2 + black: + type: bins + bins: 1: - value: 1 - value_type: float - mean: 1 - Social: *poisson_one + prob: 1.0 + min: 3 + max: 4 + Inj: + white: &inj_dur + type: distribution + distribution: + dist_type: randint + vars: + 1: + value: 1 + value_type: int + 2: + value: 3 + value_type: int + mean: 1 + black: *inj_dur + SexInj: + white: &poisson_one + type: distribution + distribution: + dist_type: poisson + vars: + 1: + value: 1 + value_type: float + mean: 1 + black: *poisson_one + Social: + white: *poisson_one + black: *poisson_one prep: - target: 0.1 + cap: 0.1 init: 0.7 - target_as_prob: false + cap_as_prob: false knowledge: prob: 0.3 @@ -282,6 +313,9 @@ exposures: hiv: true knowledge: true +haart: + use_reinit: true + features: incar: true prep: true diff --git a/tests/params/basic_seeded.yml b/tests/params/basic_seeded.yml index b5837111..a6dc0cea 100644 --- a/tests/params/basic_seeded.yml +++ b/tests/params/basic_seeded.yml @@ -41,7 +41,11 @@ demographics: init: 0.1 haart: init: 0.1 - prob: 0.1 + prob: + 0: + enroll: 0.1 + start: 0 + stop: 99 adherence: init: 0.1 prob: 0.1 @@ -64,7 +68,11 @@ demographics: value_type: float haart: init: 0.1 - prob: 0.1 + prob: + 0: + enroll: 0.1 + start: 0 + stop: 99 adherence: init: 0.1 prob: 0.1 @@ -73,14 +81,22 @@ demographics: incar: &base_incar init: 0.02 prob: 0.02 - safe_sex: 0.1 + safe_sex: &safe_sex_default + Sex: + prob: 0.1 + Inj: + prob: 0.1 + SexInj: + prob: 0.1 + Social: + prob: 0.1 injection: &base_injection unsafe_prob: 0.1 num_acts: 10 prep: &base_prep adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.3 high_risk: &base_high_risk init: 0.1 @@ -94,12 +110,12 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.5 high_risk: *base_high_risk vaccine: *base_vaccine @@ -107,7 +123,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -116,7 +132,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -125,7 +141,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -137,7 +153,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -146,7 +162,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -155,7 +171,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -164,7 +180,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -175,7 +191,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -248,9 +264,9 @@ partnership: Social: *poisson_one prep: - target: 0.1 + cap: 0.1 init: 0.7 - target_as_prob: false + cap_as_prob: false knowledge: prob: 0.3 diff --git a/tests/params/basic_seeded_error.yml b/tests/params/basic_seeded_error.yml index 83cc3f7e..373ec902 100644 --- a/tests/params/basic_seeded_error.yml +++ b/tests/params/basic_seeded_error.yml @@ -41,7 +41,11 @@ demographics: init: 0.1 haart: init: 0.1 - prob: 0.1 + prob: + 0: + enroll: 0.1 + start: 0 + stop: 99 adherence: init: 0.1 prob: 0.1 @@ -64,7 +68,11 @@ demographics: value_type: float haart: init: 0.1 - prob: 0.1 + prob: + 0: + enroll: 0.1 + start: 0 + stop: 99 adherence: init: 0.1 prob: 0.1 @@ -73,14 +81,22 @@ demographics: incar: &base_incar init: 0.02 prob: 0.02 - safe_sex: 0.1 + safe_sex: &safe_sex_default + Sex: + prob: 0.1 + Inj: + prob: 0.1 + SexInj: + prob: 0.1 + Social: + prob: 0.1 injection: &base_injection unsafe_prob: 0.1 num_acts: 10 prep: &base_prep adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.3 high_risk: &base_high_risk init: 0.1 @@ -94,12 +110,12 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: adherence: 0.1 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.5 high_risk: *base_high_risk vaccine: *base_vaccine @@ -107,7 +123,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -116,7 +132,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -125,7 +141,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -137,7 +153,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -146,7 +162,7 @@ demographics: ppl: 0.3 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -155,7 +171,7 @@ demographics: ppl: 0.2 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: *base_injection prep: *base_prep high_risk: *base_high_risk @@ -164,7 +180,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -175,7 +191,7 @@ demographics: ppl: 0.1 drug_type: *base_drug_type incar: *base_incar - safe_sex: 0.1 + safe_sex: *safe_sex_default injection: unsafe_prob: 0. num_acts: 10 @@ -248,9 +264,9 @@ partnership: Social: *poisson_one prep: - target: 0.1 + cap: 0.1 init: 0.7 - target_as_prob: false + cap_as_prob: false knowledge: prob: 0.3 diff --git a/tests/params/simple_integration.yml b/tests/params/simple_integration.yml index 81a550a8..508af68f 100644 --- a/tests/params/simple_integration.yml +++ b/tests/params/simple_integration.yml @@ -20,13 +20,20 @@ demographics: ppl: 1.0 incar: prob: 0.5 # default is off, so this only hits when turned on in integration test - safe_sex: 0.1 + safe_sex: + Sex: + prob: 1.0 + SexInj: + prob: 1.0 + Inj: + prob: 1.0 injection: - unsafe_prob: 0.1 + unsafe_prob: 0.3 + num_acts: 10 prep: adherence: 0.9 discontinue: 0.1 - target: 0.3 + cap: 0.3 init: 0.3 high_risk: init: 0.1 @@ -48,7 +55,11 @@ demographics: init: 0.1 haart: init: 0.1 - prob: 0.1 + prob: + 1: + enroll: 0.1 + start: 0 + stop: 999 adherence: init: 0.1 prob: 0.1 @@ -89,7 +100,11 @@ demographics: init: 0.1 haart: init: 0.1 - prob: 0.1 + prob: + 1: + enroll: 0.1 + start: 0 + stop: 999 adherence: init: 0.1 prob: 0.1 @@ -151,7 +166,7 @@ partnership: syringe_services: timeline: ssp_open: - start_time: 2 + start_time: 0 stop_time: 12 num_slots_start: 500 num_slots_stop: 500 @@ -161,7 +176,7 @@ partner_tracing: prob: 1 prep: - target: 0.1 + cap: 0.1 init: 0.1 start_time: -6 diff --git a/tests/partnering_test.py b/tests/partnering_test.py index 74951f6c..8bee6c36 100644 --- a/tests/partnering_test.py +++ b/tests/partnering_test.py @@ -11,9 +11,11 @@ def test_partnership_duration(params): # test duration with randint - assert get_partnership_duration(params, FakeRandom(1.0), "Inj") == 1 + assert get_partnership_duration(params, FakeRandom(1.0), "Inj", "white") == 1 # test duration with bins - assert get_partnership_duration(params, FakeRandom(0.1), "Sex") == 1 + assert get_partnership_duration(params, FakeRandom(0.1), "Sex", "white") == 1 + # test duration with second race + assert get_partnership_duration(params, FakeRandom(0.1), "Sex", "black") == 3 @pytest.mark.unit diff --git a/tests/population_io_test.py b/tests/population_io_test.py index 3c60b4b4..38a20894 100644 --- a/tests/population_io_test.py +++ b/tests/population_io_test.py @@ -96,7 +96,7 @@ def test_read_pop(tmpdir, make_population, params): @pytest.mark.unit def test_write_read_pop_compressed(tmpdir, make_population, params): - params.prep.target = 0.5 + params.prep.cap = 0.5 pop = make_population(n=10) prep_counts = deepcopy(Prep.counts) diff --git a/tests/population_test.py b/tests/population_test.py index a939a675..c7e1cdaa 100644 --- a/tests/population_test.py +++ b/tests/population_test.py @@ -248,7 +248,7 @@ def test_update_agent_partners_MSM_match(make_population, params): def test_update_agent_partners_NDU_PWID_match(make_population, params): pop = make_population(n=0) a = pop.create_agent(pop.geography.locations["world"], "white", 0, "MSM") - p = pop.create_agent(pop.geography.locations["world"], "white", 0, "MSM") + p = pop.create_agent(pop.geography.locations["world"], "black", 0, "MSM") # ensure random sex partner no assorting pop.pop_random = FakeRandom(1.1) a.drug_type = "None" @@ -265,6 +265,17 @@ def test_update_agent_partners_NDU_PWID_match(make_population, params): assert p in pop.graph.nodes() assert a.partners assert len(pop.graph.edges()) == 1 + for ( + rel + ) in ( + a.relationships + ): # check that duration uses "randomly selected" (first) partner + assert rel.duration == partnering.get_partnership_duration( + a.location.params, pop.np_random, "Sex", a.race + ) + assert rel.duration != partnering.get_partnership_duration( + a.location.params, pop.np_random, "Sex", p.race + ) @pytest.mark.unit diff --git a/tests/run_titan_test.py b/tests/run_titan_test.py new file mode 100644 index 00000000..2445e589 --- /dev/null +++ b/tests/run_titan_test.py @@ -0,0 +1,65 @@ +import pytest + +from titan.run_titan import * + + +@pytest.mark.unit +def test_sweep_range(): + assert sweep_range("param:1:2") == { + "start": 1, + "stop": 2, + "step": 1, + "param": "param", + } + assert sweep_range("param:1.5:2:0.5") == { + "start": 1.5, + "stop": 2.0, + "step": 0.5, + "param": "param", + } + + with pytest.raises(ValueError): + sweep_range("param:a:b:c") + + +@pytest.mark.unit +def test_drange(): + start = 1 + step = 0.1 + stop = 2 + i = 0 + for val in drange(start, stop, step): + i += 1 + assert val >= start and val < stop + + assert i == 10 + + +@pytest.mark.unit +def test_setup_sweeps(): + sweeps = [ + {"start": 1.5, "stop": 2.3, "step": 0.5, "param": "param1"}, + {"start": 1, "stop": 4, "step": 1, "param": "param2"}, + ] + + defs = setup_sweeps(sweeps) + + assert len(defs) == 6 + + +@pytest.mark.unit +def test_setup_seepfile(): + sweepfile = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "params", "sweepfile.csv" + ) + rows = None + defs = setup_sweeps_file(sweepfile, rows) + + assert len(defs) == 7 + assert defs[0] == {"model.seed.run": 0, "model.seed.ppl": 0} + + rows = "2:3" + defs = setup_sweeps_file(sweepfile, rows) + + assert len(defs) == 2 + assert defs[0] == {"model.seed.run": 1, "model.seed.ppl": 1} diff --git a/titan/__init__.py b/titan/__init__.py index 00adb5e6..6e01ee0c 100644 --- a/titan/__init__.py +++ b/titan/__init__.py @@ -1 +1,2 @@ # makes src a package +from .run_titan import script_init diff --git a/titan/exposures/hiv.py b/titan/exposures/hiv.py index 82004c77..19097c93 100644 --- a/titan/exposures/hiv.py +++ b/titan/exposures/hiv.py @@ -325,12 +325,7 @@ def progress_to_aids(self, model: "model.TITAN"): model: the running model """ aids_params = self.agent.location.params.hiv.aids - p = 1.0 - if self.agent.haart.active: # type: ignore[attr-defined] - if self.agent.haart.adherent: # type: ignore[attr-defined] - p = aids_params.haart_scale.adherent - else: - p = aids_params.haart_scale.non_adherent + p = self.agent.haart.aids_scale() # type: ignore[attr-defined] if model.run_random.random() < p * aids_params.prob: self.aids = True diff --git a/titan/features/haart.py b/titan/features/haart.py index 5175b24e..a1a7acc7 100644 --- a/titan/features/haart.py +++ b/titan/features/haart.py @@ -26,6 +26,7 @@ def __init__(self, agent: "agent.Agent"): super().__init__(agent) self.active = False + self.ever = False self.adherent = False @classmethod @@ -62,6 +63,7 @@ def init_agent(self, pop: "population.Population", time: int): and pop.pop_random.random() < agent_params.haart.init ): self.active = True + self.ever = True self.add_agent(self.agent) haart_adh = agent_params.haart.adherence.init @@ -91,7 +93,7 @@ def update_agent(self, model: "model.TITAN"): ) # Go on HAART if not self.active: - if self.agent.location.params.hiv.haart_cap: + if self.agent.location.params.haart.use_cap: # if HAART is based on cap instead of prob, determine number of # HAART agents based on % of diagnosed agents num_dx_agents = self.agent.hiv.dx_counts[self.agent.race][ # type: ignore[attr-defined] @@ -99,18 +101,46 @@ def update_agent(self, model: "model.TITAN"): ] num_haart_agents = self.counts[self.agent.race][self.agent.sex_type] - if num_haart_agents < (haart_params.prob * num_dx_agents): + # take value from dictionary for cap + if num_haart_agents < (haart_params.cap * num_dx_agents): self.initiate(model) else: - if model.run_random.random() < ( - haart_params.prob * model.calibration.haart.coverage - ): - self.initiate(model) - # Go off HAART - elif self.active and model.run_random.random() < haart_params.discontinue: - self.active = False - self.adherent = False - self.remove_agent(self.agent) + if self.ever and self.agent.location.params.haart.use_reinit: + if model.run_random.random() < haart_params.reinit.prob: + self.initiate(model) + else: + enroll_prob = 0 + # Find enroll probability based on time since diagnosis + haart_duration = model.time - self.agent.hiv.dx_time # type: ignore[attr-defined] + for i in haart_params.enroll.values(): + if i.start <= haart_duration < i.stop: + enroll_prob = i.prob + break + + if model.run_random.random() < ( + enroll_prob * model.calibration.haart.coverage + ): + self.initiate(model) + + # Update agents on HAART + else: + # Go off HAART + if model.run_random.random() < haart_params.discontinue: + self.active = False + self.adherent = False + self.remove_agent(self.agent) + # Become non-adherent + elif ( + self.adherent + and model.run_random.random() < haart_params.adherence.discontinue + ): + self.adherent = False + # Become adherent + elif ( + not self.adherent + and model.run_random.random() < haart_params.adherence.become + ): + self.adherent = True @classmethod def add_agent(cls, agent: "agent.Agent"): @@ -168,6 +198,14 @@ def get_transmission_risk_multiplier(self, time: int, interaction_type: str): return prob + def aids_scale(self): + prob = 1.0 + if self.active: + adherence = "adherent" if self.adherent else "non_adherent" + prob = self.agent.location.params.haart.aids_scale[adherence] + + return prob + # =========== HELPER METHODS ============ def initiate(self, model: "model.TITAN"): @@ -187,4 +225,5 @@ def initiate(self, model: "model.TITAN"): # Add agent to HAART class set, update agent params self.active = True + self.ever = True self.add_agent(self.agent) diff --git a/titan/features/prep.py b/titan/features/prep.py index 1e30e48e..6ff569c7 100644 --- a/titan/features/prep.py +++ b/titan/features/prep.py @@ -168,17 +168,17 @@ def initiate(self, model: "model.TITAN", force: bool = False): if force: self.enroll(model.run_random, model.time) - elif params.prep.target_as_prob: + elif params.prep.cap_as_prob: if "Racial" in params.prep.target_model: if ( model.run_random.random() <= params.demographics[self.agent.race] .sex_type[self.agent.sex_type] - .prep.target + .prep.cap ): self.enroll(model.run_random, model.time) else: - if model.run_random.random() <= params.prep.target: + if model.run_random.random() <= params.prep.cap: self.enroll(model.run_random, model.time) else: if "Racial" in params.prep.target_model: @@ -191,12 +191,12 @@ def initiate(self, model: "model.TITAN", force: bool = False): num_hiv_agents = len(all_hiv_agents & all_race) target_prep = (len(all_race) - num_hiv_agents) * params.demographics[ self.agent.race - ].sex_type[self.agent.sex_type].prep.target + ].sex_type[self.agent.sex_type].prep.cap else: num_prep_agents = sum(self.counts.values()) target_prep = int( (model.pop.all_agents.num_members() - len(exposures.HIV.agents)) - * params.prep.target + * params.prep.cap ) if num_prep_agents < target_prep: diff --git a/titan/features/random_trial.py b/titan/features/random_trial.py index 8ea8985a..72f420b6 100644 --- a/titan/features/random_trial.py +++ b/titan/features/random_trial.py @@ -152,7 +152,7 @@ def suitable_prep(agent, model) -> bool: if ( not agent.hiv.active and not agent.prep.active - and model.run_random.random() < agent.location.params.prep.target + and model.run_random.random() < agent.location.params.prep.cap and not agent.vaccine.active ): return True diff --git a/titan/interactions/sex.py b/titan/interactions/sex.py index 298cec46..fe005c47 100644 --- a/titan/interactions/sex.py +++ b/titan/interactions/sex.py @@ -27,7 +27,8 @@ def get_num_acts(cls, model: "model.TITAN", rel: "agent.Relationship") -> int: p_safe_sex = ( rel.agent1.location.params.demographics[rel.agent1.race] .sex_type[rel.agent1.sex_type] - .safe_sex + .safe_sex[rel.bond_type] + .prob ) # increase condom usage if diagnosed diff --git a/titan/output.py b/titan/output.py index c86e9855..4d391e65 100644 --- a/titan/output.py +++ b/titan/output.py @@ -6,6 +6,7 @@ import os import networkx as nx # type: ignore +from numpy import mean # type: ignore from .parse_params import ObjMap from . import utils @@ -284,8 +285,8 @@ def print_components( if f.tell() == 0: f.write( "run_id\trunseed\tpopseed\tt\tcompID\ttotalN\tNhiv\tNprep\tNtrtHIV" - "\tComp_Status\tPCA\tOral\tInjectable\tAware\tnidu\tcentrality\tDensity" - "\tEffectiveSize" + header + "\n" + "\tComp_Status\tPCA\tOral\tInjectable\tAware\tnidu\tcentrality\tdensity" + "\tEffectiveSize" + header + "\tdeg_cent\n" ) comp_id = 0 @@ -341,6 +342,7 @@ def print_components( else: component_status = "treatment_no_eligible" + deg_cent = mean(list(nx.degree_centrality(comp).values())) race_str = "" for race in race_list: race_str += "\t" + str(race_count[race]) @@ -350,7 +352,7 @@ def print_components( f"{nhiv}\t" f"{nprep}\t{ntrthiv}\t{component_status}\t{pca}\t{oral}\t{injectable_prep}" f"\t{aware}\t{nidu}\t{comp_centrality:.4f}\t{comp_density:.4f}" - f"\t{average_size:.4f}{race_str}\n" + f"\t{average_size:.4f}{race_str}\t{deg_cent}\n" ) comp_id += 1 diff --git a/titan/params/demographics.yml b/titan/params/demographics.yml index ba680e82..30300723 100644 --- a/titan/params/demographics.yml +++ b/titan/params/demographics.yml @@ -98,7 +98,7 @@ demographics: max: 1.0 duration: prob: - type: bins + type: bin description: Binned probabilities of incarceration duration during model run fields: prob: @@ -137,7 +137,7 @@ demographics: min: 130 max: 260 init: - type: bins + type: bin description: Binned probabilities of incarceration duration when initializing population fields: prob: @@ -189,11 +189,16 @@ demographics: min: 0.0 max: 1.0 safe_sex: - default: 0.0 - description: "Probability of safe sex (condom use) per sex act" - type: float - min: 0.0 - max: 1.0 + type: sub-dict + description: "Parameters controlling condom use/safe sex per sex act given bond type" + keys: + - bond_types + default: + prob: + default: 0.0 + type: float + min: 0.0 + max: 1.0 prep: discontinue: default: 0.0 @@ -207,9 +212,9 @@ demographics: type: float min: 0.0 max: 1.0 - target: + cap: default: 0.0 - description: "Target percentage of PrEP coverage for this population class, or probability of going on PrEP if target_as_prob is true" + description: "Ceiling/target percentage of PrEP coverage for this population class, or probability of going on PrEP if cap_as_prob is true" type: float min: 0.0 max: 1.0 @@ -297,12 +302,36 @@ demographics: type: float min: 0.0 max: 1.0 - prob: - default: 0.0 - description: "Probability that an agent in this class with HIV becomes enrolled in HAART at a given time step" + cap: type: float + default: 0.0 + description: "Percent of diagnosed agents on haart if haart cap is used" min: 0.0 max: 1.0 + enroll: + type: definition + description: "Probability that an agent in this class with HIV becomes enrolled in HAART given the agent's time since diagnosis (unused if use_cap is true)" + fields: + prob: + type: float + min: 0.0 + max: 1.0 + start: + type: int + min: 0 + stop: + type: int + min: 1 + default: + enroll_0: + start: 0 + stop: 999 + prob: 0.0 + reinit: + prob: + type: float + description: "Probability of reinitiating haart" + default: 0.0 adherence: init: default: 0.0 @@ -316,6 +345,18 @@ demographics: type: float min: 0.0 max: 1.0 + discontinue: + default: 0.0 + description: "Probability that an adherent agent will become nonadherent at a given timestep" + type: float + min: 0.0 + max: 1.0 + become: + default: 0.0 + description: "Probability that a non-adherent agent will become adherent at a given timestep" + type: float + min: 0.0 + max: 1.0 discontinue: default: 0.0 description: "Probability that an agent on HAART discontinues HAART in a given time step" diff --git a/titan/params/haart.yml b/titan/params/haart.yml new file mode 100644 index 00000000..4aec7ba3 --- /dev/null +++ b/titan/params/haart.yml @@ -0,0 +1,22 @@ +haart: + use_cap: + default: false + description: Whether "cap" defined in demographics.race.sex_type.haart.cap is used. Otherwise, haart probability is based on time since diagnosis (demographics.race.sex_type.haart.enroll). + type: boolean + use_reinit: + type: boolean + default: false + description: Whether a separate probability should be used for haart initiation among agents who have previously discontinued haart + aids_scale: + non_adherent: + description: Scalar for aids progression for agents on haart but non-adherent + default: 0.00368 + type: float + min: 0. + max: 1. + adherent: + description: Scalar for aids progression for agents adherent to haart + default: 0.0008 + type: float + min: 0. + max: 1. diff --git a/titan/params/hiv.yml b/titan/params/hiv.yml index bb6f1a49..108ef18b 100644 --- a/titan/params/hiv.yml +++ b/titan/params/hiv.yml @@ -31,28 +31,11 @@ hiv: type: float min: 0 max: 1 - haart_scale: - non_adherent: - description: Scalar for aids progression for agents on haart but non-adherent - default: 0.00368 - type: float - min: 0. - max: 1. - adherent: - description: Scalar for aids progression for agents adherent to haart - default: 0.0008 - type: float - min: 0. - max: 1. max_init_time: default: 42 description: On creation, agents are randomly assigned an hiv.time, this is the maximum time type: int min: 2 - haart_cap: - default: false - description: Whether "prob" defined in demographics.race.sex_type.haart is a percent of diagnosed, HIV+ agents instead of a per-time-step probability for HAART coverage - type: boolean start_time: default: 0 description: "What timestep to start agent interactions related to hiv transmission" diff --git a/titan/params/location.yml b/titan/params/location.yml index cfb920e1..036675c2 100644 --- a/titan/params/location.yml +++ b/titan/params/location.yml @@ -6,7 +6,7 @@ location: - locations default: type: definition - description: "Definition of applying a scalar to a parameter. The parameter should be the definition name with pipes instead of dots (e.g. `prep.target` would be `prep|target`). Some parameter categories can't be scaled, these include: model, classes, features, calibration, outputs, agent_zero, syringe_services, location. Additionally, some fields within otherwise scalable categories cannot be changed by location if their use is not tied to a specific agent (e.g. prep.pca.choice, prep.random_trial). When a parameter is related to the interaction of an agent and a partner, the params of the location where the agent lives is typically used unless the parameter use clearly relates to the partner." + description: "Definition of applying a scalar to a parameter. The parameter should be the definition name with pipes instead of dots (e.g. `prep.cap` would be `prep|cap`). Some parameter categories can't be scaled, these include: model, classes, features, calibration, outputs, agent_zero, syringe_services, location. Additionally, some fields within otherwise scalable categories cannot be changed by location if their use is not tied to a specific agent (e.g. prep.pca.choice, prep.random_trial). When a parameter is related to the interaction of an agent and a partner, the params of the location where the agent lives is typically used unless the parameter use clearly relates to the partner." fields: field: type: enum diff --git a/titan/params/partnership.yml b/titan/params/partnership.yml index b229ca35..53b8d722 100644 --- a/titan/params/partnership.yml +++ b/titan/params/partnership.yml @@ -179,7 +179,8 @@ partnership: type: sub-dict keys: - bond_types - description: "Duration of relationships given bond type" + - races + description: "Duration of relationships given agent race and partnership bond type" default: type: type: enum diff --git a/titan/params/prep.yml b/titan/params/prep.yml index c1fc26d6..609a5f0d 100644 --- a/titan/params/prep.yml +++ b/titan/params/prep.yml @@ -7,9 +7,9 @@ prep: - Inj description: Type of PrEP used in model type: array - target: + cap: default: 0.0 - description: Target coverage for PrEP coverage, or probability of initiating prep at a timestep if target_as_prob is true + description: Ceiling/target coverage for PrEP coverage, or probability of initiating prep at a timestep if cap_as_prob is true type: float min: 0 max: 1 @@ -19,9 +19,9 @@ prep: type: float min: 0. max: 1. - target_as_prob: + cap_as_prob: default: false - description: If prep.target should be treated as a probability instead of a target + description: If prep.cap should be treated as a probability instead of a cap type: boolean start_time: default: 0 diff --git a/titan/params/syringe_services.yml b/titan/params/syringe_services.yml index 8f258ff5..32ad2034 100644 --- a/titan/params/syringe_services.yml +++ b/titan/params/syringe_services.yml @@ -5,11 +5,9 @@ syringe_services: start_time: type: int description: "What time to start this prevalence of syringe services program" - min: 1 stop_time: type: int description: "What time to stop this prevalence of syringe services program" - min: 1 num_slots_start: type: int description: "Cap for number of available slots in SSP at beginning of time period" diff --git a/titan/parse_params.py b/titan/parse_params.py index 6a7dfc2a..9b7de6ab 100644 --- a/titan/parse_params.py +++ b/titan/parse_params.py @@ -86,7 +86,7 @@ def check_params(params: ObjMap): def create_params( - setting_path: Optional[str], + setting_name: Optional[str], param_path: str, outdir: str, error_on_unused: bool = False, @@ -96,7 +96,7 @@ def create_params( or not to use the base setting. Parse and create a params (ObjMap) object. args: - setting_path: path to a settings file or directory or `None` + setting_name: path to a settings file or directory or `None` param_path: path to parameter file or directory outdir: path to directory where computed params will be saved error_on_unused: throw a hard error if there are unused parameters, otherwise warnings are only printed @@ -116,8 +116,12 @@ def create_params( param_paths = [] # merge setting and params - if setting_path is not None: - param_paths.append(setting_path) + if setting_name is not None: + # check if it's a known setting or pass it through as a path + if setting_name in os.listdir(os.path.join(parent, "settings")): + param_paths.append(os.path.join(parent, "settings", setting_name)) + else: + param_paths.append(setting_name) param_paths.append(param_path) diff --git a/titan/partnering.py b/titan/partnering.py index df0436e1..ef130051 100644 --- a/titan/partnering.py +++ b/titan/partnering.py @@ -124,30 +124,34 @@ def get_mean_rel_duration(params: "parse_params.ObjMap"): args: params: The current model's parameters """ - mean_rel_duration: Dict[str, int] = {} + mean_rel_duration: Dict[str, Dict] = {} for bond in params.partnership.duration: - if params.partnership.duration[bond].type == "bins": - weights = [] - vals = [] - dur_bins = params.partnership.duration[bond].bins - for bins in dur_bins: - if bins > 1: - weights.append(dur_bins[bins].prob - dur_bins[bins - 1].prob) - else: - weights.append(dur_bins[bins].prob) - vals.append(np.average([dur_bins[bins].min, dur_bins[bins].max])) - mean_rel_duration[bond] = np.average(vals, weights=weights) - else: - mean_rel_duration[bond] = params.partnership.duration[ - bond - ].distribution.mean - assert mean_rel_duration[bond] > 0, "All bonds must have a positive duration!" + mean_rel_duration[bond] = {} + for race in params.classes.races: + if params.partnership.duration[bond][race].type == "bins": + weights = [] + vals = [] + dur_bins = params.partnership.duration[bond][race].bins + for bins in dur_bins: + if bins > 1: + weights.append(dur_bins[bins].prob - dur_bins[bins - 1].prob) + else: + weights.append(dur_bins[bins].prob) + vals.append(np.average([dur_bins[bins].min, dur_bins[bins].max])) + mean_rel_duration[bond][race] = np.average(vals, weights=weights) + else: + mean_rel_duration[bond][race] = params.partnership.duration[bond][ + race + ].distribution.mean + assert ( + mean_rel_duration[bond][race] > 0 + ), "All bonds must have a positive duration!" return mean_rel_duration def get_partnership_duration( - params: "parse_params.ObjMap", rand_gen, bond_type: str + params: "parse_params.ObjMap", rand_gen, bond_type: str, race: Optional[str] ) -> int: """ Get duration of a relationship drawn from bins or a distribution per the params [params.partnership.duration] @@ -161,8 +165,8 @@ def get_partnership_duration( number of time steps the partnership should endure """ - if params.partnership.duration[bond_type].type == "bins": - dur_info = params.partnership.duration[bond_type].bins + if params.partnership.duration[bond_type][race].type == "bins": + dur_info = params.partnership.duration[bond_type][race].bins diceroll = rand_gen.random() dur_bin = dur_info[5] @@ -174,7 +178,7 @@ def get_partnership_duration( duration = rand_gen.randint(dur_bin.min, dur_bin.max) else: - dist = params.partnership.duration[bond_type].distribution + dist = params.partnership.duration[bond_type][race].distribution duration = utils.safe_dist(dist, rand_gen) return duration diff --git a/titan/population.py b/titan/population.py index b9175b6d..64ddaba0 100644 --- a/titan/population.py +++ b/titan/population.py @@ -91,7 +91,7 @@ def __init__(self, params: "parse_params.ObjMap", id: Optional[str] = None): self.relationships: Set["ag.Relationship"] = set() # find average partnership durations - self.mean_rel_duration: Dict[str, int] = partnering.get_mean_rel_duration( + self.mean_rel_duration: Dict[str, Dict] = partnering.get_mean_rel_duration( self.params ) @@ -191,7 +191,7 @@ def create_agent( utils.safe_dist(dist_info, self.np_random) * utils.safe_divide( agent.location.params.calibration.sex.partner, - self.mean_rel_duration[bond], + self.mean_rel_duration[bond][race], ) ) # so not zero if added mid-year @@ -353,8 +353,9 @@ def update_agent_partners( no_match = True if partner: + race = utils.safe_random_choice([agent.race, partner.race], self.pop_random) duration = partnering.get_partnership_duration( - agent.location.params, self.np_random, bond_type + agent.location.params, self.np_random, bond_type, race ) relationship = ag.Relationship( agent, partner, duration, bond_type=bond_type diff --git a/titan/population_io.py b/titan/population_io.py index 4a66d71a..c8a095c2 100644 --- a/titan/population_io.py +++ b/titan/population_io.py @@ -54,7 +54,7 @@ def write(pop: Population, dir: str, compress: bool = True) -> str: returns: path, or archive name if compress is true """ - assert len(pop.relationships) > 0, "can't write empty population" + assert len(pop.relationships) > 0, "Can't write empty population" # open agent file agent_file = os.path.join(dir, f"{pop.id}_agents.csv") diff --git a/run_titan.py b/titan/run_titan.py similarity index 94% rename from run_titan.py rename to titan/run_titan.py index 8950c45d..e5575f6c 100755 --- a/run_titan.py +++ b/titan/run_titan.py @@ -5,6 +5,7 @@ from copy import copy import sys import os +from inspect import getsourcefile import shutil import argparse import itertools @@ -15,15 +16,22 @@ from typing import List, Optional import subprocess +# allow imports to work if running it as a script for development locally +if __name__ == "__main__": + PACKAGE_PARENT = ".." + SCRIPT_DIR = os.path.dirname( + os.path.realpath(os.path.join(os.getcwd(), os.path.expanduser(__file__))) + ) + sys.path.append(os.path.normpath(os.path.join(SCRIPT_DIR, PACKAGE_PARENT))) + from titan.model import TITAN from titan.population import Population import titan.population_io as pop_io from titan.parse_params import create_params from titan import utils -# how many cores can we use -NCORES = os.environ.get("SLURM_CPUS_PER_TASK", cpu_count()) -NCORES = int(NCORES) # environment variable returns string +# how many cores can we use, environment variable returns string +NCORES = int(os.environ.get("SLURM_CPUS_PER_TASK", cpu_count())) # set up args parsing parser = argparse.ArgumentParser(description="Run TITAN model") @@ -90,7 +98,7 @@ def sweep_range(string): except ValueError: raise ValueError("start, stop, and step must have same type (int or float)") - return {"param": parts[0], "start": start, "stop": stop, "step": step} + return {"param": parts[0].strip(), "start": start, "stop": stop, "step": step} parser.add_argument( @@ -339,16 +347,10 @@ def main( # generate params - if no setting, set to none setting = setting.lower() - if setting == "custom": - setting = None - else: - setting = os.path.join( - os.path.dirname(os.path.abspath(__file__)), "settings", setting - ) - assert os.path.isdir(setting), f"{setting} is not a directory" + setting_parsed = None if setting == "custom" else setting params = create_params( - setting, + setting_parsed, params_path, outfile_dir, error_on_unused=error_on_unused, @@ -411,7 +413,7 @@ def mean(seq): print(f"all tasks - total: {toc} seconds") -if __name__ == "__main__": +def script_init(): args = parser.parse_args() rows = args.rows.strip() if args.rows is not None else None sweepfile = args.sweepfile.strip() if args.sweepfile is not None else None @@ -429,3 +431,7 @@ def mean(seq): save_pop=args.savepop, pop_path=poppath, ) + + +if __name__ == "__main__": + script_init() diff --git a/settings/atlanta/assort_mix.yml b/titan/settings/atlanta/assort_mix.yml similarity index 100% rename from settings/atlanta/assort_mix.yml rename to titan/settings/atlanta/assort_mix.yml diff --git a/settings/atlanta/calibration.yml b/titan/settings/atlanta/calibration.yml similarity index 100% rename from settings/atlanta/calibration.yml rename to titan/settings/atlanta/calibration.yml diff --git a/settings/atlanta/demographics.yml b/titan/settings/atlanta/demographics.yml similarity index 91% rename from settings/atlanta/demographics.yml rename to titan/settings/atlanta/demographics.yml index 9bc31445..84238956 100644 --- a/settings/atlanta/demographics.yml +++ b/titan/settings/atlanta/demographics.yml @@ -11,11 +11,13 @@ demographics: versatile: 0.437 insertive: 0.242 receptive: 0.321 - safe_sex: 0.688 + safe_sex: + Sex: + prob: 0.688 prep: discontinue: 0.0 adherence: 0.568 - target: 0.258 + cap: 0.258 init: 0.258 drug_type: NonInj: @@ -36,7 +38,7 @@ demographics: init: 0.232 haart: &black_haart init: 0.625 - prob: 0.35 + cap: 0.35 adherence: init: 0.885 prob: 0.885 @@ -56,10 +58,12 @@ demographics: versatile: 0.544 insertive: 0.228 receptive: 0.228 - safe_sex: 0.528 + safe_sex: + Sex: + prob: 0.528 prep: adherence: 0.911 - target: 0.415 + cap: 0.415 discontinue: 0.0 init: 0.415 drug_type: @@ -78,7 +82,7 @@ demographics: aids: 34.4 haart: &white_haart init: 0.585 - prob: 0.20 + cap: 0.20 adherence: init: 0.817 prob: 0.817 diff --git a/settings/atlanta/model.yml b/titan/settings/atlanta/model.yml similarity index 100% rename from settings/atlanta/model.yml rename to titan/settings/atlanta/model.yml diff --git a/settings/atlanta/outputs.yml b/titan/settings/atlanta/outputs.yml similarity index 100% rename from settings/atlanta/outputs.yml rename to titan/settings/atlanta/outputs.yml diff --git a/settings/atlanta/partnership.yml b/titan/settings/atlanta/partnership.yml similarity index 59% rename from settings/atlanta/partnership.yml rename to titan/settings/atlanta/partnership.yml index 21d1d610..5a479b6d 100644 --- a/settings/atlanta/partnership.yml +++ b/titan/settings/atlanta/partnership.yml @@ -1,20 +1,22 @@ partnership: duration: Sex: - type: bins - bins: - 1: - prob: 0.456 - min: 1 - max: 3 - 2: - prob: 0.756 - min: 3 - max: 12 - 3: - prob: 1.0 - min: 13 - max: 24 + white: &partnership_duration + type: bins + bins: + 1: + prob: 0.456 + min: 1 + max: 3 + 2: + prob: 0.756 + min: 3 + max: 12 + 3: + prob: 1.0 + min: 13 + max: 24 + black: *partnership_duration sex: frequency: Sex: diff --git a/settings/chicago/chicago_baseCase.yml b/titan/settings/chicago/chicago_baseCase.yml similarity index 79% rename from settings/chicago/chicago_baseCase.yml rename to titan/settings/chicago/chicago_baseCase.yml index 7acd63b1..dc37cf19 100644 --- a/settings/chicago/chicago_baseCase.yml +++ b/titan/settings/chicago/chicago_baseCase.yml @@ -37,9 +37,6 @@ features: calibration: injection: act: 1.0 - sex: - partner: 1.0 - act: 1.0 acquisition: 1.0 test_frequency: 1.0 mortality: 1.0 @@ -84,7 +81,7 @@ prep: type: - Oral - Inj - target: 0.088 + cap: 0.088 init: 0.088 start_time: 0 efficacy: @@ -102,14 +99,10 @@ demographics: incar: init: 0.0 prob: 0.0 - safe_sex: 0.312 - injection: - unsafe_prob: 0.27 - num_acts: 5.0 prep: discontinue: 0.0 adherence: 0.568 - target: 0.5 + cap: 0.5 init: 0.5 high_risk: init: 0.0 @@ -144,34 +137,29 @@ demographics: partnership: duration: Social: - type: bins - bins: - 1: - prob: 0.585 - min: 1 - max: 6 - 2: - prob: 0.701 - min: 7 - max: 12 - 3: - prob: 0.822 - min: 13 - max: 24 - 4: - prob: 0.8819999999999999 - min: 25 - max: 36 - 5: - prob: 1.0 - min: 37 - max: 48 - sex: - acquisition: - MSM: - versatile: 0.00745 - insertive: 0.0011 - receptive: 0.0138 + black: + type: bins + bins: + 1: + prob: 0.585 + min: 1 + max: 6 + 2: + prob: 0.701 + min: 7 + max: 12 + 3: + prob: 0.822 + min: 13 + max: 24 + 4: + prob: 0.8819999999999999 + min: 25 + max: 36 + 5: + prob: 1.0 + min: 37 + max: 48 pca: frequency: Social: diff --git a/settings/mississippi/demographics.yml b/titan/settings/mississippi/demographics.yml similarity index 70% rename from settings/mississippi/demographics.yml rename to titan/settings/mississippi/demographics.yml index fd0989db..4d478e44 100644 --- a/settings/mississippi/demographics.yml +++ b/titan/settings/mississippi/demographics.yml @@ -9,7 +9,9 @@ demographics: versatile: 0.671 insertive: 0.169 receptive: 0.160 - safe_sex: 0.214 + safe_sex: + Sex: + prob: 0.214 prep: discontinue: 0.034 adherence: 0.860 @@ -25,7 +27,21 @@ demographics: init: 0.46 haart: init: 0.690 - prob: 0.057 + enroll: + enroll_0: + prob: 0.609 + start: 0 + stop: 1 + enroll_1: + prob: 0.074 + start: 1 + stop: 12 + enroll_2: + prob: 0.0 + start: 12 + stop: 9999 + reinit: + prob: 0.01 adherence: init: 0.500 prob: 0.500 diff --git a/settings/mississippi/model.yml b/titan/settings/mississippi/model.yml similarity index 80% rename from settings/mississippi/model.yml rename to titan/settings/mississippi/model.yml index e3bb5414..9a9f2201 100644 --- a/settings/mississippi/model.yml +++ b/titan/settings/mississippi/model.yml @@ -57,24 +57,25 @@ partnership: versatile: 0.0026625 duration: Sex: - type: distribution - distribution: - dist_type: weibull_modified - vars: - 1: - value: 0.586 - value_type: float - 2: - value: 11.532 - value_type: float - mean: 16 + black: + type: distribution + distribution: + dist_type: weibull_modified + vars: + 1: + value: 0.586 + value_type: float + 2: + value: 11.532 + value_type: float + mean: 16 prep: type: - Oral - target: 0.049 + cap: 0.049 init: 0.25 - target_as_prob: true + cap_as_prob: true efficacy: adherent: 0.96 non_adherent: 0.83 diff --git a/settings/nyc-msm/demographics.yml b/titan/settings/nyc-msm/demographics.yml similarity index 65% rename from settings/nyc-msm/demographics.yml rename to titan/settings/nyc-msm/demographics.yml index 99096618..b866c00b 100644 --- a/settings/nyc-msm/demographics.yml +++ b/titan/settings/nyc-msm/demographics.yml @@ -9,11 +9,15 @@ demographics: versatile: 0.200 insertive: 0.480 receptive: 0.320 - safe_sex: 0.275 + safe_sex: + Main: + prob: 0.275 + Casual: + prob: 0.409 prep: discontinue: 0.034 # calibration adherence: 0.565 - target: 0.028 + cap: 0.028 init: 0.252 drug_type: None: @@ -27,11 +31,30 @@ demographics: init: 0.500 haart: init: 0.764 - prob: 0.212 + enroll: + enroll_0: + start: 0 + stop: 1 + prob: 0.709 + enroll_1: + start: 1 + stop: 3 + prob: 0.212 + enroll_2: + start: 3 + stop: 12 + prob: 0.051 + enroll_3: + start: 12 + stop: 9999 + prob: 0.0 + reinit: + prob: 0.035 adherence: init: 0.489 prob: 0.489 - discontinue: 0.0076 + discontinue: 0.0689 + discontinue: 0.014 death_rate: base: 1.512 num_partners: @@ -63,11 +86,15 @@ demographics: insertive: 0.220 receptive: 0.195 versatile: 0.585 - safe_sex: 0.262 + safe_sex: + Main: + prob: 0.183 + Casual: + prob: 0.341 prep: discontinue: 0.025 # calibration adherence: 0.719 - target: 0.026 + cap: 0.026 init: 0.234 drug_type: None: @@ -81,10 +108,29 @@ demographics: init: 0.487 haart: init: 0.764 - prob: 0.193 + enroll: + 0: + prob: 0.278 + start: 0 + stop: 1 + 1: + prob: 0.193 + start: 1 + stop: 3 + 2: + prob: 0.037 + start: 3 + stop: 12 + 3: + start: 12 + stop: 9999 + prob: 0.0 + reinit: + prob: 0.032 adherence: init: 0.719 prob: 0.719 + discontinue: 0.0115 discontinue: 0.0069 # calibration death_rate: base: 0.792 @@ -112,7 +158,11 @@ demographics: sex_type: MSM: ppl: 1.0 - safe_sex: 0.126 + safe_sex: + Main: + prob: 0.126 + Casual: + prob: 0.277 sex_role: init: insertive: 0.439 @@ -121,7 +171,7 @@ demographics: prep: discontinue: 0.014 adherence: 0.738 - target: 0.031 + cap: 0.031 init: 0.279 drug_type: None: @@ -135,11 +185,30 @@ demographics: init: 0.471 haart: init: 0.791 - prob: 0.163 + enroll: + 0: + start: 0 + stop: 1 + prob: 0.742 + 1: + start: 1 + stop: 3 + prob: 0.163 + 2: + start: 3 + stop: 12 + prob: 0.029 + 3: + start: 12 + stop: 9999 + prob: 0.0 + reinit: + prob: 0.041 adherence: init: 0.707 prob: 0.707 - discontinue: 0.0059 + discontinue: 0.0745 + discontinue: 0.00883 death_rate: base: 0.0696 num_partners: diff --git a/settings/nyc-msm/hiv.yml b/titan/settings/nyc-msm/hiv.yml similarity index 81% rename from settings/nyc-msm/hiv.yml rename to titan/settings/nyc-msm/hiv.yml index 53c0f32a..73445206 100644 --- a/settings/nyc-msm/hiv.yml +++ b/titan/settings/nyc-msm/hiv.yml @@ -9,8 +9,11 @@ hiv: prep: target_model: - Racial - target_as_prob: true + cap_as_prob: true start_time: -48 efficacy: adherent: 0.96 non_adherent: 0.76 + +haart: + use_reinit: true diff --git a/titan/settings/nyc-msm/location.yml b/titan/settings/nyc-msm/location.yml new file mode 100644 index 00000000..8fc067b0 --- /dev/null +++ b/titan/settings/nyc-msm/location.yml @@ -0,0 +1,55 @@ +location: + scaling: + high_test: + demographics|white|ppl: + field: override + override: 0.384 + demographics|white|sex_type|MSM|drug_type|None|hiv|dx|prob: &high_test_prob + field: override + override: 0.333 + demographics|black|ppl: + field: override + override: 0.278 + demographics|black|sex_type|MSM|drug_type|None|hiv|dx|prob: *high_test_prob + demographics|latino|ppl: + field: override + override: 0.338 + demographics|latino|sex_type|MSM|drug_type|None|hiv|dx|prob: *high_test_prob + + mid_test: + demographics|white|ppl: + field: override + override: 0.587 + demographics|white|sex_type|MSM|drug_type|None|hiv|dx|prob: &mid_test_prob + field: override + override: 0.167 + demographics|black|ppl: + field: override + override: 0.187 + demographics|black|sex_type|MSM|drug_type|None|hiv|dx|prob: *mid_test_prob + demographics|latino|ppl: + field: override + override: 0.226 + demographics|latino|sex_type|MSM|drug_type|None|hiv|dx|prob: *mid_test_prob + + low_test: + demographics|white|ppl: + field: override + override: 0.605 + demographics|white|sex_type|MSM|drug_type|None|hiv|dx|prob: &low_test_prob + field: override + override: 0.042 + demographics|black|ppl: + field: override + override: 0.082 + demographics|black|sex_type|MSM|drug_type|None|hiv|dx|prob: *low_test_prob + demographics|latino|ppl: + field: override + override: 0.313 + demographics|latino|sex_type|MSM|drug_type|None|hiv|dx|prob: *low_test_prob + + edges: + edge_default: + location_1: high_test + location_2: high_test + distance: 0 \ No newline at end of file diff --git a/settings/nyc-msm/model.yml b/titan/settings/nyc-msm/model.yml similarity index 90% rename from settings/nyc-msm/model.yml rename to titan/settings/nyc-msm/model.yml index e2c0dd92..517c68f0 100644 --- a/settings/nyc-msm/model.yml +++ b/titan/settings/nyc-msm/model.yml @@ -27,6 +27,14 @@ classes: - sex drug_types: - None + locations: + high_test: + ppl: .190 + mid_test: + ppl: .634 + low_test: + ppl: .176 + features: prep: true diff --git a/titan/settings/nyc-msm/partnership.yml b/titan/settings/nyc-msm/partnership.yml new file mode 100644 index 00000000..fb566253 --- /dev/null +++ b/titan/settings/nyc-msm/partnership.yml @@ -0,0 +1,111 @@ +partnership: + sex: + frequency: + Main: + type: distribution + distribution: + dist_type: gamma + vars: + 1: + value: 0.439 + value_type: float + 2: + value: 15.3 + value_type: float + Casual: + type: distribution + distribution: + dist_type: gamma + vars: + 1: + value: 0.131 + value_type: float + 2: + value: 16.5 + value_type: float + haart_scaling: + MSM: # calibration + non_adherent: 0.165 + adherent: 0.00 + acquisition: + MSM: + insertive: 0.0017 + receptive: 0.0075 + versatile: 0.0046 + + duration: + Main: + white: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 70.44 + value_type: float + 2: + value: 6.72 + value_type: float + mean: 70.44 + black: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 20.88 + value_type: float + 2: + value: 2.76 + value_type: float + mean: 20.88 + latino: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 35.52 + value_type: float + 2: + value: 3.84 + value_type: float + mean: 3.84 + Casual: + white: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 11.76 + value_type: float + 2: + value: 1.32 + value_type: float + mean: 11.76 + black: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 17.16 + value_type: float + 2: + value: 1.56 + value_type: float + mean: 17.16 + latino: + type: distribution + distribution: + dist_type: wald + vars: + 1: + value: 16.32 + value_type: float + 2: + value: 1.20 + value_type: float + mean: 16.32 + diff --git a/settings/scott/demographics.yml b/titan/settings/scott/demographics.yml similarity index 83% rename from settings/scott/demographics.yml rename to titan/settings/scott/demographics.yml index ce8b837c..045142a7 100644 --- a/settings/scott/demographics.yml +++ b/titan/settings/scott/demographics.yml @@ -24,7 +24,11 @@ demographics: sex_role: init: insertive: 1.0 - safe_sex: 0.1 + safe_sex: + Sex: &condom_use_HM + prob: 0.1 + Inj: *condom_use_HM + SexInj: *condom_use_HM injection: unsafe_prob: 0.321 num_acts: 5 @@ -36,9 +40,13 @@ demographics: dx: prob: 0.202 init: 1.0 - haart: + haart: &haart_vals init: 1.0 - prob: 0.679 + enroll: + enroll_0: + prob: 0.679 + start: 0 + stop: 999 adherence: init: 1.0 prob: 1.0 @@ -82,13 +90,7 @@ demographics: hiv: 3.1 aids: 10.4 haart_adherent: 1.0 - haart: - init: 1.0 - prob: 0.679 - adherence: - init: 1.0 - prob: 1.0 - discontinue: 0.000 + haart: *haart_vals num_partners: Sex: dist_type: poisson @@ -101,7 +103,11 @@ demographics: sex_role: init: receptive: 1.0 - safe_sex: 0.2 + safe_sex: + Sex: &condom_use_HF + prob: 0.2 + Inj: *condom_use_HF + SexInj: *condom_use_HF injection: unsafe_prob: 0.321 num_acts: 5 diff --git a/settings/scott/model.yml b/titan/settings/scott/model.yml similarity index 86% rename from settings/scott/model.yml rename to titan/settings/scott/model.yml index c21faf8d..45ca3646 100644 --- a/settings/scott/model.yml +++ b/titan/settings/scott/model.yml @@ -43,12 +43,14 @@ hiv: dx: risk_reduction: sex: 0.19 - haart_cap: true start_time: -48 init: -48 aids: prob: 0.0029 +haart: + use_cap: true + timeline_scaling: timeline: burn_scale: @@ -180,35 +182,38 @@ partnership: adherent: 0.06 duration: Sex: - type: distribution - distribution: - dist_type: set_value - vars: - 1: - value: 12100 - value_type: int - mean: 12100 + white: + type: distribution + distribution: + dist_type: set_value + vars: + 1: + value: 12100 + value_type: int + mean: 12100 SexInj: - type: distribution - distribution: - dist_type: set_value - vars: - 1: - value: 121 - value_type: int - mean: 121 + white: + type: distribution + distribution: + dist_type: set_value + vars: + 1: + value: 121 + value_type: int + mean: 121 Inj: - type: distribution - distribution: - dist_type: gamma - vars: - 1: - value: 5.37 - value_type: float - 2: - value: 23.28 - value_type: float - mean: 125 + white: + type: distribution + distribution: + dist_type: gamma + vars: + 1: + value: 5.37 + value_type: float + 2: + value: 23.28 + value_type: float + mean: 125 assort_mix: assort_pwid: diff --git a/titan/utils.py b/titan/utils.py index 6de0d151..b218df56 100644 --- a/titan/utils.py +++ b/titan/utils.py @@ -176,7 +176,7 @@ def get_param_from_path(params, param_path, delimiter): def scale_param(params, param_path, scalar, delimiter="|"): """ - Given the params and a parameter path in the format prep|target, scale the + Given the params and a parameter path in the format prep|cap, scale the current value by the scalar """ scaling_item, last_key = get_param_from_path(params, param_path, delimiter) @@ -188,7 +188,7 @@ def scale_param(params, param_path, scalar, delimiter="|"): def override_param(params, param_path, value, delimiter="|"): """ - Given the params and a parameter path in the format prep|target, change the + Given the params and a parameter path in the format prep|cap, change the current value to new value """ override_item, last_key = get_param_from_path(params, param_path, delimiter)