Skip to content

Commit a1d8af8

Browse files
HenrZukilianvolmerMaxBetzDLR
authored
1350 Implement RSV model (#1352)
- Implement RSV model from https://doi.org/10.1016/S0025-5564(01)00066-9. - Model is ODE based compartmental model. - Add python bindings for better integration into fitting tools Co-authored-by: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com> Co-authored-by: MaxBetz <104758467+MaxBetzDLR@users.noreply.github.com>
1 parent 9c4d720 commit a1d8af8

File tree

18 files changed

+1336
-8
lines changed

18 files changed

+1336
-8
lines changed

cpp/CMakeLists.txt

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ if(MEMILIO_ENABLE_WARNINGS)
131131
"-Wall;-Wextra;-Wshadow;--pedantic;")
132132
endif()
133133
endif()
134+
134135
# exclude some warnings we accept
135136
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
136137
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
@@ -139,20 +140,22 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
139140
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
140141
"-Wno-unknown-warning-option;-Wno-deprecated;-Wno-gnu-zero-variadic-macro-arguments;")
141142
endif()
143+
142144
# woyrkarounds for compiler bugs or overzealous optimization/analysis
143145
if(CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "RELEASE")
144-
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
146+
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU")
145147
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
146-
"-Wno-restrict;" # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329
147-
"-Wno-maybe-uninitialized;" # https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=may%20be%20uninitialized
148-
"-Wno-stringop-overread;"
148+
"-Wno-restrict;" # https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105329
149+
"-Wno-maybe-uninitialized;" # https://gcc.gnu.org/bugzilla/buglist.cgi?quicksearch=may%20be%20uninitialized
150+
"-Wno-stringop-overread;"
149151
)
150152
elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
151153
string(APPEND MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS
152154
"-Wno-stringop-overread;"
153155
)
154156
endif()
155157
endif()
158+
156159
# finalize string by setting warnings as errors
157160
if(MEMILIO_ENABLE_WARNINGS_AS_ERRORS)
158161
if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC")
@@ -164,8 +167,6 @@ if(MEMILIO_ENABLE_WARNINGS_AS_ERRORS)
164167
endif()
165168
endif()
166169

167-
168-
169170
# add parts of the project
170171
include(thirdparty/CMakeLists.txt)
171172
add_subdirectory(memilio/ad)
@@ -190,6 +191,7 @@ if(MEMILIO_BUILD_MODELS)
190191
add_subdirectory(models/graph_abm)
191192
add_subdirectory(models/smm)
192193
add_subdirectory(models/hybrid)
194+
add_subdirectory(models/ode_mseirs4)
193195
endif()
194196

195197
if(MEMILIO_BUILD_EXAMPLES)

cpp/examples/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,3 +196,7 @@ if(MEMILIO_HAS_JSONCPP)
196196
target_link_libraries(ide_initialization_example PRIVATE memilio ide_secir)
197197
target_compile_options(ide_initialization_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
198198
endif()
199+
200+
add_executable(ode_mseirs4_example ode_mseirs4.cpp)
201+
target_link_libraries(ode_mseirs4_example PRIVATE memilio ode_mseirs4)
202+
target_compile_options(ode_mseirs4_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})

