Skip to content

Commit

Permalink
differentiate asymptomatic and presymptomatic
Browse files Browse the repository at this point in the history
  • Loading branch information
ChiragKumar9 committed Dec 6, 2024
1 parent e2861dc commit 4024481
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 26 deletions.
3 changes: 2 additions & 1 deletion docs/health_status.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ an individual's underlying health status from COVID-19, which is what we are mod
are still tracked, they are labeled as `HealthStatus::Asymptomatic` when they become infectious and
are returned to `HealthStatus::Healthy` when they recover.
3. The remainder develop at least mild symptoms. Mild symptoms develop at some time after the agent
first becomes infectious based on the incubation period. We use the COVID-19 incubation period
first becomes infectious based on the incubation period. The period before mild symptoms develop
is referred to as the `HealthStatus::Presymptomatic` phase. We use the COVID-19 incubation period
from Park et al., (2023).
4. Individuals who develop mild symptoms may develop severe symptoms. Those who do not develop severe
symptoms have symptom improvement at some time afterwards symptom onset. Symptom improvement times are
Expand Down
2 changes: 1 addition & 1 deletion input/input.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"seed": 123,
"r_0": 2.5,
"asymptomatic_probability": 0.2,
"incubation_period": [3.6, 1.5, 0.15],
"incubation_period": [1.5, 3.6, 0.15],
"hospitalization_infection_probability": 0.01,
"time_to_hospitalization": 5.0,
"hospitalization_duration": 10.0,
Expand Down
76 changes: 56 additions & 20 deletions src/infection_course_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ define_rng!(HealthStatusRng);
pub enum HealthStatusType {
Healthy,
Asymptomatic,
Presymptomatic,
Mild,
Severe,
}
Expand All @@ -39,7 +40,7 @@ pub fn init(context: &mut Context) {
context.subscribe_to_event(|context, event: PersonPropertyChangeEvent<HealthStatus>| {
// We only care about the cases of asymptomatic to mild, and mild to severe.
if event.current == HealthStatusType::Mild {
assert_eq!(event.previous, HealthStatusType::Asymptomatic);
assert_eq!(event.previous, HealthStatusType::Presymptomatic);
// Schedule the person to either recover or become severely symptomatic.
handle_mild_symptoms(context, event);
} else if event.current == HealthStatusType::Severe {
Expand All @@ -55,19 +56,25 @@ fn handle_infection_starting(
context: &mut Context,
event: PersonPropertyChangeEvent<InfectiousStatus>,
) {
context.set_person_property(
event.person_id,
HealthStatus,
HealthStatusType::Asymptomatic,
);
// Determine whether the person stays asymptomatic.
// Determine whether the person ever develops symptoms
if context.sample_bool(
HealthStatusRng,
context
.get_global_property_value(Parameters)
.unwrap()
.asymptomatic_probability,
) {
context.set_person_property(
event.person_id,
HealthStatus,
HealthStatusType::Asymptomatic,
);
} else {
context.set_person_property(
event.person_id,
HealthStatus,
HealthStatusType::Presymptomatic,
);
// Schedule the person to become mildly symptomatic at some point in the future.
// Grab a random sample from our pre-calculated incubation period times.
let incubation_time = context.sample_incubation_period_time();
Expand All @@ -85,32 +92,61 @@ fn handle_infection_ending(
context: &mut Context,
event: PersonPropertyChangeEvent<InfectiousStatus>,
) {
// This case is simple -- health status is only coupled to infectious status when a person is asymptomatic.
// This case is simple -- health status is only coupled to infectious status when a person is asymptomatic only.
// Improvement from other symptom courses is not related to infectious status and managed elsewhere.
// However, we need to check whether the person truly
if context.get_person_property(event.person_id, HealthStatus) == HealthStatusType::Asymptomatic
{
context.set_person_property(event.person_id, HealthStatus, HealthStatusType::Healthy);
}
}

/// Schedule the person to recover or become severely symptomatic at some point in the future.
fn handle_mild_symptoms(context: &mut Context, event: PersonPropertyChangeEvent<HealthStatus>) {
// Schedule the person to potentially become severely symptomatic at some point in the future.
// In the future, we will replace this with a real distribution based on NNH's parameter estimates.
let parameters = context.get_global_property_value(Parameters).unwrap();
let time_to_severe = context.sample_distr(
if context.sample_bool(
HealthStatusRng,
Exp::new(parameters.time_to_hospitalization).unwrap(),
);
context.add_plan(
context.get_current_time() + time_to_severe,
move |context| {
context.set_person_property(event.person_id, HealthStatus, HealthStatusType::Severe);
},
);
context
.get_global_property_value(Parameters)
.unwrap()
.hospitalization_infection_probability,
) {
// In the future, we will replace this with a real distribution based on NNH's parameter estimates.
let time_to_severe = context.sample_distr(
HealthStatusRng,
Exp::new(parameters.time_to_hospitalization).unwrap(),
);
context.add_plan(
context.get_current_time() + time_to_severe,
move |context| {
context.set_person_property(
event.person_id,
HealthStatus,
HealthStatusType::Severe,
);
},
);
} else {
// If the person doesn't become severely symptomatic, schedule them to recover.
// We will use symptom improvement times from our isolation guidance modeling work,
// so for now, the Exponential distribution is just a placeholder here.
let symptom_improvement_time =
context.sample_distr(HealthStatusRng, Exp::new(1.0).unwrap());
context.add_plan(
context.get_current_time() + symptom_improvement_time,
move |context| {
context.set_person_property(
event.person_id,
HealthStatus,
HealthStatusType::Healthy,
);
},
);
}
}

/// Schedule the person to recover at some point in the future.
fn handle_severe_symptoms(context: &mut Context, event: PersonPropertyChangeEvent<HealthStatus>) {
// Schedule the person to recover at some point in the future.
let parameters = context.get_global_property_value(Parameters).unwrap();
// In the future, we will replace this with a real distribution based on NNH's parameter estimates.
let time_to_recovery = context.sample_distr(
Expand Down
7 changes: 4 additions & 3 deletions src/parameters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ pub fn make_derived_parameters(context: &mut Context) {
let growth_rate = parameters.incubation_period[2];
let weibull = Weibull::new(shape, scale).unwrap();
let mut scaled_probs_incubation_period_times: Vec<f64> = linspace(0.0, 23.0, 1000)
.map(|x| weibull.pdf(x) * f64::exp(growth_rate * x))
.map(|t| weibull.pdf(t) * f64::exp(growth_rate * t))
.collect();
context
.get_data_container_mut(IncubationPeriodTimes)
Expand All @@ -92,10 +92,11 @@ pub trait ContextParametersExt {
}

impl ContextParametersExt for Context {
#[allow(clippy::cast_precision_loss)]
fn sample_incubation_period_time(&self) -> f64 {
let incubation_period_times = self.get_data_container(IncubationPeriodTimes).unwrap();
let index = self.sample_range(IncubationPeriodRng, 0..incubation_period_times.len());
incubation_period_times[index]
let index = self.sample_weighted(IncubationPeriodRng, incubation_period_times);
index as f64 / incubation_period_times.len() as f64 * 23.0
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/transmission_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ mod test {
seed: 42,
r_0,
asymptomatic_probability: 0.2,
incubation_period: [3.6, 1.5, 0.15],
incubation_period: [1.5, 3.6, 0.15],
hospitalization_infection_probability: 0.01,
time_to_hospitalization: 5.0,
hospitalization_duration: 10.0,
Expand Down

0 comments on commit 4024481

Please sign in to comment.