From c0fc2cacfce9c92868b0572959b326b867aeb27f Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 12:07:20 -0400 Subject: [PATCH 1/7] feat: allow dissolution of relationships at a specified timestep --- titan/model.py | 4 +++- titan/params/partnership.yml | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/titan/model.py b/titan/model.py index e70ad942..4ab69354 100644 --- a/titan/model.py +++ b/titan/model.py @@ -235,7 +235,9 @@ def update_all_agents(self): # If static network, ignore relationship progression if not self.params.features.static_network: for rel in copy(self.pop.relationships): - if rel.progress(): + if self.params.partnership.dissolve.time == self.time: + rel.progress(force=True) + elif rel.progress(): self.pop.remove_relationship(rel) if self.params.features.exit_enter: diff --git a/titan/params/partnership.yml b/titan/params/partnership.yml index 114ead1d..5c55662c 100644 --- a/titan/params/partnership.yml +++ b/titan/params/partnership.yml @@ -258,3 +258,7 @@ partnership: description: Probability that for a given partnering attempt, the agent tries to partner with only other agents in their component. Otherwise, the agent tries to partner with agents from any component. Network must be enabled. min: 0 max: 1 + dissolve: + time: + default: -9999 + type: int From a8d4f70ff28ff8e91cc0ce3a6d2e1332f25d3205 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 12:34:07 -0400 Subject: [PATCH 2/7] test: test new partnership dissolution --- tests/model_test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/tests/model_test.py b/tests/model_test.py index febfcd9a..e7c0397b 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -30,7 +30,7 @@ def test_model_init(params): @pytest.mark.unit -def test_update_all_agents(make_model, make_agent): +def test_update_all_agents(make_model, make_agent, make_relationship): # make agent 0 model = make_model() assert model.params.agent_zero.interaction_type == "injection" @@ -57,6 +57,22 @@ def test_update_all_agents(make_model, make_agent): assert "No agent zero!" in str(excinfo) + # check that model dissolves relationships + model.params.features.agent_zero = False + model.params.partnership.dissolve.time = 1 + model.time = 1 + a = make_agent() + p = make_agent() + r = make_relationship(a, p) + model.pop.add_relationship(r) + a.target_partners["Sex"] = 0 + a.target_partners["SexInj"] = 0 + a.target_partners["Inj"] = 0 + + assert a.has_partners() is True + model.update_all_agents() + assert a.has_partners() is False + @pytest.mark.unit def test_death_none(make_model): From 4299280087f8b94602ee0e54352d10db4db75f65 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 12:47:12 -0400 Subject: [PATCH 3/7] refactor: make list of times for dissolution --- pyproject.toml | 2 +- tests/model_test.py | 3 ++- titan/model.py | 5 ++++- titan/params/partnership.yml | 7 +++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9a56a416..b3b3fbba 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "titan-model" -version = "3.2.1" +version = "3.3.0" description = "TITAN Agent Based Model" license = "GPL-3.0-only" authors = ["Sam Bessey ", "Mary McGrath "] diff --git a/tests/model_test.py b/tests/model_test.py index e7c0397b..57d2c28e 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -59,7 +59,8 @@ def test_update_all_agents(make_model, make_agent, make_relationship): # check that model dissolves relationships model.params.features.agent_zero = False - model.params.partnership.dissolve.time = 1 + model.params.partnership.dissolve.enabled = True + model.params.partnership.dissolve.time = [1] model.time = 1 a = make_agent() p = make_agent() diff --git a/titan/model.py b/titan/model.py index 4ab69354..f3c4efc6 100644 --- a/titan/model.py +++ b/titan/model.py @@ -235,7 +235,10 @@ def update_all_agents(self): # If static network, ignore relationship progression if not self.params.features.static_network: for rel in copy(self.pop.relationships): - if self.params.partnership.dissolve.time == self.time: + if ( + self.params.partnership.dissolve.enabled + and self.time in self.params.partnership.dissolve.time + ): rel.progress(force=True) elif rel.progress(): self.pop.remove_relationship(rel) diff --git a/titan/params/partnership.yml b/titan/params/partnership.yml index 5c55662c..562bf52f 100644 --- a/titan/params/partnership.yml +++ b/titan/params/partnership.yml @@ -260,5 +260,8 @@ partnership: max: 1 dissolve: time: - default: -9999 - type: int + type: array + default: [] + enabled: + type: bool + default: false From 6ac71062fbdc6f24d5182d5793c097f9507cce9b Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 13:03:17 -0400 Subject: [PATCH 4/7] downgrade dependencies to work on oscar --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b3b3fbba..ca9374fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,11 +12,11 @@ classifiers = ["Operating System :: OS Independent"] packages = [{ include = "titan" }] [tool.poetry.dependencies] -python = "^3.8" +python = "^3.6" paraml= "^0.1" networkx = "^2.4" nanoid = "^2.0" -numpy = "^1.23" +numpy = "^1.18" black = {version = "^23.1.0", optional = true} flake8 = {version = "^3.8", optional = true} mypy = {version = "^1.0.0", optional = true} From 181edc8ece603576f6f8a6c0162e69c165dbd128 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 13:29:32 -0400 Subject: [PATCH 5/7] make dissolution a single time instead of an array --- tests/model_test.py | 2 +- titan/model.py | 2 +- titan/params/partnership.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/model_test.py b/tests/model_test.py index 57d2c28e..874eedd1 100644 --- a/tests/model_test.py +++ b/tests/model_test.py @@ -60,7 +60,7 @@ def test_update_all_agents(make_model, make_agent, make_relationship): # check that model dissolves relationships model.params.features.agent_zero = False model.params.partnership.dissolve.enabled = True - model.params.partnership.dissolve.time = [1] + model.params.partnership.dissolve.time = 1 model.time = 1 a = make_agent() p = make_agent() diff --git a/titan/model.py b/titan/model.py index f3c4efc6..aa567de3 100644 --- a/titan/model.py +++ b/titan/model.py @@ -237,7 +237,7 @@ def update_all_agents(self): for rel in copy(self.pop.relationships): if ( self.params.partnership.dissolve.enabled - and self.time in self.params.partnership.dissolve.time + and self.time == self.params.partnership.dissolve.time ): rel.progress(force=True) elif rel.progress(): diff --git a/titan/params/partnership.yml b/titan/params/partnership.yml index 562bf52f..fddfa759 100644 --- a/titan/params/partnership.yml +++ b/titan/params/partnership.yml @@ -260,8 +260,8 @@ partnership: max: 1 dissolve: time: - type: array - default: [] + type: int + default: -9999 enabled: type: bool default: false From 4001bfae808f9277506455c93cd6789a8eb1cfae Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Wed, 10 May 2023 14:41:42 -0400 Subject: [PATCH 6/7] test: integration test for relationship dissolution --- tests/integration_test.py | 33 +++++++++++++++++++++++++++++++++ titan/model.py | 1 + titan/population.py | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/integration_test.py b/tests/integration_test.py index eb37b43f..1c79e556 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -411,6 +411,39 @@ def test_static_network(make_model_integration, tmpdir): assert rel in curr_rel_ids +@pytest.mark.integration_deterministic +def test_dissolution(params_integration, tmpdir): + model = TITAN(params_integration) + inj_r = 0 + for rel in model.pop.relationships: + inj_r += 1 + assert inj_r > 0 + model.time = 1 + + model.params.partnership.dissolve.time = 1 + model.params.partnership.dissolve.enabled = True + + model.params.demographics.white.sex_type.MSM.drug_type.Inj.num_partners = ( + model.params.demographics.black.sex_type.MSM.drug_type.Inj.num_partners + ) + for agent in model.pop.all_agents: + agent.mean_num_partners["Inj"] = 0 + agent.mean_num_partners["Sex"] = 0 + agent.mean_num_partners["SexInj"] = 0 + agent.mean_num_partners["Social"] = 0 + model.pop.update_partner_targets() + + model.step(tmpdir) + + for rel in model.pop.relationships: + if rel.bond_type == "Inj": + print(rel.agent1.mean_num_partners) + print(rel.agent2.mean_num_partners) + print(rel.agent1.race, rel.agent1.sex_type, rel.agent1.drug_type) + print(rel.agent2.race, rel.agent2.sex_type, rel.agent2.drug_type) + assert rel.bond_type != "Inj" + + @pytest.mark.integration_deterministic def test_incar(params_integration, tmpdir): # turn on incar - initi is set to 0, so for these purposes, just run time diff --git a/titan/model.py b/titan/model.py index aa567de3..5c8f9748 100644 --- a/titan/model.py +++ b/titan/model.py @@ -240,6 +240,7 @@ def update_all_agents(self): and self.time == self.params.partnership.dissolve.time ): rel.progress(force=True) + self.pop.remove_relationship(rel) elif rel.progress(): self.pop.remove_relationship(rel) diff --git a/titan/population.py b/titan/population.py index 8845fa31..8a6244fb 100644 --- a/titan/population.py +++ b/titan/population.py @@ -445,7 +445,7 @@ def update_partnerability(self, a): """ for bond in self.params.classes.bond_types.keys(): if a in self.partnerable_agents[bond]: - if len(a.partners[bond]) > ( + if len(a.partners[bond]) >= ( a.target_partners[bond] * self.params.calibration.partnership.buffer ): self.partnerable_agents[bond].remove(a) From 996fc74fabd8e6e5ceb8f648b92e982c9a805d69 Mon Sep 17 00:00:00 2001 From: Sam Bessey Date: Thu, 11 May 2023 15:54:30 -0400 Subject: [PATCH 7/7] don't correct bug that wouldn't be backwards compatible --- tests/integration_test.py | 8 ++++---- titan/population.py | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/integration_test.py b/tests/integration_test.py index 1c79e556..16c1855a 100644 --- a/tests/integration_test.py +++ b/tests/integration_test.py @@ -427,10 +427,10 @@ def test_dissolution(params_integration, tmpdir): model.params.demographics.black.sex_type.MSM.drug_type.Inj.num_partners ) for agent in model.pop.all_agents: - agent.mean_num_partners["Inj"] = 0 - agent.mean_num_partners["Sex"] = 0 - agent.mean_num_partners["SexInj"] = 0 - agent.mean_num_partners["Social"] = 0 + agent.mean_num_partners["Inj"] = -1 + agent.mean_num_partners["Sex"] = -1 + agent.mean_num_partners["SexInj"] = -1 + agent.mean_num_partners["Social"] = -1 model.pop.update_partner_targets() model.step(tmpdir) diff --git a/titan/population.py b/titan/population.py index 8a6244fb..8845fa31 100644 --- a/titan/population.py +++ b/titan/population.py @@ -445,7 +445,7 @@ def update_partnerability(self, a): """ for bond in self.params.classes.bond_types.keys(): if a in self.partnerable_agents[bond]: - if len(a.partners[bond]) >= ( + if len(a.partners[bond]) > ( a.target_partners[bond] * self.params.calibration.partnership.buffer ): self.partnerable_agents[bond].remove(a)