Skip to content

Commit 4383064

Browse files
committed
Merge branch 'main' of github.com:SciCompMod/memilio-tutorials
2 parents bb4a346 + 53e95a1 commit 4383064

16 files changed

Lines changed: 2719 additions & 57 deletions

cpp-tutorials/CMakeLists.txt

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,19 @@ set(MEMILIO_BUILD_SIMULATIONS OFF)
3838

3939
# Add the subdirectory for MEmilio build.
4040
add_subdirectory(${memilio_SOURCE_DIR}/cpp ${memilio_BINARY_DIR})
41+
add_subdirectory(exercises)
4142

4243
add_executable(tutorial_ide tutorial_ide.cpp)
4344
target_link_libraries(tutorial_ide PRIVATE memilio ide_secir)
4445

45-
add_executable(exercise_ide exercises/exercise_ide.cpp)
46-
target_link_libraries(exercise_ide PRIVATE memilio ide_secir)
47-
4846
add_executable(tutorial_lct tutorial_lct.cpp)
4947
target_link_libraries(tutorial_lct PRIVATE memilio lct_secir)
5048

51-
add_executable(exercise_lct exercises/exercise_lct.cpp)
52-
target_link_libraries(exercise_lct PRIVATE memilio lct_secir)
53-
5449
add_executable(tutorial1 tutorial1.cpp)
5550
target_link_libraries(tutorial1 PRIVATE memilio ode_secir Boost::filesystem)
5651

57-
add_executable(exercise1 exercises/exercise1.cpp)
58-
target_link_libraries(exercise1 PRIVATE memilio ode_secir Boost::filesystem)
52+
add_executable(tutorial2 tutorial2.cpp)
53+
target_link_libraries(tutorial2 PRIVATE memilio ode_secir Boost::filesystem)
5954

6055
add_executable(tutorial3 tutorial3.cpp)
6156
target_link_libraries(tutorial3 PRIVATE memilio ode_secir Boost::filesystem)
@@ -75,6 +70,12 @@ target_link_libraries(tutorial7 PRIVATE memilio ode_secir Boost::filesystem)
7570
add_executable(exercise7 exercises/exercise7.cpp)
7671
target_link_libraries(exercise7 PRIVATE memilio ode_secir Boost::filesystem)
7772

