Skip to content

Commit b9ea8dd

Browse files
Merge pull request #76 from TTI-modelling/fix_daily_testing
Fix daily testing
2 parents de385cf + e6b94f3 commit b9ea8dd

File tree

5 files changed

+125
-54
lines changed

5 files changed

+125
-54
lines changed

examples/individual_level_daily_contact_testing.ipynb

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,8 @@
9494
"outputs": [],
9595
"source": [
9696
"model = bpm.IndividualTracingDailyTesting(params)\n",
97-
"model.prob_testing_positive_lfa_func = prob_testing_positive_function_lfa\n",
98-
"model.prob_testing_positive_pcr_func = prob_testing_positive_function_pcr\n",
97+
"model.prob_lfa_positive = prob_testing_positive_function_lfa\n",
98+
"model.prob_pcr_positive = prob_testing_positive_function_pcr\n",
9999
"controller = BranchingProcessController(model)"
100100
]
101101
},
@@ -108,6 +108,7 @@
108108
"outputs": [],
109109
"source": [
110110
"controller.graph_view.set_display(True)\n",
111+
"controller.timeline_view.set_display(True)\n",
111112
"controller.run_simulation(16)"
112113
]
113114
},
@@ -153,8 +154,8 @@
153154
"outputs": [],
154155
"source": [
155156
"model = bpm.IndividualTracingDailyTesting(params_policy_2)\n",
156-
"model.prob_testing_positive_lfa_func = prob_testing_positive_function_lfa\n",
157-
"model.prob_testing_positive_pcr_func = prob_testing_positive_function_pcr\n",
157+
"model.intervention.prob_testing_positive_lfa_func = prob_testing_positive_function_lfa\n",
158+
"model.intervention.prob_testing_positive_pcr_func = prob_testing_positive_function_pcr\n",
158159
"controller.model = model"
159160
]
160161
},
@@ -242,10 +243,12 @@
242243
{
243244
"cell_type": "code",
244245
"execution_count": null,
245-
"metadata": {},
246+
"metadata": {
247+
"scrolled": false
248+
},
246249
"outputs": [],
247250
"source": [
248-
"controller.graph_view.set_display(False)\n",
251+
"controller.graph_view.set_display(True)\n",
249252
"controller.timeline_view.set_display(True)\n",
250253
"controller.run_simulation(20)"
251254
]

examples/run_testing_contact_model.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import os
2+
from copy import copy
23

34
from household_contact_tracing.branching_process_controller import BranchingProcessController
45
from household_contact_tracing.branching_process_models import IndividualTracingDailyTesting
56

67

78
def main():
8-
example_1()
9-
example_2()
9+
#example_1()
10+
#example_2()
11+
example_3()
1012

1113
def example_1():
1214

@@ -103,5 +105,54 @@ def prob_pcr_positive(infectious_age):
103105
controller.run_simulation(20)
104106

105107

108+
# define some easy to look at test sensitivity functions
109+
def prob_testing_positive_function_pcr(time_relative_to_symptom_onset):
110+
# Prevents people testing positive as soon as they get it
111+
if time_relative_to_symptom_onset in [4, 5, 6]:
112+
return 0.75
113+
else:
114+
return 0
115+
116+
def prob_testing_positive_function_lfa(time_relative_to_symptom_onset):
117+
# Prevents people testing positive as soon as they get it
118+
if time_relative_to_symptom_onset in [4, 5, 6]:
119+
return 0 # this is unrealistic, but it makes it easier to see nodes being lfa tested since they wont
120+
# move to the isolation status due to lfa testing
121+
else:
122+
return 0
123+
124+
125+
def example_3():
126+
params = {"outside_household_infectivity_scaling": 0.3,
127+
"contact_tracing_success_prob": 0.7,
128+
"overdispersion": 0.32,
129+
"asymptomatic_prob": 0.2,
130+
"asymptomatic_relative_infectivity": 0.35,
131+
"infection_reporting_prob": 0.5,
132+
"LFA_testing_requires_confirmatory_PCR": False,
133+
"reduce_contacts_by": 0.5,
134+
"test_delay": 1,
135+
"contact_trace_delay": 1,
136+
"incubation_period_delay": 5,
137+
"symptom_reporting_delay": 1,
138+
"starting_infections": 5,
139+
"household_pairwise_survival_prob": 0.2,
140+
"self_isolation_duration": 10,
141+
"lateral_flow_testing_duration": 14,
142+
"propensity_risky_behaviour_lfa_testing": 0,
143+
"global_contact_reduction_risky_behaviour": 0,
144+
"household_positive_policy": "lfa_testing_no_quarantine"
145+
}
146+
147+
model = IndividualTracingDailyTesting(params)
148+
model.prob_lfa_positive = prob_testing_positive_function_lfa
149+
model.prob_pcr_positive = prob_testing_positive_function_pcr
150+
controller = BranchingProcessController(model)
151+
152+
controller.graph_view.set_display(True)
153+
controller.timeline_view.set_display(True)
154+
controller.run_simulation(16)
155+
156+
106157
if __name__ == "__main__":
107158
main()

household_contact_tracing/behaviours/intervention/increment_tracing.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -184,9 +184,8 @@ def update_contact_tracing_index(self, time: int):
184184
class IncrementTracingIndividualLevel(IncrementTracingHouseholdLevel):
185185

186186
def __init__(self, network: Network, params: dict):
187-
188187
super().__init__(network, params)
189-
self.prob_pcr_positive = self.default_prob_pcr_positive
188+
self._prob_pcr_positive = None
190189

191190
@property
192191
def prob_pcr_positive(self) -> Callable[[int], float]:
@@ -196,18 +195,10 @@ def prob_pcr_positive(self) -> Callable[[int], float]:
196195
def prob_pcr_positive(self, fn: Callable[[int], float]):
197196
self._prob_pcr_positive = fn
198197

199-
@staticmethod
200-
def default_prob_pcr_positive(infectious_age):
201-
"""Default PCR test result probability."""
202-
if infectious_age in [4, 5, 6]:
203-
return 0
204-
else:
205-
return 0
206-
207-
def pcr_test_node(self, node: Node, time: int, prob_pcr_positive: Callable):
198+
def pcr_test_node(self, node: Node, time: int):
208199
node.received_result = True
209200
infectious_age_when_tested = time - node.testing_delay - node.time_infected
210-
prob_positive_result = prob_pcr_positive(infectious_age_when_tested)
201+
prob_positive_result = self.prob_pcr_positive(infectious_age_when_tested)
211202
node.avenue_of_testing = TestType.pcr
212203

213204
if np.random.binomial(1, prob_positive_result) == 1:
@@ -216,20 +207,20 @@ def pcr_test_node(self, node: Node, time: int, prob_pcr_positive: Callable):
216207
else:
217208
node.received_positive_test_result = False
218209

219-
def receive_pcr_test_results(self, time: int, prob_pcr_positive: Callable):
210+
def receive_pcr_test_results(self, time: int):
220211
# self reporting infections
221212
for node in self.network.all_nodes():
222213
if node.time_of_reporting + node.testing_delay == time:
223214
if not node.contact_traced:
224215
if not node.received_result:
225-
self.pcr_test_node(node, time, prob_pcr_positive)
216+
self.pcr_test_node(node, time)
226217

227218
# contact traced nodes
228219
for node in self.network.all_nodes():
229220
if node.symptom_onset_time + node.testing_delay == time:
230221
if node.contact_traced:
231222
if not node.received_result:
232-
self.pcr_test_node(node, time, prob_pcr_positive)
223+
self.pcr_test_node(node, time)
233224

234225
def increment_contact_tracing(self, time: int):
235226

@@ -249,7 +240,7 @@ def increment_contact_tracing(self, time: int):
249240

250241
# Isolate all households under observation that now display symptoms (excludes those who will not take up
251242
# intervention if prob <1)
252-
self.receive_pcr_test_results(time, self.prob_pcr_positive)
243+
self.receive_pcr_test_results(time)
253244

254245
for node in self.network.all_nodes():
255246
if node.symptom_onset_time <= time:
@@ -360,18 +351,18 @@ def attempt_contact_trace_of_household(self,
360351

361352
class IncrementTracingIndividualDailyTesting(IncrementTracingIndividualLevel):
362353

363-
def receive_pcr_test_results(self, time: int, prob_pcr_positive: Callable):
354+
def receive_pcr_test_results(self, time: int):
364355
"""For nodes who would receive a PCR test result today, update"""
365356

366357
if self.lfa_tested_nodes_book_pcr_on_symptom_onset:
367-
super().receive_pcr_test_results(time, prob_pcr_positive)
358+
super().receive_pcr_test_results(time)
368359
else:
369360
for node in self.network.all_nodes():
370361
if node.time_of_reporting + node.testing_delay == time:
371362
if not node.contact_traced:
372363
if not node.received_result:
373364
if not node.being_lateral_flow_tested:
374-
self.pcr_test_node(node, time, prob_pcr_positive)
365+
self.pcr_test_node(node, time)
375366

376367
def increment_contact_tracing(self, time: int):
377368
for node in self.network.all_nodes():

household_contact_tracing/behaviours/intervention/isolation.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,19 @@ def update_isolation(self, time: int):
131131

132132

133133
class DailyTestingIsolation(HouseholdIsolation):
134+
135+
def __init__(self, network: Network, params: dict):
136+
super().__init__(network, params)
137+
self._prob_pcr_positive = None
138+
139+
@property
140+
def prob_pcr_positive(self) -> Callable[[int], float]:
141+
return self._prob_pcr_positive
142+
143+
@prob_pcr_positive.setter
144+
def prob_pcr_positive(self, fn: Callable[[int], float]):
145+
self._prob_pcr_positive = fn
146+
134147
def update_households_contact_traced(self, time: int):
135148
"""Update the contact traced status for all households that have had the
136149
contact tracing process get there."""
@@ -151,8 +164,7 @@ def update_isolation(self, time: int):
151164
if node.avenue_of_testing == TestType.pcr:
152165
if node.received_positive_test_result:
153166
if not node.household.applied_household_positive_policy:
154-
node.household.apply_positive_policy(time,
155-
self.household_positive_policy)
167+
node.household.apply_positive_policy(time, self.household_positive_policy)
156168

157169
def act_on_confirmatory_pcr_results(self, time: int):
158170
"""Once on a individual receives a positive pcr result we need to act on it.
@@ -186,20 +198,17 @@ def isolate_positive_lateral_flow_tests(self, time: int, positive_nodes: List[No
186198
not self.LFA_testing_requires_confirmatory_PCR:
187199
node.household.apply_positive_policy(time, self.household_positive_policy)
188200

189-
def act_on_positive_LFA_tests(self, time: int, prob_pcr_positive: Callable,
190-
positive_nodes: List[Node]):
201+
def act_on_positive_LFA_tests(self, time: int, positive_nodes: List[Node]):
191202
"""For nodes who test positive on their LFA test, take the appropriate action depending
192203
on the policy
193204
"""
194205
self.isolate_positive_lateral_flow_tests(time, positive_nodes)
195206

196207
if self.LFA_testing_requires_confirmatory_PCR:
197-
self.confirmatory_pcr_test_LFA_nodes(time, positive_nodes, prob_pcr_positive)
208+
self.confirmatory_pcr_test_LFA_nodes(time, positive_nodes)
198209

199-
@staticmethod
200-
def confirmatory_pcr_test_LFA_nodes(time: int, positive_nodes: List[Node],
201-
prob_pcr_positive: Callable):
210+
def confirmatory_pcr_test_LFA_nodes(self, time: int, positive_nodes: List[Node]):
202211
"""Nodes who receive a positive LFA result will be tested using a PCR test."""
203212
for node in positive_nodes:
204213
if not node.taken_confirmatory_PCR_test:
205-
node.take_confirmatory_pcr_test(time, prob_pcr_positive)
214+
node.take_confirmatory_pcr_test(time, self.prob_pcr_positive)

household_contact_tracing/branching_process_models.py

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,13 @@ class IndividualLevelTracing(HouseholdLevelTracing):
161161
contacting every individual and their contacts, whether they have tested positive or not.
162162
"""
163163
def __init__(self, params: dict):
164-
super().__init__(params)
165164
# Set the test probabilities to default values - may be overridden by user later
166165
self._prob_lfa_positive = self.default_prob_lfa_positive
167166
self._prob_pcr_positive = self.default_prob_pcr_positive
168167

168+
# Run super constructor. Note: This must be run after above, as above attributes required.
169+
super().__init__(params)
170+
169171
@property
170172
def prob_lfa_positive(self) -> Callable[[int], float]:
171173
return self._prob_lfa_positive
@@ -181,20 +183,22 @@ def prob_pcr_positive(self) -> Callable[[int], float]:
181183
@prob_pcr_positive.setter
182184
def prob_pcr_positive(self, fn: Callable[[int], float]):
183185
self._prob_pcr_positive = fn
186+
self.intervention.increment_tracing.prob_pcr_positive = fn
187+
self.intervention.isolation.prob_pcr_positive = fn
184188

185189
@staticmethod
186-
def default_prob_pcr_positive(infectious_age):
187-
"""Default PCR test result probability."""
190+
def default_prob_lfa_positive(infectious_age):
191+
"""Default LFA test result probability."""
188192
if infectious_age in [4, 5, 6]:
189-
return 0
193+
return 1
190194
else:
191195
return 0
192196

193197
@staticmethod
194-
def default_prob_lfa_positive(infectious_age):
195-
"""Default LFA test result probability."""
198+
def default_prob_pcr_positive(infectious_age):
199+
"""Default PCR test result probability."""
196200
if infectious_age in [4, 5, 6]:
197-
return 1
201+
return 0
198202
else:
199203
return 0
200204

@@ -210,10 +214,17 @@ def _initialise_infection(self):
210214

211215
def _initialise_intervention(self):
212216
""" Initialise an Intervention class, passing in the required behaviours into its constructor """
213-
return Intervention(self.network,
214-
isolation.IndividualIsolation,
215-
increment.IncrementTracingIndividualLevel,
216-
self.params)
217+
new_intervention = Intervention(self.network,
218+
isolation.IndividualIsolation,
219+
increment.IncrementTracingIndividualLevel,
220+
self.params)
221+
222+
# Set a new positive pcr probability function
223+
new_intervention.increment_tracing.prob_pcr_positive = self.prob_pcr_positive
224+
new_intervention.isolation.prob_pcr_positive = self.prob_pcr_positive
225+
226+
return new_intervention
227+
217228

218229
def simulate_one_step(self):
219230
"""Simulates one day of the infection and contact tracing."""
@@ -225,8 +236,6 @@ def simulate_one_step(self):
225236
# isolate self-reporting-nodes while they wait for tests
226237
self.intervention.isolation.update_households_contact_traced(self.time)
227238
self.intervention.isolation.update_isolation(self.time)
228-
# Set a new positive pcr probability function and propagate contact tracing
229-
self.intervention.increment_tracing.prob_pcr_positive = self.prob_pcr_positive
230239
for step in range(5):
231240
self.intervention.increment_tracing.increment_contact_tracing(self.time)
232241
# node recoveries
@@ -259,24 +268,32 @@ def _initialise_infection(self):
259268
new_infection.NewInfectionIndividualTracingDailyTesting,
260269
ContactRateReductionIndividualTracingDaily,
261270
self.params)
271+
262272
def _initialise_intervention(self):
263273
""" Initialise an Intervention class, passing in the required behaviours into its constructor """
264-
return Intervention(self.network,
265-
isolation.DailyTestingIsolation,
266-
increment.IncrementTracingIndividualDailyTesting,
267-
self.params)
274+
new_intervention = Intervention(self.network,
275+
isolation.DailyTestingIsolation,
276+
increment.IncrementTracingIndividualDailyTesting,
277+
self.params)
278+
279+
# Set a new positive pcr probability function
280+
new_intervention.increment_tracing.prob_pcr_positive = self.prob_pcr_positive
281+
new_intervention.isolation.prob_pcr_positive = self.prob_pcr_positive
282+
283+
return new_intervention
268284

269285
def simulate_one_step(self):
270286
""" Simulates one day of the infection and contact tracing.
271287
"""
288+
self.intervention.increment_tracing.receive_pcr_test_results(self.time)
272289
# isolate nodes reached by tracing, isolate nodes due to self-reporting
273290
self.intervention.isolation.isolate_self_reporting_cases(self.time)
274291
# isolate self-reporting-nodes while they wait for tests
275292
self.intervention.isolation.update_households_contact_traced(self.time)
276293
self.intervention.isolation.update_isolation(self.time)
277294
# isolate self reporting nodes
278295
positive_nodes = self.intervention.lft_nodes(self.time, self.prob_lfa_positive)
279-
self.intervention.isolation.act_on_positive_LFA_tests(self.time, self.prob_pcr_positive, positive_nodes)
296+
self.intervention.isolation.act_on_positive_LFA_tests(self.time, positive_nodes)
280297
# if we require PCR tests, to confirm infection we act on those
281298
if self.intervention.increment_tracing.LFA_testing_requires_confirmatory_PCR:
282299
self.intervention.increment_tracing.act_on_confirmatory_pcr_results(self.time)

0 commit comments

Comments
 (0)