-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
293 additions
and
85 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,169 @@ | ||
# Overview | ||
Infection attempts are not always successful (i.e. result in transmission) and many factors modify | ||
the probability of infection attempt success. These "transmission modifiers" can originate from the | ||
natural history of infection or from interventions, which are measures that are aimed to reduce | ||
the overall probability of transmission. More often than not, we are concerned with transmission | ||
modifiers that reduce the probability of infection attempt success, such as cross-protective | ||
immunity, in the case of a natural modifiers, or wearing a facemask, in the case of | ||
intervention-based transmission modifiers. Transmission modifiers are not strictly independent, | ||
nor is it convenient to repeatedly define their specific use cases that require independent | ||
querying. Therefore, this module provides a framework to incorporate transmission modifiers in a | ||
flexible and generalized manner such that all modifiers are detected and aggregated to alter the | ||
overall relative transmission potential during an infection attempt. | ||
|
||
## Hook to transmission manager | ||
In this module, we assume that an interaction between an infectious transmitter (I) and a susceptible contact (S) has already occured. We alter the [transmission manager](transmission.md) function `evaluate_transmission` that is conditioned on such an interaction. This is done using the registered transmission modifiers of both I and S, which can be accessed through the `query_infection_modifiers` function of the context trait extension `ContextTransmissionModifierExt`. | ||
|
||
```rust | ||
fn query_infection_modifers(&mut self, transmitter_id: PersonId, contact_id: PersonId) -> f64 { | ||
self.compute_relative_transmission(transmitter_id) * self.compute_relative_transmission(contact_id) | ||
} | ||
``` | ||
|
||
in which we assume the infectiousness of transmitter I to be independent of and additive to the risk of infection of contact S. | ||
|
||
The API hook to `evaluate_transmission` is independent of innate transmissiveness, which determines the number and timing of infection attempts made by infectious individual I. | ||
|
||
```rust | ||
fn evaluate_transmission(context: &mut Context, contact_id: PersonId, transmitter_id: PersonId) { | ||
if context.get_person_property(contact_id, InfectiousStatus) | ||
== InfectiousStatusType::Susceptible | ||
{ | ||
let relative_transmission = context.query_infection_modifiers(transmitter_id, contact_id) | ||
let transmission_success = | ||
context.sample_range(TransmissionRng, 0.0..1.0) < relative_transmission; | ||
|
||
// Set the contact to infectious with probability additive relative transmission. | ||
if transmission_success { | ||
context.set_person_property( | ||
contact_id, | ||
InfectiousStatus, | ||
InfectiousStatusType::Infectious, | ||
); | ||
} | ||
} | ||
} | ||
``` | ||
|
||
From this example, we see that the `evaluate_transmission` function is conditioned on the potential infection interaction having occurred and that innate transmissiveness, along with generating the contact to begin with, is managed elsewhere. We are strictly concerned with the relative probability of a transmission event occurring given the attempt, modulated through the single fraction `transmission_success`. | ||
|
||
# API | ||
## InterventionContainer | ||
In order to obtain the total effect of all transmission modifiers acting on the relative transmission potential between I and S during an infection attempt, we need to register relative infectiousness and risk, respectively, to the `Context` state and `PersonId` of interest. We therefore use a `HashMap` to relate `InfectiousStatusType` to transmission modifiers with `TypeId` and a supplied relative transmission function. We also provide a map of `InfectiousStatusType` to an aggregator function that determines how the transmission modifiers interact. | ||
|
||
```rust | ||
struct TransmissionModifierContainer { | ||
transmission_modifier_map: HashMap<InfectiousStatusType, HashMap<TypeId, Box<TransmissionModifierFn>>>, | ||
modifier_aggregator: HashMap<InfectiousStatusType, Box<TransmissionAggregatorFn>>, | ||
} | ||
``` | ||
|
||
`TransmissionModifierFn` and `TransmissionAggregateFn` are types defined in the module that return the `float` from a single modification effect and from the total effect across modifications, respectively. | ||
|
||
```rust | ||
type TransmissionModifierFn = dyn Fn(&Context, PersonId) -> f64; | ||
type TransmissionAggregatorFn = dyn Fn(&Vec<(TypeId, f64)>) -> f64; | ||
``` | ||
|
||
In the `TransmissionModifierContainer`, we implement a method for running the aggregator function for some reference to a `Vec` of interventions using a specified `InfectiousStatusType` key. | ||
We then want to assign a default method for the `modifier_aggregator` that assumes each effect is independent, which is the most common anticipated assumption. | ||
|
||
```rust | ||
impl TransmissionModifierContainer { | ||
fn run_aggregator(&self, infectious_status: InfectiousStatusType, modifiers: &Vec<(TypeId, f64)>) -> f64{ | ||
self.modifier_aggregator | ||
.get(&infectious_status) | ||
.unwrap_or(&Self::default_aggregator()) | ||
(modifiers) | ||
} | ||
|
||
fn default_aggregator() -> Box<TransmissionAggregatorFn> { | ||
Box::new(|modifiers: &Vec<(TypeId, f64)>| -> f64 { | ||
let mut aggregate_effects = 1.0; | ||
|
||
for (_, effect) in interventions { | ||
aggregate_effects *= effect; | ||
} | ||
|
||
aggregate_effects | ||
}) | ||
} | ||
} | ||
``` | ||
|
||
From the `default_aggregator`, we can see that transmission modifier effects directly change relative infectiousness and risk. If some transmission modifier is parameterized with an efficacy that reduces the probability of transmission, we would return `effect = 1.0 - efficacy` within the `TransmissionModifierFn`. | ||
|
||
## Registration and computation | ||
To connect the `TransmissionModifierContainer` with modules that are particular implementations of transmission modifiers, we require a trait extension that builds in functionality to register interventions and their aggregators, as well as call the aggregator to compute the total relative transmission potential change due to the modifications for a given individual. Further, the `transmission_manager` needs access to this trait extension for `query_infection_modifiers` to be called. | ||
|
||
```rust | ||
trait ContextTransmissionModifierExt { | ||
fn register_transmission_modifier<T: PersonProperty + 'static + std::cmp::Eq + std::hash::Hash>(&mut self, infectious_status: InfectiousStatusType, person_property: T, instance_dict: Vec<(T::Value, f64)>); | ||
fn register_transmission_aggregator(&mut self, infectious_status: InfectiousStatusType, agg_function: Box<TransmissionAggregatorFn>); | ||
fn compute_relative_transmission(&mut self, person_id: PersonId) -> f64; | ||
fn query_infection_modifiers(&mut self, transmitter_id: PersonId, contact_id: PersonId) -> f64; | ||
} | ||
``` | ||
|
||
To `register_transmission_modifier`, the user must specify the `InfectiousStatus` and `PersonProperty` of interest and then provide a `modifier_key` that associates `PersonProperty` values to `float`s. These inputs are supplied to a default closure that obtains the realtive trasnmission potential modification through direct value association. We will want to abstract out this closure so that a generic function of type `InterventionFn` can be supplied instead, where the float associated with the `PersonProperty` value is simply a function input. | ||
|
||
```rust | ||
fn register_transmission_modifier<T: PersonProperty + 'static + std::cmp::Eq + std::hash::Hash>(&mut self, infectious_status: InfectiousStatusType, person_property: T, modifier_key: HashMap<T::Value, f64>) { | ||
|
||
let mut transmission_modifier_map = HashMap::new(); | ||
transmission_modifier_map.insert(TypeId::of::<T>(), move |context: &mut Context, person_id| -> f64 { | ||
let property_val = context.get_person_property(person_id, person_property); | ||
|
||
modifier_key.get(property_val) | ||
.unwrap_or(1.0) | ||
}); | ||
|
||
let transmission_modifier_container = self.get_data_container_mut(TransmissionModifierPlugin); | ||
|
||
// mismatched types: instance_map is of type {closure} but expected {dyn Fn(...)} | ||
transmission_modifier_container | ||
.transmission_modifier_map | ||
.insert(infectious_status, transmission_modifier_map); | ||
|
||
} | ||
``` | ||
|
||
To register the `transmission_aggregator` functions, the user similarly provides the `infectious_status` and a boxed aggregator function. | ||
|
||
```rust | ||
fn register_transmission_aggregator(&mut self, infectious_status: InfectiousStatusType, agg_function: Box<TransmissionAggregatorFn>) { | ||
let transmission_modifier_container = self.get_data_container_mut(TransmissionModifierPlugin); | ||
|
||
transmission_modifier_container | ||
.modifier_aggregator | ||
.insert(&infectious_status, agg_function); | ||
} | ||
``` | ||
|
||
We already defined the default aggregator through `impl TransmissionModifierContainer` so that the user does not need to specify all or even any aggregators in order to `run_aggregator` in `compute_relative_transmission`. | ||
|
||
To calculate the total relative trasnmission potential change for an individual during an infection attempt, `Context` and the `PersonId` are supplied to `compute_relative_transmission`, which is agnostic of the `InfectiousStatus` of the `PersonId` supplied. | ||
|
||
```rust | ||
fn compute_relative_transmission(&mut self, person_id: PersonId) -> f64 { | ||
let infectious_status =self.get_person_property(person_id, InfectiousStatus); | ||
|
||
let mut registered_modifiers = Vec::new(); | ||
let transmission_modifier_plugin = self.get_data_container(TransmissionModifierPlugin).unwrap(); | ||
let transmission_modifier_map = transmission_modifier_plugin | ||
.transmission_modifier_map | ||
.get(&infectious_status) | ||
.unwrap(); | ||
|
||
for (t, f) in transmission_modifier_map { | ||
registered_modifiers.push((*t, f(self, person_id))); | ||
} | ||
|
||
transmission_modifier_plugin.run_aggregator(infectious_status, ®istered_modifiers) | ||
} | ||
``` | ||
We use automatic detection of all the modifications applied to a person, which is necessary to remove multiple manual calls to query particular interventions that would otherwise be error-prone and inflexible. | ||
|
||
## Modifier scope | ||
For example, facemasks modify the relative transmission potential of an infection attempt. The decision to wear a facemask based on a person's risk category or symptom category is an intervention-level behavior that does not directly modify relative transmission potential, meaning that such choices are excluded from this module. In contrast, symptoms may also modify the efficacy of wearing a facemask, which is a higher order modification that would need to be accounted for in the changes to relative transmission potential caused by facemasks. | ||
Compare this higher order modification to the instance in which an individual may be less likely to wear a mask at home, or may wear it for less time. This is a modifier created by the location of the infection attempt, and is thus separate from the relative transmission modifiers module. |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use crate::transmission_manager::{InfectiousStatus, InfectiousStatusType}; | ||
use ixa::{define_data_plugin, Context, ContextPeopleExt, PersonId, PersonProperty}; | ||
use std::{any::TypeId, boxed::Box, collections::HashMap}; | ||
|
||
type InterventionFn = dyn Fn(&Context, PersonId) -> f64; | ||
type AggregatorFn = dyn Fn(&Vec<(TypeId, f64)>) -> f64; | ||
|
||
struct InterventionContainer { | ||
intervention_map: HashMap<InfectiousStatusType, HashMap<TypeId, Box<InterventionFn>>>, | ||
aggregator: HashMap<InfectiousStatusType, Box<AggregatorFn>>, | ||
} | ||
|
||
impl InterventionContainer { | ||
fn run_aggregator( | ||
&self, | ||
infectious_status: InfectiousStatusType, | ||
interventions: &Vec<(TypeId, f64)>, | ||
) -> f64 { | ||
self.aggregator | ||
.get(&infectious_status) | ||
.unwrap_or(&Self::default_aggregator())(interventions) | ||
} | ||
|
||
fn default_aggregator() -> Box<AggregatorFn> { | ||
Box::new(|interventions: &Vec<(TypeId, f64)>| -> f64 { | ||
let mut aggregate_effects = 1.0; | ||
|
||
for (_, effect) in interventions { | ||
aggregate_effects *= effect; | ||
} | ||
|
||
aggregate_effects | ||
}) | ||
} | ||
} | ||
|
||
define_data_plugin!( | ||
InterventionPlugin, | ||
InterventionContainer, | ||
InterventionContainer { | ||
intervention_map: HashMap::new(), | ||
aggregator: HashMap::new(), | ||
} | ||
); | ||
|
||
pub trait ContextTransmissionModifierExt { | ||
fn register_intervention<T: PersonProperty + 'static + std::cmp::Eq + std::hash::Hash>( | ||
&mut self, | ||
infectious_status: InfectiousStatusType, | ||
person_property: T, | ||
instance_dict: Vec<(T::Value, f64)>, | ||
); | ||
fn register_aggregator( | ||
&mut self, | ||
agg_functions: Vec<(InfectiousStatusType, Box<AggregatorFn>)>, | ||
); | ||
fn compute_intervention(&mut self, person_id: PersonId) -> f64; | ||
fn query_modifers(&mut self, transmitter_id: PersonId, contact_id: PersonId) -> f64; | ||
} | ||
|
||
impl ContextTransmissionModifierExt for Context { | ||
fn register_intervention<T: PersonProperty + 'static + std::cmp::Eq + std::hash::Hash>( | ||
&mut self, | ||
infectious_status: InfectiousStatusType, | ||
person_property: T, | ||
instance_dict: Vec<(T::Value, f64)>, | ||
) { | ||
let mut instance_map = HashMap::<TypeId, Box<InterventionFn>>::new(); | ||
instance_map.insert( | ||
TypeId::of::<T>(), | ||
Box::new(move |context: &Context, person_id| -> f64 { | ||
let property_val = context.get_person_property(person_id, person_property); | ||
|
||
for item in &instance_dict { | ||
if property_val == item.0 { | ||
return item.1; | ||
} | ||
} | ||
// Return a default 1.0 (no relative change if unregistered) | ||
return 1.0; | ||
}), | ||
); | ||
|
||
let intervention_container = self.get_data_container_mut(InterventionPlugin); | ||
|
||
intervention_container | ||
.intervention_map | ||
.insert(infectious_status, instance_map); | ||
} | ||
|
||
fn register_aggregator( | ||
&mut self, | ||
agg_functions: Vec<(InfectiousStatusType, Box<AggregatorFn>)>, | ||
) { | ||
let intervention_container = self.get_data_container_mut(InterventionPlugin); | ||
|
||
for item in agg_functions { | ||
intervention_container.aggregator.insert(item.0, item.1); | ||
} | ||
} | ||
|
||
fn compute_intervention(&mut self, person_id: PersonId) -> f64 { | ||
let infectious_status = self.get_person_property(person_id, InfectiousStatus); | ||
|
||
let mut registered_interventions: Vec<(TypeId, f64)> = Vec::new(); | ||
let intervention_plugin = self.get_data_container(InterventionPlugin).unwrap(); | ||
let intervention_map = intervention_plugin | ||
.intervention_map | ||
.get(&infectious_status) | ||
.unwrap(); | ||
|
||
for (t, f) in intervention_map { | ||
registered_interventions.push((*t, f(self, person_id))); | ||
} | ||
|
||
intervention_plugin.run_aggregator(infectious_status, ®istered_interventions) | ||
} | ||
|
||
fn query_modifers(&mut self, transmitter_id: PersonId, contact_id: PersonId) -> f64 { | ||
self.compute_intervention(transmitter_id) * self.compute_intervention(contact_id) | ||
} | ||
} |