Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 16 additions & 14 deletions examples/network-hhmodel/README.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
# Example: Agent-based SEIR model with contact networks

This example demonstrates the use of the `network` module of `ixa`.
This example demonstrates a network model in `ixa`.

There are three CSV files:
There are three data files:

- `Households.csv` represents 500 households of size 1-12. Individuals have age
category and sex properties. Within the model, these individuals are placed in
a densely connected network.
- `AgeUnder5Edges.csv` contains the edges connecting those aged under 5
- `AgeUnder5Edges.csv` contains the edges connecting those aged under 5.
- `Age5to17Edges.csv` contains the edges connecting those aged 5-17.

In `network.rs`, three corresponding edge types are created using the
`define_edge_type!` macro and the networks are formed by adding edges to the
context using `add_edge_bidi`.
The parameter `sar` is the secondary attack rate within each household, used to compute the within-household transmission rate. The parameter `relative_rate` is the ratio of the transmission rates between versus within households; it should be less than one.

In `seir.rs`, a SEIR model is implemented with different betas by network edge
type. Edge queries (`get_matching_edges`) allow us to identify the neighbors of
the infected individuals and consider whether they become exposed.
The simulation runs via:

`loader.rs` reads in the `Household.csv` file and `parameters.rs` sets up global
properties for the SEIR model.
- `parameters.rs` sets up global properties for the model.
- `loader.rs` reads in the `Household.csv` file and instantiates the people in it.
- `network.rs` forms a dense network of household contacts, then reads in the other
contact files and instantiates those network edges. The edges are tracked as
model entities. This module also selects which individuals have effective contact
during each time period.
- `seir.rs` manages transmission, infections, and disease trajectories.
- `incidence_report.rs` sets up a report with information on who became infected
by whom during the simulation and saves the information to a csv in an `\output`
folder.

`incidence_report.rs` sets up a report with information on who became infected
by whom during the simulation and saves the information to a csv in an `\output`
folder.
Note that the relative rate of transmission between households (relative to within households) is a property of the network edges. For technical reasons, ixa properties must implement `Eq`, which Rust floats do not. This example manually implements equality logic; future ixa versions may have other solutions.

## How to run the model

