Skip to content

Commit 610a823

Browse files
HenrZukilianvolmer
andauthored
1420 Implement SEIRDB model (#1424)
Extended the SEIR model to a SEIRDB (with D: dead but not yet buried and B: buried) structure. Additional parameters are: a probability to recover (instead of getting "Removed" from SEIR) with counterprobability to die a time between dying and getting buried a probability to infect from dead but not yet buried state D Co-authored-by: Kilian Volmer <13285635+kilianvolmer@users.noreply.github.com>
1 parent 6ace2e5 commit 610a823

File tree

20 files changed

+1458
-14
lines changed

20 files changed

+1458
-14
lines changed

cpp/CMakeLists.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ if(MEMILIO_BUILD_MODELS)
183183
add_subdirectory(models/glct_secir)
184184
add_subdirectory(models/ide_secir)
185185
add_subdirectory(models/ide_seir)
186-
add_subdirectory(models/ode_seir)
186+
add_subdirectory(models/ode_seir)
187+
add_subdirectory(models/ode_seirdb)
187188
add_subdirectory(models/ode_seair)
188189
add_subdirectory(models/ode_sir)
189190
add_subdirectory(models/sde_sir)

cpp/examples/CMakeLists.txt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,17 @@ add_executable(adapt_rk_example adapt_rk_test.cpp)
1818
target_link_libraries(adapt_rk_example PRIVATE memilio)
1919
target_compile_options(adapt_rk_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
2020

21-
add_executable(ode_seir_example ode_seir.cpp)
22-
target_link_libraries(ode_seir_example PRIVATE memilio ode_seir)
23-
target_compile_options(ode_seir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
24-
25-
add_executable(ode_seir_ageres_example ode_seir_ageres.cpp)
26-
target_link_libraries(ode_seir_ageres_example PRIVATE memilio ode_seir)
27-
target_compile_options(ode_seir_ageres_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
21+
add_executable(ode_seir_example ode_seir.cpp)
22+
target_link_libraries(ode_seir_example PRIVATE memilio ode_seir)
23+
target_compile_options(ode_seir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
24+
25+
add_executable(ode_seirdb_example ode_seirdb.cpp)
26+
target_link_libraries(ode_seirdb_example PRIVATE memilio ode_seirdb)
27+
target_compile_options(ode_seirdb_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
28+
29+
add_executable(ode_seir_ageres_example ode_seir_ageres.cpp)
30+
target_link_libraries(ode_seir_ageres_example PRIVATE memilio ode_seir)
31+
target_compile_options(ode_seir_ageres_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
2832

2933
add_executable(ode_sir_example ode_sir.cpp)
3034
target_link_libraries(ode_sir_example PRIVATE memilio ode_sir)

cpp/examples/ode_seirdb.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
21+
#include "ode_seirdb/model.h"
22+
#include "ode_seirdb/infection_state.h"
23+
#include "ode_seirdb/parameters.h"
24+
#include "memilio/compartments/simulation.h"
25+
#include "memilio/utils/logging.h"
26+
#include "memilio/utils/time_series.h"
27+
28+
int main()
29+
{
30+
mio::set_log_level(mio::LogLevel::debug);
31+
32+
ScalarType t0 = 0;
33+
ScalarType tmax = 30.;
34+
ScalarType dt = 1.0;
35+
36+
mio::log_info("Simulating ODE SEIRDB; t={} ... {} with dt = {}.", t0, tmax, dt);
37+
38+
mio::oseirdb::Model<ScalarType> model(1);
39+
40+
ScalarType total_population = 10000;
41+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Exposed}] = 100;
42+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Infected}] = 100;
43+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Recovered}] = 100;
44+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Dead}] = 100;
45+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Buried}] = 100;
46+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Susceptible}] =
47+
total_population - model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Exposed}] -
48+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Infected}] -
49+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Recovered}] -
50+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Dead}] -
51+
model.populations[{mio::AgeGroup(0), mio::oseirdb::InfectionState::Buried}];
52+
53+
model.parameters.set<mio::oseirdb::TimeExposed<ScalarType>>(5.2);
54+
model.parameters.set<mio::oseirdb::TimeInfected<ScalarType>>(6.0);
55+
model.parameters.set<mio::oseirdb::TimeToBurial<ScalarType>>(5.5);
56+
model.parameters.set<mio::oseirdb::TransmissionProbabilityOnContact<ScalarType>>(0.1);
57+
model.parameters.set<mio::oseirdb::TransmissionProbabilityFromDead<ScalarType>>(0.01);
58+
model.parameters.set<mio::oseirdb::ProbabilityToRecover<ScalarType>>(0.75);
59+
60+
mio::ContactMatrixGroup<ScalarType>& contact_matrix =
61+
model.parameters.get<mio::oseirdb::ContactPatterns<ScalarType>>();
62+
contact_matrix[0].get_baseline().setConstant(2.7);
63+
contact_matrix[0].add_damping(0.7, mio::SimulationTime<ScalarType>(10.));
64+
65+
model.check_constraints();
66+
67+
auto result = mio::simulate<ScalarType>(t0, tmax, dt, model);
68+
69+
result.print_table({"S", "E", "I", "R", "D", "B"});
70+
std::cout << "\nnumber total: " << result.get_last_value().sum() << "\n";
71+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_library(ode_seirdb
2+
infection_state.h
3+
model.h
4+
model.cpp
5+
parameters.h
6+
)
7+
target_link_libraries(ode_seirdb PUBLIC memilio)
8+
target_include_directories(ode_seirdb PUBLIC
9+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
10+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
11+
)
12+
target_compile_options(ode_seirdb PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})

