|
| 1 | +#include "memilio/config.h" |
| 2 | +#include "ode_secir/model.h" |
| 3 | +#include "ode_secir/infection_state.h" |
| 4 | +#include "ode_secir/parameters.h" |
| 5 | +#include "memilio/mobility/metapopulation_mobility_instant.h" |
| 6 | +#include "memilio/compartments/simulation.h" |
| 7 | +#include "memilio/data/analyze_result.h" |
| 8 | + |
| 9 | +int main() |
| 10 | +{ |
| 11 | + // In the previous tutorials, we saw how to set up and run an age-resolved ODE-based SECIR-type model. However, one limiting assumption of simple ODE-based models is the assumption of homogenous mixing within the population. To overcome this limitation and incorporate spatial heterogeneity, in this example we show how to use MEmilio's graph-based metapopulation model. This model realizes mobility between regions via graph edges, while every region is represented by a graph node containing it's own ODE-based model. |
| 12 | + |
| 13 | + // *** Set up model. *** |
| 14 | + // We set the simulation start time `t0`, the end time `tmax` and the initial step size `dt` as: |
| 15 | + ScalarType t0 = 0; |
| 16 | + ScalarType tmax = 100; |
| 17 | + ScalarType dt = 0.1; |
| 18 | + |
| 19 | + // Next, we need to specify the parameters. We will initialize a metapopulation model with two regions. The total population as well as the epidemiological parameters will be the same for both regions. |
| 20 | + ScalarType total_population_per_region = 100000; |
| 21 | + |
| 22 | + // We use a model with three age groups for both regions: |
| 23 | + size_t num_agegroups = 3; |
| 24 | + // Create model with three age groups |
| 25 | + mio::osecir::Model<ScalarType> model(num_agegroups); |
| 26 | + |
| 27 | + // Now, we have to set the epidemiological model parameters which are dependent on age group. A list of all parameters can be found at https://memilio.readthedocs.io/en/latest/cpp/models/osecir.html. |
| 28 | + // We choose an increasing risk of severe and critical infections for age group 2 and 3 compared to age group 1. The other parameters are equal for all age groups. |
| 29 | + |
| 30 | + for (size_t i = 0; i < num_agegroups; i++) { |
| 31 | + // Set infection state stay times (in days) |
| 32 | + model.parameters.get<mio::osecir::TimeExposed<ScalarType>>()[mio::AgeGroup(i)] = 3.2; |
| 33 | + model.parameters.get<mio::osecir::TimeInfectedNoSymptoms<ScalarType>>()[mio::AgeGroup(i)] = 2.; |
| 34 | + model.parameters.get<mio::osecir::TimeInfectedSymptoms<ScalarType>>()[mio::AgeGroup(i)] = 6.; |
| 35 | + model.parameters.get<mio::osecir::TimeInfectedSevere<ScalarType>>()[mio::AgeGroup(i)] = 12.; |
| 36 | + model.parameters.get<mio::osecir::TimeInfectedCritical<ScalarType>>()[mio::AgeGroup(i)] = 9.; |
| 37 | + // Set infection state transition probabilities |
| 38 | + model.parameters.get<mio::osecir::TransmissionProbabilityOnContact<ScalarType>>()[mio::AgeGroup(i)] = 0.1; |
| 39 | + model.parameters.get<mio::osecir::RelativeTransmissionNoSymptoms<ScalarType>>()[mio::AgeGroup(i)] = 0.67; |
| 40 | + model.parameters.get<mio::osecir::RecoveredPerInfectedNoSymptoms<ScalarType>>()[mio::AgeGroup(i)] = 0.2; |
| 41 | + model.parameters.get<mio::osecir::RiskOfInfectionFromSymptomatic<ScalarType>>()[mio::AgeGroup(i)] = 0.25; |
| 42 | + model.parameters.get<mio::osecir::DeathsPerCritical<ScalarType>>()[mio::AgeGroup(i)] = 0.3; |
| 43 | + } |
| 44 | + |
| 45 | + // The groups have an increasing risk of severe and critical infections |
| 46 | + model.parameters.get<mio::osecir::SeverePerInfectedSymptoms<ScalarType>>()[mio::AgeGroup(0)] = 0.2; |
| 47 | + model.parameters.get<mio::osecir::SeverePerInfectedSymptoms<ScalarType>>()[mio::AgeGroup(1)] = 0.2 * 1.5; |
| 48 | + model.parameters.get<mio::osecir::SeverePerInfectedSymptoms<ScalarType>>()[mio::AgeGroup(2)] = 0.2 * 2; |
| 49 | + model.parameters.get<mio::osecir::CriticalPerSevere<ScalarType>>()[mio::AgeGroup(0)] = 0.25; |
| 50 | + model.parameters.get<mio::osecir::CriticalPerSevere<ScalarType>>()[mio::AgeGroup(1)] = 0.25 * 1.5; |
| 51 | + model.parameters.get<mio::osecir::CriticalPerSevere<ScalarType>>()[mio::AgeGroup(2)] = 0.25 * 2; |
| 52 | + |
| 53 | + // Set contact frequency |
| 54 | + ScalarType contact_frequency = 10; |
| 55 | + mio::ContactMatrixGroup<ScalarType>& contact_matrix = |
| 56 | + model.parameters.get<mio::osecir::ContactPatterns<ScalarType>>(); |
| 57 | + contact_matrix[0] = mio::ContactMatrix<ScalarType>( |
| 58 | + Eigen::MatrixX<ScalarType>::Constant(num_agegroups, num_agegroups, contact_frequency)); |
| 59 | + |
| 60 | + // Next, we create the graph via: |
| 61 | + mio::Graph<mio::SimulationNode<ScalarType, mio::osecir::Simulation<ScalarType>>, mio::MobilityEdge<ScalarType>> |
| 62 | + graph; |
| 63 | + |
| 64 | + // we want to add two regions (nodes) to the graph, therefore we need two copies of the model |
| 65 | + auto model_region1 = model; |
| 66 | + auto model_region2 = model; |
| 67 | + |
| 68 | + // In the graph-based metapopulation model, every graph node gets it's own ODE-based model which is copied when adding a graph node and handing the model to it as parameter. Therefore we can choose different initial conditions (as well as differing parameters) for different graph nodes. In our example, we simulate two regions with only one region having initially infected individuals. We choose 1% initially infected for that region while the other region starts with a totally susceptible population. |
| 69 | + // The model compartments for the first node are initialized via: |
| 70 | + for (size_t i = 0; i < num_agegroups; i++) { |
| 71 | + model_region1.populations[{mio::AgeGroup(i), mio::osecir::InfectionState::Exposed}] = |
| 72 | + 0.005 * total_population_per_region / num_agegroups; |
| 73 | + model_region1.populations[{mio::AgeGroup(i), mio::osecir::InfectionState::InfectedNoSymptoms}] = |
| 74 | + 0.005 * total_population_per_region / num_agegroups; |
| 75 | + model_region1.populations.set_difference_from_group_total<mio::AgeGroup>( |
| 76 | + {mio::AgeGroup(i), mio::osecir::InfectionState::Susceptible}, total_population_per_region / num_agegroups); |
| 77 | + } |
| 78 | + |
| 79 | + // We set the second model's initial populations to totally susceptible |
| 80 | + for (size_t i = 0; i < num_agegroups; i++) { |
| 81 | + model_region2.populations.set_difference_from_group_total<mio::AgeGroup>( |
| 82 | + {mio::AgeGroup(i), mio::osecir::InfectionState::Susceptible}, total_population_per_region / num_agegroups); |
| 83 | + } |
| 84 | + |
| 85 | + // After having initialized the models, we add two nodes (regions) to the graph |
| 86 | + graph.add_node(0, model_region1, t0, dt); |
| 87 | + |
| 88 | + //EXERCISE: Please add the second node to the graph. |
| 89 | + |
| 90 | + // If we would simulate the graph-based metapopulation model now, we would just have two independent ODE-based SECIR-type models running with different initial conditions. In reality, there is usually exchange between regions through individuals travelling or commuting from one region to another. This can be realized via graph edges. |
| 91 | + // We here use a symmetric mobility i.e. we have the same number of individuals that travel from node 0 to node 1 as vice versa. We let 10% of the population commute via the edges twice a day. |
| 92 | + graph.add_edge( |
| 93 | + 0, 1, Eigen::VectorX<ScalarType>::Constant((size_t)mio::osecir::InfectionState::Count * num_agegroups, 0.1)); |
| 94 | + |
| 95 | + // EXERCISE: Please add an edge from node 1 to node 0. |
| 96 | + |
| 97 | + // Exchange commuters twice a day |
| 98 | + double dt_exchange = 0.5; |
| 99 | + |
| 100 | + // *** Simulate model. *** |
| 101 | + // We now have finished initializing the metapopulation model. The graph-based simulation is created and advanced until `tmax` |
| 102 | + auto sim = mio::make_mobility_sim<ScalarType>(t0, dt_exchange, std::move(graph)); |
| 103 | + |
| 104 | + // EXERCISE: Please advance the simulation until `tmax`. |
| 105 | + |
| 106 | + // As every graph node has its own model, we get one result time series per node. Those can be accessed as follows |
| 107 | + auto result_region0 = sim.get_graph().nodes()[0].property.get_result(); |
| 108 | + auto result_region1 = sim.get_graph().nodes()[1].property.get_result(); |
| 109 | + // Interpolate time series to full days. |
| 110 | + auto interpolated_result_r0 = mio::interpolate_simulation_result(result_region0); |
| 111 | + |
| 112 | + // *** Print results. *** |
| 113 | + interpolated_result_r0.print_table(); |
| 114 | +} |
0 commit comments