73+
add_executable(tutorial10 tutorial10.cpp)
74+
target_link_libraries(tutorial10 PRIVATE memilio ode_secir Boost::filesystem)
75+
76+
add_executable(tutorial11 tutorial11.cpp)
77+
target_link_libraries(tutorial11 PRIVATE memilio ode_secir Boost::filesystem)
78+
7879
add_executable(tutorial12_ensemble_sims tutorial12_ensemble_sims.cpp)
7980
target_link_libraries(tutorial12_ensemble_sims PRIVATE memilio ode_secir)
8081

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
add_executable(exercise1 exercise1.cpp)
3+
target_link_libraries(exercise1 PRIVATE memilio ode_secir Boost::filesystem)
4+
5+
add_executable(exercise2 exercise2.cpp)
6+
target_link_libraries(exercise2 PRIVATE memilio ode_secir Boost::filesystem)
7+
8+
add_executable(exercise10 exercise10.cpp)
9+
target_link_libraries(exercise10 PRIVATE memilio ode_secir Boost::filesystem)
10+
11+
add_executable(exercise11 exercise11.cpp)
12+
target_link_libraries(exercise11 PRIVATE memilio ode_secir Boost::filesystem)
13+
14+
add_executable(exercise_ide exercise_ide.cpp)
15+
target_link_libraries(exercise_ide PRIVATE memilio ide_secir)
16+
17+
add_executable(exercise_lct exercise_lct.cpp)
18+
target_link_libraries(exercise_lct PRIVATE memilio lct_secir)
19+
target_include_directories(exercise_lct PRIVATE ${CMAKE_SOURCE_DIR})
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
#include "ode_secir/model.h"
2+
#include "memilio/compartments/simulation.h"
3+
#include "memilio/data/analyze_result.h"
4+
5+
#include <iostream>
6+
7+
// *** Introduction ***
8+
// In Tutorial 3, we applied contact reductions (Dampings) uniformly to a single contact matrix. In reality,
9+
// contacts between individuals occur in different locations: at home, at school, at work, and in other places
10+
// such as transport. Different non-pharmaceutical interventions (NPIs) target different settings, for instance
11+
// a school closure reduces school contacts, while a home-office mandate mainly reduces work contacts.
12+
//
13+
// Large-scale studies, such as the POLYMOD project, have measured social contact patterns across different
14+
// locations (like home, school, work, and others) in various countries. For instance, see
15+
// Mossong J, Hens N, Jit M, Beutels P, Auranen K, et al. (2008) Social Contacts and Mixing Patterns Relevant to the Spread of Infectious Diseases.
16+
// PLoS Med 5(3): e74. https://doi.org/10.1371/journal.pmed.0050074
17+
// Prem K, Cook AR, Jit M (2017) Projecting social contact matrices in 152 countries using contact surveys and demographic data.
18+
// PLoS Comput Biol 13(9): e1005697. https://doi.org/10.1371/journal.pcbi.1005697
19+
//
20+
// In this tutorial, we extend the approach from Tutorial 3 by splitting the contact matrix into
21+
// location-specific contact matrices using ContactMatrixGroup. This allows us to apply NPIs to individual
22+
// locations, allowing a more realistic and detailed representation of intervention effects.
23+
24+
// Location indices matching the four contact contexts.
25+
enum class Location : size_t
26+
{
27+
Home = 0,
28+
School = 1,
29+
Work = 2,
30+
Other = 3
31+
};
32+
33+
// *** Location-specific Contact Matrices ***
34+
// Instead of a single contact matrix, we now use a ContactMatrixGroup, containing one ContactMatrix per
35+
// location. Each ContactMatrix has two components:
36+
// Baseline: the typical number of daily contacts at the location under regular conditions.
37+
// Minimum: the minimal contact rate even under the strictest restrictions, as some contacts, especially
38+
// at home, cannot be fully avoided.
39+
//
40+
// The effective contact rate at simulation time t for a location l is:
41+
// C_l(t) = C_l_baseline - D_l(t) * (C_l_baseline - C_l_minimum)
42+
// where D_l(t) <= 1 is the damping coefficient. For D > 0 contacts are reduced; for D = 1 they reach the
43+
// minimum; and for D < 0 contacts are increased above the baseline. The upper bound D <= 1 is enforced by
44+
// MEmilio's C++ core. The ODE model uses the sum of all location-specific matrices as the total contact rate.
45+
//
46+
// The total baseline contact rate (sum over all locations) equals 4+3+2+1 = 10 contacts/day, matching Tutorial 3.
47+
48+
ScalarType total_population = 100000;
49+
ScalarType t0 = 0;
50+
ScalarType tmax = 100;
51+
ScalarType dt = 0.1;
52+
53+
// Create an OSECIR model with location-specific contact matrices.
54+
mio::osecir::Model<ScalarType> create_model()
55+
{
56+
mio::osecir::Model<ScalarType> m(1); // one age group
57+
58+
// Set infection state stay times (in days)
59+
m.parameters.get<mio::osecir::TimeExposed<ScalarType>>() = 3.2;
60+
m.parameters.get<mio::osecir::TimeInfectedNoSymptoms<ScalarType>>() = 2.;
61+
m.parameters.get<mio::osecir::TimeInfectedSymptoms<ScalarType>>() = 6.;
62+
m.parameters.get<mio::osecir::TimeInfectedSevere<ScalarType>>() = 12.;
63+
m.parameters.get<mio::osecir::TimeInfectedCritical<ScalarType>>() = 8.;
64+
65+
// Set infection state transition probabilities
66+
m.parameters.get<mio::osecir::RelativeTransmissionNoSymptoms<ScalarType>>() = 0.67;
67+
m.parameters.get<mio::osecir::TransmissionProbabilityOnContact<ScalarType>>() = 0.1;
68+
m.parameters.get<mio::osecir::RecoveredPerInfectedNoSymptoms<ScalarType>>() = 0.2;
69+
m.parameters.get<mio::osecir::RiskOfInfectionFromSymptomatic<ScalarType>>() = 0.25;
70+
m.parameters.get<mio::osecir::SeverePerInfectedSymptoms<ScalarType>>() = 0.2;
71+
m.parameters.get<mio::osecir::CriticalPerSevere<ScalarType>>() = 0.25;
72+
m.parameters.get<mio::osecir::DeathsPerCritical<ScalarType>>() = 0.3;
73+
74+
// Create ContactMatrixGroup: 4 location matrices, each of size 1x1 (one age group).
75+
mio::ContactMatrixGroup<ScalarType> contacts(4, 1);
76+
// EXERCISE: Set up the ContactMatrix for each location with the correct baseline and (where applicable)
77+
// minimum values. The baselines should sum to 10 contacts/day, matching Tutorial 3.
78+
// Home: baseline = 4, minimum = 1 (irreducible household contacts)
79+
// School: baseline = 3
80+
// Work: baseline = 2
81+
// Other: baseline = 1
82+
// Hint: use mio::ContactMatrix<ScalarType>(Eigen::MatrixX<ScalarType>::Constant(1, 1, <value>))
83+
// or the two-argument form for baseline + minimum.
84+
// contacts[(size_t)Location::Home] = ???
85+
// contacts[(size_t)Location::School] = ???
86+
// contacts[(size_t)Location::Work] = ???
87+
// contacts[(size_t)Location::Other] = ???
88+
89+
m.parameters.get<mio::osecir::ContactPatterns<ScalarType>>() = contacts;
90+
91+
// Initial populations: 0.5% Exposed, 0.5% InfectedNoSymptoms, rest Susceptible
92+
m.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::Exposed}] = 0.005 * total_population;
93+
m.populations[{mio::AgeGroup(0), mio::osecir::InfectionState::InfectedNoSymptoms}] = 0.005 * total_population;
94+
m.populations.set_difference_from_total({mio::AgeGroup(0), mio::osecir::InfectionState::Susceptible},
95+
total_population);
96+
return m;
97+
}
98+
99+
int main()
100+
{
101+
// *** Baseline Simulation Without NPIs ***
102+
// We first create and simulate the model without any interventions to obtain a baseline.
103+
auto model_no_npi = create_model();
104+
auto result_no_npi = mio::osecir::simulate<ScalarType>(t0, tmax, dt, model_no_npi);
105+
106+
// *** Adding Location-specific NPIs ***
107+
// We now model a lockdown scenario in which three NPIs are activated simultaneously on day 20:
108+
//
109+
// Intervention | Location | Damping D | Effect
110+
// School closure | School | 1.0 | All school contacts eliminated
111+
// Home-office mandate | Work | 0.5 | Work contacts reduced by 50%
112+
// Public transport restriction| Other | 0.8 | Other contacts reduced by 80%
113+
//
114+
// The key difference from Tutorial 3 is that each Damping is applied to a specific location matrix by
115+
// indexing cont_freq_mat[location_index] before calling add_damping. In Tutorial 3, add_damping was called
116+
// on the whole group, reducing all contact matrices simultaneously. Home contacts are left unrestricted
117+
// here, but they cannot fall below the minimum of 1 contact/day regardless.
118+
119+
ScalarType t_npi_start = 20.0; // day at which interventions start
120+
121+
auto model_with_npi = create_model();
122+
auto& cm = model_with_npi.parameters.get<mio::osecir::ContactPatterns<ScalarType>>().get_cont_freq_mat();
123+
124+
// EXERCISE: Add the three location-specific dampings that take effect at t_npi_start:
125+
// School closure: D = 1.0 (all school contacts eliminated)
126+
// Home-office mandate: D = 0.5 (work contacts halved)
127+
// Public transport restriction: D = 0.8 (other contacts reduced by 80%)
128+
// Hint: cm[(size_t)Location::School].add_damping(<coeff>, mio::SimulationTime<ScalarType>(t_npi_start));
129+
// ???
130+
131+
// We can verify the effective total contact rates before and after the NPIs by evaluating the contact
132+
// matrix group at a specific point in time.
133+
auto contacts_before = cm.get_matrix_at(mio::SimulationTime<ScalarType>(t_npi_start - 1));
134+
auto contacts_after = cm.get_matrix_at(mio::SimulationTime<ScalarType>(t_npi_start + 1));
135+
std::cout << "Total contacts before NPIs (day " << (int)(t_npi_start - 1) << "): " << contacts_before(0, 0)
136+
<< " / day\n";
137+
std::cout << "Total contacts after NPIs (day " << (int)(t_npi_start + 1) << "): " << contacts_after(0, 0)
138+
<< " / day\n";
139+
140+
// We simulate the NPI scenario from t0 to tmax.
141+
auto result_with_npi = mio::osecir::simulate<ScalarType>(t0, tmax, dt, model_with_npi);
142+
143+
// *** Print results ***
144+
// Interpolate both results to full integer days and print the InfectedSymptoms column for comparison.
145+
auto interp_no_npi = mio::interpolate_simulation_result(result_no_npi);
146+
auto interp_with_npi = mio::interpolate_simulation_result(result_with_npi);
147+
148+
std::cout << "\n--- Without NPIs ---\n";
149+
interp_no_npi.print_table({"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"}, 12, 4);
150+
std::cout << "\n--- With location-specific NPIs ---\n";
151+
interp_with_npi.print_table({"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"}, 12, 4);
152+
153+
// Optional: export to CSV for plotting in Python (see tutorial10.py for the corresponding visualization).
154+
// interp_no_npi.export_csv("result_no_npi.csv",
155+
// {"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"});
156+
// interp_with_npi.export_csv("result_with_npi.csv",
157+
// {"S", "E", "C", "C_confirmed", "I", "I_confirmed", "H", "U", "R", "D"});
158+
159+
// *** Summary ***
160+
// In this tutorial, we have introduced location-specific contact patterns via ContactMatrixGroup and
161+
// applied targeted NPIs to individual location matrices. Key takeaways:
162+
// - A ContactMatrixGroup collects one ContactMatrix per location (Home, School, Work, Other).
163+
// - Each ContactMatrix has a baseline (normal contacts) and a minimum (irreducible lower bound).
164+
// - Location-specific NPIs are applied with cont_freq_mat[location_index].add_damping(...).
165+
// In contrast, Tutorial 3 used add_damping on the whole group (all matrices simultaneously).
166+
// - The effective contact rate per location is C_eff = C_baseline - D*(C_baseline - C_minimum),
167+
// and the ODE model receives the sum over all locations.
168+
169+
return 0;
170+
}

0 commit comments

Comments
 (0)