Expand Down
2 changes: 1 addition & 1 deletion examples/network-hhmodel/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"sar": 0.15,
"shape": 15.0,
"infection_duration": 5.0,
"between_hh_transmission_reduction": 3.0,
"relative_rate": 0.333,
"output_dir": "examples/network-hhmodel/output",
"data_dir": "examples/network-hhmodel/data"
}
Expand Down
8 changes: 4 additions & 4 deletions examples/network-hhmodel/incidence_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ mod test {
sar: 1.0,
shape: 15.0,
infection_duration: 5.0,
between_hh_transmission_reduction: 1.0,
relative_rate: 1.0,
data_dir: output_dir.to_str().unwrap().to_string(),
output_dir: output_dir.to_str().unwrap().to_string(),
};
Expand All @@ -145,8 +145,8 @@ mod test {
.set_global_property_value(Parameters, parameters.clone())
.unwrap();

let people = loader::init(&mut context);
network::init(&mut context, &people);
loader::init(&mut context);
network::init(&mut context, 1.0);
incidence_report::init(&mut context).unwrap();

context.subscribe_to_event(
Expand All @@ -158,7 +158,7 @@ mod test {
let to_infect: Vec<PersonId> = vec![context.sample_entity(MainRng, Person).unwrap()];

#[allow(clippy::vec_init_then_push)]
seir::init(&mut context, &to_infect);
seir::init(&mut context, &to_infect, 1.0);

context.execute();
}
Expand Down
113 changes: 50 additions & 63 deletions examples/network-hhmodel/loader.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ixa::impl_property;
use ixa::prelude::*;
use serde::{Deserialize, Serialize};

use crate::{example_dir, Person, PersonId};
use crate::{example_dir, Person};

#[derive(Serialize, Deserialize, Copy, Clone, PartialEq, Eq, Debug, Hash)]
pub struct Id(pub u16);
Expand Down Expand Up @@ -39,98 +39,85 @@ struct PeopleRecord {
household_id: HouseholdId,
}

fn create_person_from_record(context: &mut Context, record: &PeopleRecord) -> PersonId {
context
.add_entity(with!(
Person,
record.id,
record.age_group,
record.sex,
record.household_id
))
.unwrap()
}

pub fn open_csv(file_name: &str) -> Reader<File> {
let current_dir = example_dir();
let file_path = current_dir.join(file_name);
csv::Reader::from_path(file_path).unwrap()
}

pub fn init(context: &mut Context) -> Vec<PersonId> {
pub fn init(context: &mut Context) {
// Load csv and deserialize records
let mut reader = open_csv("Households.csv");
let mut people = Vec::new();

for result in reader.deserialize() {
let record: PeopleRecord = result.expect("Failed to parse record");
people.push(create_person_from_record(context, &record));
context
.add_entity(with!(
Person,
record.id,
record.age_group,
record.sex,
record.household_id
))
.unwrap();
}

context.index_property::<Person, Id>();
context.index_property::<Person, HouseholdId>();

people
}

#[cfg(test)]
mod tests {
use ixa::context::Context;
use ixa::random::ContextRandomExt;

use super::*;

const EXPECTED_ROWS: usize = 1606;

#[test]
fn test_init_expected_rows() {
let mut context = Context::new();
context.init_random(42);
init(&mut context);
assert_eq!(context.get_entity_count::<Person>(), EXPECTED_ROWS);
}

// Check there is exactly one matching entity
fn assert_exists1(
context: &Context,
id: Id,
age_group: AgeGroup,
sex: Sex,
hh_id: HouseholdId,
) {
assert_eq!(
context.query_entity_count(with!(Person, id, age_group, sex, hh_id)),
1
);
}

#[test]
fn test_some_people_load_correctly() {
let mut context = Context::new();
context.init_random(42);

let people = init(&mut context);
init(&mut context);

let person = people[0];
assert!(context.match_entity(
person,
with!(
Person,
Id(676),
AgeGroup::Age18to64,
Sex::Female,
HouseholdId(1)
)
));

let person = people[246];
assert!(context.match_entity(
person,
with!(
Person,
Id(213),
AgeGroup::AgeUnder5,
Sex::Female,
HouseholdId(162)
)
));

let person = people[1591];
assert!(context.match_entity(
person,
with!(
Person,
Id(1591),
AgeGroup::Age65Plus,
Sex::Male,
HouseholdId(496)
)
));
// e.g., the person with data id 676 should be 18-64, female, in household 1
assert_exists1(
&context,
Id(676),
AgeGroup::Age18to64,
Sex::Female,
HouseholdId(1),
);

assert_exists1(
&context,
Id(213),
AgeGroup::AgeUnder5,
Sex::Female,
HouseholdId(162),
);

assert_exists1(
&context,
Id(1591),
AgeGroup::Age65Plus,
Sex::Male,
HouseholdId(496),
);
}
}
14 changes: 10 additions & 4 deletions examples/network-hhmodel/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ mod parameters;
mod seir;
use std::path::PathBuf;

use parameters::Parameters;

define_entity!(Person);
define_rng!(MainRng);

Expand All @@ -27,14 +29,19 @@ fn initialize(context: &mut Context) {
context.init_random(1);

// Load people from csv and set up some base properties
let people = loader::init(context);
loader::init(context);

// Load parameters from json
let file_path = example_dir().join("config.json");
context.load_global_properties(&file_path).unwrap();

let parameters = context
.get_global_property_value(Parameters)
.unwrap()
.clone();

// Load network
network::init(context, &people);
network::init(context, parameters.relative_rate);

// Initialize incidence report
incidence_report::init(context).unwrap();
Expand All @@ -43,6 +50,5 @@ fn initialize(context: &mut Context) {
let to_infect: Vec<PersonId> = vec![context.sample_entity(MainRng, Person).unwrap()];

#[allow(clippy::vec_init_then_push)]
seir::init(context, &to_infect);
context.execute();
seir::init(context, &to_infect, 1.0);
}
Loading