cpp/examples/ode_mseirs4.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Henrik Zunker
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
#include "ode_mseirs4/model.h"
21+
#include "memilio/compartments/simulation.h"
22+
#include <iostream>
23+
24+
int main()
25+
{
26+
using FP = double;
27+
mio::omseirs4::Model<FP> model;
28+
auto& params = model.parameters;
29+
30+
// Example parameter values for day-based time unit (t in days)
31+
params.get<mio::omseirs4::BaseTransmissionRate<FP>>() = 0.4; // b0 per day
32+
params.get<mio::omseirs4::SeasonalAmplitude<FP>>() = 0.15; // b1
33+
params.get<mio::omseirs4::SeasonalPhase<FP>>() = 0.0; // phi; for phase shift use 2*pi*offsetDays/365
34+
params.get<mio::omseirs4::NaturalBirthDeathRate<FP>>() = 1.0 / (70.0 * 365.0); // mu per day
35+
params.get<mio::omseirs4::LossMaternalImmunityRate<FP>>() = 1.0 / 90.0; // xi per day (~3 months)
36+
params.get<mio::omseirs4::ProgressionRate<FP>>() = 1.0 / 7.0; // sigma per day (≈7 days latent)
37+
params.get<mio::omseirs4::RecoveryRate<FP>>() = 1.0 / 14.0; // nu per day (≈14 days infectious)
38+
params.get<mio::omseirs4::ImmunityWaningRate<FP>>() = 1.0 / (5.0 * 365.0); // gamma per day (5 years)
39+
// factors default already set (0.5, 0.35, 0.25) as in the paper https://doi.org/10.1016/S0025-5564(01)00066-9.
40+
41+
// Initial population (absolute counts), set all compartments explicitly.
42+
double N = 1e6;
43+
44+
// Initial populations.
45+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::MaternalImmune)}] =
46+
5000.0;
47+
48+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::E1)}] = 300.0;
49+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::E2)}] = 150.0;
50+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::E3)}] = 80.0;
51+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::E4)}] = 70.0;
52+
53+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::I1)}] = 200.0;
54+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::I2)}] = 100.0;
55+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::I3)}] = 50.0;
56+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::I4)}] = 50.0;
57+
58+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::R1)}] = 40000.0;
59+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::R2)}] = 30000.0;
60+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::R3)}] = 20000.0;
61+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::R4)}] = 10000.0;
62+
63+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::S2)}] = 100000.0;
64+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::S3)}] = 50000.0;
65+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::S4)}] = 50000.0;
66+
67+
// Compute S1 as residual to match N
68+
double assigned = 5000.0 + (300.0 + 150.0 + 80.0 + 70.0) + (200.0 + 100.0 + 50.0 + 50.0) +
69+
(40000.0 + 30000.0 + 20000.0 + 10000.0) + (100000.0 + 50000.0 + 50000.0);
70+
double S1 = N - assigned;
71+
if (S1 < 0)
72+
S1 = 0;
73+
model.populations[{mio::Index<mio::omseirs4::InfectionState>(mio::omseirs4::InfectionState::S1)}] = S1;
74+
75+
model.check_constraints();
76+
77+
// simulate
78+
double t0 = 0.0;
79+
double tmax = 20.0; // days
80+
double dt = 1.0; // daily output
81+
auto result = mio::simulate(t0, tmax, dt, model);
82+
83+
// print header
84+
std::cout << "t M S1 S2 S3 S4 E1 E2 E3 E4 I1 I2 I3 I4 R1 R2 R3 R4\n";
85+
for (size_t i = 0; i < (size_t)result.get_num_time_points(); ++i) {
86+
std::cout << result.get_time(i);
87+
const auto& y = result.get_value(i);
88+
for (size_t k = 0; k < (size_t)mio::omseirs4::InfectionState::Count; ++k) {
89+
std::cout << ' ' << y[(Eigen::Index)k];
90+
}
91+
std::cout << '\n';
92+
}
93+
return 0;
94+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_library(ode_mseirs4
2+
infection_state.h
3+
parameters.h
4+
model.h
5+
model.cpp
6+
)
7+
target_link_libraries(ode_mseirs4 PUBLIC memilio)
8+
target_include_directories(ode_mseirs4 PUBLIC
9+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
10+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
11+
)
12+
target_compile_options(ode_mseirs4 PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})

cpp/models/ode_mseirs4/README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# ODE MSEIRS4 Model
2+
3+
Implementation of the MSEIRS4 model (maternal immunity + susceptible stages S1-S4 + exposed, infectious, recovered stages with four parallel infection/recovery classes).
4+
This model is designed for Respiratory Syncytial Virus (RSV) and is based on:
5+
6+
- Weber A, Weber M, Milligan P. (2001). *Modeling epidemics caused by respiratory syncytial virus (RSV).* Mathematical Biosciences 172(2): 95–113. `DOI:10.1016/S0025-5564(01)00066-9 <https://doi.org/10.1016/S0025-5564(01)00066-9>`_
7+
8+
9+
## Meaning of indices 1–4 (S1–S4, E1–E4, I1–I4, R1–R4)
10+
11+
- S1: fully susceptible after loss of maternal immunity (highest susceptibility).
12+
- S2: susceptible after first infection (R1 → S2 via waning; reduced susceptibility).
13+
- S3: susceptible after second infection (R2 → S3).
14+
- S4: susceptible after ≥3 infections (R3 → S4 and R4 → S4; lowest susceptibility).
15+
16+
Correspondingly, E_k/I_k/R_k represent exposed/infectious/recovered states of the k-th infection episode. All infectious classes (I1..I4) contribute to transmission equally in the basic formulation of the paper.
17+
18+
The model includes seasonality by adjusting the transmission rate with a cosine function.
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Henrik Zunker
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
#ifndef ODE_MSEIRS4_INFECTION_STATE_H
21+
#define ODE_MSEIRS4_INFECTION_STATE_H
22+
23+
namespace mio
24+
{
25+
namespace omseirs4
26+
{
27+
enum class InfectionState
28+
{
29+
MaternalImmune = 0,
30+
S1,
31+
S2,
32+
S3,
33+
S4,
34+
E1,
35+
E2,
36+
E3,
37+
E4,
38+
I1,
39+
I2,
40+
I3,
41+
I4,
42+
R1,
43+
R2,
44+
R3,
45+
R4,
46+
Count
47+
};
48+
49+
} // namespace omseirs4
50+
} // namespace mio
51+
52+
#endif // ODE_MSEIRS4_INFECTION_STATE_H

cpp/models/ode_mseirs4/model.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Henrik Zunker
5+
*
6+
* Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this file except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
#include "ode_mseirs4/model.h"
21+
22+
namespace mio
23+
{
24+
namespace omseirs4
25+
{
26+
27+
} // namespace omseirs4
28+
} // namespace mio

0 commit comments

Comments
 (0)