Skip to content

Commit 3586d84

Browse files
an-jungannawendlerjubicker
authored
1294 add LCT SECIR model with two disease strains (#1340)
- added LCT SECIR model for 2 diseases on the basis of the LCT SECIR model - added corresponding tests Co-authored-by: annawendler <[email protected]> Co-authored-by: jubicker <[email protected]>
1 parent 564fbf1 commit 3586d84

15 files changed

Lines changed: 3181 additions & 29 deletions

File tree

cpp/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ if(MEMILIO_BUILD_MODELS)
180180
add_subdirectory(models/ode_secirts)
181181
add_subdirectory(models/ode_secirvvs)
182182
add_subdirectory(models/lct_secir)
183+
add_subdirectory(models/lct_secir_2_diseases)
183184
add_subdirectory(models/glct_secir)
184185
add_subdirectory(models/ide_secir)
185186
add_subdirectory(models/ide_seir)

cpp/examples/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,10 @@ add_executable(lct_secir_example lct_secir.cpp)
142142
target_link_libraries(lct_secir_example PRIVATE memilio lct_secir)
143143
target_compile_options(lct_secir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
144144

145+
add_executable(lct_secir_2_diseases_example lct_secir_2_diseases.cpp)
146+
target_link_libraries(lct_secir_2_diseases_example PRIVATE memilio lct_secir_2_diseases)
147+
target_compile_options(lct_secir_2_diseases_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
148+
145149
add_executable(glct_secir_example glct_secir.cpp)
146150
target_link_libraries(glct_secir_example PRIVATE memilio glct_secir)
147151
target_compile_options(glct_secir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
/*
2+
* Copyright (C) 2020-2026 MEmilio
3+
*
4+
* Authors: Annika Jungklaus, Lena Ploetzke
5+
*
6+
* Contact: Martin J. Kuehn <[email protected]>
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 "lct_secir_2_diseases/model.h"
22+
#include "lct_secir_2_diseases/infection_state.h"
23+
#include "memilio/config.h"
24+
#include "memilio/utils/time_series.h"
25+
#include "memilio/epidemiology/lct_infection_state.h"
26+
#include "memilio/utils/logging.h"
27+
#include "memilio/compartments/simulation.h"
28+
#include "memilio/data/analyze_result.h"
29+
#include <vector>
30+
31+
int main()
32+
{
33+
// Simple example to demonstrate how to run a simulation using an LCT-SECIR-2-DISEASE model.
34+
// One single AgeGroup/Category member is used here.
35+
// Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario.
36+
// The number of subcompartments can be chosen for most of the compartments:
37+
constexpr size_t NumExposed_1a = 2, NumInfectedNoSymptoms_1a = 3, NumInfectedSymptoms_1a = 3,
38+
NumInfectedSevere_1a = 3, NumInfectedCritical_1a = 2, NumExposed_2a = 1,
39+
NumInfectedNoSymptoms_2a = 2, NumInfectedSymptoms_2a = 2, NumInfectedSevere_2a = 2,
40+
NumInfectedCritical_2a = 1, NumExposed_1b = 2, NumInfectedNoSymptoms_1b = 3,
41+
NumInfectedSymptoms_1b = 3, NumInfectedSevere_1b = 3, NumInfectedCritical_1b = 2,
42+
NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 2, NumInfectedSymptoms_2b = 2,
43+
NumInfectedSevere_2b = 2, NumInfectedCritical_2b = 1;
44+
// The compartment for Susceptible people and all compartments for Dead and Recovered people must have exactly one subcompartment:
45+
constexpr size_t NumSusceptible = 1, NumDead_a = 1, NumDead_b = 1, NumRecovered_1a = 1, NumRecovered_1b = 1,
46+
NumRecovered_2ab = 1;
47+
using InfState = mio::lsecir2d::InfectionState;
48+
using LctState =
49+
mio::LctInfectionState<ScalarType, InfState, NumSusceptible, NumExposed_1a, NumInfectedNoSymptoms_1a,
50+
NumInfectedSymptoms_1a, NumInfectedSevere_1a, NumInfectedCritical_1a, NumRecovered_1a,
51+
NumDead_a, NumExposed_2a, NumInfectedNoSymptoms_2a, NumInfectedSymptoms_2a,
52+
NumInfectedSevere_2a, NumInfectedCritical_2a, NumExposed_1b, NumInfectedNoSymptoms_1b,
53+
NumInfectedSymptoms_1b, NumInfectedSevere_1b, NumInfectedCritical_1b, NumRecovered_1b,
54+
NumDead_b, NumExposed_2b, NumInfectedNoSymptoms_2b, NumInfectedSymptoms_2b,
55+
NumInfectedSevere_2b, NumInfectedCritical_2b, NumRecovered_2ab>;
56+
using Model = mio::lsecir2d::Model<ScalarType, LctState>;
57+
Model model;
58+
59+
ScalarType tmax = 5;
60+
61+
// Set Parameters.
62+
model.parameters.get<mio::lsecir2d::TimeExposed_a<ScalarType>>()[0] = 3.;
63+
model.parameters.get<mio::lsecir2d::TimeExposed_b<ScalarType>>()[0] = 3.;
64+
model.parameters.get<mio::lsecir2d::TimeInfectedNoSymptoms_a<ScalarType>>()[0] = 3.;
65+
model.parameters.get<mio::lsecir2d::TimeInfectedNoSymptoms_b<ScalarType>>()[0] = 3.;
66+
model.parameters.get<mio::lsecir2d::TimeInfectedSymptoms_a<ScalarType>>()[0] = 3.;
67+
model.parameters.get<mio::lsecir2d::TimeInfectedSymptoms_b<ScalarType>>()[0] = 3.;
68+
model.parameters.get<mio::lsecir2d::TimeInfectedSevere_a<ScalarType>>()[0] = 3.;
69+
model.parameters.get<mio::lsecir2d::TimeInfectedSevere_b<ScalarType>>()[0] = 3.;
70+
model.parameters.get<mio::lsecir2d::TimeInfectedCritical_a<ScalarType>>()[0] = 3.;
71+
model.parameters.get<mio::lsecir2d::TimeInfectedCritical_b<ScalarType>>()[0] = 3.;
72+
73+
model.parameters.get<mio::lsecir2d::TransmissionProbabilityOnContact_a<ScalarType>>()[0] = 0.1;
74+
model.parameters.get<mio::lsecir2d::TransmissionProbabilityOnContact_b<ScalarType>>()[0] = 0.1;
75+
76+
mio::ContactMatrixGroup<ScalarType>& contact_matrix =
77+
model.parameters.get<mio::lsecir2d::ContactPatterns<ScalarType>>();
78+
contact_matrix[0] = mio::ContactMatrix<ScalarType>(Eigen::MatrixX<ScalarType>::Constant(1, 1, 10));
79+
// From SimulationTime 5, the contact pattern is reduced to 30% of the initial value.
80+
contact_matrix[0].add_damping(0.7, mio::SimulationTime<ScalarType>(5.));
81+
82+
model.parameters.get<mio::lsecir2d::RelativeTransmissionNoSymptoms_a<ScalarType>>()[0] = 0.7;
83+
model.parameters.get<mio::lsecir2d::RelativeTransmissionNoSymptoms_b<ScalarType>>()[0] = 0.7;
84+
model.parameters.get<mio::lsecir2d::RiskOfInfectionFromSymptomatic_a<ScalarType>>()[0] = 0.25;
85+
model.parameters.get<mio::lsecir2d::RiskOfInfectionFromSymptomatic_b<ScalarType>>()[0] = 0.25;
86+
model.parameters.get<mio::lsecir2d::RecoveredPerInfectedNoSymptoms_a<ScalarType>>()[0] = 0.09;
87+
model.parameters.get<mio::lsecir2d::RecoveredPerInfectedNoSymptoms_b<ScalarType>>()[0] = 0.09;
88+
model.parameters.get<mio::lsecir2d::SeverePerInfectedSymptoms_a<ScalarType>>()[0] = 0.2;
89+
model.parameters.get<mio::lsecir2d::SeverePerInfectedSymptoms_b<ScalarType>>()[0] = 0.2;
90+
model.parameters.get<mio::lsecir2d::CriticalPerSevere_a<ScalarType>>()[0] = 0.25;
91+
model.parameters.get<mio::lsecir2d::CriticalPerSevere_b<ScalarType>>()[0] = 0.25;
92+
model.parameters.get<mio::lsecir2d::DeathsPerCritical_a<ScalarType>>()[0] = 0.3;
93+
model.parameters.get<mio::lsecir2d::DeathsPerCritical_b<ScalarType>>()[0] = 0.3;
94+
95+
// Simple example how to initialize model without flows.
96+
// Define the initial values with the distribution of the population into subcompartments.
97+
// This method of defining the initial values using a vector of vectors is not necessary, but should remind you
98+
// how the entries of the initial value vector relate to the defined template parameters of the model or the number
99+
// of subcompartments. It is also possible to define the initial values directly.
100+
std::vector<std::vector<ScalarType>> initial_populations = {
101+
{200}, {0, 0}, {30, 10, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0}, {0}, {0}, {0},
102+
{0, 0}, {10, 0}, {0, 0}, {0}, {10, 0}, {30, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0},
103+
{0}, {0}, {100}, {0, 0}, {0, 0}, {0, 0}, {0}, {0}};
104+
105+
// Assert that initial_populations has the right shape.
106+
if (initial_populations.size() != (size_t)InfState::Count) {
107+
mio::log_error("The number of vectors in initial_populations does not match the number of InfectionStates.");
108+
return 1;
109+
}
110+
if ((initial_populations[(size_t)InfState::Susceptible].size() !=
111+
LctState::get_num_subcompartments<InfState::Susceptible>()) ||
112+
(initial_populations[(size_t)InfState::Exposed_1a].size() != NumExposed_1a) ||
113+
(initial_populations[(size_t)InfState::InfectedNoSymptoms_1a].size() != NumInfectedNoSymptoms_1a) ||
114+
(initial_populations[(size_t)InfState::InfectedSymptoms_1a].size() != NumInfectedSymptoms_1a) ||
115+
(initial_populations[(size_t)InfState::InfectedSevere_1a].size() != NumInfectedSevere_1a) ||
116+
(initial_populations[(size_t)InfState::InfectedCritical_1a].size() != NumInfectedCritical_1a) ||
117+
(initial_populations[(size_t)InfState::Exposed_2a].size() != NumExposed_2a) ||
118+
(initial_populations[(size_t)InfState::InfectedNoSymptoms_2a].size() != NumInfectedNoSymptoms_2a) ||
119+
(initial_populations[(size_t)InfState::InfectedSymptoms_2a].size() != NumInfectedSymptoms_2a) ||
120+
(initial_populations[(size_t)InfState::InfectedSevere_2a].size() != NumInfectedSevere_2a) ||
121+
(initial_populations[(size_t)InfState::InfectedCritical_2a].size() != NumInfectedCritical_2a) ||
122+
(initial_populations[(size_t)InfState::Recovered_1a].size() !=
123+
LctState::get_num_subcompartments<InfState::Recovered_1a>()) ||
124+
(initial_populations[(size_t)InfState::Dead_a].size() !=
125+
LctState::get_num_subcompartments<InfState::Dead_a>()) ||
126+
(initial_populations[(size_t)InfState::Exposed_1b].size() != NumExposed_1b) ||
127+
(initial_populations[(size_t)InfState::InfectedNoSymptoms_1b].size() != NumInfectedNoSymptoms_1b) ||
128+
(initial_populations[(size_t)InfState::InfectedSymptoms_1b].size() != NumInfectedSymptoms_1b) ||
129+
(initial_populations[(size_t)InfState::InfectedSevere_1b].size() != NumInfectedSevere_1b) ||
130+
(initial_populations[(size_t)InfState::InfectedCritical_1b].size() != NumInfectedCritical_1b) ||
131+
(initial_populations[(size_t)InfState::Recovered_1b].size() !=
132+
LctState::get_num_subcompartments<InfState::Recovered_1b>()) ||
133+
(initial_populations[(size_t)InfState::Dead_b].size() !=
134+
LctState::get_num_subcompartments<InfState::Dead_b>()) ||
135+
(initial_populations[(size_t)InfState::Exposed_2b].size() != NumExposed_2b) ||
136+
(initial_populations[(size_t)InfState::InfectedNoSymptoms_2b].size() != NumInfectedNoSymptoms_2b) ||
137+
(initial_populations[(size_t)InfState::InfectedSymptoms_2b].size() != NumInfectedSymptoms_2b) ||
138+
(initial_populations[(size_t)InfState::InfectedSevere_2b].size() != NumInfectedSevere_2b) ||
139+
(initial_populations[(size_t)InfState::InfectedCritical_2b].size() != NumInfectedCritical_2b) ||
140+
(initial_populations[(size_t)InfState::Recovered_2ab].size() !=
141+
LctState::get_num_subcompartments<InfState::Recovered_2ab>())) {
142+
mio::log_error("The length of at least one vector in initial_populations does not match the related number of "
143+
"subcompartments.");
144+
return 1;
145+
}
146+
// Transfer the initial values in initial_populations to the model.
147+
std::vector<ScalarType> flat_initial_populations;
148+
for (auto&& vec : initial_populations) {
149+
flat_initial_populations.insert(flat_initial_populations.end(), vec.begin(), vec.end());
150+
}
151+
for (size_t i = 0; i < LctState::Count; i++) {
152+
model.populations[i] = flat_initial_populations[i];
153+
}
154+
155+
// Perform a simulation.
156+
mio::TimeSeries<ScalarType> result = mio::simulate<ScalarType, Model>(0, tmax, 0.1, model);
157+
// The simulation result is divided by subcompartments.
158+
// We call the function calculate_compartments to get a result according to the InfectionStates.
159+
mio::TimeSeries<ScalarType> population_no_subcompartments = model.calculate_compartments(result);
160+
auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments);
161+
162+
interpolated_results.print_table({" S", " E1a", " C1a", " I1a", " H1a", " U1a", " Ra",
163+
" Da", " E2a", " C2a", " I2a", " H2a", " U2a", " E1b",
164+
" C1b", " I1b", " H1b", " U1b", " Rb", " Db", " E2b",
165+
" C2b", " I2b", " H2b", " U2b", " Rab"},
166+
6, 2);
167+
}

cpp/memilio/epidemiology/lct_infection_state.h

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -96,34 +96,17 @@ class LctInfectionState
9696

9797
Eigen::VectorX<FP> compartments((Eigen::Index)InfectionState::Count);
9898
// Use segment of the vector subcompartments of each InfectionState and sum up the values of subcompartments.
99-
compartments[(Eigen::Index)InfectionState::Susceptible] = subcompartments[0];
100-
compartments[(Eigen::Index)InfectionState::Exposed] =
101-
subcompartments
102-
.segment(get_first_index<InfectionState::Exposed>(), get_num_subcompartments<InfectionState::Exposed>())
103-
.sum();
104-
compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms] =
105-
subcompartments
106-
.segment(get_first_index<InfectionState::InfectedNoSymptoms>(),
107-
get_num_subcompartments<InfectionState::InfectedNoSymptoms>())
108-
.sum();
109-
compartments[(Eigen::Index)InfectionState::InfectedSymptoms] =
110-
subcompartments
111-
.segment(get_first_index<InfectionState::InfectedSymptoms>(),
112-
get_num_subcompartments<InfectionState::InfectedSymptoms>())
113-
.sum();
114-
compartments[(Eigen::Index)InfectionState::InfectedSevere] =
115-
subcompartments
116-
.segment(get_first_index<InfectionState::InfectedSevere>(),
117-
get_num_subcompartments<InfectionState::InfectedSevere>())
118-
.sum();
119-
compartments[(Eigen::Index)InfectionState::InfectedCritical] =
120-
subcompartments
121-
.segment(get_first_index<InfectionState::InfectedCritical>(),
122-
get_num_subcompartments<InfectionState::InfectedCritical>())
123-
.sum();
124-
compartments[(Eigen::Index)InfectionState::Recovered] =
125-
subcompartments[get_first_index<InfectionState::Recovered>()];
126-
compartments[(Eigen::Index)InfectionState::Dead] = subcompartments[get_first_index<InfectionState::Dead>()];
99+
for (int i = 0; i < (Eigen::Index)InfectionState::Count; i++) {
100+
InfectionState State = static_cast<InfectionState>(i);
101+
// first index of first subcompartment:
102+
size_t index = 0;
103+
for (size_t j = 0; j < (size_t)(State); j++) {
104+
index = index + m_subcompartment_numbers[j];
105+
}
106+
// number of subcompartments:
107+
size_t num_subcomp = m_subcompartment_numbers[(size_t)State];
108+
compartments[i] = subcompartments.segment(index, num_subcomp).sum();
109+
}
127110

128111
return compartments;
129112
}

cpp/models/lct_secir/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# LCT SECIR model
22

3-
This directory contains a model implementation based on an ODE formulation using the linear chain trick.
3+
This directory contains a model implementation based on an ODE formulation using the Linear Chain Trick.
44
To get started, check out the [official documentation](https://memilio.readthedocs.io/en/latest/cpp/models/lsecir.html)
55
or the [code example](../../examples/lct_secir.cpp).
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
add_library(lct_secir_2_diseases
2+
infection_state.h
3+
model.h
4+
model.cpp
5+
parameters.h
6+
)
7+
target_link_libraries(lct_secir_2_diseases PUBLIC memilio)
8+
target_include_directories(lct_secir_2_diseases PUBLIC
9+
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>
10+
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
11+
)
12+
target_compile_options(lct_secir_2_diseases PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS})
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# LCT SECIR TWO DISEASES model
2+
3+
This directory contains a model implementation for two different diseases or two variants of a disease based on an ODE formulation using the Linear Chain Trick.
4+
To get started, check out the [official documentation](https://memilio.readthedocs.io/en/latest/cpp/models/lsecir2d.html)
5+
or the [LCT2D minimal example](../../examples/lct_secir_2_diseases.cpp).
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright (C) 2020-2026 MEmilio
3+
*
4+
* Authors: Annika Jungklaus, Lena Ploetzke
5+
*
6+
* Contact: Martin J. Kuehn <[email protected]>
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+
#ifndef LCT_SECIR_2_DISEASES_INFECTIONSTATE_H
22+
#define LCT_SECIR_2_DISEASES_INFECTIONSTATE_H
23+
24+
namespace mio
25+
{
26+
namespace lsecir2d
27+
{
28+
29+
/**
30+
* @brief The InfectionState enum describes the basic
31+
* categories for the infection state of persons.
32+
*/
33+
enum class InfectionState
34+
{
35+
Susceptible = 0,
36+
// Notation: State_[Infection number][disease]
37+
// first infection with disease a
38+
Exposed_1a = 1,
39+
InfectedNoSymptoms_1a = 2,
40+
InfectedSymptoms_1a = 3,
41+
InfectedSevere_1a = 4,
42+
InfectedCritical_1a = 5,
43+
Recovered_1a = 6,
44+
Dead_a = 7,
45+
// second infection with disease a
46+
Exposed_2a = 8,
47+
InfectedNoSymptoms_2a = 9,
48+
InfectedSymptoms_2a = 10,
49+
InfectedSevere_2a = 11,
50+
InfectedCritical_2a = 12,
51+
// first infection with disease b
52+
Exposed_1b = 13,
53+
InfectedNoSymptoms_1b = 14,
54+
InfectedSymptoms_1b = 15,
55+
InfectedSevere_1b = 16,
56+
InfectedCritical_1b = 17,
57+
Recovered_1b = 18,
58+
Dead_b = 19,
59+
// second infection with disease b
60+
Exposed_2b = 20,
61+
InfectedNoSymptoms_2b = 21,
62+
InfectedSymptoms_2b = 22,
63+
InfectedSevere_2b = 23,
64+
InfectedCritical_2b = 24,
65+
// Recovered from both diseases
66+
Recovered_2ab = 25,
67+
Count = 26
68+
};
69+
70+
} // namespace lsecir2d
71+
} // namespace mio
72+
73+
#endif // LCT_SECIR_2_DISEASES_INFECTIONSTATE_H
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright (C) 2020-2026 MEmilio
3+
*
4+
* Authors: Annika Jungklaus, Lena Ploetzke
5+
*
6+
* Contact: Martin J. Kuehn <[email protected]>
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 "lct_secir_2_diseases/model.h"
22+
23+
namespace mio
24+
{
25+
namespace lsecir2d
26+
{
27+
28+
} // namespace lsecir2d
29+
} // namespace mio

0 commit comments

Comments
 (0)