cpp/models/ode_seirdb/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# ODE SEIRDB compartment model
2+
3+
This directory contains a SEIRDB (Susceptible-Exposed-Infected-Recovered-Dead-Buried) model implementation based on an ODE formulation.
4+
It extends the SEIR model by introducing explicit Dead and Buried compartments with configurable recovery probability, burial delay, and infectiousness of the dead.
5+
6+
To get started, check out the [official documentation](https://memilio.readthedocs.io/en/latest/cpp/models/oseirdb.html)
7+
or the [code example](../../examples/ode_seirdb.cpp).
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Daniel Abele, Jan Kleinert, Martin J. Kuehn
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 SEIRDB_INFECTIONSTATE_H
21+
#define SEIRDB_INFECTIONSTATE_H
22+
23+
namespace mio
24+
{
25+
namespace oseirdb
26+
{
27+
28+
/**
29+
* @brief The InfectionState enum describes the possible
30+
* categories for the infectious state of persons
31+
*/
32+
enum class InfectionState
33+
{
34+
Susceptible,
35+
Exposed,
36+
Infected,
37+
Recovered,
38+
Dead,
39+
Buried,
40+
Count
41+
};
42+
43+
} // namespace oseirdb
44+
} // namespace mio
45+
46+
#endif // SEIRDB_INFECTIONSTATE_H

cpp/models/ode_seirdb/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: Daniel Abele, Jan Kleinert, Martin J. Kuehn
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_seirdb/model.h"
21+
22+
namespace mio
23+
{
24+
namespace oseirdb
25+
{
26+
27+
} // namespace oseirdb
28+
} // namespace mio

cpp/models/ode_seirdb/model.h

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
/*
2+
* Copyright (C) 2020-2025 MEmilio
3+
*
4+
* Authors: Daniel Abele, Jan Kleinert, Martin J. Kuehn
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 SEIRDB_MODEL_H
21+
#define SEIRDB_MODEL_H
22+
23+
#include "memilio/compartments/flow_model.h"
24+
#include "memilio/config.h"
25+
#include "memilio/epidemiology/age_group.h"
26+
#include "memilio/epidemiology/populations.h"
27+
#include "memilio/math/interpolation.h"
28+
#include "memilio/utils/time_series.h"
29+
#include "ode_seirdb/infection_state.h"
30+
#include "ode_seirdb/parameters.h"
31+
32+
GCC_CLANG_DIAGNOSTIC(push)
33+
GCC_CLANG_DIAGNOSTIC(ignored "-Wshadow")
34+
#include <Eigen/Dense>
35+
GCC_CLANG_DIAGNOSTIC(pop)
36+
37+
namespace mio
38+
{
39+
namespace oseirdb
40+
{
41+
42+
/********************
43+
* define the model *
44+
********************/
45+
46+
// clang-format off
47+
using Flows = TypeList<Flow<InfectionState::Susceptible, InfectionState::Exposed>,
48+
Flow<InfectionState::Exposed, InfectionState::Infected>,
49+
Flow<InfectionState::Infected, InfectionState::Recovered>,
50+
Flow<InfectionState::Infected, InfectionState::Dead>,
51+
Flow<InfectionState::Dead, InfectionState::Buried>>;
52+
// clang-format on
53+
template <typename FP>
54+
class Model
55+
: public FlowModel<FP, InfectionState, mio::Populations<FP, AgeGroup, InfectionState>, Parameters<FP>, Flows>
56+
{
57+
using Base = FlowModel<FP, InfectionState, mio::Populations<FP, AgeGroup, InfectionState>, Parameters<FP>, Flows>;
58+
59+
public:
60+
using typename Base::ParameterSet;
61+
using typename Base::Populations;
62+
63+
Model(const Populations& pop, const ParameterSet& params)
64+
: Base(pop, params)
65+
{
66+
}
67+
68+
Model(int num_agegroups)
69+
: Base(Populations({AgeGroup(num_agegroups), InfectionState::Count}), ParameterSet(AgeGroup(num_agegroups)))
70+
{
71+
}
72+
73+
void get_flows(Eigen::Ref<const Eigen::VectorX<FP>> pop, Eigen::Ref<const Eigen::VectorX<FP>> y, FP t,
74+
Eigen::Ref<Eigen::VectorX<FP>> flows) const override
75+
{
76+
const Index<AgeGroup> age_groups = reduce_index<Index<AgeGroup>>(this->populations.size());
77+
const auto& params = this->parameters;
78+
79+
for (auto i : make_index_range(age_groups)) {
80+
const size_t Si = this->populations.get_flat_index({i, InfectionState::Susceptible});
81+
const size_t Ei = this->populations.get_flat_index({i, InfectionState::Exposed});
82+
const size_t Ii = this->populations.get_flat_index({i, InfectionState::Infected});
83+
const size_t Di = this->populations.get_flat_index({i, InfectionState::Dead});
84+
85+
for (auto j : make_index_range(age_groups)) {
86+
const size_t Sj = this->populations.get_flat_index({j, InfectionState::Susceptible});
87+
const size_t Ej = this->populations.get_flat_index({j, InfectionState::Exposed});
88+
const size_t Ij = this->populations.get_flat_index({j, InfectionState::Infected});
89+
const size_t Rj = this->populations.get_flat_index({j, InfectionState::Recovered});
90+
const size_t Dj = this->populations.get_flat_index({j, InfectionState::Dead});
91+
const size_t Bj = this->populations.get_flat_index({j, InfectionState::Buried});
92+
93+
const FP Nj = pop[Sj] + pop[Ej] + pop[Ij] + pop[Rj] + pop[Dj] + pop[Bj];
94+
const FP divNj = (Nj < Limits<FP>::zero_tolerance()) ? FP(0.0) : FP(1.0 / Nj);
95+
const FP contact_rate = params.template get<ContactPatterns<FP>>().get_cont_freq_mat().get_matrix_at(
96+
SimulationTime<FP>(t))(i.get(), j.get()) *
97+
divNj;
98+
const FP coeffStoE = contact_rate * params.template get<TransmissionProbabilityOnContact<FP>>()[i];
99+
const FP coeffStoEDead = contact_rate * params.template get<TransmissionProbabilityFromDead<FP>>()[i];
100+
101+
flows[Base::template get_flat_flow_index<InfectionState::Susceptible, InfectionState::Exposed>(i)] +=
102+
y[Si] * (coeffStoE * pop[Ij] + coeffStoEDead * pop[Dj]);
103+
}
104+
flows[Base::template get_flat_flow_index<InfectionState::Exposed, InfectionState::Infected>(i)] =
105+
(1.0 / params.template get<TimeExposed<FP>>()[i]) * y[Ei];
106+
107+
const FP inv_time_infected = 1.0 / params.template get<TimeInfected<FP>>()[i];
108+
const FP prob_recover = params.template get<ProbabilityToRecover<FP>>()[i];
109+
const FP prob_die = 1.0 - prob_recover;
110+
flows[Base::template get_flat_flow_index<InfectionState::Infected, InfectionState::Recovered>(i)] =
111+
inv_time_infected * prob_recover * y[Ii];
112+
flows[Base::template get_flat_flow_index<InfectionState::Infected, InfectionState::Dead>(i)] =
113+
inv_time_infected * prob_die * y[Ii];
114+
flows[Base::template get_flat_flow_index<InfectionState::Dead, InfectionState::Buried>(i)] =
115+
(1.0 / params.template get<TimeToBurial<FP>>()[i]) * y[Di];
116+
}
117+
}
118+
119+
/**
120+
* serialize this.
121+
* @see mio::serialize
122+
*/
123+
template <class IOContext>
124+
void serialize(IOContext& io) const
125+
{
126+
auto obj = io.create_object("Model");
127+
obj.add_element("Parameters", this->parameters);
128+
obj.add_element("Populations", this->populations);
129+
}
130+
131+
/**
132+
* deserialize an object of this class.
133+
* @see mio::deserialize
134+
*/
135+
template <class IOContext>
136+
static IOResult<Model> deserialize(IOContext& io)
137+
{
138+
auto obj = io.expect_object("Model");
139+
auto par = obj.expect_element("Parameters", Tag<ParameterSet>{});
140+
auto pop = obj.expect_element("Populations", Tag<Populations>{});
141+
return apply(
142+
io,
143+
[](auto&& par_, auto&& pop_) {
144+
return Model{pop_, par_};
145+
},
146+
par, pop);
147+
}
148+
};
149+
150+
} // namespace oseirdb
151+
} // namespace mio
152+
153+
#endif // SEIRDB_MODEL_H

0 commit comments

Comments
 (0)