generated from CDCgov/template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add global properties and example (#47)
Closes issue #24
- Loading branch information
1 parent
8566100
commit 9cc4c13
Showing
16 changed files
with
603 additions
and
6 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,45 @@ | ||
# Parameters loading in Ixa | ||
|
||
The goal of global properties is to load values for parameters or variables that will be accessed all throughout the simulation. For this example, we will build on the basic-infection example and focus on parameters that can't change over time and are read from a config file. | ||
|
||
```yaml | ||
population: 1000 | ||
seed: 123 | ||
foi: 0.1 | ||
infection_duration: 5.0 | ||
``` | ||
To read parameters, we create a struct called Parameters and read from the configuration file. | ||
```rust | ||
use ixa::context::Context; | ||
use ixa::global_properties::GlobalPropertiesContext; | ||
|
||
mod global_properties; | ||
mod people; | ||
pub struct ParametersValues { | ||
population: usize, | ||
max_time: f64, | ||
seed: u64, | ||
foi: f64, | ||
infection_duration:f64, | ||
} | ||
``` | ||
Parameters are read using a `load-parameters.rs` module which implements the method `load_parameters_from_config` and sets the parameters as a global property, which can be accessed by the other modules. | ||
|
||
```rust | ||
fn main() { | ||
let mut context = Context::new(); | ||
|
||
define_global_property!(Parameters, ParametersValues); | ||
context.load_parameters_from_config(ParameterValues, "config.yaml"); | ||
|
||
let parameters = context.get_global_property_value(Parameters); | ||
|
||
context.add_plan(parameters.max_time, |context| { | ||
context.shutdown(); | ||
}); | ||
print!("{:?}", parameters); | ||
context.execute(); | ||
} | ||
``` |
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,45 @@ | ||
use ixa::context::Context; | ||
use ixa::global_properties::ContextGlobalPropertiesExt; | ||
use ixa::people::PersonPropertyChangeEvent; | ||
use ixa::report::ContextReportExt; | ||
use ixa::{create_report_trait, report::Report}; | ||
use std::path::PathBuf; | ||
|
||
use crate::InfectionStatus; | ||
use crate::InfectionStatusType; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
use crate::Parameters; | ||
|
||
#[derive(Serialize, Deserialize, Clone)] | ||
struct IncidenceReportItem { | ||
time: f64, | ||
person_id: String, | ||
infection_status: InfectionStatus, | ||
} | ||
|
||
create_report_trait!(IncidenceReportItem); | ||
|
||
fn handle_infection_status_change( | ||
context: &mut Context, | ||
event: PersonPropertyChangeEvent<InfectionStatusType>, | ||
) { | ||
context.send_report(IncidenceReportItem { | ||
time: context.get_current_time(), | ||
person_id: format!("{}", event.person_id), | ||
infection_status: event.current, | ||
}); | ||
} | ||
|
||
pub fn init(context: &mut Context) { | ||
let parameters = context.get_global_property_value(Parameters).clone(); | ||
context | ||
.report_options() | ||
.directory(PathBuf::from(parameters.output_dir)); | ||
context.add_report::<IncidenceReportItem>(¶meters.output_file); | ||
context.subscribe_to_event( | ||
|context, event: PersonPropertyChangeEvent<InfectionStatusType>| { | ||
handle_infection_status_change(context, event); | ||
}, | ||
); | ||
} |
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,95 @@ | ||
use ixa::context::Context; | ||
use ixa::define_rng; | ||
use ixa::global_properties::ContextGlobalPropertiesExt; | ||
use ixa::people::{ContextPeopleExt, PersonId, PersonPropertyChangeEvent}; | ||
use ixa::random::ContextRandomExt; | ||
use rand_distr::Exp; | ||
|
||
use crate::InfectionStatus; | ||
use crate::InfectionStatusType; | ||
use crate::Parameters; | ||
|
||
define_rng!(InfectionRng); | ||
|
||
fn schedule_recovery(context: &mut Context, person_id: PersonId) { | ||
let parameters = context.get_global_property_value(Parameters).clone(); | ||
let infection_duration = parameters.infection_duration; | ||
let recovery_time = context.get_current_time() | ||
+ context.sample_distr(InfectionRng, Exp::new(1.0 / infection_duration).unwrap()); | ||
context.add_plan(recovery_time, move |context| { | ||
context.set_person_property(person_id, InfectionStatusType, InfectionStatus::R); | ||
}); | ||
} | ||
|
||
fn handle_infection_status_change( | ||
context: &mut Context, | ||
event: PersonPropertyChangeEvent<InfectionStatusType>, | ||
) { | ||
if matches!(event.current, InfectionStatus::I) { | ||
schedule_recovery(context, event.person_id); | ||
} | ||
} | ||
|
||
pub fn init(context: &mut Context) { | ||
context.subscribe_to_event( | ||
move |context, event: PersonPropertyChangeEvent<InfectionStatusType>| { | ||
handle_infection_status_change(context, event); | ||
}, | ||
); | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use ixa::context::Context; | ||
use ixa::define_data_plugin; | ||
use ixa::global_properties::ContextGlobalPropertiesExt; | ||
use ixa::people::{ContextPeopleExt, PersonPropertyChangeEvent}; | ||
use ixa::random::ContextRandomExt; | ||
define_data_plugin!(RecoveryPlugin, usize, 0); | ||
|
||
use crate::parameters_loader::ParametersValues; | ||
|
||
#[test] | ||
fn test_handle_infection_change() { | ||
let p_values = ParametersValues { | ||
population: 10, | ||
max_time: 10.0, | ||
seed: 42, | ||
foi: 0.15, | ||
infection_duration: 5.0, | ||
output_dir: ".".to_string(), | ||
output_file: ".".to_string(), | ||
}; | ||
let mut context = Context::new(); | ||
|
||
context.set_global_property_value(Parameters, p_values); | ||
context.init_random(42); | ||
init(&mut context); | ||
|
||
context.subscribe_to_event( | ||
move |context, event: PersonPropertyChangeEvent<InfectionStatusType>| { | ||
if matches!(event.current, InfectionStatus::R) { | ||
*context.get_data_container_mut(RecoveryPlugin) += 1; | ||
} | ||
}, | ||
); | ||
|
||
let population_size: usize = 10; | ||
for _ in 0..population_size { | ||
let person = context.add_person(); | ||
|
||
// This is necessary to emit an event | ||
context.get_person_property(person, InfectionStatusType); | ||
|
||
context.add_plan(1.0, move |context| { | ||
context.set_person_property(person, InfectionStatusType, InfectionStatus::I); | ||
}); | ||
} | ||
context.execute(); | ||
assert_eq!(population_size, context.get_current_population()); | ||
let recovered_size: usize = *context.get_data_container(RecoveryPlugin).unwrap(); | ||
|
||
assert_eq!(recovered_size, population_size); | ||
} | ||
} |
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,9 @@ | ||
{ | ||
"population": 1000, | ||
"max_time": 200.0, | ||
"seed": 123, | ||
"foi": 0.15, | ||
"infection_duration": 5.0, | ||
"output_dir": "examples/parameter-loading", | ||
"output_file": "incidence" | ||
} |
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,7 @@ | ||
population = 10 | ||
max_time = 20.0 | ||
seed = 123 | ||
foi = 0.15 | ||
infection_duration = 5.0 | ||
output_dir = "examples/parameter-loading" | ||
output_file = "incidence" |
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,55 @@ | ||
use ixa::people::ContextPeopleExt; | ||
use ixa::random::ContextRandomExt; | ||
use ixa::{ | ||
context::Context, define_person_property, define_person_property_with_default, | ||
global_properties::ContextGlobalPropertiesExt, | ||
}; | ||
use std::path::Path; | ||
|
||
mod incidence_report; | ||
mod infection_manager; | ||
mod parameters_loader; | ||
mod transmission_manager; | ||
|
||
use crate::parameters_loader::Parameters; | ||
|
||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, Serialize, Deserialize)] | ||
pub enum InfectionStatus { | ||
S, | ||
I, | ||
R, | ||
} | ||
define_person_property_with_default!(InfectionStatusType, InfectionStatus, InfectionStatus::S); | ||
|
||
fn main() { | ||
let mut context = Context::new(); | ||
let file_path = Path::new("examples") | ||
.join("parameter-loading") | ||
.join("input.json"); | ||
|
||
match parameters_loader::init_parameters(&mut context, &file_path) { | ||
Ok(()) => { | ||
let parameters = context.get_global_property_value(Parameters).clone(); | ||
context.init_random(parameters.seed); | ||
|
||
for _ in 0..parameters.population { | ||
context.add_person(); | ||
} | ||
|
||
transmission_manager::init(&mut context); | ||
infection_manager::init(&mut context); | ||
incidence_report::init(&mut context); | ||
|
||
context.add_plan(parameters.max_time, |context| { | ||
context.shutdown(); | ||
}); | ||
println!("{parameters:?}"); | ||
context.execute(); | ||
} | ||
Err(ixa_error) => { | ||
println!("Could not read parameters: {ixa_error}"); | ||
} | ||
} | ||
} |
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,26 @@ | ||
use ixa::context::Context; | ||
use ixa::global_properties::ContextGlobalPropertiesExt; | ||
use std::fmt::Debug; | ||
use std::path::Path; | ||
|
||
use ixa::define_global_property; | ||
use ixa::error::IxaError; | ||
use serde::{Deserialize, Serialize}; | ||
|
||
#[derive(Serialize, Deserialize, Debug, Clone)] | ||
pub struct ParametersValues { | ||
pub population: usize, | ||
pub max_time: f64, | ||
pub seed: u64, | ||
pub foi: f64, | ||
pub infection_duration: f64, | ||
pub output_dir: String, | ||
pub output_file: String, | ||
} | ||
define_global_property!(Parameters, ParametersValues); | ||
|
||
pub fn init_parameters(context: &mut Context, file_path: &Path) -> Result<(), IxaError> { | ||
let parameters_json = context.load_parameters_from_json::<ParametersValues>(file_path)?; | ||
context.set_global_property_value(Parameters, parameters_json); | ||
Ok(()) | ||
} |
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,20 @@ | ||
library(tidyverse) | ||
library(jsonlite) | ||
dir <- file.path("examples", "parameter-loading") | ||
params <- read_json(file.path(dir, "input.json")) | ||
population <- params$population | ||
foi <- params$foi | ||
|
||
output_df <- read_csv(file.path(dir, "incidence.csv")) |> | ||
dplyr::filter(infection_status == "I") |> | ||
group_by(time) |> | ||
mutate(inf = n()) |> | ||
ungroup() |> | ||
mutate(inf = cumsum(inf)) | ||
|
||
time_array <- 0:ceiling(max(output_df$time)) | ||
|
||
expected_susc <- population * exp(-foi * time_array) | ||
|
||
plot(output_df$time, population - output_df$inf, ylim = c(0,population)) | ||
lines(time_array, expected_susc, col = "red") |
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,78 @@ | ||
use ixa::context::Context; | ||
use ixa::define_rng; | ||
use ixa::global_properties::ContextGlobalPropertiesExt; | ||
use ixa::people::ContextPeopleExt; | ||
use ixa::random::ContextRandomExt; | ||
|
||
use crate::InfectionStatus; | ||
use crate::InfectionStatusType; | ||
use crate::Parameters; | ||
use rand_distr::Exp; | ||
|
||
define_rng!(TransmissionRng); | ||
|
||
fn attempt_infection(context: &mut Context) { | ||
let population_size: usize = context.get_current_population(); | ||
let person_to_infect = | ||
context.get_person_id(context.sample_range(TransmissionRng, 0..population_size)); | ||
let person_status: InfectionStatus = | ||
context.get_person_property(person_to_infect, InfectionStatusType); | ||
let parameters = context.get_global_property_value(Parameters).clone(); | ||
|
||
if matches!(person_status, InfectionStatus::S) { | ||
context.set_person_property(person_to_infect, InfectionStatusType, InfectionStatus::I); | ||
} | ||
|
||
// With a food-borne illness (i.e., constant force of infection), | ||
// each _person_ experiences an exponentially distributed | ||
// time until infected. Here, we use a per-person force of infection derived from the population-level to represent a constant risk of infection for individuals in the population. | ||
|
||
// An alternative implementation calculates each person's time to infection | ||
// at the beginning of the simulation and scheudles their infection at that time. | ||
|
||
#[allow(clippy::cast_precision_loss)] | ||
let next_attempt_time = context.get_current_time() | ||
+ context.sample_distr(TransmissionRng, Exp::new(parameters.foi).unwrap()) | ||
/ population_size as f64; | ||
|
||
if next_attempt_time <= parameters.max_time { | ||
context.add_plan(next_attempt_time, move |context| { | ||
attempt_infection(context); | ||
}); | ||
} | ||
} | ||
|
||
pub fn init(context: &mut Context) { | ||
context.add_plan(0.0, |context| { | ||
attempt_infection(context); | ||
}); | ||
} | ||
|
||
#[cfg(test)] | ||
mod test { | ||
use super::*; | ||
use crate::parameters_loader::ParametersValues; | ||
use ixa::context::Context; | ||
|
||
#[test] | ||
fn test_attempt_infection() { | ||
let p_values = ParametersValues { | ||
population: 10, | ||
max_time: 10.0, | ||
seed: 42, | ||
foi: 0.15, | ||
infection_duration: 5.0, | ||
output_dir: ".".to_string(), | ||
output_file: ".".to_string(), | ||
}; | ||
|
||
let mut context = Context::new(); | ||
context.set_global_property_value(Parameters, p_values); | ||
context.init_random(123); | ||
let pid = context.add_person(); | ||
attempt_infection(&mut context); | ||
let person_status = context.get_person_property(pid, InfectionStatusType); | ||
assert_eq!(person_status, InfectionStatus::I); | ||
context.execute(); | ||
} | ||
} |
Oops, something went wrong.