From 3ef230ffe51da6cbb968c20cc6e5e2e7012adb4c Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 5 Jun 2025 13:07:52 +0200 Subject: [PATCH 01/16] change year in copyright --- cpp/examples/d_abm.cpp | 2 +- cpp/examples/graph_abm.cpp | 2 +- cpp/examples/smm.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cpp/examples/d_abm.cpp b/cpp/examples/d_abm.cpp index bf77e1755f..3f9f39c025 100644 --- a/cpp/examples/d_abm.cpp +++ b/cpp/examples/d_abm.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2020-2024 German Aerospace Center (DLR-SC) +* Copyright (C) 2020-2025 German Aerospace Center (DLR-SC) * * Authors: Julia Bicker, René Schmieding * diff --git a/cpp/examples/graph_abm.cpp b/cpp/examples/graph_abm.cpp index 34e89485c9..03b8ecdb57 100644 --- a/cpp/examples/graph_abm.cpp +++ b/cpp/examples/graph_abm.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2020-2024 MEmilio +* Copyright (C) 2020-2025 MEmilio * * Authors: Julia Bicker * diff --git a/cpp/examples/smm.cpp b/cpp/examples/smm.cpp index cb63adf39b..e8fe3f1e03 100644 --- a/cpp/examples/smm.cpp +++ b/cpp/examples/smm.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2020-2024 German Aerospace Center (DLR-SC) +* Copyright (C) 2020-2025 German Aerospace Center (DLR-SC) * * Authors: Julia Bicker, René Schmieding * From 1caf0d0b7a77125f72592c1f751c10a8514b8ba7 Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 17 Jul 2025 16:41:46 +0200 Subject: [PATCH 02/16] lct for 2 diseases on basis of lct_secir model, started changing Infection States, Parameters etc. to reflect 2 diseases --- .gitignore | 2 + cpp/CMakeLists.txt | 1 + cpp/examples/CMakeLists.txt | 4 + cpp/examples/lct_secir_2_disease.cpp | 116 +++ cpp/models/lct_secir_2_disease/CMakeLists.txt | 12 + cpp/models/lct_secir_2_disease/README.md | 66 ++ .../lct_secir_2_disease/infection_state.h | 128 ++++ cpp/models/lct_secir_2_disease/model.cpp | 29 + cpp/models/lct_secir_2_disease/model.h | 383 ++++++++++ cpp/models/lct_secir_2_disease/parameters.h | 658 ++++++++++++++++++ 10 files changed, 1399 insertions(+) create mode 100644 cpp/examples/lct_secir_2_disease.cpp create mode 100644 cpp/models/lct_secir_2_disease/CMakeLists.txt create mode 100644 cpp/models/lct_secir_2_disease/README.md create mode 100644 cpp/models/lct_secir_2_disease/infection_state.h create mode 100644 cpp/models/lct_secir_2_disease/model.cpp create mode 100644 cpp/models/lct_secir_2_disease/model.h create mode 100644 cpp/models/lct_secir_2_disease/parameters.h diff --git a/.gitignore b/.gitignore index 8c57a85643..109f22a7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -283,4 +283,6 @@ docs/xml docs/source/api docs/source/generated +settings.json + # End of https://www.gitignore.io/api/c++,node,python diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 8f74a60499..fa4cae5ad4 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -174,6 +174,7 @@ if(MEMILIO_BUILD_MODELS) add_subdirectory(models/ode_secirts) add_subdirectory(models/ode_secirvvs) add_subdirectory(models/lct_secir) + add_subdirectory(models/lct_secir_2_disease) add_subdirectory(models/glct_secir) add_subdirectory(models/ide_secir) add_subdirectory(models/ide_seir) diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index df52b850fd..6f8bde3add 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -128,6 +128,10 @@ add_executable(lct_secir_example lct_secir.cpp) target_link_libraries(lct_secir_example PRIVATE memilio lct_secir) target_compile_options(lct_secir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(lct_secir_2_disease_example lct_secir_2_disease.cpp) +target_link_libraries(lct_secir_2_disease_example PRIVATE memilio lct_secir_2_disease) +target_compile_options(lct_secir_2_disease_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) + add_executable(glct_secir_example glct_secir.cpp) target_link_libraries(glct_secir_example PRIVATE memilio glct_secir) target_compile_options(glct_secir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/examples/lct_secir_2_disease.cpp b/cpp/examples/lct_secir_2_disease.cpp new file mode 100644 index 0000000000..7b0949e05d --- /dev/null +++ b/cpp/examples/lct_secir_2_disease.cpp @@ -0,0 +1,116 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "lct_secir_2_disease/model.h" +#include "lct_secir_2_disease/infection_state.h" +#include "memilio/config.h" +#include "memilio/utils/time_series.h" +#include "memilio/epidemiology/uncertain_matrix.h" +#include "memilio/epidemiology/lct_infection_state.h" +#include "memilio/math/eigen.h" +#include "memilio/utils/logging.h" +#include "memilio/compartments/simulation.h" +#include "memilio/data/analyze_result.h" + +#include + +int main() +{ + // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. + // One single AgeGroup/Category member is used here. + // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. + constexpr size_t NumExposed = 2, NumInfectedNoSymptoms = 3, NumInfectedSymptoms = 1, NumInfectedSevere = 1, + NumInfectedCritical = 5; + using InfState = mio::lsecir2d::InfectionState; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir2d::Model; + Model model; + + // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually + // defined initial vector is used to initialize the LCT model. + + ScalarType tmax = 10; + + // Set Parameters. + model.parameters.get()[0] = 3.2; + model.parameters.get()[0] = 2.; + model.parameters.get()[0] = 5.8; + model.parameters.get()[0] = 9.5; + model.parameters.get()[0] = 7.1; + + model.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. + contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); + + model.parameters.get()[0] = 0.7; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.09; + model.parameters.get()[0] = 0.2; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.3; + + // Simple example how to initialize model without flows. + // Define the initial values with the distribution of the population into subcompartments. + // This method of defining the initial values using a vector of vectors is not necessary, but should remind you + // how the entries of the initial value vector relate to the defined template parameters of the model or the number + // of subcompartments. It is also possible to define the initial values directly. + std::vector> initial_populations = {{750}, {30, 20}, {20, 10, 10}, {50}, + {50}, {10, 10, 5, 3, 2}, {20}, {10}}; + + // Assert that initial_populations has the right shape. + if (initial_populations.size() != (size_t)InfState::Count) { + mio::log_error("The number of vectors in initial_populations does not match the number of InfectionStates."); + return 1; + } + if ((initial_populations[(size_t)InfState::Susceptible].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Exposed].size() != NumExposed) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms].size() != NumInfectedNoSymptoms) || + (initial_populations[(size_t)InfState::InfectedSymptoms].size() != NumInfectedSymptoms) || + (initial_populations[(size_t)InfState::InfectedSevere].size() != NumInfectedSevere) || + (initial_populations[(size_t)InfState::InfectedCritical].size() != NumInfectedCritical) || + (initial_populations[(size_t)InfState::Recovered].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Dead].size() != LctState::get_num_subcompartments())) { + mio::log_error("The length of at least one vector in initial_populations does not match the related number of " + "subcompartments."); + return 1; + } + // Transfer the initial values in initial_populations to the model. + std::vector flat_initial_populations; + for (auto&& vec : initial_populations) { + flat_initial_populations.insert(flat_initial_populations.end(), vec.begin(), vec.end()); + } + for (size_t i = 0; i < LctState::Count; i++) { + model.populations[i] = flat_initial_populations[i]; + } + + // Perform a simulation. + mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); + // The simulation result is divided by subcompartments. + // We call the function calculate_compartments to get a result according to the InfectionStates. + mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); + auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); + interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 4); +} diff --git a/cpp/models/lct_secir_2_disease/CMakeLists.txt b/cpp/models/lct_secir_2_disease/CMakeLists.txt new file mode 100644 index 0000000000..f4a7037f4f --- /dev/null +++ b/cpp/models/lct_secir_2_disease/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(lct_secir_2_disease + infection_state.h + model.h + model.cpp + parameters.h +) +target_link_libraries(lct_secir_2_disease PUBLIC memilio) +target_include_directories(lct_secir_2_disease PUBLIC + $ + $ +) +target_compile_options(lct_secir_2_disease PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/models/lct_secir_2_disease/README.md b/cpp/models/lct_secir_2_disease/README.md new file mode 100644 index 0000000000..ee5b3c024f --- /dev/null +++ b/cpp/models/lct_secir_2_disease/README.md @@ -0,0 +1,66 @@ +# LCT SECIR model + +This model is based on the Linear Chain Trick. + +The Linear Chain Trick provides the option to use Erlang-distributed stay times in the compartments through the use of subcompartments. +The normal ODE models have (possibly unrealistic) exponentially distributed stay times. +The LCT model can still be described by an ordinary differential equation system. + +For the concept see +- Lena Plötzke, "Der Linear Chain Trick in der epidemiologischen Modellierung als Kompromiss zwischen gewöhnlichen und Integro-Differentialgleichungen", 2023. (https://elib.dlr.de/200381/, German only) +- P. J. Hurtado und A. S. Kirosingh, "Generalizations of the ‘Linear Chain Trick’: incorporating more flexible dwell time distributions into mean field ODE models“, 2019. (https://doi.org/10.1007/s00285-019-01412-w) + +The eight compartments +- `Susceptible` ($S$), may become exposed at any time +- `Exposed` ($E$), becomes infected after some time +- `InfectedNoSymptoms` ($I_{NS}$), becomes InfectedSymptoms or Recovered after some time +- `InfectedSymptoms` ($I_{Sy}$), becomes InfectedSevere or Recovered after some time +- `InfectedSevere` ($I_{Sev}$), becomes InfectedCritical or Recovered after some time +- `InfectedCritical` ($I_{Cr}$), becomes Recovered or Dead after some time +- `Recovered` ($R$) +- `Dead` ($D$) + +are used to simulate the spread of the disease. +It is possible to include subcompartments for the five compartments Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. +You can divide the population according to different groups, e.g. AgeGroups or gender and choose parameters according to groups. + +Below is an overview of the model architecture and its compartments without a stratification according to groups. + +![tikzLCTSECIR](https://github.com/SciCompMod/memilio/assets/70579874/6a5d5a95-20f9-4176-8894-c091bd48bfb7) + +| Mathematical variable | C++ variable name | Description | +|---------------------------- | --------------- | -------------------------------------------------------------------------------------------------- | +| $\phi$ | `ContactPatterns` | Average number of contacts of a person per day. | +| $\rho$ | `TransmissionProbabilityOnContact` | Transmission risk for people located in the susceptible compartments. | +| $\xi_{I_{NS}}$ | `RelativeTransmissionNoSymptoms` | Proportion of nonsymptomatically infected people who are not isolated. | +| $\xi_{I_{Sy}}$ | `RiskOfInfectionFromSymptomatic` | Proportion of infected people with symptoms who are not isolated. | +| $N$ | `m_N0` | Total population. | +| $D$ | `D` | Number of death people. | +| $n_E$ | Defined in `LctStates` | Number of subcompartments of the Exposed compartment. | +| $n_{NS}$ | Defined in `LctStates` | Number of subcompartments of the InfectedNoSymptoms compartment. | +| $n_{Sy}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSymptoms compartment. | +| $n_{Sev}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSevere compartment.| +| $n_{Cr}$ | Defined in `LctStates` | Number of subcompartments of the InfectedCritical compartment. | +| $T_E$ | `TimeExposed` | Average time in days an individual stays in the Exposed compartment. | +| $T_{I_{NS}}$ | `TimeInfectedNoSymptoms` | Average time in days an individual stays in the InfectedNoSymptoms compartment. | +| $T_{I_{Sy}}$ | `TimeInfectedSymptoms` | Average time in days an individual stays in the InfectedSymptoms compartment. | +| $T_{I_{Sev}}$ | `TimeInfectedSevere` | Average time in days an individual stays in the InfectedSevere compartment. | +| $T_{I_{Cr}}$ | `TimeInfectedCritical` | Average time in days an individual stays in the InfectedCritical compartment. | +| $\mu_{I_{NS}}^{R}$ | `RecoveredPerInfectedNoSymptoms` | Probability of transition from compartment InfectedNoSymptoms to Recovered. | +| $\mu_{I_{Sy}}^{I_{Sev}}$ | `SeverePerInfectedSymptoms` | Probability of transition from compartment InfectedSymptoms to InfectedSevere. | +| $\mu_{I_{Sev}}^{I_{Cr}}$ | `CriticalPerSevere` | Probability of transition from compartment InfectedSevere to InfectedCritical. | +| $\mu_{I_{Cr}}^{D}$ | `DeathsPerCritical` | Probability of dying when in compartment InfectedCritical. | + +The notation of the compartments with indices here stands for subcompartments and not for age groups. Accordingly, $I_{NS,n_{NS}}$, for example, stands for the number of people in the $n_{NS}$-th subcompartment of the InfectedNoSymptoms compartment. + + +## Examples + +A simple example can be found at [LCT minimal example](../../examples/lct_secir.cpp). + +## Initialization + +- The file [parameters_io](parameters_io.h) provides functionality to compute an initial value vector for the LCT-SECIR model based on real data. + +- The file [initializer_flows](initializer_flows.h) provides functionality to compute an initial value vector for the LCT-SECIR model based on initial data in the form of a TimeSeries of InfectionTransitions. For the concept of the InfectionTransitions or flows, see also the IDE-SECIR model. This method can be particularly useful if a comparison is to be made with an IDE model with matching initialization or if the real data is in the form of flows. + diff --git a/cpp/models/lct_secir_2_disease/infection_state.h b/cpp/models/lct_secir_2_disease/infection_state.h new file mode 100644 index 0000000000..51e09a71ec --- /dev/null +++ b/cpp/models/lct_secir_2_disease/infection_state.h @@ -0,0 +1,128 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef LCT_SECIR_2_DISEASE_INFECTIONSTATE_H +#define LCT_SECIR_2_DISEASE_INFECTIONSTATE_H + +namespace mio +{ +namespace lsecir2d +{ + +/** + * @brief The InfectionState enum describes the basic + * categories for the infection state of persons. + */ +enum class InfectionState +{ + Susceptible = 0, + // State_[Infection number][disease] + // first infection with disease a + Exposed_1a = 1, + InfectedNoSymptoms_1a = 2, + InfectedSymptoms_1a = 3, + InfectedSevere_1a = 4, + InfectedCritical_1a = 5, + // R and D for disease a + Recovered_a = 6, + Dead_a = 7, + // second infection with disease b + Exposed_2b = 8, + InfectedNoSymptoms_2b = 9, + InfectedSymptoms_2b = 10, + InfectedSevere_2b = 11, + InfectedCritical_2b = 12, + // R and D for disease b + Recovered_b = 13, + Dead_b = 14, + // first infection with disease b + Exposed_1b = 15, + InfectedNoSymptoms_1b = 16, + InfectedSymptoms_1b = 17, + InfectedSevere_1b = 18, + InfectedCritical_1b = 19, + // second infection with disease a + Exposed_2a = 20, + InfectedNoSymptoms_2a = 21, + InfectedSymptoms_2a = 22, + InfectedSevere_2a = 23, + InfectedCritical_2a = 24, + // Recovered from both diseases + Recovered_ab = 25, + Count = 8 +}; + +/** + * @brief The InfectionTransition enum describes the possible + * transitions of the infectious state of persons. + */ +enum class InfectionTransition +{ + // first infection with a + SusceptibleToExposed_1a = 0, + Exposed_1aToInfectedNoSymptoms_1a = 1, + InfectedNoSymptoms_1aToInfectedSymptoms_1a = 2, + InfectedNoSymptoms_1aToRecovered_a = 3, + InfectedSymptoms_1aToInfectedSevere_1a = 4, + InfectedSymptoms_1aToRecovered_a = 5, + InfectedSevere_1aToInfectedCritical_1a = 6, + InfectedSevere_1aToRecovered_a = 7, + InfectedCritical_1aToDead_a = 8, + InfectedCritical_1aToRecovered_a = 9, + // second infection with b + Recovered_aToExposed_2b = 10, + Exposed_2bToInfectedNoSymptoms_2b = 11, + InfectedNoSymptoms_2bToInfectedSymptoms_2b = 12, + InfectedNoSymptoms_2bToRecovered_ab = 13, + InfectedSymptoms_2bToInfectedSevere_2b = 14, + InfectedSymptoms_2bToRecovered_ab = 15, + InfectedSevere_2bToInfectedCritical_2b = 16, + InfectedSevere_2bToRecovered_ab = 17, + InfectedCritical_2bToDead_b = 18, + InfectedCritical_2bToRecovered_ab = 19, + // first infection with b + SusceptibleToExposed_1b = 20, + Exposed_1bToInfectedNoSymptoms_1b = 21, + InfectedNoSymptoms_1bToInfectedSymptoms_1b = 22, + InfectedNoSymptoms_1bToRecovered_b = 23, + InfectedSymptoms_1bToInfectedSevere_1b = 24, + InfectedSymptoms_1bToRecovered_b = 25, + InfectedSevere_1bToInfectedCritical_1b = 26, + InfectedSevere_1bToRecovered_b = 27, + InfectedCritical_1bToDead_b = 28, + InfectedCritical_1bToRecovered_b = 29, + // second infection with a + Recovered_bToExposed_2a = 30, + Exposed_2aToInfectedNoSymptoms_2a = 31, + InfectedNoSymptoms_2aToInfectedSymptoms_2a = 32, + InfectedNoSymptoms_2aToRecovered_ab = 33, + InfectedSymptoms_2aToInfectedSevere_2a = 34, + InfectedSymptoms_2aToRecovered_ab = 35, + InfectedSevere_2aToInfectedCritical_2a = 36, + InfectedSevere_2aToRecovered_ab = 37, + InfectedCritical_2aToDead_a = 38, + InfectedCritical_2aToRecovered_ab = 39, + Count = 40 +}; + +} // namespace lsecir2d +} // namespace mio + +#endif // LCT_SECIR_2_DISEASE_INFECTIONSTATE_H \ No newline at end of file diff --git a/cpp/models/lct_secir_2_disease/model.cpp b/cpp/models/lct_secir_2_disease/model.cpp new file mode 100644 index 0000000000..f4c250e14f --- /dev/null +++ b/cpp/models/lct_secir_2_disease/model.cpp @@ -0,0 +1,29 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "lct_secir_2_disease/model.h" + +namespace mio +{ +namespace lsecir2d +{ + +} // namespace lsecir2d +} // namespace mio \ No newline at end of file diff --git a/cpp/models/lct_secir_2_disease/model.h b/cpp/models/lct_secir_2_disease/model.h new file mode 100644 index 0000000000..c0f5678a41 --- /dev/null +++ b/cpp/models/lct_secir_2_disease/model.h @@ -0,0 +1,383 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef LCT_SECIR_2_DISEASE_MODEL_H +#define LCT_SECIR_2_DISEASE_MODEL_H + +#include "lct_secir_2_disease/parameters.h" +#include "lct_secir_2_disease/infection_state.h" +#include "memilio/compartments/compartmentalmodel.h" +#include "memilio/epidemiology/lct_populations.h" +#include "memilio/config.h" +#include "memilio/utils/time_series.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/type_list.h" +#include "memilio/utils/metaprogramming.h" +/* +#include "memilio/epidemiology/lct_infection_state.h" +#include "memilio/math/eigen.h" +*/ +namespace mio +{ +namespace lsecir2d +{ +/** + * @brief Class that defines an LCT-SECIR model. + * + * @tparam LctStates The LCT model can work with any number of LctStates, where each LctState corresponds to a group, + * e.g. one AgeGroup. The purpose of the LctStates is to define the number of subcompartments for each InfectionState. + * If you do not want to divide the population into groups, just use one LctState. + * If you want to divide the population according to more than one category, e.g. sex and age, + * you have to specify one LctState for each pair of groups, e.g. for (female, A00-A04), (female, A05-A14) etc. + * This is because the number of subcompartments can be different for each group. + * Therefore, the number of LctStates also determines the number of groups. + */ +template +class Model + : public CompartmentalModel, Parameters> +{ +public: + using LctStatesGroups = TypeList; + using Base = CompartmentalModel, Parameters>; + using typename Base::ParameterSet; + using typename Base::Populations; + static size_t constexpr num_groups = sizeof...(LctStates); + static_assert(num_groups >= 1, "The number of LctStates provided should be at least one."); + + /// @brief Default constructor. + Model() + : Base(Populations(), ParameterSet(num_groups)) + { + } + + /** @brief Constructor using Populations and ParameterSet. + * @param[in] pop An instance of the Populations class. + * @param[in] params Parameters used to be used in the simulation. + */ + Model(const Populations& pop, const ParameterSet& params) + : Base(pop, params) + { + } + + /** + * @brief Evaluates the right-hand-side f of the ODE dydt = f(y, t). + * + * The LCT-SECIR model is defined through ordinary differential equations of the form dydt = f(y, t). + * y is a vector containing the number of individuals for each (sub-) compartment. + * This function evaluates the right-hand-side f of the ODE and can be used in an ODE solver. + * @param[in] pop The current state of the population in the geographic unit we are considering. + * @param[in] y The current state of the model (or a subpopulation) as a flat array. + * @param[in] t The current time. + * @param[out] dydt A reference to the calculated output. + */ + void get_derivatives(Eigen::Ref> pop, + Eigen::Ref> y, ScalarType t, + Eigen::Ref> dydt) const override + { + // Vectors are sorted such that we first have all InfectionState%s for AgeGroup 0, + // afterwards all for AgeGroup 1 and so on. + dydt.setZero(); + get_derivatives_impl(pop, y, t, dydt); + } + + /** + * @brief Cumulates a simulation result with subcompartments to produce a result that divides the population only + * into the infection states defined in InfectionState. + * + * If the model is used for simulation, we will get a result in form of a TimeSeries with infection states divided + * in subcompartments. + * The function calculates a TimeSeries without subcompartments from another TimeSeries with subcompartments. + * This is done by summing up the numbers in the subcompartments. + * @param[in] subcompartments_ts Result of a simulation with the model. + * @return Result of the simulation divided in infection states without subcompartments. + * Returns TimeSeries with values -1 if calculation is not possible. + */ + TimeSeries calculate_compartments(const TimeSeries& subcompartments_ts) const + { + Eigen::Index count_InfStates = (Eigen::Index)InfectionState::Count; + Eigen::Index num_compartments = count_InfStates * num_groups; + TimeSeries compartments_ts(num_compartments); + if (!(this->populations.get_num_compartments() == (size_t)subcompartments_ts.get_num_elements())) { + log_error("Result does not match InfectionState of the Model."); + Eigen::VectorX wrong_size = Eigen::VectorX::Constant(num_compartments, -1); + compartments_ts.add_time_point(-1, wrong_size); + return compartments_ts; + } + Eigen::VectorX compartments(num_compartments); + for (Eigen::Index timepoint = 0; timepoint < subcompartments_ts.get_num_time_points(); ++timepoint) { + compress_vector(subcompartments_ts[timepoint], compartments); + compartments_ts.add_time_point(subcompartments_ts.get_time(timepoint), compartments); + } + + return compartments_ts; + } + + /** + * @brief Checks that the model satisfies all constraints (e.g. parameter or population constraints). + * @return Returns true if one or more constraints are not satisfied, false otherwise. + */ + bool check_constraints() const + { + + return (check_constraints_impl() || this->parameters.check_constraints() || + this->populations.check_constraints()); + } + +private: + /** + * @brief Converts a vector with subcompartments in a vector without subcompartments + * by summing up subcompartment values. + * This is done recursively for each group which corresponds to a slice of the vector. + * + * @tparam group The group specifying the slice of the vector being considered. + * @param[in] subcompartments The vector that should be converted. + * @param[out] compartments Reference to the vector where the output is stored. + */ + template + void compress_vector(const Eigen::VectorX& subcompartments, + Eigen::VectorX& compartments) const + { + static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); + using LctStateGroup = type_at_index_t; + + // Define first index of the group Group in a vector including all compartments without a resolution + // in subcompartments. + Eigen::Index count_InfStates = (Eigen::Index)InfectionState::Count; + Eigen::Index first_index_group_comps = Group * count_InfStates; + + // Use function from the LctState of the Group to calculate the vector without subcompartments + // using the corresponding vector with subcompartments. + compartments.segment(first_index_group_comps, count_InfStates) = + LctStateGroup::calculate_compartments(subcompartments.segment( + this->populations.template get_first_index_of_group(), LctStateGroup::Count)); + + // Function call for next group if applicable. + if constexpr (Group + 1 < num_groups) { + compress_vector(subcompartments, compartments); + } + } + + /** + * @brief Evaluates the right-hand-side f of the ODE dydt = f(y, t) recursively for each group. + * + * See also the function get_derivative. + * For each group, one slice of the output vector is calculated. + * @tparam group The group specifying the slice of the vector being considered. + * @param[in] pop The current state of the population in the geographic unit we are considering. + * @param[in] y The current state of the model (or a subpopulation) as a flat array. + * @param[in] t The current time. + * @param[out] dydt A reference to the calculated output. + */ + template + void get_derivatives_impl(Eigen::Ref> pop, + Eigen::Ref> y, ScalarType t, + Eigen::Ref> dydt) const + { + static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); + using LctStateGroup = type_at_index_t; + + size_t first_index_group = this->populations.template get_first_index_of_group(); + auto params = this->parameters; + ScalarType flow = 0; + + // Indices of first subcompartment of the InfectionState for the group in the vectors. + size_t Ei_first_index = first_index_group + LctStateGroup::template get_first_index(); + size_t INSi_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISyi_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISevi_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ICri_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t Ri = first_index_group + LctStateGroup::template get_first_index(); + size_t Di = first_index_group + LctStateGroup::template get_first_index(); + + // Calculate derivative of the Susceptible compartment. + interact(pop, y, t, dydt); + + // Calculate derivative of the Exposed compartment. + dydt[Ei_first_index] = -dydt[first_index_group]; + for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + // Variable flow stores the value of the flow from one subcompartment to the next one. + // Ei_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_first_index + // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ei_first_index + subcomp]; + // Subtract flow from dydt[Ei_first_index + subcomp] and add to next subcompartment. + dydt[Ei_first_index + subcomp] -= flow; + dydt[Ei_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedNoSymptoms compartment. + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[INSi_first_index + subcomp]; + dydt[INSi_first_index + subcomp] -= flow; + dydt[INSi_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedSymptoms compartment. + // Flow from last (sub-) compartment of C must be split between + // the first subcompartment of InfectedSymptoms and Recovered. + dydt[Ri] = dydt[ISyi_first_index] * params.template get()[Group]; + dydt[ISyi_first_index] = + dydt[ISyi_first_index] * (1 - params.template get()[Group]); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISyi_first_index + subcomp]; + dydt[ISyi_first_index + subcomp] -= flow; + dydt[ISyi_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedSevere compartment. + dydt[Ri] += dydt[ISevi_first_index] * (1 - params.template get()[Group]); + dydt[ISevi_first_index] = dydt[ISevi_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISevi_first_index + subcomp]; + dydt[ISevi_first_index + subcomp] -= flow; + dydt[ISevi_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedCritical compartment. + dydt[Ri] += dydt[ICri_first_index] * (1 - params.template get()[Group]); + dydt[ICri_first_index] = dydt[ICri_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments() - 1; + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ICri_first_index + subcomp]; + dydt[ICri_first_index + subcomp] -= flow; + dydt[ICri_first_index + subcomp + 1] = flow; + } + // Last flow from InfectedCritical has to be divided between Recovered and Dead. + // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ri - 1]; + dydt[Ri - 1] -= flow; + dydt[Ri] = dydt[Ri] + (1 - params.template get()[Group]) * flow; + dydt[Di] = params.template get()[Group] * flow; + + // Function call for next group if applicable. + if constexpr (Group + 1 < num_groups) { + get_derivatives_impl(pop, y, t, dydt); + } + } + + /** + * @brief Calculates the derivative of the Susceptible compartment for Group1. + * + * This is done recursively by calculating the interaction terms with each group. + * @tparam Group1 The group for which the derivative of the Susceptible compartment should be calculated. + * @tparam Group2 The group that Group1 interacts with. + * @param[in] pop The current state of the population in the geographic unit we are considering. + * @param[in] y The current state of the model (or a subpopulation) as a flat array. + * @param[in] t The current time. + * @param[out] dydt A reference to the calculated output. + */ + template + void interact(Eigen::Ref> pop, Eigen::Ref> y, + ScalarType t, Eigen::Ref> dydt) const + { + static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0), + "The template parameters Group1 & Group2 should be valid."); + using LctStateGroup2 = type_at_index_t; + size_t Si_1 = this->populations.template get_first_index_of_group(); + ScalarType infectedNoSymptoms_2 = 0; + ScalarType infectedSymptoms_2 = 0; + auto params = this->parameters; + + size_t first_index_group2 = this->populations.template get_first_index_of_group(); + + // Calculate sum of all subcompartments for InfectedNoSymptoms of Group2. + infectedNoSymptoms_2 = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum(); + // Calculate sum of all subcompartments for InfectedSymptoms of Group2. + infectedSymptoms_2 = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum(); + // Size of the Subpopulation Group2 without dead people. + double N_2 = pop.segment(first_index_group2, LctStateGroup2::Count - 1).sum(); + const double divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; + ScalarType season_val = 1 + params.template get() * + sin(3.141592653589793 * ((params.template get() + t) / 182.5 + 0.5)); + dydt[Si_1] += -y[Si_1] * divN_2 * season_val * params.template get()[Group1] * + params.template get().get_cont_freq_mat().get_matrix_at(t)( + static_cast(Group1), static_cast(Group2)) * + (params.template get()[Group2] * infectedNoSymptoms_2 + + params.template get()[Group2] * infectedSymptoms_2); + // Function call for next interacting group if applicable. + if constexpr (Group2 + 1 < num_groups) { + interact(pop, y, t, dydt); + } + } + + /** + * @brief Checks whether LctState of a group satisfies all constraints. + * Recursively, it checks that all groups satisfy the constraints. + * + * @tparam group The group for which the constraints should be checked. + * @return Returns true if one or more constraints are not satisfied, false otherwise. + */ + template + bool check_constraints_impl() const + { + static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); + using LctStateGroup = type_at_index_t; + + if (LctStateGroup::template get_num_subcompartments() != 1) { + log_warning("Constraint check: The number of subcompartments for Susceptibles of group {} should be one!", + Group); + return true; + } + if (LctStateGroup::template get_num_subcompartments() != 1) { + log_warning("Constraint check: The number of subcompartments for Recovered of group {} should be one!", + Group); + return true; + } + if (LctStateGroup::template get_num_subcompartments() != 1) { + log_warning("Constraint check: The number of subcompartments for Dead of group {} should be one!", Group); + return true; + } + + if constexpr (Group == num_groups - 1) { + return false; + } + else { + return check_constraints_impl(); + } + } +}; + +} // namespace lsecir2d +} // namespace mio + +#endif // LCTSECIR_MODEL_H diff --git a/cpp/models/lct_secir_2_disease/parameters.h b/cpp/models/lct_secir_2_disease/parameters.h new file mode 100644 index 0000000000..4884d6e003 --- /dev/null +++ b/cpp/models/lct_secir_2_disease/parameters.h @@ -0,0 +1,658 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#ifndef LCT_SECIR_2_DISEASE_PARAMS_H +#define LCT_SECIR_2_DISEASE_PARAMS_H + +#include "memilio/config.h" +#include "memilio/utils/parameter_set.h" +#include "memilio/utils/logging.h" +#include "memilio/utils/uncertain_value.h" +/* +#include "memilio/math/eigen.h" +#include "memilio/epidemiology/uncertain_matrix.h" */ + +namespace mio +{ +namespace lsecir2d +{ + +/********************************************** +* Define Parameters of the LCT-SECIHURD model * +**********************************************/ + +/** + * @brief Average time spent in the Exposed compartment for disease a. + */ +struct TimeExposed_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeExposed_a"; + } +}; + +/** + * @brief Average time spent in the TimeInfectedNoSymptoms before developing + * symptoms or recover for disease a in day unit. + */ +struct TimeInfectedNoSymptoms_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedNoSymptoms_a"; + } +}; + +/** + * @brief Average time spent in the TimeInfectedSymptoms before going to hospital + * or recover for disease a in day unit. + */ +struct TimeInfectedSymptoms_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedNoSymptoms_a"; + } +}; + +/** + * @brief Average time being in the Hospital before treated by ICU or recover for disease a in day unit. + */ +struct TimeInfectedSevere_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedSevere_a"; + } +}; + +/** + * @brief Average time treated by ICU before dead or recover for disease a in day unit. + */ +struct TimeInfectedCritical_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedCritical_a"; + } +}; + +/** + * @brief Probability of getting infected from a contact for disease a. + */ +struct TransmissionProbabilityOnContact_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.1); + } + static std::string name() + { + return "TransmissionProbabilityOnContact_a"; + } +}; + +/** + * @brief Average time spent in the Exposed compartment for disease b. + */ +struct TimeExposed_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeExposed_b"; + } +}; + +/** + * @brief Average time spent in the TimeInfectedNoSymptoms before developing + * symptoms or recover for disease b in day unit. + */ +struct TimeInfectedNoSymptoms_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedNoSymptoms_b"; + } +}; + +/** + * @brief Average time spent in the TimeInfectedSymptoms before going to hospital + * or recover for disease b in day unit. + */ +struct TimeInfectedSymptoms_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedNoSymptoms_b"; + } +}; + +/** + * @brief Average time being in the Hospital before treated by ICU or recover for disease b in day unit. + */ +struct TimeInfectedSevere_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedSevere_b"; + } +}; + +/** + * @brief Average time treated by ICU before dead or recover for disease b in day unit. + */ +struct TimeInfectedCritical_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "TimeInfectedCritical_b"; + } +}; + +/** + * @brief Probability of getting infected from a contact for disease b. + */ +struct TransmissionProbabilityOnContact_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.1); + } + static std::string name() + { + return "TransmissionProbabilityOnContact_b"; + } +}; + +/** + * @brief The contact patterns within the society are modelled using an UncertainContactMatrix. + */ +struct ContactRate { + using Type = UncertainValue; + + static Type get_default() + { + return Type(10); + } + static std::string name() + { + return "ContactRate"; + } +}; + +/** + * @brief The relative InfectedNoSymptoms infectability for disease a. + */ +struct RelativeTransmissionNoSymptoms_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "RelativeTransmissionNoSymptoms_a"; + } +}; + +/** + * @brief The risk of infection from symptomatic cases for disease a. + */ +struct RiskOfInfectionFromSymptomatic_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "RiskOfInfectionFromSymptomatic_a"; + } +}; + +/** + * @brief The relative InfectedNoSymptoms infectability for disease b. + */ +struct RelativeTransmissionNoSymptoms_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "RelativeTransmissionNoSymptoms_b"; + } +}; + +/** + * @brief The risk of infection from symptomatic cases for disease b. + */ +struct RiskOfInfectionFromSymptomatic_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(1); + } + static std::string name() + { + return "RiskOfInfectionFromSymptomatic_b"; + } +}; + +/** + * @brief The percentage of asymptomatic cases for disease a in the SECIR model. + */ +struct RecoveredPerInfectedNoSymptoms_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "RecoveredPerInfectedNoSymptoms_a"; + } +}; + +/** + * @brief The percentage of hospitalized patients per infected patients for disease a in the SECIR model. + */ +struct SeverePerInfectedSymptoms_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "SeverePerInfectedSymptoms_a"; + } +}; + +/** + * @brief The percentage of ICU patients per hospitalized patients for disease a in the SECIR model. + */ +struct CriticalPerSevere_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "CriticalPerSevere_a"; + } +}; + +/** + * @brief The percentage of dead patients per ICU patients for disease a in the SECIR model. + */ +struct DeathsPerCritical_a { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "DeathsPerCritical_a"; + } +}; + +/** + * @brief The percentage of asymptomatic cases for disease b in the SECIR model. + */ +struct RecoveredPerInfectedNoSymptoms_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "RecoveredPerInfectedNoSymptoms_b"; + } +}; + +/** + * @brief The percentage of hospitalized patients per infected patients for disease b in the SECIR model. + */ +struct SeverePerInfectedSymptoms_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "SeverePerInfectedSymptoms_b"; + } +}; + +/** + * @brief The percentage of ICU patients per hospitalized patients for disease b in the SECIR model. + */ +struct CriticalPerSevere_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "CriticalPerSevere_b"; + } +}; + +/** + * @brief The percentage of dead patients per ICU patients for disease b in the SECIR model. + */ +struct DeathsPerCritical_b { + using Type = UncertainValue; + static Type get_default() + { + return Type(0.5); + } + static std::string name() + { + return "DeathsPerCritical_b"; + } +}; + +/** + * @brief The start day in the LCT SECIR model. + * The start day defines in which season the simulation is started. + * If the start day is 180 and simulation takes place from t0=0 to + * tmax=100 the days 180 to 280 of the year are simulated. + */ +struct StartDay { + using Type = ScalarType; + static Type get_default(size_t) + { + return 0.; + } + static std::string name() + { + return "StartDay"; + } +}; + +/** + * @brief The seasonality in the LCT-SECIR model. + * The seasonality is given as (1+k*sin()) where the sine + * curve is below one in summer and above one in winter. + */ +struct Seasonality { + using Type = ScalarType; + static Type get_default(size_t) + { + return 0.; + } + static std::string name() + { + return "Seasonality"; + } +}; + +using ParametersBase = + ParameterSet; + +/** + * @brief Parameters of an LCT-SECIR model. + */ +class Parameters : public ParametersBase +{ +public: + /** + * @brief Constructor. + * @param num_groups The number of groups considered in the LCT model. + */ + /**Parameters(size_t num_groups) + : ParametersBase(num_groups) + , m_num_groups{num_groups} + { + } + + size_t get_num_groups() const + { + return m_num_groups; + }**/ + + /** + * @brief Checks whether all parameters satisfy their corresponding constraints and throws errors, if they do not. + * @return Returns true if one (or more) constraint(s) are not satisfied, otherwise false. + */ + bool check_constraints() const + { + if (this->get() < 0.0 || this->get() > 0.5) { + log_warning("Constraint check: Parameter Seasonality should lie between {:0.4f} and {:.4f}", 0.0, 0.5); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeExposed_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeExposed_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedNoSymptoms_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedNoSymptoms_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSymptoms_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSymptoms_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSevere_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSevere_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedCritical_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 1.0) { + log_error("Constraint check: Parameter TimeInfectedCritical_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter TransmissionProbabilityOnContact_a smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter TransmissionProbabilityOnContact_b smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_a smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_b smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || + this->get() > 1.0) { + log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter SeverePerInfectedSymptoms_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter SeverePerInfectedSymptoms_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter CriticalPerSevere_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter CriticalPerSevere_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter DeathsPerCritical_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get() < 0.0 || this->get() > 1.0) { + log_error("Constraint check: Parameter DeathsPerCritical_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + return false; + } + +private: + Parameters(ParametersBase&& base) + : ParametersBase(std::move(base)) + //, m_num_groups(this->template get().get_cont_freq_mat().get_num_groups()) + { + } + + //size_t m_num_groups; + +public: + /** + * deserialize an object of this class. + * @see mio::deserialize + */ + template + static IOResult deserialize(IOContext& io) + { + BOOST_OUTCOME_TRY(auto&& base, ParametersBase::deserialize(io)); + return success(Parameters(std::move(base))); + } +}; + +} // namespace lsecir2d +} // namespace mio + +#endif // LCT_SECIR_2_DISEASE_PARAMS_H From a75cb66bb57c07b340cbbbe72bd9bab20303fb2a Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 17 Jul 2025 16:45:07 +0200 Subject: [PATCH 03/16] lct for 2 diseases on basis of lct_secir model, changed Infection States, Parameters etc. to reflect 2 diseases --- cpp/examples/lct_secir_2_disease.cpp | 131 +++-- .../epidemiology/lct2d_infection_state.h | 221 ++++++++ cpp/memilio/epidemiology/lct2d_populations.h | 216 +++++++ cpp/models/lct_secir_2_disease/README.md | 2 +- .../lct_secir_2_disease/infection_state.h | 88 +-- cpp/models/lct_secir_2_disease/model.h | 530 +++++++++++++++--- cpp/models/lct_secir_2_disease/parameters.h | 447 ++++++++------- 7 files changed, 1253 insertions(+), 382 deletions(-) create mode 100644 cpp/memilio/epidemiology/lct2d_infection_state.h create mode 100644 cpp/memilio/epidemiology/lct2d_populations.h diff --git a/cpp/examples/lct_secir_2_disease.cpp b/cpp/examples/lct_secir_2_disease.cpp index 7b0949e05d..91e6e14218 100644 --- a/cpp/examples/lct_secir_2_disease.cpp +++ b/cpp/examples/lct_secir_2_disease.cpp @@ -22,9 +22,9 @@ #include "lct_secir_2_disease/infection_state.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" -#include "memilio/epidemiology/uncertain_matrix.h" -#include "memilio/epidemiology/lct_infection_state.h" -#include "memilio/math/eigen.h" +//#include "memilio/epidemiology/uncertain_matrix.h" +#include "memilio/epidemiology/lct2d_infection_state.h" +//#include "memilio/math/eigen.h" #include "memilio/utils/logging.h" #include "memilio/compartments/simulation.h" #include "memilio/data/analyze_result.h" @@ -36,12 +36,22 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. - constexpr size_t NumExposed = 2, NumInfectedNoSymptoms = 3, NumInfectedSymptoms = 1, NumInfectedSevere = 1, - NumInfectedCritical = 5; - using InfState = mio::lsecir2d::InfectionState; - using LctState = mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + constexpr size_t NumExposed_1a = 2, NumInfectedNoSymptoms_1a = 3, NumInfectedSymptoms_1a = 1, + NumInfectedSevere_1a = 1, NumInfectedCritical_1a = 5, NumExposed_2a = 2, + NumInfectedNoSymptoms_2a = 3, NumInfectedSymptoms_2a = 1, NumInfectedSevere_2a = 1, + NumInfectedCritical_2a = 5, NumExposed_1b = 2, NumInfectedNoSymptoms_1b = 3, + NumInfectedSymptoms_1b = 1, NumInfectedSevere_1b = 1, NumInfectedCritical_1b = 5, + NumExposed_2b = 2, NumInfectedNoSymptoms_2b = 3, NumInfectedSymptoms_2b = 1, + NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 5; + using InfState = mio::lsecir2d::InfectionState; + using LctState = + mio::LctInfectionState; + using Model = mio::lsecir2d::Model; Model model; // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually @@ -50,33 +60,69 @@ int main() ScalarType tmax = 10; // Set Parameters. - model.parameters.get()[0] = 3.2; - model.parameters.get()[0] = 2.; - model.parameters.get()[0] = 5.8; - model.parameters.get()[0] = 9.5; - model.parameters.get()[0] = 7.1; + model.parameters.get()[0] = 3.2; + model.parameters.get()[0] = 3.2; + model.parameters.get()[0] = 2.; + model.parameters.get()[0] = 2.; + model.parameters.get()[0] = 5.8; + model.parameters.get()[0] = 5.8; + model.parameters.get()[0] = 9.5; + model.parameters.get()[0] = 9.5; + model.parameters.get()[0] = 7.1; + model.parameters.get()[0] = 7.1; - model.parameters.get()[0] = 0.05; + model.parameters.get()[0] = 0.05; + model.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); - model.parameters.get()[0] = 0.7; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.09; - model.parameters.get()[0] = 0.2; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.3; + model.parameters.get()[0] = 0.7; + model.parameters.get()[0] = 0.7; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.09; + model.parameters.get()[0] = 0.09; + model.parameters.get()[0] = 0.2; + model.parameters.get()[0] = 0.2; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.25; + model.parameters.get()[0] = 0.3; + model.parameters.get()[0] = 0.3; // Simple example how to initialize model without flows. // Define the initial values with the distribution of the population into subcompartments. // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{750}, {30, 20}, {20, 10, 10}, {50}, - {50}, {10, 10, 5, 3, 2}, {20}, {10}}; + std::vector> initial_populations = {{750}, + {30, 20}, + {20, 10, 10}, + {50}, + {50}, + {10, 10, 5, 3, 2}, + {30, 20}, + {20, 10, 10}, + {50}, + {50}, + {10, 10, 5, 3, 2}, + {20}, + {10}, + {30, 20}, + {20, 10, 10}, + {50}, + {50}, + {10, 10, 5, 3, 2}, + {30, 20}, + {20, 10, 10}, + {50}, + {50}, + {10, 10, 5, 3, 2}, + {20}, + {10}, + {0}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -85,14 +131,32 @@ int main() } if ((initial_populations[(size_t)InfState::Susceptible].size() != LctState::get_num_subcompartments()) || - (initial_populations[(size_t)InfState::Exposed].size() != NumExposed) || - (initial_populations[(size_t)InfState::InfectedNoSymptoms].size() != NumInfectedNoSymptoms) || - (initial_populations[(size_t)InfState::InfectedSymptoms].size() != NumInfectedSymptoms) || - (initial_populations[(size_t)InfState::InfectedSevere].size() != NumInfectedSevere) || - (initial_populations[(size_t)InfState::InfectedCritical].size() != NumInfectedCritical) || - (initial_populations[(size_t)InfState::Recovered].size() != - LctState::get_num_subcompartments()) || - (initial_populations[(size_t)InfState::Dead].size() != LctState::get_num_subcompartments())) { + (initial_populations[(size_t)InfState::Exposed_1a].size() != NumExposed_1a) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_1a].size() != NumInfectedNoSymptoms_1a) || + (initial_populations[(size_t)InfState::InfectedSymptoms_1a].size() != NumInfectedSymptoms_1a) || + (initial_populations[(size_t)InfState::InfectedSevere_1a].size() != NumInfectedSevere_1a) || + (initial_populations[(size_t)InfState::InfectedCritical_1a].size() != NumInfectedCritical_1a) || + (initial_populations[(size_t)InfState::Exposed_2a].size() != NumExposed_2a) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_2a].size() != NumInfectedNoSymptoms_2a) || + (initial_populations[(size_t)InfState::InfectedSymptoms_2a].size() != NumInfectedSymptoms_2a) || + (initial_populations[(size_t)InfState::InfectedSevere_2a].size() != NumInfectedSevere_2a) || + (initial_populations[(size_t)InfState::InfectedCritical_2a].size() != NumInfectedCritical_2a) || + (initial_populations[(size_t)InfState::Recovered_a].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Dead_a].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Exposed_1b].size() != NumExposed_1b) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_1b].size() != NumInfectedNoSymptoms_1b) || + (initial_populations[(size_t)InfState::InfectedSymptoms_1b].size() != NumInfectedSymptoms_1b) || + (initial_populations[(size_t)InfState::InfectedSevere_1b].size() != NumInfectedSevere_1b) || + (initial_populations[(size_t)InfState::InfectedCritical_1b].size() != NumInfectedCritical_1b) || + (initial_populations[(size_t)InfState::Exposed_2b].size() != NumExposed_2b) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_2b].size() != NumInfectedNoSymptoms_2b) || + (initial_populations[(size_t)InfState::InfectedSymptoms_2b].size() != NumInfectedSymptoms_2b) || + (initial_populations[(size_t)InfState::InfectedSevere_2b].size() != NumInfectedSevere_2b) || + (initial_populations[(size_t)InfState::InfectedCritical_2b].size() != NumInfectedCritical_2b) || + (initial_populations[(size_t)InfState::Recovered_ab].size() != + LctState::get_num_subcompartments())) { mio::log_error("The length of at least one vector in initial_populations does not match the related number of " "subcompartments."); return 1; @@ -112,5 +176,8 @@ int main() // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); - interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 4); + interpolated_results.print_table({"S", "E1a", "C1a", "I1a", "H1a", "U1a", "E2a", "C2a", "I2a", + "H2a", "U2a", "Ra", "Da", "E1b", "C1b", "I1b", "H1b", "U1b", + "E2b", "C2b", "I2b", "H2b", "U2b", "Rb", "Db", "Rab"}, + 12, 4); } diff --git a/cpp/memilio/epidemiology/lct2d_infection_state.h b/cpp/memilio/epidemiology/lct2d_infection_state.h new file mode 100644 index 0000000000..705b391355 --- /dev/null +++ b/cpp/memilio/epidemiology/lct2d_infection_state.h @@ -0,0 +1,221 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef MIO_EPI_LCT_INFECTION_STATE_H +#define MIO_EPI_LCT_INFECTION_STATE_H + +#include "memilio/config.h" +#include "memilio/math/eigen.h" + +#include + +namespace mio +{ +/** + * @brief Provides the functionality to be able to work with subcompartments in an LCT model. + + * This class just stores the number of subcompartments for each InfectionState and not the number of individuals in + * each subcompartment. + * + * @tparam InfectionStates An enum class that defines the basic infection states. + * @tparam Ns Number of subcompartments for each infection state defined in InfectionState. + * The number of given template arguments must be equal to the entry Count from InfectionStates. + */ +template +class LctInfectionState +{ +public: + using InfectionState = InfectionStates; + static_assert((size_t)InfectionState::Count == sizeof...(Ns), + "The number of the size_t's provided as template parameters must be " + "the same as the entry Count of InfectionState."); + + static_assert(((Ns > 0) && ...), "The number of subcompartments must be at least 1."); + + /** + * @brief Gets the number of subcompartments in an infection state. + * + * @tparam State Infection state for which the number of subcompartments should be returned. + * @return Number of subcompartments for State. Returned value is always at least one. + */ + template + static constexpr size_t get_num_subcompartments() + { + static_assert(State < InfectionState::Count, "State must be a a valid InfectionState."); + return m_subcompartment_numbers[(size_t)State]; + } + + /** + * @brief Gets the index of the first subcompartment of an infection state. + * + * In a simulation, the number of individuals in the subcompartments are stored in vectors. + * Accordingly, the index of the first subcompartment of State in such a vector is returned. + * @tparam State: Infection state for which the index should be returned. + * @return Index of the first subcompartment for a vector with one entry per subcompartment. + * Returned value is always non-negative. + */ + template + static constexpr size_t get_first_index() + { + static_assert(State < InfectionState::Count, "State must be a a valid InfectionState."); + size_t index = 0; + for (size_t i = 0; i < (size_t)(State); i++) { + index = index + m_subcompartment_numbers[i]; + } + return index; + } + + /** + * @brief Cumulates a vector with the number of individuals in each subcompartment (with subcompartments + * according to the LctInfectionState) to produce a Vector that divides the population only into the infection + * states defined in InfectionStates. + * + * @param[in] subcompartments Vector with number of individuals in each subcompartment. + * The size of the vector has to match the LctInfectionState. + * @return Vector with accumulated values for the InfectionStates. + */ + static Eigen::VectorX calculate_compartments(const Eigen::VectorX& subcompartments) + { + assert(subcompartments.rows() == Count); + + Eigen::VectorX compartments((Eigen::Index)InfectionState::Count); + // Use segment of the vector subcompartments of each InfectionState and sum up the values of subcompartments. + compartments[(Eigen::Index)InfectionState::Susceptible] = subcompartments[0]; + compartments[(Eigen::Index)InfectionState::Exposed_1a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_1a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSymptoms_1a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSevere_1a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedCritical_1a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::Exposed_2a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_2a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSymptoms_2a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSevere_2a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedCritical_2a] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::Recovered_a] = + subcompartments[get_first_index()]; + compartments[(Eigen::Index)InfectionState::Dead_a] = subcompartments[get_first_index()]; + compartments[(Eigen::Index)InfectionState::Exposed_1b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_1b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSymptoms_1b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSevere_1b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedCritical_1b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::Exposed_2b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_2b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSymptoms_2b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedSevere_2b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::InfectedCritical_2b] = + subcompartments + .segment(get_first_index(), + get_num_subcompartments()) + .sum(); + compartments[(Eigen::Index)InfectionState::Recovered_b] = + subcompartments[get_first_index()]; + compartments[(Eigen::Index)InfectionState::Dead_b] = subcompartments[get_first_index()]; + compartments[(Eigen::Index)InfectionState::Recovered_ab] = + subcompartments[get_first_index()]; + + return compartments; + } + + static constexpr size_t Count{(... + Ns)}; + +private: + static constexpr const std::array m_subcompartment_numbers{ + Ns...}; ///< Vector which defines the number of subcompartments for each infection state of InfectionState. +}; + +} // namespace mio + +#endif diff --git a/cpp/memilio/epidemiology/lct2d_populations.h b/cpp/memilio/epidemiology/lct2d_populations.h new file mode 100644 index 0000000000..a017d32fa0 --- /dev/null +++ b/cpp/memilio/epidemiology/lct2d_populations.h @@ -0,0 +1,216 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +#ifndef MIO_EPI_LCT_POPULATIONS_H +#define MIO_EPI_LCT_POPULATIONS_H + +#include "boost/type_traits/make_void.hpp" +#include "memilio/config.h" +#include "memilio/utils/uncertain_value.h" +#include "memilio/math/eigen.h" +#include "memilio/epidemiology/lct2d_infection_state.h" +#include "memilio/utils/type_list.h" +#include "memilio/utils/metaprogramming.h" + +namespace mio +{ + +/** + * @brief A class template for compartment populations of LCT models. + * + * Populations can be split up into different categories, e.g. by age group, yearly income group, gender etc. + * In LCT models, we want to use different numbers of subcompartments, i.e. different LctStates, + * for each group of a category. + * (Therefore, we can't use the normal Populations class because it expects the same InfectionStates for each group.) + * + * This template must be instantiated with one LctState for each group of a category. + * The purpose of the LctStates is to define the number of subcompartments for each InfectionState. + * The number of LctStates also determines the number of groups. + * If you want to use more than one category, e.g. age and gender, you have to define num_age_groups * num_genders + * LctStates, because the number of subcompartments can be different + * even for (female, A05-A14) and (female, A80+) or (male, A05-A14). + * + * The class created from this template contains a "flat array" of compartment populations and some functions for + * retrieving or setting the populations. The order in the flat array is: First, all (sub-)compartments of the + * first group, afterwards all (sub-)compartments of the second group and so on. + * + */ + +template +class LctPopulations +{ +public: + using Type = UncertainValue; + using InternalArrayType = Eigen::Array; + using LctStatesGroups = TypeList; + static size_t constexpr num_groups = sizeof...(LctStates); ///< Number of groups. + static_assert(num_groups >= 1, "The number of LctStates provided should be at least one."); + + /// @brief Default constructor. + LctPopulations() + { + set_count(); + m_y = InternalArrayType::Constant(m_count, 1, 0.); + } + + /** + * @brief get_num_compartments Returns the number of compartments. + * @return Number of compartments which equals the flat array size. + */ + size_t get_num_compartments() const + { + return m_count; + } + + /** + * @brief Returns a reference to the internally stored flat array. + * @return Const reference to the InternalArrayType instance. + */ + auto const& array() const + { + return m_y; + } + auto& array() + { + return m_y; + } + + /** + * @brief Returns the entry of the array given a flat index. + * @param index A flat index. + * @return The value of the internal array at the index. + */ + Type& operator[](size_t index) + { + assert(index < m_count); + return m_y[index]; + } + + /** + * @brief Gets the first index of a group in the flat array. + * @tparam group The group for which the index should be returned. + * @return The index of the first entry of group in the flat array. + */ + template + size_t get_first_index_of_group() const + { + static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); + if constexpr (Group == 0) { + return 0; + } + else { + return get_first_index_of_group() + type_at_index_t::Count; + } + } + /** + * @brief Returns an Eigen copy of the vector of populations. + * This can be used as initial conditions for the ODE solver. + * @return Eigen::VectorXd of populations. + */ + inline Eigen::VectorX get_compartments() const + { + return m_y.array().template cast(); + } + + /** + * @brief Returns the total population of a group. + * @tparam group The group for which the total population should be calculated. + * @return Total population of the group. + */ + template + FP get_group_total() const + { + return m_y.array() + .template cast() + .segment(get_first_index_of_group(), type_at_index_t::Count) + .sum(); + } + + /** + * @brief Returns the total population of all compartments and groups. + * @return Total population. + */ + FP get_total() const + { + return m_y.array().template cast().sum(); + } + + /** + * @brief Checks whether all compartments have non-negative values. + * This function can be used to prevent slightly negative function values in compartment sizes that are produced + * due to rounding errors if, e.g., population sizes were computed in a complex way. + * + * Attention: This function should be used with care. It can not and will not set model parameters and + * compartments to meaningful values. In most cases it is preferable to use check_constraints, + * and correct values manually before proceeding with the simulation. + * The main usage for apply_constraints is in automated tests using random values for initialization. + * + * @return Returns true if one (or more) constraint(s) were corrected, otherwise false. + */ + bool apply_constraints() + { + bool corrected = false; + for (int i = 0; i < m_y.array().size(); i++) { + if (m_y.array()[i] < 0) { + log_warning("Constraint check: Compartment size {:d} changed from {:.4f} to {:d}", i, m_y.array()[i], + 0); + m_y.array()[i] = 0.; + corrected = true; + } + } + return corrected; + } + + /** + * @brief Checks whether all compartments have non-negative values and logs an error if constraint is not satisfied. + * @return Returns true if one or more constraints are not satisfied, false otherwise. + */ + bool check_constraints() const + { + if ((m_y.array() < 0.).any()) { + log_error("Constraint check: At least one compartment size is smaller {}.", 0); + return true; + } + return false; + } + +private: + /** + * @brief Sets recursively the total number of (sub-)compartments over all groups. + * The number also corresponds to the size of the internal vector. + */ + template + void set_count() + { + if constexpr (Group == 0) { + m_count = 0; + } + if constexpr (Group < num_groups) { + m_count += type_at_index_t::Count; + set_count(); + } + } + + size_t m_count; //< Number of groups stored. + InternalArrayType m_y{}; //< An array containing the number of people in the groups. +}; + +} // namespace mio + +#endif // MIO_EPI_LCT_POPULATIONS_H diff --git a/cpp/models/lct_secir_2_disease/README.md b/cpp/models/lct_secir_2_disease/README.md index ee5b3c024f..73ad6efa74 100644 --- a/cpp/models/lct_secir_2_disease/README.md +++ b/cpp/models/lct_secir_2_disease/README.md @@ -1,4 +1,4 @@ -# LCT SECIR model +# LCT SECIR TWO DISEASES model This model is based on the Linear Chain Trick. diff --git a/cpp/models/lct_secir_2_disease/infection_state.h b/cpp/models/lct_secir_2_disease/infection_state.h index 51e09a71ec..bfe68b2eb7 100644 --- a/cpp/models/lct_secir_2_disease/infection_state.h +++ b/cpp/models/lct_secir_2_disease/infection_state.h @@ -40,33 +40,33 @@ enum class InfectionState InfectedSymptoms_1a = 3, InfectedSevere_1a = 4, InfectedCritical_1a = 5, + // second infection with disease a + Exposed_2a = 6, + InfectedNoSymptoms_2a = 7, + InfectedSymptoms_2a = 8, + InfectedSevere_2a = 9, + InfectedCritical_2a = 10, // R and D for disease a - Recovered_a = 6, - Dead_a = 7, + Recovered_a = 11, + Dead_a = 12, + // first infection with disease b + Exposed_1b = 13, + InfectedNoSymptoms_1b = 14, + InfectedSymptoms_1b = 15, + InfectedSevere_1b = 16, + InfectedCritical_1b = 17, // second infection with disease b - Exposed_2b = 8, - InfectedNoSymptoms_2b = 9, - InfectedSymptoms_2b = 10, - InfectedSevere_2b = 11, - InfectedCritical_2b = 12, + Exposed_2b = 18, + InfectedNoSymptoms_2b = 19, + InfectedSymptoms_2b = 20, + InfectedSevere_2b = 21, + InfectedCritical_2b = 22, // R and D for disease b - Recovered_b = 13, - Dead_b = 14, - // first infection with disease b - Exposed_1b = 15, - InfectedNoSymptoms_1b = 16, - InfectedSymptoms_1b = 17, - InfectedSevere_1b = 18, - InfectedCritical_1b = 19, - // second infection with disease a - Exposed_2a = 20, - InfectedNoSymptoms_2a = 21, - InfectedSymptoms_2a = 22, - InfectedSevere_2a = 23, - InfectedCritical_2a = 24, + Recovered_b = 23, + Dead_b = 24, // Recovered from both diseases Recovered_ab = 25, - Count = 8 + Count = 26 }; /** @@ -86,17 +86,17 @@ enum class InfectionTransition InfectedSevere_1aToRecovered_a = 7, InfectedCritical_1aToDead_a = 8, InfectedCritical_1aToRecovered_a = 9, - // second infection with b - Recovered_aToExposed_2b = 10, - Exposed_2bToInfectedNoSymptoms_2b = 11, - InfectedNoSymptoms_2bToInfectedSymptoms_2b = 12, - InfectedNoSymptoms_2bToRecovered_ab = 13, - InfectedSymptoms_2bToInfectedSevere_2b = 14, - InfectedSymptoms_2bToRecovered_ab = 15, - InfectedSevere_2bToInfectedCritical_2b = 16, - InfectedSevere_2bToRecovered_ab = 17, - InfectedCritical_2bToDead_b = 18, - InfectedCritical_2bToRecovered_ab = 19, + // second infection with a + Recovered_bToExposed_2a = 10, + Exposed_2aToInfectedNoSymptoms_2a = 11, + InfectedNoSymptoms_2aToInfectedSymptoms_2a = 12, + InfectedNoSymptoms_2aToRecovered_ab = 13, + InfectedSymptoms_2aToInfectedSevere_2a = 14, + InfectedSymptoms_2aToRecovered_ab = 15, + InfectedSevere_2aToInfectedCritical_2a = 16, + InfectedSevere_2aToRecovered_ab = 17, + InfectedCritical_2aToDead_a = 18, + InfectedCritical_2aToRecovered_ab = 19, // first infection with b SusceptibleToExposed_1b = 20, Exposed_1bToInfectedNoSymptoms_1b = 21, @@ -108,17 +108,17 @@ enum class InfectionTransition InfectedSevere_1bToRecovered_b = 27, InfectedCritical_1bToDead_b = 28, InfectedCritical_1bToRecovered_b = 29, - // second infection with a - Recovered_bToExposed_2a = 30, - Exposed_2aToInfectedNoSymptoms_2a = 31, - InfectedNoSymptoms_2aToInfectedSymptoms_2a = 32, - InfectedNoSymptoms_2aToRecovered_ab = 33, - InfectedSymptoms_2aToInfectedSevere_2a = 34, - InfectedSymptoms_2aToRecovered_ab = 35, - InfectedSevere_2aToInfectedCritical_2a = 36, - InfectedSevere_2aToRecovered_ab = 37, - InfectedCritical_2aToDead_a = 38, - InfectedCritical_2aToRecovered_ab = 39, + // second infection with b + Recovered_aToExposed_2b = 30, + Exposed_2bToInfectedNoSymptoms_2b = 31, + InfectedNoSymptoms_2bToInfectedSymptoms_2b = 32, + InfectedNoSymptoms_2bToRecovered_ab = 33, + InfectedSymptoms_2bToInfectedSevere_2b = 34, + InfectedSymptoms_2bToRecovered_ab = 35, + InfectedSevere_2bToInfectedCritical_2b = 36, + InfectedSevere_2bToRecovered_ab = 37, + InfectedCritical_2bToDead_b = 38, + InfectedCritical_2bToRecovered_ab = 39, Count = 40 }; diff --git a/cpp/models/lct_secir_2_disease/model.h b/cpp/models/lct_secir_2_disease/model.h index c0f5678a41..44c3da7ec5 100644 --- a/cpp/models/lct_secir_2_disease/model.h +++ b/cpp/models/lct_secir_2_disease/model.h @@ -24,7 +24,7 @@ #include "lct_secir_2_disease/parameters.h" #include "lct_secir_2_disease/infection_state.h" #include "memilio/compartments/compartmentalmodel.h" -#include "memilio/epidemiology/lct_populations.h" +#include "memilio/epidemiology/lct2d_populations.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" #include "memilio/utils/logging.h" @@ -33,6 +33,7 @@ /* #include "memilio/epidemiology/lct_infection_state.h" #include "memilio/math/eigen.h" +#include */ namespace mio { @@ -58,7 +59,7 @@ class Model using Base = CompartmentalModel, Parameters>; using typename Base::ParameterSet; using typename Base::Populations; - static size_t constexpr num_groups = sizeof...(LctStates); + static size_t constexpr num_groups = 1; //sizeof...(LctStates); only one (age) group for the beginning static_assert(num_groups >= 1, "The number of LctStates provided should be at least one."); /// @brief Default constructor. @@ -169,9 +170,10 @@ class Model this->populations.template get_first_index_of_group(), LctStateGroup::Count)); // Function call for next group if applicable. + /* only one (age) group, do not need this if constexpr (Group + 1 < num_groups) { compress_vector(subcompartments, compartments); - } + } */ } /** @@ -193,100 +195,387 @@ class Model static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); using LctStateGroup = type_at_index_t; - size_t first_index_group = this->populations.template get_first_index_of_group(); - auto params = this->parameters; - ScalarType flow = 0; + size_t first_index_group = + this->populations.template get_first_index_of_group(); // susceptible compartment? + auto params = this->parameters; + ScalarType flow = 0; // Indices of first subcompartment of the InfectionState for the group in the vectors. - size_t Ei_first_index = first_index_group + LctStateGroup::template get_first_index(); - size_t INSi_first_index = - first_index_group + LctStateGroup::template get_first_index(); - size_t ISyi_first_index = - first_index_group + LctStateGroup::template get_first_index(); - size_t ISevi_first_index = - first_index_group + LctStateGroup::template get_first_index(); - size_t ICri_first_index = - first_index_group + LctStateGroup::template get_first_index(); - size_t Ri = first_index_group + LctStateGroup::template get_first_index(); - size_t Di = first_index_group + LctStateGroup::template get_first_index(); + size_t Ei_1a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t Ei_2a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t Ei_1b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t Ei_2b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t INSi_1a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t INSi_2a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t INSi_1b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t INSi_2b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISyi_1a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISyi_2a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISyi_1b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISyi_2b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISevi_1a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISevi_2a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISevi_1b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ISevi_2b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ICri_1a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ICri_2a_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ICri_1b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t ICri_2b_first_index = + first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_a = first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_b = first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_ab = first_index_group + LctStateGroup::template get_first_index(); + size_t Di_a = first_index_group + LctStateGroup::template get_first_index(); + size_t Di_b = first_index_group + LctStateGroup::template get_first_index(); // Calculate derivative of the Susceptible compartment. - interact(pop, y, t, dydt); + // outflow generated by disease a and disease b both + double part_a = 0; + double part_b = 0; + interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); // S is affected by both diseases + // split flow + double div_part_both = (part_a + part_b < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); + + // Start with derivatives of first infections, so 1a and 1b compartments + + // Calculate derivative of the Exposed_1x compartments, split flow from susceptible. + // Exposed 1 a: + dydt[Ei_1a_first_index] = -dydt[first_index_group] * part_a * div_part_both; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + // Variable flow stores the value of the flow from one subcompartment to the next one. + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index + // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ei_1a_first_index + subcomp]; + // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. + dydt[Ei_1a_first_index + subcomp] -= flow; + dydt[Ei_1a_first_index + subcomp + 1] = flow; + } + // Exposed 1 b: + dydt[Ei_1b_first_index] = -dydt[first_index_group] * part_b * div_part_both; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + // Variable flow stores the value of the flow from one subcompartment to the next one. + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1b_first_index + // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ei_1b_first_index + subcomp]; + // Subtract flow from dydt[Ei_1b_first_index + subcomp] and add to next subcompartment. + dydt[Ei_1b_first_index + subcomp] -= flow; + dydt[Ei_1b_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedNoSymptoms_1a compartment. + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = + (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[INSi_1a_first_index + subcomp]; + dydt[INSi_1a_first_index + subcomp] -= flow; + dydt[INSi_1a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedNoSymptoms_1b compartment. + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = + (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[INSi_1b_first_index + subcomp]; + dydt[INSi_1b_first_index + subcomp] -= flow; + dydt[INSi_1b_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedSymptoms_1a compartment. + // Flow from last (sub-) compartment of C_1a must be split between + // the first subcompartment of InfectedSymptoms_1a and Recovered_a. + dydt[Ri_a] = dydt[ISyi_1a_first_index] * params.template get()[Group]; + dydt[ISyi_1a_first_index] = + dydt[ISyi_1a_first_index] * (1 - params.template get()[Group]); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISyi_1a_first_index + subcomp]; + dydt[ISyi_1a_first_index + subcomp] -= flow; + dydt[ISyi_1a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedSymptoms_1b compartment. + // Flow from last (sub-) compartment of C_1b must be split between + // the first subcompartment of InfectedSymptoms_1b and Recovered_b. + dydt[Ri_b] = dydt[ISyi_1b_first_index] * params.template get()[Group]; + dydt[ISyi_1b_first_index] = + dydt[ISyi_1b_first_index] * (1 - params.template get()[Group]); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISyi_1b_first_index + subcomp]; + dydt[ISyi_1b_first_index + subcomp] -= flow; + dydt[ISyi_1b_first_index + subcomp + 1] = flow; + } - // Calculate derivative of the Exposed compartment. - dydt[Ei_first_index] = -dydt[first_index_group]; - for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); + // Calculate derivative of the InfectedSevere_1a compartment. + // again split the flow from the last subcompartment of I_1a + dydt[Ri_a] += dydt[ISevi_1a_first_index] * (1 - params.template get()[Group]); + dydt[ISevi_1a_first_index] = + dydt[ISevi_1a_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISevi_1a_first_index + subcomp]; + dydt[ISevi_1a_first_index + subcomp] -= flow; + dydt[ISevi_1a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedSevere compartment. + // again split the flow from the last subcompartment of I_1b + dydt[Ri_b] += dydt[ISevi_1b_first_index] * (1 - params.template get()[Group]); + dydt[ISevi_1b_first_index] = + dydt[ISevi_1b_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISevi_1b_first_index + subcomp]; + dydt[ISevi_1b_first_index + subcomp] -= flow; + dydt[ISevi_1b_first_index + subcomp + 1] = flow; + } + + // Calculate derivative of the InfectedCritical compartment. + // again split flow from last subcompartment of H_1a between R_a and U_1a + dydt[Ri_a] += dydt[ICri_1a_first_index] * (1 - params.template get()[Group]); + dydt[ICri_1a_first_index] = dydt[ICri_1a_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments() - 1; + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ICri_1a_first_index + subcomp]; + dydt[ICri_1a_first_index + subcomp] -= flow; + dydt[ICri_1a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedCritical compartment. + // again split flow from last subcompartment of H_1b between R_b and U_1b + dydt[Ri_b] += dydt[ICri_1b_first_index] * (1 - params.template get()[Group]); + dydt[ICri_1b_first_index] = dydt[ICri_1b_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments() - 1; + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ICri_1b_first_index + subcomp]; + dydt[ICri_1b_first_index + subcomp] -= flow; + dydt[ICri_1b_first_index + subcomp + 1] = flow; + } + + // Last flow from InfectedCritical has to be divided between Recovered and Dead. + // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. + // for 1a + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * + y[ICri_1a_first_index + + LctStateGroup::template get_num_subcompartments() - 1]; + dydt[ICri_1a_first_index + + LctStateGroup::template get_num_subcompartments() - 1] -= flow; + dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get()[Group]) * flow; + dydt[Di_a] = params.template get()[Group] * flow; + // for 1b + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * + y[ICri_1b_first_index + + LctStateGroup::template get_num_subcompartments() - 1]; + dydt[ICri_1b_first_index + + LctStateGroup::template get_num_subcompartments() - 1] -= flow; + dydt[Ri_b] = dydt[Ri_b] + (1 - params.template get()[Group]) * flow; + dydt[Di_b] = params.template get()[Group] * flow; + + // second infection 2x + + // outflow from Recovered_x, people getting infected for the second time + double temp_Ra = dydt[Ri_a]; + interact(pop, y, t, dydt, &part_a, &part_b, Ri_a, 1); // outflow from R_a is only affected by b + double temp_Rb = dydt[Ri_b]; + interact(pop, y, t, dydt, &part_a, &part_b, Ri_b, 0); // outflow from R_a is only affected by a + + // Calculate derivative of the Exposed_2x compartments + // Exposed 2 a + dydt[Ei_2a_first_index] = -(dydt[Ri_b] - temp_Rb); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { // Variable flow stores the value of the flow from one subcompartment to the next one. - // Ei_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_first_index + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ei_first_index + subcomp]; - // Subtract flow from dydt[Ei_first_index + subcomp] and add to next subcompartment. - dydt[Ei_first_index + subcomp] -= flow; - dydt[Ei_first_index + subcomp + 1] = flow; + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ei_2a_first_index + subcomp]; + // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. + dydt[Ei_2a_first_index + subcomp] -= flow; + dydt[Ei_2a_first_index + subcomp + 1] = flow; + } + // Exposed 2 b + dydt[Ei_2b_first_index] = -(dydt[Ri_a] - temp_Ra); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { + // Variable flow stores the value of the flow from one subcompartment to the next one. + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index + // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[Ei_2b_first_index + subcomp]; + // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. + dydt[Ei_2b_first_index + subcomp] -= flow; + dydt[Ei_2b_first_index + subcomp + 1] = flow; } - // Calculate derivative of the InfectedNoSymptoms compartment. + // Calculate derivative of the InfectedNoSymptoms_2a compartment. for (size_t subcomp = 0; - subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[INSi_first_index + subcomp]; - dydt[INSi_first_index + subcomp] -= flow; - dydt[INSi_first_index + subcomp + 1] = flow; + flow = + (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[INSi_2a_first_index + subcomp]; + dydt[INSi_2a_first_index + subcomp] -= flow; + dydt[INSi_2a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedNoSymptoms_2b compartment. + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = + (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[INSi_2b_first_index + subcomp]; + dydt[INSi_2b_first_index + subcomp] -= flow; + dydt[INSi_2b_first_index + subcomp + 1] = flow; } - // Calculate derivative of the InfectedSymptoms compartment. - // Flow from last (sub-) compartment of C must be split between - // the first subcompartment of InfectedSymptoms and Recovered. - dydt[Ri] = dydt[ISyi_first_index] * params.template get()[Group]; - dydt[ISyi_first_index] = - dydt[ISyi_first_index] * (1 - params.template get()[Group]); + // Calculate derivative of the InfectedSymptoms_2a compartment. + // Flow from last (sub-) compartment of C_2a must be split between + // the first subcompartment of InfectedSymptoms_2a and Recovered_ab. + dydt[Ri_ab] = dydt[ISyi_2a_first_index] * params.template get()[Group]; + dydt[ISyi_2a_first_index] = + dydt[ISyi_2a_first_index] * (1 - params.template get()[Group]); for (size_t subcomp = 0; - subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISyi_first_index + subcomp]; - dydt[ISyi_first_index + subcomp] -= flow; - dydt[ISyi_first_index + subcomp + 1] = flow; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISyi_2a_first_index + subcomp]; + dydt[ISyi_2a_first_index + subcomp] -= flow; + dydt[ISyi_2a_first_index + subcomp + 1] = flow; + } + // Calculate derivative of the InfectedSymptoms_2b compartment. + // Flow from last (sub-) compartment of C_2b must be split between + // the first subcompartment of InfectedSymptoms_2b and Recovered_ab. + dydt[Ri_ab] = dydt[ISyi_2b_first_index] * params.template get()[Group]; + dydt[ISyi_2b_first_index] = + dydt[ISyi_2b_first_index] * (1 - params.template get()[Group]); + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISyi_2b_first_index + subcomp]; + dydt[ISyi_2b_first_index + subcomp] -= flow; + dydt[ISyi_2b_first_index + subcomp + 1] = flow; } + // Calculate derivative of the InfectedSevere_2a compartment. + // again split the flow from the last subcompartment of I_2a + dydt[Ri_ab] += dydt[ISevi_2a_first_index] * (1 - params.template get()[Group]); + dydt[ISevi_2a_first_index] = + dydt[ISevi_2a_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISevi_2a_first_index + subcomp]; + dydt[ISevi_2a_first_index + subcomp] -= flow; + dydt[ISevi_2a_first_index + subcomp + 1] = flow; + } // Calculate derivative of the InfectedSevere compartment. - dydt[Ri] += dydt[ISevi_first_index] * (1 - params.template get()[Group]); - dydt[ISevi_first_index] = dydt[ISevi_first_index] * params.template get()[Group]; + // again split the flow from the last subcompartment of I_2b + dydt[Ri_ab] += dydt[ISevi_2b_first_index] * (1 - params.template get()[Group]); + dydt[ISevi_2b_first_index] = + dydt[ISevi_2b_first_index] * params.template get()[Group]; for (size_t subcomp = 0; - subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISevi_first_index + subcomp]; - dydt[ISevi_first_index + subcomp] -= flow; - dydt[ISevi_first_index + subcomp + 1] = flow; + subcomp < LctStateGroup::template get_num_subcompartments(); + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ISevi_2b_first_index + subcomp]; + dydt[ISevi_2b_first_index + subcomp] -= flow; + dydt[ISevi_2b_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedCritical compartment. - dydt[Ri] += dydt[ICri_first_index] * (1 - params.template get()[Group]); - dydt[ICri_first_index] = dydt[ICri_first_index] * params.template get()[Group]; + // again split flow from last subcompartment of H_2a between R_ab and U_2a + dydt[Ri_ab] += dydt[ICri_2a_first_index] * (1 - params.template get()[Group]); + dydt[ICri_2a_first_index] = dydt[ICri_2a_first_index] * params.template get()[Group]; for (size_t subcomp = 0; - subcomp < LctStateGroup::template get_num_subcompartments() - 1; + subcomp < LctStateGroup::template get_num_subcompartments() - 1; subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ICri_first_index + subcomp]; - dydt[ICri_first_index + subcomp] -= flow; - dydt[ICri_first_index + subcomp + 1] = flow; + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ICri_2a_first_index + subcomp]; + dydt[ICri_2a_first_index + subcomp] -= flow; + dydt[ICri_2a_first_index + subcomp + 1] = flow; } + // Calculate derivative of the InfectedCritical compartment. + // again split flow from last subcompartment of H_1b between R_b and U_1b + dydt[Ri_ab] += dydt[ICri_2b_first_index] * (1 - params.template get()[Group]); + dydt[ICri_2b_first_index] = dydt[ICri_2b_first_index] * params.template get()[Group]; + for (size_t subcomp = 0; + subcomp < LctStateGroup::template get_num_subcompartments() - 1; + subcomp++) { + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * y[ICri_2b_first_index + subcomp]; + dydt[ICri_2b_first_index + subcomp] -= flow; + dydt[ICri_2b_first_index + subcomp + 1] = flow; + } + // Last flow from InfectedCritical has to be divided between Recovered and Dead. // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ri - 1]; - dydt[Ri - 1] -= flow; - dydt[Ri] = dydt[Ri] + (1 - params.template get()[Group]) * flow; - dydt[Di] = params.template get()[Group] * flow; + // for 2a + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * + y[ICri_2a_first_index + + LctStateGroup::template get_num_subcompartments() - 1]; + dydt[ICri_2a_first_index + + LctStateGroup::template get_num_subcompartments() - 1] -= flow; + dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; + dydt[Di_a] = params.template get()[Group] * flow; + // for 1b + flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get()[Group]) * + y[ICri_2b_first_index + + LctStateGroup::template get_num_subcompartments() - 1]; + dydt[ICri_2b_first_index + + LctStateGroup::template get_num_subcompartments() - 1] -= flow; + dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; + dydt[Di_b] = params.template get()[Group] * flow; // Function call for next group if applicable. + /* only one (age) group, do not need this if constexpr (Group + 1 < num_groups) { get_derivatives_impl(pop, y, t, dydt); - } + }*/ } + // for calculating flows that are caused by people becoming infected (transmission of infection, therefore needs infectious people) + // not just natural progression of the illness that would happen without external forces /** * @brief Calculates the derivative of the Susceptible compartment for Group1. * @@ -300,44 +589,108 @@ class Model */ template void interact(Eigen::Ref> pop, Eigen::Ref> y, - ScalarType t, Eigen::Ref> dydt) const + ScalarType t, Eigen::Ref> dydt, double* part_a, double* part_b, + size_t compartment_index, int which_disease) const { static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0), "The template parameters Group1 & Group2 should be valid."); - using LctStateGroup2 = type_at_index_t; - size_t Si_1 = this->populations.template get_first_index_of_group(); - ScalarType infectedNoSymptoms_2 = 0; - ScalarType infectedSymptoms_2 = 0; - auto params = this->parameters; + using LctStateGroup2 = type_at_index_t; + //size_t Si_1 = this->populations.template get_first_index_of_group(); + ScalarType infectedNoSymptoms_2_a = 0; + ScalarType infectedSymptoms_2_a = 0; + ScalarType infectedNoSymptoms_2_b = 0; + ScalarType infectedSymptoms_2_b = 0; + auto params = this->parameters; size_t first_index_group2 = this->populations.template get_first_index_of_group(); - // Calculate sum of all subcompartments for InfectedNoSymptoms of Group2. - infectedNoSymptoms_2 = + // Calculate sum of all subcompartments for InfectedNoSymptoms for disease a of Group2. + infectedNoSymptoms_2_a = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum() + + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum(); + // Calculate sum of all subcompartments for InfectedSymptoms for disease a of Group2. + infectedSymptoms_2_a = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum() + + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum(); + // Calculate sum of all subcompartments for InfectedNoSymptoms for disease b of Group2. + infectedNoSymptoms_2_b = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum() + pop.segment(first_index_group2 + - LctStateGroup2::template get_first_index(), - LctStateGroup2::template get_num_subcompartments()) + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) .sum(); - // Calculate sum of all subcompartments for InfectedSymptoms of Group2. - infectedSymptoms_2 = + // Calculate sum of all subcompartments for InfectedSymptoms for disease b of Group2. + infectedSymptoms_2_a = + pop.segment(first_index_group2 + + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) + .sum() + pop.segment(first_index_group2 + - LctStateGroup2::template get_first_index(), - LctStateGroup2::template get_num_subcompartments()) + LctStateGroup2::template get_first_index(), + LctStateGroup2::template get_num_subcompartments()) .sum(); // Size of the Subpopulation Group2 without dead people. double N_2 = pop.segment(first_index_group2, LctStateGroup2::Count - 1).sum(); const double divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; ScalarType season_val = 1 + params.template get() * sin(3.141592653589793 * ((params.template get() + t) / 182.5 + 0.5)); - dydt[Si_1] += -y[Si_1] * divN_2 * season_val * params.template get()[Group1] * - params.template get().get_cont_freq_mat().get_matrix_at(t)( - static_cast(Group1), static_cast(Group2)) * - (params.template get()[Group2] * infectedNoSymptoms_2 + - params.template get()[Group2] * infectedSymptoms_2); - // Function call for next interacting group if applicable. + + if (which_disease == 0) { // disease a + dydt[compartment_index] += + -y[compartment_index] * divN_2 * season_val * + params.template get().get_cont_freq_mat().get_matrix_at(t)( + static_cast(Group1), static_cast(Group2)) * + params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_a + + params.template get()[Group2] * infectedSymptoms_2_a); + } + else if (which_disease == 1) { // disease b + dydt[compartment_index] += + -y[compartment_index] * divN_2 * season_val * + params.template get().get_cont_freq_mat().get_matrix_at(t)( + static_cast(Group1), static_cast(Group2)) * + (params.template get()[Group2] * infectedNoSymptoms_2_b + + params.template get()[Group2] * infectedSymptoms_2_b); + } + else if (which_disease == 2) { // both diseases + dydt[compartment_index] += + -y[compartment_index] * divN_2 * season_val * + params.template get().get_cont_freq_mat().get_matrix_at(t)( + static_cast(Group1), static_cast(Group2)) * + (params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_a + + params.template get()[Group2] * infectedSymptoms_2_a) + + params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_b + + params.template get()[Group2] * infectedSymptoms_2_b)); + } + /* only one group, you do not need this if constexpr (Group2 + 1 < num_groups) { interact(pop, y, t, dydt); - } + }*/ + + *part_a = params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_a + + params.template get()[Group2] * infectedSymptoms_2_a); + + *part_b = params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_b + + params.template get()[Group2] * infectedSymptoms_2_b); } /** @@ -358,12 +711,15 @@ class Model Group); return true; } - if (LctStateGroup::template get_num_subcompartments() != 1) { + if (LctStateGroup::template get_num_subcompartments() != 1 || + LctStateGroup::template get_num_subcompartments() != 1 || + LctStateGroup::template get_num_subcompartments() != 1) { log_warning("Constraint check: The number of subcompartments for Recovered of group {} should be one!", Group); return true; } - if (LctStateGroup::template get_num_subcompartments() != 1) { + if (LctStateGroup::template get_num_subcompartments() != 1 || + LctStateGroup::template get_num_subcompartments() != 1) { log_warning("Constraint check: The number of subcompartments for Dead of group {} should be one!", Group); return true; } diff --git a/cpp/models/lct_secir_2_disease/parameters.h b/cpp/models/lct_secir_2_disease/parameters.h index 4884d6e003..32e3106eaa 100644 --- a/cpp/models/lct_secir_2_disease/parameters.h +++ b/cpp/models/lct_secir_2_disease/parameters.h @@ -25,6 +25,8 @@ #include "memilio/utils/parameter_set.h" #include "memilio/utils/logging.h" #include "memilio/utils/uncertain_value.h" +//#include "memilio/math/eigen.h" +#include "memilio/epidemiology/uncertain_matrix.h" /* #include "memilio/math/eigen.h" #include "memilio/epidemiology/uncertain_matrix.h" */ @@ -42,10 +44,10 @@ namespace lsecir2d * @brief Average time spent in the Exposed compartment for disease a. */ struct TimeExposed_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -58,10 +60,10 @@ struct TimeExposed_a { * symptoms or recover for disease a in day unit. */ struct TimeInfectedNoSymptoms_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -74,10 +76,10 @@ struct TimeInfectedNoSymptoms_a { * or recover for disease a in day unit. */ struct TimeInfectedSymptoms_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -89,10 +91,10 @@ struct TimeInfectedSymptoms_a { * @brief Average time being in the Hospital before treated by ICU or recover for disease a in day unit. */ struct TimeInfectedSevere_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -104,10 +106,10 @@ struct TimeInfectedSevere_a { * @brief Average time treated by ICU before dead or recover for disease a in day unit. */ struct TimeInfectedCritical_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -119,10 +121,10 @@ struct TimeInfectedCritical_a { * @brief Probability of getting infected from a contact for disease a. */ struct TransmissionProbabilityOnContact_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -134,10 +136,10 @@ struct TransmissionProbabilityOnContact_a { * @brief Average time spent in the Exposed compartment for disease b. */ struct TimeExposed_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -150,10 +152,10 @@ struct TimeExposed_b { * symptoms or recover for disease b in day unit. */ struct TimeInfectedNoSymptoms_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -166,10 +168,10 @@ struct TimeInfectedNoSymptoms_b { * or recover for disease b in day unit. */ struct TimeInfectedSymptoms_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -181,10 +183,10 @@ struct TimeInfectedSymptoms_b { * @brief Average time being in the Hospital before treated by ICU or recover for disease b in day unit. */ struct TimeInfectedSevere_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -196,10 +198,10 @@ struct TimeInfectedSevere_b { * @brief Average time treated by ICU before dead or recover for disease b in day unit. */ struct TimeInfectedCritical_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -211,10 +213,10 @@ struct TimeInfectedCritical_b { * @brief Probability of getting infected from a contact for disease b. */ struct TransmissionProbabilityOnContact_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.1); + return Type::Constant(size, 1, 0.2); } static std::string name() { @@ -225,16 +227,17 @@ struct TransmissionProbabilityOnContact_b { /** * @brief The contact patterns within the society are modelled using an UncertainContactMatrix. */ -struct ContactRate { - using Type = UncertainValue; - - static Type get_default() +struct ContactPatterns { + using Type = UncertainContactMatrix; + static Type get_default(size_t size = 1) // no age groups { - return Type(10); + mio::ContactMatrixGroup contact_matrix(1, (Eigen::Index)size); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant((Eigen::Index)size, (Eigen::Index)size, 10.)); + return Type(contact_matrix); } static std::string name() { - return "ContactRate"; + return "ContactPatterns"; } }; @@ -242,10 +245,10 @@ struct ContactRate { * @brief The relative InfectedNoSymptoms infectability for disease a. */ struct RelativeTransmissionNoSymptoms_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -257,10 +260,10 @@ struct RelativeTransmissionNoSymptoms_a { * @brief The risk of infection from symptomatic cases for disease a. */ struct RiskOfInfectionFromSymptomatic_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -272,10 +275,10 @@ struct RiskOfInfectionFromSymptomatic_a { * @brief The relative InfectedNoSymptoms infectability for disease b. */ struct RelativeTransmissionNoSymptoms_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -287,10 +290,10 @@ struct RelativeTransmissionNoSymptoms_b { * @brief The risk of infection from symptomatic cases for disease b. */ struct RiskOfInfectionFromSymptomatic_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(1); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -302,10 +305,10 @@ struct RiskOfInfectionFromSymptomatic_b { * @brief The percentage of asymptomatic cases for disease a in the SECIR model. */ struct RecoveredPerInfectedNoSymptoms_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -317,10 +320,10 @@ struct RecoveredPerInfectedNoSymptoms_a { * @brief The percentage of hospitalized patients per infected patients for disease a in the SECIR model. */ struct SeverePerInfectedSymptoms_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -332,10 +335,10 @@ struct SeverePerInfectedSymptoms_a { * @brief The percentage of ICU patients per hospitalized patients for disease a in the SECIR model. */ struct CriticalPerSevere_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -347,10 +350,10 @@ struct CriticalPerSevere_a { * @brief The percentage of dead patients per ICU patients for disease a in the SECIR model. */ struct DeathsPerCritical_a { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.1); } static std::string name() { @@ -362,10 +365,10 @@ struct DeathsPerCritical_a { * @brief The percentage of asymptomatic cases for disease b in the SECIR model. */ struct RecoveredPerInfectedNoSymptoms_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -377,10 +380,10 @@ struct RecoveredPerInfectedNoSymptoms_b { * @brief The percentage of hospitalized patients per infected patients for disease b in the SECIR model. */ struct SeverePerInfectedSymptoms_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -392,10 +395,10 @@ struct SeverePerInfectedSymptoms_b { * @brief The percentage of ICU patients per hospitalized patients for disease b in the SECIR model. */ struct CriticalPerSevere_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.5); } static std::string name() { @@ -407,10 +410,10 @@ struct CriticalPerSevere_b { * @brief The percentage of dead patients per ICU patients for disease b in the SECIR model. */ struct DeathsPerCritical_b { - using Type = UncertainValue; - static Type get_default() + using Type = Eigen::VectorX>; + static Type get_default(size_t size = 1) { - return Type(0.5); + return Type::Constant(size, 1, 0.1); } static std::string name() { @@ -457,7 +460,7 @@ using ParametersBase = ParameterSetget() < 0.0 || this->get() > 0.5) { - log_warning("Constraint check: Parameter Seasonality should lie between {:0.4f} and {:.4f}", 0.0, 0.5); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeExposed_a is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeExposed_b is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedNoSymptoms_a is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedNoSymptoms_b is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedSymptoms_a is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedSymptoms_b is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedSevere_a is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedSevere_b is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedCritical_a is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 1.0) { - log_error("Constraint check: Parameter TimeInfectedCritical_b is smaller than {:.4f}", 1.0); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter TransmissionProbabilityOnContact_a smaller {:d} or larger {:d}", 0, - 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter TransmissionProbabilityOnContact_b smaller {:d} or larger {:d}", 0, - 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_a smaller {:d} or larger {:d}", 0, - 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_b smaller {:d} or larger {:d}", 0, - 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || - this->get() > 1.0) { - log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter SeverePerInfectedSymptoms_a smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter SeverePerInfectedSymptoms_b smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter CriticalPerSevere_a smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter CriticalPerSevere_b smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter DeathsPerCritical_a smaller {:d} or larger {:d}", 0, 1); - return true; - } - - if (this->get() < 0.0 || this->get() > 1.0) { - log_error("Constraint check: Parameter DeathsPerCritical_b smaller {:d} or larger {:d}", 0, 1); - return true; + for (size_t i = 0; i < m_num_groups; ++i) { + if (this->get() < 0.0 || this->get() > 0.5) { + log_warning("Constraint check: Parameter Seasonality should lie between {:0.4f} and {:.4f}", 0.0, 0.5); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeExposed_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeExposed_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedNoSymptoms_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedNoSymptoms_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSymptoms_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSymptoms_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSevere_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedSevere_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedCritical_a is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 1.0) { + log_error("Constraint check: Parameter TimeInfectedCritical_b is smaller than {:.4f}", 1.0); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter TransmissionProbabilityOnContact_a smaller {:d} or larger {:d}", + 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter TransmissionProbabilityOnContact_b smaller {:d} or larger {:d}", + 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_a smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_b smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_a smaller {:d} or larger {:d}", + 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_b smaller {:d} or larger {:d}", + 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_a smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_b smaller {:d} or larger {:d}", 0, + 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter SeverePerInfectedSymptoms_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || + this->get()[i] > 1.0) { + log_error("Constraint check: Parameter SeverePerInfectedSymptoms_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + log_error("Constraint check: Parameter CriticalPerSevere_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + log_error("Constraint check: Parameter CriticalPerSevere_b smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + log_error("Constraint check: Parameter DeathsPerCritical_a smaller {:d} or larger {:d}", 0, 1); + return true; + } + + if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + log_error("Constraint check: Parameter DeathsPerCritical_b smaller {:d} or larger {:d}", 0, 1); + return true; + } } return false; @@ -633,11 +644,11 @@ class Parameters : public ParametersBase private: Parameters(ParametersBase&& base) : ParametersBase(std::move(base)) - //, m_num_groups(this->template get().get_cont_freq_mat().get_num_groups()) + , m_num_groups(this->template get().get_cont_freq_mat().get_num_groups()) { } - //size_t m_num_groups; + size_t m_num_groups; public: /** From e2dcde1378ebbba723d935b4a7d5ac9a8d14d563 Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 31 Jul 2025 12:00:29 +0200 Subject: [PATCH 04/16] Working LCT2D Model (no age groups) with test --- cpp/CMakeLists.txt | 2 +- cpp/examples/CMakeLists.txt | 6 +- cpp/examples/lct_secir.cpp | 25 +- ...2_disease.cpp => lct_secir_2_diseases.cpp} | 98 +- cpp/models/lct_secir_2_disease/CMakeLists.txt | 12 - .../lct_secir_2_diseases/CMakeLists.txt | 12 + .../README.md | 0 .../infection_state.h | 36 +- .../model.cpp | 2 +- .../model.h | 48 +- .../parameters.h | 32 +- cpp/tests/CMakeLists.txt | 1 + cpp/tests/test_lct_secir_2_diseases.cpp | 977 ++++++++++++++++++ 13 files changed, 1109 insertions(+), 142 deletions(-) rename cpp/examples/{lct_secir_2_disease.cpp => lct_secir_2_diseases.cpp} (70%) delete mode 100644 cpp/models/lct_secir_2_disease/CMakeLists.txt create mode 100644 cpp/models/lct_secir_2_diseases/CMakeLists.txt rename cpp/models/{lct_secir_2_disease => lct_secir_2_diseases}/README.md (100%) rename cpp/models/{lct_secir_2_disease => lct_secir_2_diseases}/infection_state.h (87%) rename cpp/models/{lct_secir_2_disease => lct_secir_2_diseases}/model.cpp (94%) rename cpp/models/{lct_secir_2_disease => lct_secir_2_diseases}/model.h (95%) rename cpp/models/{lct_secir_2_disease => lct_secir_2_diseases}/parameters.h (96%) create mode 100644 cpp/tests/test_lct_secir_2_diseases.cpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index fa4cae5ad4..099ba5c41f 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -174,7 +174,7 @@ if(MEMILIO_BUILD_MODELS) add_subdirectory(models/ode_secirts) add_subdirectory(models/ode_secirvvs) add_subdirectory(models/lct_secir) - add_subdirectory(models/lct_secir_2_disease) + add_subdirectory(models/lct_secir_2_diseases) add_subdirectory(models/glct_secir) add_subdirectory(models/ide_secir) add_subdirectory(models/ide_seir) diff --git a/cpp/examples/CMakeLists.txt b/cpp/examples/CMakeLists.txt index 6f8bde3add..1eb1a1433e 100644 --- a/cpp/examples/CMakeLists.txt +++ b/cpp/examples/CMakeLists.txt @@ -128,9 +128,9 @@ add_executable(lct_secir_example lct_secir.cpp) target_link_libraries(lct_secir_example PRIVATE memilio lct_secir) target_compile_options(lct_secir_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) -add_executable(lct_secir_2_disease_example lct_secir_2_disease.cpp) -target_link_libraries(lct_secir_2_disease_example PRIVATE memilio lct_secir_2_disease) -target_compile_options(lct_secir_2_disease_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) +add_executable(lct_secir_2_diseases_example lct_secir_2_diseases.cpp) +target_link_libraries(lct_secir_2_diseases_example PRIVATE memilio lct_secir_2_diseases) +target_compile_options(lct_secir_2_diseases_example PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) add_executable(glct_secir_example glct_secir.cpp) target_link_libraries(glct_secir_example PRIVATE memilio glct_secir) diff --git a/cpp/examples/lct_secir.cpp b/cpp/examples/lct_secir.cpp index 03278b1982..123759adf0 100644 --- a/cpp/examples/lct_secir.cpp +++ b/cpp/examples/lct_secir.cpp @@ -37,8 +37,8 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. - constexpr size_t NumExposed = 2, NumInfectedNoSymptoms = 3, NumInfectedSymptoms = 1, NumInfectedSevere = 1, - NumInfectedCritical = 5; + constexpr size_t NumExposed = 1, NumInfectedNoSymptoms = 1, NumInfectedSymptoms = 1, NumInfectedSevere = 1, + NumInfectedCritical = 1; using InfState = mio::lsecir::InfectionState; using LctState = mio::LctInfectionState; @@ -49,16 +49,16 @@ int main() // defined initial vector is used to initialize the LCT model. bool use_initializer_flows = false; - ScalarType tmax = 10; + ScalarType tmax = 5; // Set Parameters. - model.parameters.get()[0] = 3.2; - model.parameters.get()[0] = 2.; - model.parameters.get()[0] = 5.8; - model.parameters.get()[0] = 9.5; - model.parameters.get()[0] = 7.1; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 0.05; + model.parameters.get()[0] = 0.1; mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); @@ -117,8 +117,7 @@ int main() // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{750}, {30, 20}, {20, 10, 10}, {50}, - {50}, {10, 10, 5, 3, 2}, {20}, {10}}; + std::vector> initial_populations = {{9000}, {1000}, {0}, {0}, {0}, {0}, {0}, {0}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -154,10 +153,10 @@ int main() } // Perform a simulation. - mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); + mio::TimeSeries result = mio::simulate(0, tmax, 0.1, model); // The simulation result is divided by subcompartments. // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); - interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 4); + interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 3); } diff --git a/cpp/examples/lct_secir_2_disease.cpp b/cpp/examples/lct_secir_2_diseases.cpp similarity index 70% rename from cpp/examples/lct_secir_2_disease.cpp rename to cpp/examples/lct_secir_2_diseases.cpp index 91e6e14218..7bb5290319 100644 --- a/cpp/examples/lct_secir_2_disease.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -18,8 +18,8 @@ * limitations under the License. */ -#include "lct_secir_2_disease/model.h" -#include "lct_secir_2_disease/infection_state.h" +#include "lct_secir_2_diseases/model.h" +#include "lct_secir_2_diseases/infection_state.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" //#include "memilio/epidemiology/uncertain_matrix.h" @@ -36,43 +36,42 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. - constexpr size_t NumExposed_1a = 2, NumInfectedNoSymptoms_1a = 3, NumInfectedSymptoms_1a = 1, - NumInfectedSevere_1a = 1, NumInfectedCritical_1a = 5, NumExposed_2a = 2, - NumInfectedNoSymptoms_2a = 3, NumInfectedSymptoms_2a = 1, NumInfectedSevere_2a = 1, - NumInfectedCritical_2a = 5, NumExposed_1b = 2, NumInfectedNoSymptoms_1b = 3, - NumInfectedSymptoms_1b = 1, NumInfectedSevere_1b = 1, NumInfectedCritical_1b = 5, - NumExposed_2b = 2, NumInfectedNoSymptoms_2b = 3, NumInfectedSymptoms_2b = 1, - NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 5; + constexpr size_t NumExposed_1a = 1, NumInfectedNoSymptoms_1a = 1, NumInfectedSymptoms_1a = 1, + NumInfectedSevere_1a = 1, NumInfectedCritical_1a = 1, NumExposed_2a = 1, + NumInfectedNoSymptoms_2a = 1, NumInfectedSymptoms_2a = 1, NumInfectedSevere_2a = 1, + NumInfectedCritical_2a = 1, NumExposed_1b = 1, NumInfectedNoSymptoms_1b = 1, + NumInfectedSymptoms_1b = 1, NumInfectedSevere_1b = 1, NumInfectedCritical_1b = 1, + NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 1, NumInfectedSymptoms_2b = 1, + NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 1; using InfState = mio::lsecir2d::InfectionState; - using LctState = - mio::LctInfectionState; + using LctState = mio::LctInfectionState< + InfState, 3, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, + NumInfectedCritical_1a, 1, 1, NumExposed_2a, NumInfectedNoSymptoms_2a, NumInfectedSymptoms_2a, + NumInfectedSevere_2a, NumInfectedCritical_2a, NumExposed_1b, NumInfectedNoSymptoms_1b, NumInfectedSymptoms_1b, + NumInfectedSevere_1b, NumInfectedCritical_1b, 1, 1, NumExposed_2b, NumInfectedNoSymptoms_2b, + NumInfectedSymptoms_2b, NumInfectedSevere_2b, NumInfectedCritical_2b, 1>; using Model = mio::lsecir2d::Model; Model model; // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually // defined initial vector is used to initialize the LCT model. - ScalarType tmax = 10; + ScalarType tmax = 5; // Set Parameters. - model.parameters.get()[0] = 3.2; - model.parameters.get()[0] = 3.2; - model.parameters.get()[0] = 2.; - model.parameters.get()[0] = 2.; - model.parameters.get()[0] = 5.8; - model.parameters.get()[0] = 5.8; - model.parameters.get()[0] = 9.5; - model.parameters.get()[0] = 9.5; - model.parameters.get()[0] = 7.1; - model.parameters.get()[0] = 7.1; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 0.05; - model.parameters.get()[0] = 0.05; + model.parameters.get()[0] = 0.1; + model.parameters.get()[0] = 0.1; mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); @@ -97,32 +96,9 @@ int main() // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{750}, - {30, 20}, - {20, 10, 10}, - {50}, - {50}, - {10, 10, 5, 3, 2}, - {30, 20}, - {20, 10, 10}, - {50}, - {50}, - {10, 10, 5, 3, 2}, - {20}, - {10}, - {30, 20}, - {20, 10, 10}, - {50}, - {50}, - {10, 10, 5, 3, 2}, - {30, 20}, - {20, 10, 10}, - {50}, - {50}, - {10, 10, 5, 3, 2}, - {20}, - {10}, - {0}}; + std::vector> initial_populations = {{0, 0, 0}, {0}, {0}, {0}, {0}, {0}, {9000}, {0}, {0}, + {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {1000}, {0}, {0}, {0}, {0}, {0}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -171,13 +147,15 @@ int main() } // Perform a simulation. - mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); + mio::TimeSeries result = mio::simulate(0, tmax, 0.1, model); // The simulation result is divided by subcompartments. // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); - interpolated_results.print_table({"S", "E1a", "C1a", "I1a", "H1a", "U1a", "E2a", "C2a", "I2a", - "H2a", "U2a", "Ra", "Da", "E1b", "C1b", "I1b", "H1b", "U1b", - "E2b", "C2b", "I2b", "H2b", "U2b", "Rb", "Db", "Rab"}, - 12, 4); + + interpolated_results.print_table({" S", " E1a", " C1a", " I1a", " H1a", " U1a", " Ra", + " Da", " E2a", " C2a", " I2a", " H2a", " U2a", " E1b", + " C1b", " I1b", " H1b", " U1b", " Rb", " Db", " E2b", + " C2b", " I2b", " H2b", " U2b", " Rab"}, + 7, 3); } diff --git a/cpp/models/lct_secir_2_disease/CMakeLists.txt b/cpp/models/lct_secir_2_disease/CMakeLists.txt deleted file mode 100644 index f4a7037f4f..0000000000 --- a/cpp/models/lct_secir_2_disease/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -add_library(lct_secir_2_disease - infection_state.h - model.h - model.cpp - parameters.h -) -target_link_libraries(lct_secir_2_disease PUBLIC memilio) -target_include_directories(lct_secir_2_disease PUBLIC - $ - $ -) -target_compile_options(lct_secir_2_disease PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/models/lct_secir_2_diseases/CMakeLists.txt b/cpp/models/lct_secir_2_diseases/CMakeLists.txt new file mode 100644 index 0000000000..ab0fcda2e2 --- /dev/null +++ b/cpp/models/lct_secir_2_diseases/CMakeLists.txt @@ -0,0 +1,12 @@ +add_library(lct_secir_2_diseases + infection_state.h + model.h + model.cpp + parameters.h +) +target_link_libraries(lct_secir_2_diseases PUBLIC memilio) +target_include_directories(lct_secir_2_diseases PUBLIC + $ + $ +) +target_compile_options(lct_secir_2_diseases PRIVATE ${MEMILIO_CXX_FLAGS_ENABLE_WARNING_ERRORS}) diff --git a/cpp/models/lct_secir_2_disease/README.md b/cpp/models/lct_secir_2_diseases/README.md similarity index 100% rename from cpp/models/lct_secir_2_disease/README.md rename to cpp/models/lct_secir_2_diseases/README.md diff --git a/cpp/models/lct_secir_2_disease/infection_state.h b/cpp/models/lct_secir_2_diseases/infection_state.h similarity index 87% rename from cpp/models/lct_secir_2_disease/infection_state.h rename to cpp/models/lct_secir_2_diseases/infection_state.h index bfe68b2eb7..1315b5cbb9 100644 --- a/cpp/models/lct_secir_2_disease/infection_state.h +++ b/cpp/models/lct_secir_2_diseases/infection_state.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef LCT_SECIR_2_DISEASE_INFECTIONSTATE_H -#define LCT_SECIR_2_DISEASE_INFECTIONSTATE_H +#ifndef LCT_SECIR_2_DISEASES_INFECTIONSTATE_H +#define LCT_SECIR_2_DISEASES_INFECTIONSTATE_H namespace mio { @@ -40,30 +40,32 @@ enum class InfectionState InfectedSymptoms_1a = 3, InfectedSevere_1a = 4, InfectedCritical_1a = 5, + Recovered_a = 6, + Dead_a = 7, // second infection with disease a - Exposed_2a = 6, - InfectedNoSymptoms_2a = 7, - InfectedSymptoms_2a = 8, - InfectedSevere_2a = 9, - InfectedCritical_2a = 10, + Exposed_2a = 8, + InfectedNoSymptoms_2a = 9, + InfectedSymptoms_2a = 10, + InfectedSevere_2a = 11, + InfectedCritical_2a = 12, // R and D for disease a - Recovered_a = 11, - Dead_a = 12, + // first infection with disease b Exposed_1b = 13, InfectedNoSymptoms_1b = 14, InfectedSymptoms_1b = 15, InfectedSevere_1b = 16, InfectedCritical_1b = 17, + Recovered_b = 18, + Dead_b = 19, // second infection with disease b - Exposed_2b = 18, - InfectedNoSymptoms_2b = 19, - InfectedSymptoms_2b = 20, - InfectedSevere_2b = 21, - InfectedCritical_2b = 22, + Exposed_2b = 20, + InfectedNoSymptoms_2b = 21, + InfectedSymptoms_2b = 22, + InfectedSevere_2b = 23, + InfectedCritical_2b = 24, // R and D for disease b - Recovered_b = 23, - Dead_b = 24, + // Recovered from both diseases Recovered_ab = 25, Count = 26 @@ -125,4 +127,4 @@ enum class InfectionTransition } // namespace lsecir2d } // namespace mio -#endif // LCT_SECIR_2_DISEASE_INFECTIONSTATE_H \ No newline at end of file +#endif // LCT_SECIR_2_DISEASES_INFECTIONSTATE_H \ No newline at end of file diff --git a/cpp/models/lct_secir_2_disease/model.cpp b/cpp/models/lct_secir_2_diseases/model.cpp similarity index 94% rename from cpp/models/lct_secir_2_disease/model.cpp rename to cpp/models/lct_secir_2_diseases/model.cpp index f4c250e14f..07914a306c 100644 --- a/cpp/models/lct_secir_2_disease/model.cpp +++ b/cpp/models/lct_secir_2_diseases/model.cpp @@ -18,7 +18,7 @@ * limitations under the License. */ -#include "lct_secir_2_disease/model.h" +#include "lct_secir_2_diseases/model.h" namespace mio { diff --git a/cpp/models/lct_secir_2_disease/model.h b/cpp/models/lct_secir_2_diseases/model.h similarity index 95% rename from cpp/models/lct_secir_2_disease/model.h rename to cpp/models/lct_secir_2_diseases/model.h index 44c3da7ec5..50caa5cfbc 100644 --- a/cpp/models/lct_secir_2_disease/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -21,8 +21,8 @@ #ifndef LCT_SECIR_2_DISEASE_MODEL_H #define LCT_SECIR_2_DISEASE_MODEL_H -#include "lct_secir_2_disease/parameters.h" -#include "lct_secir_2_disease/infection_state.h" +#include "lct_secir_2_diseases/parameters.h" +#include "lct_secir_2_diseases/infection_state.h" #include "memilio/compartments/compartmentalmodel.h" #include "memilio/epidemiology/lct2d_populations.h" #include "memilio/config.h" @@ -249,15 +249,17 @@ class Model // Calculate derivative of the Susceptible compartment. // outflow generated by disease a and disease b both - double part_a = 0; - double part_b = 0; - interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); // S is affected by both diseases + double part_a = 0.; + double part_b = 0.; + interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, first_index_group, + 2); // S is affected by both diseases, try group=0 (no age groups) // split flow - double div_part_both = (part_a + part_b < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); + double div_part_both = + ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); // Start with derivatives of first infections, so 1a and 1b compartments - // Calculate derivative of the Exposed_1x compartments, split flow from susceptible. + // Calculate derivative of the Exposed_1x compartments, split flow from Susceptible. // Exposed 1 a: dydt[Ei_1a_first_index] = -dydt[first_index_group] * part_a * div_part_both; for (size_t subcomp = 0; @@ -348,7 +350,7 @@ class Model dydt[ISevi_1a_first_index + subcomp] -= flow; dydt[ISevi_1a_first_index + subcomp + 1] = flow; } - // Calculate derivative of the InfectedSevere compartment. + // Calculate derivative of the InfectedSevere_1b compartment. // again split the flow from the last subcompartment of I_1b dydt[Ri_b] += dydt[ISevi_1b_first_index] * (1 - params.template get()[Group]); dydt[ISevi_1b_first_index] = @@ -397,7 +399,7 @@ class Model dydt[ICri_1a_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get()[Group]) * flow; - dydt[Di_a] = params.template get()[Group] * flow; + dydt[Di_a] = dydt[Di_a] + params.template get()[Group] * flow; // for 1b flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * @@ -406,15 +408,17 @@ class Model dydt[ICri_1b_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; dydt[Ri_b] = dydt[Ri_b] + (1 - params.template get()[Group]) * flow; - dydt[Di_b] = params.template get()[Group] * flow; + dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; // second infection 2x // outflow from Recovered_x, people getting infected for the second time double temp_Ra = dydt[Ri_a]; - interact(pop, y, t, dydt, &part_a, &part_b, Ri_a, 1); // outflow from R_a is only affected by b + interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_a, + 1); // outflow from R_a is only affected by b, no age groups double temp_Rb = dydt[Ri_b]; - interact(pop, y, t, dydt, &part_a, &part_b, Ri_b, 0); // outflow from R_a is only affected by a + interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_b, + 0); // outflow from R_b is only affected by a, no age groups // Calculate derivative of the Exposed_2x compartments // Exposed 2 a @@ -468,7 +472,7 @@ class Model // Calculate derivative of the InfectedSymptoms_2a compartment. // Flow from last (sub-) compartment of C_2a must be split between // the first subcompartment of InfectedSymptoms_2a and Recovered_ab. - dydt[Ri_ab] = dydt[ISyi_2a_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ISyi_2a_first_index] * params.template get()[Group]; dydt[ISyi_2a_first_index] = dydt[ISyi_2a_first_index] * (1 - params.template get()[Group]); for (size_t subcomp = 0; @@ -482,7 +486,7 @@ class Model // Calculate derivative of the InfectedSymptoms_2b compartment. // Flow from last (sub-) compartment of C_2b must be split between // the first subcompartment of InfectedSymptoms_2b and Recovered_ab. - dydt[Ri_ab] = dydt[ISyi_2b_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ISyi_2b_first_index] * params.template get()[Group]; dydt[ISyi_2b_first_index] = dydt[ISyi_2b_first_index] * (1 - params.template get()[Group]); for (size_t subcomp = 0; @@ -556,8 +560,8 @@ class Model dydt[ICri_2a_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; - dydt[Di_a] = params.template get()[Group] * flow; - // for 1b + dydt[Di_a] = dydt[Di_a] + params.template get()[Group] * flow; + // for 2b flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * y[ICri_2b_first_index + @@ -565,7 +569,7 @@ class Model dydt[ICri_2b_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; - dydt[Di_b] = params.template get()[Group] * flow; + dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; // Function call for next group if applicable. /* only one (age) group, do not need this @@ -602,6 +606,8 @@ class Model ScalarType infectedSymptoms_2_b = 0; auto params = this->parameters; + //compartment_index = Si_1; + size_t first_index_group2 = this->populations.template get_first_index_of_group(); // Calculate sum of all subcompartments for InfectedNoSymptoms for disease a of Group2. @@ -635,7 +641,7 @@ class Model LctStateGroup2::template get_num_subcompartments()) .sum(); // Calculate sum of all subcompartments for InfectedSymptoms for disease b of Group2. - infectedSymptoms_2_a = + infectedSymptoms_2_b = pop.segment(first_index_group2 + LctStateGroup2::template get_first_index(), LctStateGroup2::template get_num_subcompartments()) @@ -645,7 +651,10 @@ class Model LctStateGroup2::template get_num_subcompartments()) .sum(); // Size of the Subpopulation Group2 without dead people. - double N_2 = pop.segment(first_index_group2, LctStateGroup2::Count - 1).sum(); + double N_2 = pop.segment(first_index_group2, LctStateGroup2::Count).sum(); // sum over all compartments + N_2 = N_2 - pop.segment(LctStateGroup2::template get_first_index(), 1).sum() - + pop.segment(LctStateGroup2::template get_first_index(), 1) + .sum(); // all people minus dead people const double divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; ScalarType season_val = 1 + params.template get() * sin(3.141592653589793 * ((params.template get() + t) / 182.5 + 0.5)); @@ -664,6 +673,7 @@ class Model -y[compartment_index] * divN_2 * season_val * params.template get().get_cont_freq_mat().get_matrix_at(t)( static_cast(Group1), static_cast(Group2)) * + params.template get()[Group1] * (params.template get()[Group2] * infectedNoSymptoms_2_b + params.template get()[Group2] * infectedSymptoms_2_b); } diff --git a/cpp/models/lct_secir_2_disease/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h similarity index 96% rename from cpp/models/lct_secir_2_disease/parameters.h rename to cpp/models/lct_secir_2_diseases/parameters.h index 32e3106eaa..797f3ec95b 100644 --- a/cpp/models/lct_secir_2_disease/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -18,8 +18,8 @@ * limitations under the License. */ -#ifndef LCT_SECIR_2_DISEASE_PARAMS_H -#define LCT_SECIR_2_DISEASE_PARAMS_H +#ifndef LCT_SECIR_2_DISEASES_PARAMS_H +#define LCT_SECIR_2_DISEASES_PARAMS_H #include "memilio/config.h" #include "memilio/utils/parameter_set.h" @@ -37,7 +37,7 @@ namespace lsecir2d { /********************************************** -* Define Parameters of the LCT-SECIHURD model * +* Define Parameters of the LCT-SECIHURD-2-DISEASES model * **********************************************/ /** @@ -216,7 +216,7 @@ struct TransmissionProbabilityOnContact_b { using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { - return Type::Constant(size, 1, 0.2); + return Type::Constant(size, 1, 1.); } static std::string name() { @@ -302,7 +302,7 @@ struct RiskOfInfectionFromSymptomatic_b { }; /** - * @brief The percentage of asymptomatic cases for disease a in the SECIR model. + * @brief The percentage of asymptomatic cases for disease a. */ struct RecoveredPerInfectedNoSymptoms_a { using Type = Eigen::VectorX>; @@ -317,7 +317,7 @@ struct RecoveredPerInfectedNoSymptoms_a { }; /** - * @brief The percentage of hospitalized patients per infected patients for disease a in the SECIR model. + * @brief The percentage of hospitalized patients per infected patients for disease a. */ struct SeverePerInfectedSymptoms_a { using Type = Eigen::VectorX>; @@ -332,7 +332,7 @@ struct SeverePerInfectedSymptoms_a { }; /** - * @brief The percentage of ICU patients per hospitalized patients for disease a in the SECIR model. + * @brief The percentage of ICU patients per hospitalized patients for disease a. */ struct CriticalPerSevere_a { using Type = Eigen::VectorX>; @@ -347,7 +347,7 @@ struct CriticalPerSevere_a { }; /** - * @brief The percentage of dead patients per ICU patients for disease a in the SECIR model. + * @brief The percentage of dead patients per ICU patients for disease a. */ struct DeathsPerCritical_a { using Type = Eigen::VectorX>; @@ -362,7 +362,7 @@ struct DeathsPerCritical_a { }; /** - * @brief The percentage of asymptomatic cases for disease b in the SECIR model. + * @brief The percentage of asymptomatic cases for disease b. */ struct RecoveredPerInfectedNoSymptoms_b { using Type = Eigen::VectorX>; @@ -377,7 +377,7 @@ struct RecoveredPerInfectedNoSymptoms_b { }; /** - * @brief The percentage of hospitalized patients per infected patients for disease b in the SECIR model. + * @brief The percentage of hospitalized patients per infected patients for disease b. */ struct SeverePerInfectedSymptoms_b { using Type = Eigen::VectorX>; @@ -392,7 +392,7 @@ struct SeverePerInfectedSymptoms_b { }; /** - * @brief The percentage of ICU patients per hospitalized patients for disease b in the SECIR model. + * @brief The percentage of ICU patients per hospitalized patients for disease b. */ struct CriticalPerSevere_b { using Type = Eigen::VectorX>; @@ -407,7 +407,7 @@ struct CriticalPerSevere_b { }; /** - * @brief The percentage of dead patients per ICU patients for disease b in the SECIR model. + * @brief The percentage of dead patients per ICU patients for disease b. */ struct DeathsPerCritical_b { using Type = Eigen::VectorX>; @@ -422,7 +422,7 @@ struct DeathsPerCritical_b { }; /** - * @brief The start day in the LCT SECIR model. + * @brief The start day in the LCT-SECIR-2-DISEASES model. * The start day defines in which season the simulation is started. * If the start day is 180 and simulation takes place from t0=0 to * tmax=100 the days 180 to 280 of the year are simulated. @@ -440,7 +440,7 @@ struct StartDay { }; /** - * @brief The seasonality in the LCT-SECIR model. + * @brief The seasonality in the LCT-SECIR-2-DISEASES model. * The seasonality is given as (1+k*sin()) where the sine * curve is below one in summer and above one in winter. */ @@ -467,7 +467,7 @@ using ParametersBase = CriticalPerSevere_b, DeathsPerCritical_b, StartDay, Seasonality>; /** - * @brief Parameters of an LCT-SECIR model. + * @brief Parameters of an LCT-SECIR-2-DISEASES model. */ class Parameters : public ParametersBase { @@ -666,4 +666,4 @@ class Parameters : public ParametersBase } // namespace lsecir2d } // namespace mio -#endif // LCT_SECIR_2_DISEASE_PARAMS_H +#endif // LCT_SECIR_2_DISEASES_PARAMS_H diff --git a/cpp/tests/CMakeLists.txt b/cpp/tests/CMakeLists.txt index 217e0f0321..0a934c376b 100644 --- a/cpp/tests/CMakeLists.txt +++ b/cpp/tests/CMakeLists.txt @@ -70,6 +70,7 @@ set(TESTSOURCES test_ide_secir_ageres.cpp test_state_age_function.cpp test_lct_secir.cpp + test_lct_secir_2_diseases.cpp test_lct_initializer_flows.cpp test_glct_secir.cpp test_ad.cpp diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp new file mode 100644 index 0000000000..aed9abd0b2 --- /dev/null +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -0,0 +1,977 @@ +/* +* Copyright (C) 2020-2025 MEmilio +* +* Authors: Lena Ploetzke +* +* Contact: Martin J. Kuehn +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include "lct_secir_2_diseases/model.h" +#include "lct_secir_2_diseases/infection_state.h" +#include "lct_secir_2_diseases/parameters.h" +#include "lct_secir/model.h" +#include "memilio/config.h" +#include "memilio/utils/time_series.h" +#include "memilio/utils/logging.h" +#include "memilio/epidemiology/contact_matrix.h" +//#include "memilio/math/eigen.h" +#include "memilio/compartments/simulation.h" +#include "load_test_data.h" + +//#include +#include + +#include +#include "boost/numeric/odeint/stepper/runge_kutta_cash_karp54.hpp" + +// Test confirms that default construction of an LCT2D model works. +TEST(TestLCTSecir2d, simulateDefault) +{ + using InfState = mio::lsecir2d::InfectionState; + using LctState = + mio::LctInfectionState; + using Model = mio::lsecir2d::Model; + ScalarType t0 = 0; + ScalarType tmax = 1; + ScalarType dt = 0.1; + + Eigen::VectorX init = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); + init[0] = 200; + init[3] = 50; // people infected with disease a + init[5] = 30; + init[15] = 50; // people infected with disease b + init[17] = 30; + + Model model; + for (size_t i = 0; i < LctState::Count; i++) { + model.populations[i] = init[i]; + } + + mio::TimeSeries result = mio::simulate(t0, tmax, dt, model); + + EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); + ScalarType sum_pop = init.sum(); + for (Eigen::Index i = 0; i < result.get_num_time_points(); i++) { + EXPECT_NEAR(sum_pop, result[i].sum(), 1e-5); // check that total pop. is constant + } +} + +/* Tests comparing the result for an LCT SECIR 2 DISEASE model with transmission prob. 0 for one disease + with the result of the equivalent LCT SECIR model. */ +// 1. Test: First infection for disease a (transmission prob. of disease b = 0) +TEST(TestLCTSecir2d, compareWithLCTSecir1) +{ + using InfState2d = mio::lsecir2d::InfectionState; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; + + using InfState_lct = mio::lsecir::InfectionState; + using LctState_lct = mio::LctInfectionState; + using Model_lct = mio::lsecir::Model; + + ScalarType t0 = 0; + ScalarType tmax = 5; + ScalarType dt = 0.1; + + // Initialization vector for lct2d model. + Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState2d::Count, 0); + init_lct2d[0] = 200; // lct and lct2d use different infection states + init_lct2d[3] = 50; // make sure initial pop. is in the same compartments for lct and lct2d + init_lct2d[5] = 30; + + // Define LCT2D model. + Model_2d model_lct2d; + //Set initial values + for (size_t i = 0; i < LctState2d::Count; i++) { + model_lct2d.populations[i] = init_lct2d[i]; + } + + // Set Parameters. + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + + model_lct2d.parameters.get()[0] = 0.05; + model_lct2d.parameters.get()[0] = 0.; + + mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get() = 50; + model_lct2d.parameters.get() = 0.1; + + // Initialization vector for LCT model. + Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState_lct::Count, 0); + init_lct[0] = 200; + init_lct[3] = 50; + init_lct[5] = 30; + + // Define LCT model. + Model_lct model_lct; + //Set initial values + for (size_t i = 0; i < LctState_lct::Count; i++) { + model_lct.populations[i] = init_lct[i]; + } + + // Set Parameters. + model_lct.parameters.get()[0] = 3.2; + model_lct.parameters.get()[0] = 2; + model_lct.parameters.get()[0] = 5.8; + model_lct.parameters.get()[0] = 9.5; + model_lct.parameters.get()[0] = 7.1; + + model_lct.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get()[0] = 0.7; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get() = 50; + model_lct.parameters.get() = 0.1; + model_lct.parameters.get()[0] = 0.09; + model_lct.parameters.get()[0] = 0.2; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get()[0] = 0.3; + + // Simulate + mio::TimeSeries result_lct2d = mio::simulate( + t0, tmax, dt, model_lct2d, + std::make_shared>()); + + mio::TimeSeries result_lct = mio::simulate( + t0, tmax, dt, model_lct, + std::make_shared>()); + + // Simulation results should be equal. + // Compare LCT with infection 1a in LCT2D + ASSERT_EQ(result_lct.get_num_time_points(), result_lct2d.get_num_time_points()); + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); + + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Susceptible], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Susceptible], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Exposed], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_1a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedNoSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedNoSymptoms_1a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSymptoms_1a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedCritical], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedCritical_1a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSevere], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_1a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Recovered], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Dead], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_a], 1e-5); + } +} + +// 2. Test: First infection with disease b (transmission prob. of disease a = 0) +TEST(TestLCTSecir2d, compareWithLCTSecir2) +{ + using InfState2d = mio::lsecir2d::InfectionState; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; + ScalarType t0 = 0; + ScalarType tmax = 5; + ScalarType dt = 0.1; + + // Initialization vector for lct2d model. + Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState2d::Count, 0); + init_lct2d[0] = 200; // lct and lct2d use different infection states + init_lct2d[15] = 50; // make sure initial pop. is in the same compartments for lct and lct2d + init_lct2d[17] = 30; + + // Define LCT2D model. + Model_2d model_lct2d; + //Set initial values + for (size_t i = 0; i < LctState2d::Count; i++) { + model_lct2d.populations[i] = init_lct2d[i]; + } + + // Set Parameters. + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + + model_lct2d.parameters.get()[0] = 0.; + model_lct2d.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get() = 50; + model_lct2d.parameters.get() = 0.1; + + using InfState = mio::lsecir::InfectionState; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir::Model; + + // Initialization vector for LCT model. + Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); + init_lct[0] = 200; + init_lct[3] = 50; + init_lct[5] = 30; + + // Define LCT model. + Model model_lct; + //Set initial values + for (size_t i = 0; i < LctState::Count; i++) { + model_lct.populations[i] = init_lct[i]; + } + + // Set Parameters. + model_lct.parameters.get()[0] = 3.2; + model_lct.parameters.get()[0] = 2; + model_lct.parameters.get()[0] = 5.8; + model_lct.parameters.get()[0] = 9.5; + model_lct.parameters.get()[0] = 7.1; + + model_lct.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get()[0] = 0.7; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get() = 50; + model_lct.parameters.get() = 0.1; + model_lct.parameters.get()[0] = 0.09; + model_lct.parameters.get()[0] = 0.2; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get()[0] = 0.3; + + // Simulate + mio::TimeSeries result_lct2d = mio::simulate( + t0, tmax, dt, model_lct2d, + std::make_shared>()); + + mio::TimeSeries result_lct = mio::simulate( + t0, tmax, dt, model_lct, + std::make_shared>()); + + // Simulation results should be equal. + // Compare LCT with Infection 1b in LCT2D + ASSERT_EQ(result_lct.get_num_time_points(), result_lct2d.get_num_time_points()); + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); + + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Susceptible], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Susceptible], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Exposed], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_1b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedNoSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedNoSymptoms_1b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSymptoms_1b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedCritical], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedCritical_1b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSevere], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_1b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Recovered], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Dead], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_b], 1e-5); + } +} + +// 3. Test: Second infection with disease a (transmission prob. of disease b = 0, start in Recovered_b)*/ +TEST(TestLCTSecir2d, compareWithLCTSecir3) +{ + using InfState_2d = mio::lsecir2d::InfectionState; + using LctState_2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; + + using InfState_lct = mio::lsecir::InfectionState; + using LctState_lct = mio::LctInfectionState; + using Model_lct = mio::lsecir::Model; + + ScalarType t0 = 0; + ScalarType tmax = 5; + ScalarType dt = 0.1; + + // Initialization vector for lct2d model. + Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState_2d::Count, 0); + init_lct2d[18] = 200; // lct and lct2d use different infection states + init_lct2d[10] = 50; // make sure initial pop. is in the same compartments for lct and lct2d + init_lct2d[12] = 30; + + // Define LCT2D model. + Model_2d model_lct2d; + //Set initial values + for (size_t i = 0; i < LctState_2d::Count; i++) { + model_lct2d.populations[i] = init_lct2d[i]; + } + + // Set Parameters. + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + + model_lct2d.parameters.get()[0] = 0.05; + model_lct2d.parameters.get()[0] = 0.; + + mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get() = 50; + model_lct2d.parameters.get() = 0.1; + + // Initialization vector for LCT model. + Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState_lct::Count, 0); + init_lct[0] = 200; + init_lct[3] = 50; + init_lct[5] = 30; + + // Define LCT model. + Model_lct model_lct; + //Set initial values + for (size_t i = 0; i < LctState_lct::Count; i++) { + model_lct.populations[i] = init_lct[i]; + } + + // Set Parameters. + model_lct.parameters.get()[0] = 3.2; + model_lct.parameters.get()[0] = 2; + model_lct.parameters.get()[0] = 5.8; + model_lct.parameters.get()[0] = 9.5; + model_lct.parameters.get()[0] = 7.1; + + model_lct.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get()[0] = 0.7; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get() = 50; + model_lct.parameters.get() = 0.1; + model_lct.parameters.get()[0] = 0.09; + model_lct.parameters.get()[0] = 0.2; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get()[0] = 0.3; + + // Simulate + mio::TimeSeries result_lct2d = mio::simulate( + t0, tmax, dt, model_lct2d, + std::make_shared>()); + + mio::TimeSeries result_lct = mio::simulate( + t0, tmax, dt, model_lct, + std::make_shared>()); + + // Simulation results should be equal. + // Compare LCT with Infection 2a in LCT2D + ASSERT_EQ(result_lct.get_num_time_points(), result_lct2d.get_num_time_points()); + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); + + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Susceptible], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Exposed], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_2a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedNoSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedNoSymptoms_2a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSymptoms_2a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedCritical], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedCritical_2a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSevere], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_2a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Recovered], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_ab], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Dead], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_a], 1e-5); + } +} + +// 4. Test: Second infection with disease b (transmission prob. of disease a = 0, start in Recovered_a)*/ +TEST(TestLCTSecir2d, compareWithLCTSecir4) +{ + using InfState2d = mio::lsecir2d::InfectionState; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; + ScalarType t0 = 0; + ScalarType tmax = 5; + ScalarType dt = 0.1; + + // Initialization vector for lct2d model. + Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState2d::Count, 0); + init_lct2d[6] = 200; // lct and lct2d use different infection states + init_lct2d[22] = 50; // make sure initial pop. is in the same compartments for lct and lct2d + init_lct2d[24] = 30; + + // Define LCT2D model. + Model_2d model_lct2d; + //Set initial values + for (size_t i = 0; i < LctState2d::Count; i++) { + model_lct2d.populations[i] = init_lct2d[i]; + } + + // Set Parameters. + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + model_lct2d.parameters.get()[0] = 3.2; + model_lct2d.parameters.get()[0] = 2; + model_lct2d.parameters.get()[0] = 5.8; + model_lct2d.parameters.get()[0] = 9.5; + model_lct2d.parameters.get()[0] = 7.1; + + model_lct2d.parameters.get()[0] = 0.; + model_lct2d.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get()[0] = 0.7; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.09; + model_lct2d.parameters.get()[0] = 0.2; + model_lct2d.parameters.get()[0] = 0.25; + model_lct2d.parameters.get()[0] = 0.3; + model_lct2d.parameters.get() = 50; + model_lct2d.parameters.get() = 0.1; + + using InfState = mio::lsecir::InfectionState; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir::Model; + + // Initialization vector for LCT model. + Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); + init_lct[0] = 200; + init_lct[3] = 50; + init_lct[5] = 30; + + // Define LCT model. + Model model_lct; + //Set initial values + for (size_t i = 0; i < LctState::Count; i++) { + model_lct.populations[i] = init_lct[i]; + } + + // Set Parameters. + model_lct.parameters.get()[0] = 3.2; + model_lct.parameters.get()[0] = 2; + model_lct.parameters.get()[0] = 5.8; + model_lct.parameters.get()[0] = 9.5; + model_lct.parameters.get()[0] = 7.1; + + model_lct.parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get()[0] = 0.7; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get() = 50; + model_lct.parameters.get() = 0.1; + model_lct.parameters.get()[0] = 0.09; + model_lct.parameters.get()[0] = 0.2; + model_lct.parameters.get()[0] = 0.25; + model_lct.parameters.get()[0] = 0.3; + + // Simulate + mio::TimeSeries result_lct2d = mio::simulate( + t0, tmax, dt, model_lct2d, + std::make_shared>()); + + mio::TimeSeries result_lct = mio::simulate( + t0, tmax, dt, model_lct, + std::make_shared>()); + + // Simulation results should be equal. + // Compare LCT with Infection 2b in LCT2D + ASSERT_EQ(result_lct.get_num_time_points(), result_lct2d.get_num_time_points()); + for (int i = 0; i < 4; ++i) { + EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); + + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Susceptible], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_a], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Exposed], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_2b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedNoSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedNoSymptoms_2b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSymptoms], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSymptoms_2b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedCritical], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedCritical_2b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSevere], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_2b], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Recovered], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_ab], 1e-5); + EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Dead], + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_b], 1e-5); + } +} + +// Model setup to compare result with a previous output. +class ModelTestLCTSecir2d : public testing::Test +{ +public: + using InfState = mio::lsecir2d::InfectionState; + using LctState = + mio::LctInfectionState; + using Model = mio::lsecir2d::Model; + +protected: + virtual void SetUp() + { + // Define initial distribution of the population in the subcompartments. + std::vector> initial_populations = {{200}, {0}, {0}, {30}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {0}, {0}, {0}, {0}, {30}, {0}, {0}, + {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}}; + model = new Model(); + // Transfer the initial values in initial_populations to the model. + std::vector flat_initial_populations; + for (auto&& vec : initial_populations) { + flat_initial_populations.insert(flat_initial_populations.end(), vec.begin(), vec.end()); + } + for (size_t i = 0; i < LctState::Count; i++) { + model->populations[i] = flat_initial_populations[i]; + } + + // Set parameters. + model->parameters.get()[0] = 3.2; + model->parameters.get()[0] = 2; + model->parameters.get()[0] = 5.8; + model->parameters.get()[0] = 9.5; + model->parameters.get()[0] = 7.1; + model->parameters.get()[0] = 0.05; + model->parameters.get()[0] = 3.2; + model->parameters.get()[0] = 2; + model->parameters.get()[0] = 5.8; + model->parameters.get()[0] = 9.5; + model->parameters.get()[0] = 7.1; + model->parameters.get()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix = model->parameters.get(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(2.)); + + model->parameters.get()[0] = 0.7; + model->parameters.get()[0] = 0.25; + model->parameters.get()[0] = 0.09; + model->parameters.get()[0] = 0.2; + model->parameters.get()[0] = 0.25; + model->parameters.get()[0] = 0.3; + model->parameters.get()[0] = 0.7; + model->parameters.get()[0] = 0.25; + model->parameters.get()[0] = 0.09; + model->parameters.get()[0] = 0.2; + model->parameters.get()[0] = 0.25; + model->parameters.get()[0] = 0.3; + } + + virtual void TearDown() + { + delete model; + } + +public: + Model* model = nullptr; +}; + +/* +// Test compares a simulation with the result of a previous run stored in a.csv file. +// to do +TEST_F(ModelTestLCTSecir2d, compareWithPreviousRun) +{ + ScalarType tmax = 3; + mio::TimeSeries result = mio::simulate( + 0, tmax, 0.5, *model, + std::make_shared>()); + + // Compare subcompartments. + auto compare = load_test_data_csv("lct-secir-2d-subcompartments-compare.csv"); + + ASSERT_EQ(compare.size(), static_cast(result.get_num_time_points())); + for (size_t i = 0; i < compare.size(); i++) { + ASSERT_EQ(compare[i].size(), static_cast(result.get_num_elements()) + 1) << "at row " << i; + EXPECT_NEAR(result.get_time(i), compare[i][0], 1e-7) << "at row " << i; + for (size_t j = 1; j < compare[i].size(); j++) { + EXPECT_NEAR(result.get_value(i)[j - 1], compare[i][j], 1e-7) << " at row " << i; + } + } + + // Compare InfectionState compartments. + mio::TimeSeries population = model->calculate_compartments(result); + auto compare_population = load_test_data_csv("lct-secir-2d-compartments-compare.csv"); + + ASSERT_EQ(compare_population.size(), static_cast(population.get_num_time_points())); + for (size_t i = 0; i < compare_population.size(); i++) { + ASSERT_EQ(compare_population[i].size(), static_cast(population.get_num_elements()) + 1) + << "at row " << i; + EXPECT_NEAR(population.get_time(i), compare_population[i][0], 1e-7) << "at row " << i; + for (size_t j = 1; j < compare_population[i].size(); j++) { + EXPECT_NEAR(population.get_value(i)[j - 1], compare_population[i][j], 1e-7) << " at row " << i; + } + } +} +*/ + +// Test calculate_compartments with a TimeSeries that has an incorrect number of elements. +TEST_F(ModelTestLCTSecir2d, testCalculatePopWrongSize) +{ + // Deactivate temporarily log output because an error is expected. + mio::set_log_level(mio::LogLevel::off); + // TimeSeries has to have LctState::Count elements. + size_t wrong_size = LctState::Count - 2; + // Define TimeSeries with wrong_size elements. + mio::TimeSeries wrong_num_elements(wrong_size); + Eigen::VectorX vec_wrong_size = Eigen::VectorX::Ones(wrong_size); + wrong_num_elements.add_time_point(-10, vec_wrong_size); + wrong_num_elements.add_time_point(-9, vec_wrong_size); + // Call the calculate_compartments function with the TimeSeries with a wrong number of elements. + mio::TimeSeries population = model->calculate_compartments(wrong_num_elements); + // A TimeSeries of the right size with values -1 is expected. + ASSERT_EQ(1, population.get_num_time_points()); + for (int i = 0; i < population.get_num_elements(); i++) { + EXPECT_EQ(-1, population.get_last_value()[i]); + } + // Reactive log output. + mio::set_log_level(mio::LogLevel::warn); +} + +//Check constraints of Parameters class. +TEST(TestLCTSecir2d, testConstraintsParameters) +{ + // Deactivate temporarily log output for next tests. + mio::set_log_level(mio::LogLevel::off); + + // Check for exceptions of parameters. + mio::lsecir2d::Parameters parameters_lct2d(1); + parameters_lct2d.get()[0] = 0; + parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get()[0] = 6.1; + parameters_lct2d.get()[0] = 11.1; + parameters_lct2d.get()[0] = 17.1; + parameters_lct2d.get()[0] = 0.01; + parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get()[0] = 6.1; + parameters_lct2d.get()[0] = 11.1; + parameters_lct2d.get()[0] = 17.1; + parameters_lct2d.get()[0] = 0.01; + mio::ContactMatrixGroup& contact_matrix = parameters_lct2d.get(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + + parameters_lct2d.get()[0] = 1; + parameters_lct2d.get()[0] = 1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 1; + parameters_lct2d.get()[0] = 1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get()[0] = 0.1; + + // Check improper TimeExposed. + bool constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 3.1; + + parameters_lct2d.get()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 3.1; + + // Check TimeInfectedNoSymptoms. + parameters_lct2d.get()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 3.1; + + parameters_lct2d.get()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 3.1; + + // Check TimeInfectedSymptoms. + parameters_lct2d.get()[0] = -0.1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 6.1; + + parameters_lct2d.get()[0] = -0.1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 6.1; + + // Check TimeInfectedSevere. + parameters_lct2d.get()[0] = 0.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 11.1; + + parameters_lct2d.get()[0] = 0.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 11.1; + + // Check TimeInfectedCritical. + parameters_lct2d.get()[0] = 0.; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 17.1; + + parameters_lct2d.get()[0] = 0.; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 17.1; + + // Check TransmissionProbabilityOnContact. + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.01; + + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.01; + + // Check RelativeTransmissionNoSymptoms. + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 1; + + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 1; + + // Check RiskOfInfectionFromSymptomatic. + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 1; + + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 1; + + // Check RecoveredPerInfectedNoSymptoms. + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + parameters_lct2d.get()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + // Check SeverePerInfectedSymptoms. + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + // Check CriticalPerSevere. + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + // Check DeathsPerCritical. + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + parameters_lct2d.get()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.get()[0] = 0.1; + + // Check Seasonality. + parameters_lct2d.set(1); + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_TRUE(constraint_check); + parameters_lct2d.set(0.1); + + // Check with correct parameters. + constraint_check = parameters_lct2d.check_constraints(); + EXPECT_FALSE(constraint_check); + + // Reactive log output. + mio::set_log_level(mio::LogLevel::warn); +} + +// Check constraints of the Model setup. +TEST(TestLCTSecir2d, testConstraintsModel) +{ + // Deactivate temporarily log output for next tests. + mio::set_log_level(mio::LogLevel::off); + + using InfState = mio::lsecir2d::InfectionState; + + // Check for improper number of subcompartments for Susceptible. + using LctStatewrongSusceptibles = + mio::LctInfectionState; + using ModelwrongSusceptibles = mio::lsecir2d::Model; + ModelwrongSusceptibles modelwrongSusceptibles; + bool constraint_check = modelwrongSusceptibles.check_constraints(); + EXPECT_TRUE(constraint_check); + + // Check for improper number of subcompartments for Recovered. + using LctStatewrongRecovered_a = + mio::LctInfectionState; + using ModelwrongRecovered_a = mio::lsecir2d::Model; + ModelwrongRecovered_a modelwrongRecovered_a; + constraint_check = modelwrongRecovered_a.check_constraints(); + EXPECT_TRUE(constraint_check); + + using LctStatewrongRecovered_b = + mio::LctInfectionState; + using ModelwrongRecovered_b = mio::lsecir2d::Model; + ModelwrongRecovered_b modelwrongRecovered_b; + constraint_check = modelwrongRecovered_b.check_constraints(); + EXPECT_TRUE(constraint_check); + + using LctStatewrongRecovered_ab = + mio::LctInfectionState; + using ModelwrongRecovered_ab = mio::lsecir2d::Model; + ModelwrongRecovered_ab modelwrongRecovered_ab; + constraint_check = modelwrongRecovered_ab.check_constraints(); + EXPECT_TRUE(constraint_check); + + // Check for improper number of subcompartments for Dead. + using LctStatewrongDead_a = + mio::LctInfectionState; + using ModelwrongDead_a = mio::lsecir2d::Model; + ModelwrongDead_a modelwrongDead_a; + constraint_check = modelwrongDead_a.check_constraints(); + EXPECT_TRUE(constraint_check); + + using LctStatewrongDead_b = + mio::LctInfectionState; + using ModelwrongDead_b = mio::lsecir2d::Model; + ModelwrongDead_b modelwrongDead_b; + constraint_check = modelwrongDead_b.check_constraints(); + EXPECT_TRUE(constraint_check); + + // Check with a negative number in the initial population distribution. + using LctStatevalid = + mio::LctInfectionState; + using Model = mio::lsecir2d::Model; + Model model; + model.populations[0] = -1000; + constraint_check = model.check_constraints(); + EXPECT_TRUE(constraint_check); + + // Reactive log output. + mio::set_log_level(mio::LogLevel::warn); + + // Check for valid Setup. + model.populations[0] = 1000; + constraint_check = model.check_constraints(); + EXPECT_FALSE(constraint_check); +} From 62945e1684306bc133997cce6bc8e6bab94b5617 Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 7 Aug 2025 13:05:30 +0200 Subject: [PATCH 05/16] LCT2D Model with age groups --- cpp/examples/lct_secir.cpp | 4 +- cpp/examples/lct_secir_2_diseases.cpp | 20 ++- .../epidemiology/lct2d_infection_state.h | 2 +- cpp/memilio/epidemiology/lct2d_populations.h | 2 +- cpp/models/lct_secir_2_diseases/README.md | 115 ++++++++++-------- .../lct_secir_2_diseases/infection_state.h | 8 +- cpp/models/lct_secir_2_diseases/model.cpp | 2 +- cpp/models/lct_secir_2_diseases/model.h | 90 ++++++-------- cpp/models/lct_secir_2_diseases/parameters.h | 12 +- cpp/tests/test_lct_secir_2_diseases.cpp | 36 +++--- 10 files changed, 141 insertions(+), 150 deletions(-) diff --git a/cpp/examples/lct_secir.cpp b/cpp/examples/lct_secir.cpp index 123759adf0..cc4ed51729 100644 --- a/cpp/examples/lct_secir.cpp +++ b/cpp/examples/lct_secir.cpp @@ -42,7 +42,7 @@ int main() using InfState = mio::lsecir::InfectionState; using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using Model = mio::lsecir::Model; Model model; // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually @@ -61,7 +61,7 @@ int main() model.parameters.get()[0] = 0.1; mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); diff --git a/cpp/examples/lct_secir_2_diseases.cpp b/cpp/examples/lct_secir_2_diseases.cpp index 7bb5290319..13ec4c737a 100644 --- a/cpp/examples/lct_secir_2_diseases.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * @@ -22,18 +22,15 @@ #include "lct_secir_2_diseases/infection_state.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" -//#include "memilio/epidemiology/uncertain_matrix.h" #include "memilio/epidemiology/lct2d_infection_state.h" -//#include "memilio/math/eigen.h" #include "memilio/utils/logging.h" #include "memilio/compartments/simulation.h" #include "memilio/data/analyze_result.h" - #include int main() { - // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. + // Simple example to demonstrate how to run a simulation using an LCT-SECIR-2-DISEASE model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. constexpr size_t NumExposed_1a = 1, NumInfectedNoSymptoms_1a = 1, NumInfectedSymptoms_1a = 1, @@ -45,7 +42,7 @@ int main() NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 1; using InfState = mio::lsecir2d::InfectionState; using LctState = mio::LctInfectionState< - InfState, 3, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, + InfState, 1, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, NumInfectedCritical_1a, 1, 1, NumExposed_2a, NumInfectedNoSymptoms_2a, NumInfectedSymptoms_2a, NumInfectedSevere_2a, NumInfectedCritical_2a, NumExposed_1b, NumInfectedNoSymptoms_1b, NumInfectedSymptoms_1b, NumInfectedSevere_1b, NumInfectedCritical_1b, 1, 1, NumExposed_2b, NumInfectedNoSymptoms_2b, @@ -53,9 +50,6 @@ int main() using Model = mio::lsecir2d::Model; Model model; - // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually - // defined initial vector is used to initialize the LCT model. - ScalarType tmax = 5; // Set Parameters. @@ -96,9 +90,9 @@ int main() // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{0, 0, 0}, {0}, {0}, {0}, {0}, {0}, {9000}, {0}, {0}, - {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, - {0}, {0}, {1000}, {0}, {0}, {0}, {0}, {0}}; + std::vector> initial_populations = {{2000}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {100}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, + {0}, {0}, {100}, {0}, {0}, {0}, {0}, {0}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -157,5 +151,5 @@ int main() " Da", " E2a", " C2a", " I2a", " H2a", " U2a", " E1b", " C1b", " I1b", " H1b", " U1b", " Rb", " Db", " E2b", " C2b", " I2b", " H2b", " U2b", " Rab"}, - 7, 3); + 6, 2); } diff --git a/cpp/memilio/epidemiology/lct2d_infection_state.h b/cpp/memilio/epidemiology/lct2d_infection_state.h index 705b391355..d9e8a001f4 100644 --- a/cpp/memilio/epidemiology/lct2d_infection_state.h +++ b/cpp/memilio/epidemiology/lct2d_infection_state.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * diff --git a/cpp/memilio/epidemiology/lct2d_populations.h b/cpp/memilio/epidemiology/lct2d_populations.h index a017d32fa0..20b9f905b5 100644 --- a/cpp/memilio/epidemiology/lct2d_populations.h +++ b/cpp/memilio/epidemiology/lct2d_populations.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * diff --git a/cpp/models/lct_secir_2_diseases/README.md b/cpp/models/lct_secir_2_diseases/README.md index 73ad6efa74..4e2e15c9e6 100644 --- a/cpp/models/lct_secir_2_diseases/README.md +++ b/cpp/models/lct_secir_2_diseases/README.md @@ -1,66 +1,83 @@ # LCT SECIR TWO DISEASES model -This model is based on the Linear Chain Trick. +This model describes infection with two independent (e.g. no co-infection) diseases a and b, based on the Linear Chain Trick. -The Linear Chain Trick provides the option to use Erlang-distributed stay times in the compartments through the use of subcompartments. +The Linear Chain Trick (LCT) provides the option to use Erlang-distributed stay times in the compartments through the use of subcompartments. The normal ODE models have (possibly unrealistic) exponentially distributed stay times. The LCT model can still be described by an ordinary differential equation system. -For the concept see +For the concept of LCT models with one disease see - Lena Plötzke, "Der Linear Chain Trick in der epidemiologischen Modellierung als Kompromiss zwischen gewöhnlichen und Integro-Differentialgleichungen", 2023. (https://elib.dlr.de/200381/, German only) - P. J. Hurtado und A. S. Kirosingh, "Generalizations of the ‘Linear Chain Trick’: incorporating more flexible dwell time distributions into mean field ODE models“, 2019. (https://doi.org/10.1007/s00285-019-01412-w) -The eight compartments -- `Susceptible` ($S$), may become exposed at any time -- `Exposed` ($E$), becomes infected after some time -- `InfectedNoSymptoms` ($I_{NS}$), becomes InfectedSymptoms or Recovered after some time -- `InfectedSymptoms` ($I_{Sy}$), becomes InfectedSevere or Recovered after some time -- `InfectedSevere` ($I_{Sev}$), becomes InfectedCritical or Recovered after some time -- `InfectedCritical` ($I_{Cr}$), becomes Recovered or Dead after some time -- `Recovered` ($R$) -- `Dead` ($D$) +For each infection there are the infection states Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere, InfectedCritical, Recovered and Dead +The infectious compartments are InfectedNoSymptoms and InfectedSymptoms, all other compartments are considered to be not infectious + +There are two possibilities for a susceptible individual (since we assume no co-infection): +1. Get infected with disease a, then (if not dead) get infected with disease b +2. Get infected with disease b, then (if not dead) get infected with disease a + +This leads to the following compartments: +- `Susceptible` ($S$), may get infected with a or b (-> Exposed_1a or Exposed_1b) at any time +Infection 1a: +- `Exposed_1a` ($E_{1a}$), becomes InfectedNoSymptoms_1a after some time +- `InfectedNoSymptoms_1a` ($I_{NS, 1a}_$), becomes InfectedSymptoms_1a or Recovered_a after some time +- `InfectedSymptoms_1a` ($I_{Sy, 1a}$), becomes InfectedSevere_1a or Recovered_a after some time +- `InfectedSevere_1a` ($I_{Sev, 1a}$), becomes InfectedCritical_1a or Recovered_a after some time +- `InfectedCritical_1a` ($I_{Cr, 1a}$), becomes Recovered_a or Dead_a after some time +- `Recovered_a` ($R_a$), immune to a, may get infected with b (-> Exposed_2b) at any time +- `Dead_a` ($D_a$), absorbing state +Infection 1b: +- `Exposed_1b` ($E_{1b}$), becomes InfectedNoSymptoms_1b after some time +- `InfectedNoSymptoms_1b` ($I_{NS, 1b}$), becomes InfectedSymptoms_1b or Recovered_1b after some time +- `InfectedSymptoms_1b` ($I_{Sy, 1b}$), becomes InfectedSevere_1b or Recovered_b after some time +- `InfectedSevere_1b` ($I_{Sev, 1b}$), becomes InfectedCritical_1b or Recovered_b after some time +- `InfectedCritical_1b` ($I_{Cr, 1b}$), becomes Recovered_b or Dead_b after some time +- `Recovered_b` ($R_b$), immune to b, may get infected with b (-> Exposed_2a) at any time +- `Dead_b` ($D_b$), absorbing state +Infection 2a: +- `Exposed_2a` ($E_{2a}$), becomes InfectedNoSympotoms_2a after some time +- `InfectedNoSymptoms_2a` ($I_{NS, 2a}$), becomes InfectedSymptoms_2a or Recovered_ab after some time +- `InfectedSymptoms_2a` ($I_{Sy, 2a}$), becomes InfectedSevere_2a or Recovered_ab after some time +- `InfectedSevere_2a` ($I_{Sev, 2a}$), becomes InfectedCritical_2a or Recovered_ab after some time +- `InfectedCritical_2a` ($I_{Cr, 2a}$), becomes Recovered_ab or Dead_a after some time +- `Recovered_ab` ($R_{ab}$), absorbing state, immune to a and b +Infection 2b: +- `Exposed_2b` ($E_{2b}$), becomes infected after some time +- `InfectedNoSymptoms_2b` ($I_{NS,2b}$), becomes InfectedSymptoms_2b or Recovered_ab after some time +- `InfectedSymptoms_2b` ($I_{Sy,2b}$), becomes InfectedSevere_2b or Recovered_ab after some time +- `InfectedSevere_2b` ($I_{Sev, 2b}$), becomes InfectedCritical_2b or Recovered_ab after some time +- `InfectedCritical_2b` ($I_{Cr, 2b}$), becomes Recovered_ab or Dead_b after some time + -are used to simulate the spread of the disease. It is possible to include subcompartments for the five compartments Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. You can divide the population according to different groups, e.g. AgeGroups or gender and choose parameters according to groups. -Below is an overview of the model architecture and its compartments without a stratification according to groups. - -![tikzLCTSECIR](https://github.com/SciCompMod/memilio/assets/70579874/6a5d5a95-20f9-4176-8894-c091bd48bfb7) - -| Mathematical variable | C++ variable name | Description | -|---------------------------- | --------------- | -------------------------------------------------------------------------------------------------- | -| $\phi$ | `ContactPatterns` | Average number of contacts of a person per day. | -| $\rho$ | `TransmissionProbabilityOnContact` | Transmission risk for people located in the susceptible compartments. | -| $\xi_{I_{NS}}$ | `RelativeTransmissionNoSymptoms` | Proportion of nonsymptomatically infected people who are not isolated. | -| $\xi_{I_{Sy}}$ | `RiskOfInfectionFromSymptomatic` | Proportion of infected people with symptoms who are not isolated. | -| $N$ | `m_N0` | Total population. | -| $D$ | `D` | Number of death people. | -| $n_E$ | Defined in `LctStates` | Number of subcompartments of the Exposed compartment. | -| $n_{NS}$ | Defined in `LctStates` | Number of subcompartments of the InfectedNoSymptoms compartment. | -| $n_{Sy}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSymptoms compartment. | -| $n_{Sev}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSevere compartment.| -| $n_{Cr}$ | Defined in `LctStates` | Number of subcompartments of the InfectedCritical compartment. | -| $T_E$ | `TimeExposed` | Average time in days an individual stays in the Exposed compartment. | -| $T_{I_{NS}}$ | `TimeInfectedNoSymptoms` | Average time in days an individual stays in the InfectedNoSymptoms compartment. | -| $T_{I_{Sy}}$ | `TimeInfectedSymptoms` | Average time in days an individual stays in the InfectedSymptoms compartment. | -| $T_{I_{Sev}}$ | `TimeInfectedSevere` | Average time in days an individual stays in the InfectedSevere compartment. | -| $T_{I_{Cr}}$ | `TimeInfectedCritical` | Average time in days an individual stays in the InfectedCritical compartment. | -| $\mu_{I_{NS}}^{R}$ | `RecoveredPerInfectedNoSymptoms` | Probability of transition from compartment InfectedNoSymptoms to Recovered. | -| $\mu_{I_{Sy}}^{I_{Sev}}$ | `SeverePerInfectedSymptoms` | Probability of transition from compartment InfectedSymptoms to InfectedSevere. | -| $\mu_{I_{Sev}}^{I_{Cr}}$ | `CriticalPerSevere` | Probability of transition from compartment InfectedSevere to InfectedCritical. | -| $\mu_{I_{Cr}}^{D}$ | `DeathsPerCritical` | Probability of dying when in compartment InfectedCritical. | - -The notation of the compartments with indices here stands for subcompartments and not for age groups. Accordingly, $I_{NS,n_{NS}}$, for example, stands for the number of people in the $n_{NS}$-th subcompartment of the InfectedNoSymptoms compartment. +The parameters depend on the disease (a or b), the number of subcompartments depends on the individual compartment (since it can be set independently). + + +| Mathematical variable | C++ variable name | Description | +|---------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- | +| $\phi$ | `ContactPatterns` | Average number of contacts of a person per day. | +| $\rho_i$ | `TransmissionProbabilityOnContact_i` | Transmission risk for people located in the susceptible compartments, i in {a,b}. | +| $\xi_{I_{NS},i}$ | `RelativeTransmissionNoSymptoms_i` | Proportion of nonsymptomatically infected people who are not isolated, i in {a,b}. | +| $\xi_{I_{Sy},i}$ | `RiskOfInfectionFromSymptomatic_i` | Proportion of infected people with symptoms who are not isolated, i in {a,b}. | +| $n_{E,i}$ | Defined in `LctStates` | Number of subcompartments of the Exposed compartment, i in {1a, 2a, 1b,2b}. | +| $n_{NS,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedNoSymptoms compartment, i in {1a, 2a, 1b,2b}. | +| $n_{Sy,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSymptoms compartment, i in {1a, 2a, 1b,2b}. | +| $n_{Sev,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSevere compartment, i in {1a, 2a, 1b,2b}. | +| $n_{Cr,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedCritical compartment, i in {1a, 2a, 1b,2b}. | +| $T_{E,i}$ | `TimeExposed` | Average time in days an individual stays in the Exposed compartment, i in {a,b}. | +| $T_{I_{NS},i}$ | `TimeInfectedNoSymptoms` | Average time in days an individual stays in the InfectedNoSymptoms compartment, i in {a,b}. | +| $T_{I_{Sy},i}$ | `TimeInfectedSymptoms` | Average time in days an individual stays in the InfectedSymptoms compartmen, i in {a,b}t. | +| $T_{I_{Sev},i}$ | `TimeInfectedSevere` | Average time in days an individual stays in the InfectedSevere compartment, i in {a,b}. | +| $T_{I_{Cr},i}$ | `TimeInfectedCritical` | Average time in days an individual stays in the InfectedCritical compartment, i in {a,b}. | +| $\mu_{I_{NS},i}^{R}$ | `RecoveredPerInfectedNoSymptoms` | Probability of transition from compartment InfectedNoSymptoms to Recovered, i in {a,b}. | +| $\mu_{I_{Sy},i}^{I_{Sev}}$ | `SeverePerInfectedSymptoms` | Probability of transition from compartment InfectedSymptoms to InfectedSevere, i in {a,b}. | +| $\mu_{I_{Sev},i}^{I_{Cr}}$ | `CriticalPerSevere` | Probability of transition from compartment InfectedSevere to InfectedCritical, i in {a,b}. | +| $\mu_{I_{Cr},i}^{D}$ | `DeathsPerCritical` | Probability of dying when in compartment InfectedCritical, i in {a,b}. | ## Examples -A simple example can be found at [LCT minimal example](../../examples/lct_secir.cpp). - -## Initialization - -- The file [parameters_io](parameters_io.h) provides functionality to compute an initial value vector for the LCT-SECIR model based on real data. - -- The file [initializer_flows](initializer_flows.h) provides functionality to compute an initial value vector for the LCT-SECIR model based on initial data in the form of a TimeSeries of InfectionTransitions. For the concept of the InfectionTransitions or flows, see also the IDE-SECIR model. This method can be particularly useful if a comparison is to be made with an IDE model with matching initialization or if the real data is in the form of flows. - +A simple example can be found at [LCT2D minimal example](../../examples/lct_secir_2_diseases.cpp). diff --git a/cpp/models/lct_secir_2_diseases/infection_state.h b/cpp/models/lct_secir_2_diseases/infection_state.h index 1315b5cbb9..92ef39c442 100644 --- a/cpp/models/lct_secir_2_diseases/infection_state.h +++ b/cpp/models/lct_secir_2_diseases/infection_state.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * @@ -33,7 +33,7 @@ namespace lsecir2d enum class InfectionState { Susceptible = 0, - // State_[Infection number][disease] + // Notation: State_[Infection number][disease] // first infection with disease a Exposed_1a = 1, InfectedNoSymptoms_1a = 2, @@ -48,8 +48,6 @@ enum class InfectionState InfectedSymptoms_2a = 10, InfectedSevere_2a = 11, InfectedCritical_2a = 12, - // R and D for disease a - // first infection with disease b Exposed_1b = 13, InfectedNoSymptoms_1b = 14, @@ -64,8 +62,6 @@ enum class InfectionState InfectedSymptoms_2b = 22, InfectedSevere_2b = 23, InfectedCritical_2b = 24, - // R and D for disease b - // Recovered from both diseases Recovered_ab = 25, Count = 26 diff --git a/cpp/models/lct_secir_2_diseases/model.cpp b/cpp/models/lct_secir_2_diseases/model.cpp index 07914a306c..77a3b42578 100644 --- a/cpp/models/lct_secir_2_diseases/model.cpp +++ b/cpp/models/lct_secir_2_diseases/model.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index 50caa5cfbc..b8b38ecd6f 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungkalus, Lena Ploetzke * * Contact: Martin J. Kuehn * @@ -30,19 +30,15 @@ #include "memilio/utils/logging.h" #include "memilio/utils/type_list.h" #include "memilio/utils/metaprogramming.h" -/* -#include "memilio/epidemiology/lct_infection_state.h" -#include "memilio/math/eigen.h" -#include -*/ + namespace mio { namespace lsecir2d { /** - * @brief Class that defines an LCT-SECIR model. + * @brief Class that defines an LCT-SECIR-2-DISEASE model. * - * @tparam LctStates The LCT model can work with any number of LctStates, where each LctState corresponds to a group, + * @tparam LctStates The LCT2D model can work with any number of LctStates, where each LctState corresponds to a group, * e.g. one AgeGroup. The purpose of the LctStates is to define the number of subcompartments for each InfectionState. * If you do not want to divide the population into groups, just use one LctState. * If you want to divide the population according to more than one category, e.g. sex and age, @@ -59,7 +55,7 @@ class Model using Base = CompartmentalModel, Parameters>; using typename Base::ParameterSet; using typename Base::Populations; - static size_t constexpr num_groups = 1; //sizeof...(LctStates); only one (age) group for the beginning + static size_t constexpr num_groups = sizeof...(LctStates); static_assert(num_groups >= 1, "The number of LctStates provided should be at least one."); /// @brief Default constructor. @@ -80,7 +76,7 @@ class Model /** * @brief Evaluates the right-hand-side f of the ODE dydt = f(y, t). * - * The LCT-SECIR model is defined through ordinary differential equations of the form dydt = f(y, t). + * The LCT-SECIR-2-DISEASE model is defined through ordinary differential equations of the form dydt = f(y, t). * y is a vector containing the number of individuals for each (sub-) compartment. * This function evaluates the right-hand-side f of the ODE and can be used in an ODE solver. * @param[in] pop The current state of the population in the geographic unit we are considering. @@ -170,10 +166,9 @@ class Model this->populations.template get_first_index_of_group(), LctStateGroup::Count)); // Function call for next group if applicable. - /* only one (age) group, do not need this if constexpr (Group + 1 < num_groups) { compress_vector(subcompartments, compartments); - } */ + } } /** @@ -195,10 +190,9 @@ class Model static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); using LctStateGroup = type_at_index_t; - size_t first_index_group = - this->populations.template get_first_index_of_group(); // susceptible compartment? - auto params = this->parameters; - ScalarType flow = 0; + size_t first_index_group = this->populations.template get_first_index_of_group(); + auto params = this->parameters; + ScalarType flow = 0; // Indices of first subcompartment of the InfectionState for the group in the vectors. size_t Ei_1a_first_index = @@ -251,14 +245,12 @@ class Model // outflow generated by disease a and disease b both double part_a = 0.; double part_b = 0.; - interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, first_index_group, - 2); // S is affected by both diseases, try group=0 (no age groups) + interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); // split flow double div_part_both = ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); - // Start with derivatives of first infections, so 1a and 1b compartments - + // Start with derivatives of First Infections (X_1a, X_1b) // Calculate derivative of the Exposed_1x compartments, split flow from Susceptible. // Exposed 1 a: dydt[Ei_1a_first_index] = -dydt[first_index_group] * part_a * div_part_both; @@ -410,9 +402,8 @@ class Model dydt[Ri_b] = dydt[Ri_b] + (1 - params.template get()[Group]) * flow; dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; - // second infection 2x - - // outflow from Recovered_x, people getting infected for the second time + // Second Infection (X_2a, X_2b) + // outflow from Recovered, people getting infected for the second time double temp_Ra = dydt[Ri_a]; interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_a, 1); // outflow from R_a is only affected by b, no age groups @@ -420,17 +411,17 @@ class Model interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_b, 0); // outflow from R_b is only affected by a, no age groups - // Calculate derivative of the Exposed_2x compartments + // Calculate derivative of the Exposed_2i compartments // Exposed 2 a dydt[Ei_2a_first_index] = -(dydt[Ri_b] - temp_Rb); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { // Variable flow stores the value of the flow from one subcompartment to the next one. - // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index + // Ei_2a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_2a_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * y[Ei_2a_first_index + subcomp]; - // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. + // Subtract flow from dydt[Ei_2a_first_index + subcomp] and add to next subcompartment. dydt[Ei_2a_first_index + subcomp] -= flow; dydt[Ei_2a_first_index + subcomp + 1] = flow; } @@ -439,11 +430,11 @@ class Model for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { // Variable flow stores the value of the flow from one subcompartment to the next one. - // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index + // Ei_2b_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_2b_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * y[Ei_2b_first_index + subcomp]; - // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. + // Subtract flow from dydt[Ei_2b_first_index + subcomp] and add to next subcompartment. dydt[Ei_2b_first_index + subcomp] -= flow; dydt[Ei_2b_first_index + subcomp + 1] = flow; } @@ -572,24 +563,26 @@ class Model dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; // Function call for next group if applicable. - /* only one (age) group, do not need this if constexpr (Group + 1 < num_groups) { get_derivatives_impl(pop, y, t, dydt); - }*/ + } } - // for calculating flows that are caused by people becoming infected (transmission of infection, therefore needs infectious people) - // not just natural progression of the illness that would happen without external forces /** - * @brief Calculates the derivative of the Susceptible compartment for Group1. + * @brief Calculates flows that are caused by people becoming infected (outflow from compartment S, Ra or Rb) for Group1. * * This is done recursively by calculating the interaction terms with each group. - * @tparam Group1 The group for which the derivative of the Susceptible compartment should be calculated. + * @tparam Group1 The group for which the derivative of the compartment should be calculated. * @tparam Group2 The group that Group1 interacts with. * @param[in] pop The current state of the population in the geographic unit we are considering. * @param[in] y The current state of the model (or a subpopulation) as a flat array. * @param[in] t The current time. * @param[out] dydt A reference to the calculated output. + * The flow from Susceptible needs to be split into 2 parts for Exposed_1a and Exposed_1b: + * @param[out] part_a Reference to amount of flow caused by people infected with disease a. + * @param[out] part_b Reference to amount of flow caused by people infected with disease b. + * @param[in] compartment_index Index of the compartment for that the outflow will be calculated. + * @param[in] which_disease Index for which infected people cause the outflow (0 = a, 1 = b, 2 = a and b). */ template void interact(Eigen::Ref> pop, Eigen::Ref> y, @@ -598,16 +591,13 @@ class Model { static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0), "The template parameters Group1 & Group2 should be valid."); - using LctStateGroup2 = type_at_index_t; - //size_t Si_1 = this->populations.template get_first_index_of_group(); + using LctStateGroup2 = type_at_index_t; ScalarType infectedNoSymptoms_2_a = 0; ScalarType infectedSymptoms_2_a = 0; ScalarType infectedNoSymptoms_2_b = 0; ScalarType infectedSymptoms_2_b = 0; auto params = this->parameters; - //compartment_index = Si_1; - size_t first_index_group2 = this->populations.template get_first_index_of_group(); // Calculate sum of all subcompartments for InfectedNoSymptoms for disease a of Group2. @@ -689,18 +679,17 @@ class Model (params.template get()[Group2] * infectedNoSymptoms_2_b + params.template get()[Group2] * infectedSymptoms_2_b)); } - /* only one group, you do not need this - if constexpr (Group2 + 1 < num_groups) { - interact(pop, y, t, dydt); - }*/ + // To split the outflow from S between E_1a and E_1b: + *part_a += params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_a + + params.template get()[Group2] * infectedSymptoms_2_a); + *part_b += params.template get()[Group1] * + (params.template get()[Group2] * infectedNoSymptoms_2_b + + params.template get()[Group2] * infectedSymptoms_2_b); - *part_a = params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_a + - params.template get()[Group2] * infectedSymptoms_2_a); - - *part_b = params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_b + - params.template get()[Group2] * infectedSymptoms_2_b); + if constexpr (Group2 + 1 < num_groups) { + interact(pop, y, t, dydt, part_a, part_b, compartment_index, which_disease); + } } /** @@ -745,5 +734,4 @@ class Model } // namespace lsecir2d } // namespace mio - -#endif // LCTSECIR_MODEL_H +#endif // LCT_SECIR_2_DISEASE_MODEL_H diff --git a/cpp/models/lct_secir_2_diseases/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h index 797f3ec95b..c237a909a0 100644 --- a/cpp/models/lct_secir_2_diseases/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * @@ -25,20 +25,16 @@ #include "memilio/utils/parameter_set.h" #include "memilio/utils/logging.h" #include "memilio/utils/uncertain_value.h" -//#include "memilio/math/eigen.h" #include "memilio/epidemiology/uncertain_matrix.h" -/* -#include "memilio/math/eigen.h" -#include "memilio/epidemiology/uncertain_matrix.h" */ namespace mio { namespace lsecir2d { -/********************************************** +/********************************************************* * Define Parameters of the LCT-SECIHURD-2-DISEASES model * -**********************************************/ +**********************************************************/ /** * @brief Average time spent in the Exposed compartment for disease a. @@ -474,7 +470,7 @@ class Parameters : public ParametersBase public: /** * @brief Constructor. - * @param num_groups The number of groups considered in the LCT model. + * @param num_groups The number of groups considered in the LCT2D model. */ Parameters(size_t num_groups) : ParametersBase(num_groups) diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index aed9abd0b2..83db0800ca 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * @@ -68,7 +68,7 @@ TEST(TestLCTSecir2d, simulateDefault) } } -/* Tests comparing the result for an LCT SECIR 2 DISEASE model with transmission prob. 0 for one disease +/* Tests comparing the result for an LCT SECIR 2 DISEASE model (with transmission prob. 0 for one disease and 2 age groups) with the result of the equivalent LCT SECIR model. */ // 1. Test: First infection for disease a (transmission prob. of disease b = 0) TEST(TestLCTSecir2d, compareWithLCTSecir1) @@ -76,11 +76,11 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) using InfState2d = mio::lsecir2d::InfectionState; using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using Model_2d = mio::lsecir2d::Model; using InfState_lct = mio::lsecir::InfectionState; using LctState_lct = mio::LctInfectionState; - using Model_lct = mio::lsecir::Model; + using Model_lct = mio::lsecir::Model; ScalarType t0 = 0; ScalarType tmax = 5; @@ -115,7 +115,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) model_lct2d.parameters.get()[0] = 0.; mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct2d.parameters.get()[0] = 0.7; @@ -156,7 +156,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) model_lct.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct.parameters.get()[0] = 0.7; @@ -208,7 +208,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) using InfState2d = mio::lsecir2d::InfectionState; using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using Model_2d = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 5; ScalarType dt = 0.1; @@ -242,7 +242,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) model_lct2d.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct2d.parameters.get()[0] = 0.7; @@ -262,7 +262,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) using InfState = mio::lsecir::InfectionState; using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using Model = mio::lsecir::Model; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); @@ -287,7 +287,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) model_lct.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct.parameters.get()[0] = 0.7; @@ -339,11 +339,11 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) using InfState_2d = mio::lsecir2d::InfectionState; using LctState_2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using Model_2d = mio::lsecir2d::Model; using InfState_lct = mio::lsecir::InfectionState; using LctState_lct = mio::LctInfectionState; - using Model_lct = mio::lsecir::Model; + using Model_lct = mio::lsecir::Model; ScalarType t0 = 0; ScalarType tmax = 5; @@ -378,7 +378,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) model_lct2d.parameters.get()[0] = 0.; mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct2d.parameters.get()[0] = 0.7; @@ -419,7 +419,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) model_lct.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct.parameters.get()[0] = 0.7; @@ -471,7 +471,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) using InfState2d = mio::lsecir2d::InfectionState; using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using Model_2d = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 5; ScalarType dt = 0.1; @@ -505,7 +505,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) model_lct2d.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct2d.parameters.get()[0] = 0.7; @@ -525,7 +525,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) using InfState = mio::lsecir::InfectionState; using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using Model = mio::lsecir::Model; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); @@ -550,7 +550,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) model_lct.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); model_lct.parameters.get()[0] = 0.7; From 424020aad1f6c02ed05dd9e7fe7233c65e48b3ee Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 7 Aug 2025 13:59:23 +0200 Subject: [PATCH 06/16] fix gitignore --- .gitignore | 2 -- 1 file changed, 2 deletions(-) diff --git a/.gitignore b/.gitignore index 109f22a7f9..8c57a85643 100644 --- a/.gitignore +++ b/.gitignore @@ -283,6 +283,4 @@ docs/xml docs/source/api docs/source/generated -settings.json - # End of https://www.gitignore.io/api/c++,node,python From 72678a5de4f5ccc469012804a40ed8896233c7a5 Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 7 Aug 2025 14:07:56 +0200 Subject: [PATCH 07/16] header fix --- cpp/models/lct_secir_2_diseases/model.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index b8b38ecd6f..6805704619 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -23,7 +23,7 @@ #include "lct_secir_2_diseases/parameters.h" #include "lct_secir_2_diseases/infection_state.h" -#include "memilio/compartments/compartmentalmodel.h" +#include "memilio/compartments/compartmental_model.h" #include "memilio/epidemiology/lct2d_populations.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" From 4165ebe995af9ee7fb27633d9404fcab909ba8fa Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 7 Aug 2025 15:28:48 +0200 Subject: [PATCH 08/16] new test for codecov --- cpp/tests/test_lct_secir_2_diseases.cpp | 36 +++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index 83db0800ca..1dadfc7905 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -26,11 +26,10 @@ #include "memilio/utils/time_series.h" #include "memilio/utils/logging.h" #include "memilio/epidemiology/contact_matrix.h" -//#include "memilio/math/eigen.h" +#include "memilio/data/analyze_result.h" #include "memilio/compartments/simulation.h" #include "load_test_data.h" -//#include #include #include @@ -596,6 +595,39 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) } } +TEST(TestLCTSecir2d, testSubcompartments) +{ + using InfState = mio::lsecir2d::InfectionState; + using LctState = + mio::LctInfectionState; + using Model = mio::lsecir2d::Model; + ScalarType t0 = 0; + ScalarType tmax = 1; + ScalarType dt = 0.1; + + std::vector> init = { + {200}, {0, 0}, {30, 10, 10}, {0, 0, 0}, {10, 10, 10}, {0, 0, 0}, {0}, {0}, {0, 0}, + {30, 10, 10}, {0, 0, 0}, {10, 10, 10}, {0, 0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, + {0}, {0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0}}; + + Model model; + + // Transfer the initial values in initial_populations to the model. + std::vector flat_init; + for (auto&& vec : init) { + flat_init.insert(flat_init.end(), vec.begin(), vec.end()); + } + for (size_t i = 0; i < LctState::Count; i++) { + model.populations[i] = flat_init[i]; + } + + mio::TimeSeries result = mio::simulate(t0, tmax, dt, model); + mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); + auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); + + EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); +} + // Model setup to compare result with a previous output. class ModelTestLCTSecir2d : public testing::Test { From f5b26f07d9e03493f6be7d57a8c4ee0160ee4563 Mon Sep 17 00:00:00 2001 From: an-jung Date: Fri, 22 Aug 2025 11:05:14 +0200 Subject: [PATCH 09/16] readthedocs for lct 2 diseases model --- cpp/examples/lct_secir_2_diseases.cpp | 27 +- .../epidemiology/lct2d_infection_state.h | 221 --------- cpp/memilio/epidemiology/lct2d_populations.h | 216 -------- .../epidemiology/lct_infection_state.h | 39 +- cpp/models/lct_secir_2_diseases/model.h | 20 +- cpp/models/lct_secir_2_diseases/parameters.h | 4 +- cpp/tests/test_lct_secir_2_diseases.cpp | 43 +- docs/source/cpp/lct.rst | 1 + docs/source/cpp/models/lsecir.rst | 2 +- docs/source/cpp/models/lsecir2d.rst | 466 ++++++++++++++++++ 10 files changed, 510 insertions(+), 529 deletions(-) delete mode 100644 cpp/memilio/epidemiology/lct2d_infection_state.h delete mode 100644 cpp/memilio/epidemiology/lct2d_populations.h create mode 100644 docs/source/cpp/models/lsecir2d.rst diff --git a/cpp/examples/lct_secir_2_diseases.cpp b/cpp/examples/lct_secir_2_diseases.cpp index 13ec4c737a..5a258042e6 100644 --- a/cpp/examples/lct_secir_2_diseases.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -22,7 +22,7 @@ #include "lct_secir_2_diseases/infection_state.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" -#include "memilio/epidemiology/lct2d_infection_state.h" +#include "memilio/epidemiology/lct_infection_state.h" #include "memilio/utils/logging.h" #include "memilio/compartments/simulation.h" #include "memilio/data/analyze_result.h" @@ -33,13 +33,13 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR-2-DISEASE model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. - constexpr size_t NumExposed_1a = 1, NumInfectedNoSymptoms_1a = 1, NumInfectedSymptoms_1a = 1, - NumInfectedSevere_1a = 1, NumInfectedCritical_1a = 1, NumExposed_2a = 1, - NumInfectedNoSymptoms_2a = 1, NumInfectedSymptoms_2a = 1, NumInfectedSevere_2a = 1, - NumInfectedCritical_2a = 1, NumExposed_1b = 1, NumInfectedNoSymptoms_1b = 1, - NumInfectedSymptoms_1b = 1, NumInfectedSevere_1b = 1, NumInfectedCritical_1b = 1, - NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 1, NumInfectedSymptoms_2b = 1, - NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 1; + constexpr size_t NumExposed_1a = 2, NumInfectedNoSymptoms_1a = 3, NumInfectedSymptoms_1a = 3, + NumInfectedSevere_1a = 3, NumInfectedCritical_1a = 2, NumExposed_2a = 1, + NumInfectedNoSymptoms_2a = 2, NumInfectedSymptoms_2a = 2, NumInfectedSevere_2a = 2, + NumInfectedCritical_2a = 1, NumExposed_1b = 2, NumInfectedNoSymptoms_1b = 3, + NumInfectedSymptoms_1b = 3, NumInfectedSevere_1b = 3, NumInfectedCritical_1b = 2, + NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 2, NumInfectedSymptoms_2b = 2, + NumInfectedSevere_2b = 2, NumInfectedCritical_2b = 1; using InfState = mio::lsecir2d::InfectionState; using LctState = mio::LctInfectionState< InfState, 1, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, @@ -50,7 +50,7 @@ int main() using Model = mio::lsecir2d::Model; Model model; - ScalarType tmax = 5; + ScalarType tmax = 10; // Set Parameters. model.parameters.get()[0] = 3.; @@ -90,9 +90,10 @@ int main() // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{2000}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, - {0}, {100}, {0}, {0}, {0}, {0}, {0}, {0}, {0}, - {0}, {0}, {100}, {0}, {0}, {0}, {0}, {0}}; + std::vector> initial_populations = { + {200}, {0, 0}, {30, 10, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0}, {0}, {0}, {0}, + {0, 0}, {10, 0}, {0, 0}, {0}, {10, 0}, {30, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0}, + {0}, {0}, {100}, {0, 0}, {0, 0}, {0, 0}, {0}, {0}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -141,7 +142,7 @@ int main() } // Perform a simulation. - mio::TimeSeries result = mio::simulate(0, tmax, 0.1, model); + mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); // The simulation result is divided by subcompartments. // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); diff --git a/cpp/memilio/epidemiology/lct2d_infection_state.h b/cpp/memilio/epidemiology/lct2d_infection_state.h deleted file mode 100644 index d9e8a001f4..0000000000 --- a/cpp/memilio/epidemiology/lct2d_infection_state.h +++ /dev/null @@ -1,221 +0,0 @@ -/* -* Copyright (C) 2020-2025 MEmilio -* -* Authors: Annika Jungklaus, Lena Ploetzke -* -* Contact: Martin J. Kuehn -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -#ifndef MIO_EPI_LCT_INFECTION_STATE_H -#define MIO_EPI_LCT_INFECTION_STATE_H - -#include "memilio/config.h" -#include "memilio/math/eigen.h" - -#include - -namespace mio -{ -/** - * @brief Provides the functionality to be able to work with subcompartments in an LCT model. - - * This class just stores the number of subcompartments for each InfectionState and not the number of individuals in - * each subcompartment. - * - * @tparam InfectionStates An enum class that defines the basic infection states. - * @tparam Ns Number of subcompartments for each infection state defined in InfectionState. - * The number of given template arguments must be equal to the entry Count from InfectionStates. - */ -template -class LctInfectionState -{ -public: - using InfectionState = InfectionStates; - static_assert((size_t)InfectionState::Count == sizeof...(Ns), - "The number of the size_t's provided as template parameters must be " - "the same as the entry Count of InfectionState."); - - static_assert(((Ns > 0) && ...), "The number of subcompartments must be at least 1."); - - /** - * @brief Gets the number of subcompartments in an infection state. - * - * @tparam State Infection state for which the number of subcompartments should be returned. - * @return Number of subcompartments for State. Returned value is always at least one. - */ - template - static constexpr size_t get_num_subcompartments() - { - static_assert(State < InfectionState::Count, "State must be a a valid InfectionState."); - return m_subcompartment_numbers[(size_t)State]; - } - - /** - * @brief Gets the index of the first subcompartment of an infection state. - * - * In a simulation, the number of individuals in the subcompartments are stored in vectors. - * Accordingly, the index of the first subcompartment of State in such a vector is returned. - * @tparam State: Infection state for which the index should be returned. - * @return Index of the first subcompartment for a vector with one entry per subcompartment. - * Returned value is always non-negative. - */ - template - static constexpr size_t get_first_index() - { - static_assert(State < InfectionState::Count, "State must be a a valid InfectionState."); - size_t index = 0; - for (size_t i = 0; i < (size_t)(State); i++) { - index = index + m_subcompartment_numbers[i]; - } - return index; - } - - /** - * @brief Cumulates a vector with the number of individuals in each subcompartment (with subcompartments - * according to the LctInfectionState) to produce a Vector that divides the population only into the infection - * states defined in InfectionStates. - * - * @param[in] subcompartments Vector with number of individuals in each subcompartment. - * The size of the vector has to match the LctInfectionState. - * @return Vector with accumulated values for the InfectionStates. - */ - static Eigen::VectorX calculate_compartments(const Eigen::VectorX& subcompartments) - { - assert(subcompartments.rows() == Count); - - Eigen::VectorX compartments((Eigen::Index)InfectionState::Count); - // Use segment of the vector subcompartments of each InfectionState and sum up the values of subcompartments. - compartments[(Eigen::Index)InfectionState::Susceptible] = subcompartments[0]; - compartments[(Eigen::Index)InfectionState::Exposed_1a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_1a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSymptoms_1a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSevere_1a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedCritical_1a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::Exposed_2a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_2a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSymptoms_2a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSevere_2a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedCritical_2a] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::Recovered_a] = - subcompartments[get_first_index()]; - compartments[(Eigen::Index)InfectionState::Dead_a] = subcompartments[get_first_index()]; - compartments[(Eigen::Index)InfectionState::Exposed_1b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_1b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSymptoms_1b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSevere_1b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedCritical_1b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::Exposed_2b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms_2b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSymptoms_2b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSevere_2b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedCritical_2b] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::Recovered_b] = - subcompartments[get_first_index()]; - compartments[(Eigen::Index)InfectionState::Dead_b] = subcompartments[get_first_index()]; - compartments[(Eigen::Index)InfectionState::Recovered_ab] = - subcompartments[get_first_index()]; - - return compartments; - } - - static constexpr size_t Count{(... + Ns)}; - -private: - static constexpr const std::array m_subcompartment_numbers{ - Ns...}; ///< Vector which defines the number of subcompartments for each infection state of InfectionState. -}; - -} // namespace mio - -#endif diff --git a/cpp/memilio/epidemiology/lct2d_populations.h b/cpp/memilio/epidemiology/lct2d_populations.h deleted file mode 100644 index 20b9f905b5..0000000000 --- a/cpp/memilio/epidemiology/lct2d_populations.h +++ /dev/null @@ -1,216 +0,0 @@ -/* -* Copyright (C) 2020-2025 MEmilio -* -* Authors: Annika Jungklaus, Lena Ploetzke -* -* Contact: Martin J. Kuehn -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ -#ifndef MIO_EPI_LCT_POPULATIONS_H -#define MIO_EPI_LCT_POPULATIONS_H - -#include "boost/type_traits/make_void.hpp" -#include "memilio/config.h" -#include "memilio/utils/uncertain_value.h" -#include "memilio/math/eigen.h" -#include "memilio/epidemiology/lct2d_infection_state.h" -#include "memilio/utils/type_list.h" -#include "memilio/utils/metaprogramming.h" - -namespace mio -{ - -/** - * @brief A class template for compartment populations of LCT models. - * - * Populations can be split up into different categories, e.g. by age group, yearly income group, gender etc. - * In LCT models, we want to use different numbers of subcompartments, i.e. different LctStates, - * for each group of a category. - * (Therefore, we can't use the normal Populations class because it expects the same InfectionStates for each group.) - * - * This template must be instantiated with one LctState for each group of a category. - * The purpose of the LctStates is to define the number of subcompartments for each InfectionState. - * The number of LctStates also determines the number of groups. - * If you want to use more than one category, e.g. age and gender, you have to define num_age_groups * num_genders - * LctStates, because the number of subcompartments can be different - * even for (female, A05-A14) and (female, A80+) or (male, A05-A14). - * - * The class created from this template contains a "flat array" of compartment populations and some functions for - * retrieving or setting the populations. The order in the flat array is: First, all (sub-)compartments of the - * first group, afterwards all (sub-)compartments of the second group and so on. - * - */ - -template -class LctPopulations -{ -public: - using Type = UncertainValue; - using InternalArrayType = Eigen::Array; - using LctStatesGroups = TypeList; - static size_t constexpr num_groups = sizeof...(LctStates); ///< Number of groups. - static_assert(num_groups >= 1, "The number of LctStates provided should be at least one."); - - /// @brief Default constructor. - LctPopulations() - { - set_count(); - m_y = InternalArrayType::Constant(m_count, 1, 0.); - } - - /** - * @brief get_num_compartments Returns the number of compartments. - * @return Number of compartments which equals the flat array size. - */ - size_t get_num_compartments() const - { - return m_count; - } - - /** - * @brief Returns a reference to the internally stored flat array. - * @return Const reference to the InternalArrayType instance. - */ - auto const& array() const - { - return m_y; - } - auto& array() - { - return m_y; - } - - /** - * @brief Returns the entry of the array given a flat index. - * @param index A flat index. - * @return The value of the internal array at the index. - */ - Type& operator[](size_t index) - { - assert(index < m_count); - return m_y[index]; - } - - /** - * @brief Gets the first index of a group in the flat array. - * @tparam group The group for which the index should be returned. - * @return The index of the first entry of group in the flat array. - */ - template - size_t get_first_index_of_group() const - { - static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); - if constexpr (Group == 0) { - return 0; - } - else { - return get_first_index_of_group() + type_at_index_t::Count; - } - } - /** - * @brief Returns an Eigen copy of the vector of populations. - * This can be used as initial conditions for the ODE solver. - * @return Eigen::VectorXd of populations. - */ - inline Eigen::VectorX get_compartments() const - { - return m_y.array().template cast(); - } - - /** - * @brief Returns the total population of a group. - * @tparam group The group for which the total population should be calculated. - * @return Total population of the group. - */ - template - FP get_group_total() const - { - return m_y.array() - .template cast() - .segment(get_first_index_of_group(), type_at_index_t::Count) - .sum(); - } - - /** - * @brief Returns the total population of all compartments and groups. - * @return Total population. - */ - FP get_total() const - { - return m_y.array().template cast().sum(); - } - - /** - * @brief Checks whether all compartments have non-negative values. - * This function can be used to prevent slightly negative function values in compartment sizes that are produced - * due to rounding errors if, e.g., population sizes were computed in a complex way. - * - * Attention: This function should be used with care. It can not and will not set model parameters and - * compartments to meaningful values. In most cases it is preferable to use check_constraints, - * and correct values manually before proceeding with the simulation. - * The main usage for apply_constraints is in automated tests using random values for initialization. - * - * @return Returns true if one (or more) constraint(s) were corrected, otherwise false. - */ - bool apply_constraints() - { - bool corrected = false; - for (int i = 0; i < m_y.array().size(); i++) { - if (m_y.array()[i] < 0) { - log_warning("Constraint check: Compartment size {:d} changed from {:.4f} to {:d}", i, m_y.array()[i], - 0); - m_y.array()[i] = 0.; - corrected = true; - } - } - return corrected; - } - - /** - * @brief Checks whether all compartments have non-negative values and logs an error if constraint is not satisfied. - * @return Returns true if one or more constraints are not satisfied, false otherwise. - */ - bool check_constraints() const - { - if ((m_y.array() < 0.).any()) { - log_error("Constraint check: At least one compartment size is smaller {}.", 0); - return true; - } - return false; - } - -private: - /** - * @brief Sets recursively the total number of (sub-)compartments over all groups. - * The number also corresponds to the size of the internal vector. - */ - template - void set_count() - { - if constexpr (Group == 0) { - m_count = 0; - } - if constexpr (Group < num_groups) { - m_count += type_at_index_t::Count; - set_count(); - } - } - - size_t m_count; //< Number of groups stored. - InternalArrayType m_y{}; //< An array containing the number of people in the groups. -}; - -} // namespace mio - -#endif // MIO_EPI_LCT_POPULATIONS_H diff --git a/cpp/memilio/epidemiology/lct_infection_state.h b/cpp/memilio/epidemiology/lct_infection_state.h index fbccebb4db..8206ca4f50 100644 --- a/cpp/memilio/epidemiology/lct_infection_state.h +++ b/cpp/memilio/epidemiology/lct_infection_state.h @@ -96,34 +96,17 @@ class LctInfectionState Eigen::VectorX compartments((Eigen::Index)InfectionState::Count); // Use segment of the vector subcompartments of each InfectionState and sum up the values of subcompartments. - compartments[(Eigen::Index)InfectionState::Susceptible] = subcompartments[0]; - compartments[(Eigen::Index)InfectionState::Exposed] = - subcompartments - .segment(get_first_index(), get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedNoSymptoms] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSymptoms] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedSevere] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::InfectedCritical] = - subcompartments - .segment(get_first_index(), - get_num_subcompartments()) - .sum(); - compartments[(Eigen::Index)InfectionState::Recovered] = - subcompartments[get_first_index()]; - compartments[(Eigen::Index)InfectionState::Dead] = subcompartments[get_first_index()]; + for (int i = 0; i < (Eigen::Index)InfectionState::Count; i++) { + InfectionState State = static_cast(i); + // first index of first subcompartment: + size_t index = 0; + for (size_t j = 0; j < (size_t)(State); j++) { + index = index + m_subcompartment_numbers[j]; + } + // number of subcompartments: + int num_subcomp = m_subcompartment_numbers[(size_t)State]; + compartments[i] = subcompartments.segment(index, num_subcomp).sum(); + } return compartments; } diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index 6805704619..4ebed88b47 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -24,7 +24,7 @@ #include "lct_secir_2_diseases/parameters.h" #include "lct_secir_2_diseases/infection_state.h" #include "memilio/compartments/compartmental_model.h" -#include "memilio/epidemiology/lct2d_populations.h" +#include "memilio/epidemiology/lct_populations.h" #include "memilio/config.h" #include "memilio/utils/time_series.h" #include "memilio/utils/logging.h" @@ -243,8 +243,8 @@ class Model // Calculate derivative of the Susceptible compartment. // outflow generated by disease a and disease b both - double part_a = 0.; - double part_b = 0.; + double part_a = 0.; // part of people getting infected with disease a + double part_b = 0.; // part of people getting infected with disease b interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); // split flow double div_part_both = @@ -279,7 +279,8 @@ class Model dydt[Ei_1b_first_index + subcomp + 1] = flow; } - // Calculate derivative of the InfectedNoSymptoms_1a compartment. + // Calculate derivative of the InfectedNoSymptoms_1a compartment + // flow from each subcompartment to the next for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { @@ -289,7 +290,8 @@ class Model dydt[INSi_1a_first_index + subcomp] -= flow; dydt[INSi_1a_first_index + subcomp + 1] = flow; } - // Calculate derivative of the InfectedNoSymptoms_1b compartment. + // Calculate derivative of the InfectedNoSymptoms_1b compartment + // flow from each subcompartment to the next for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { @@ -381,9 +383,9 @@ class Model dydt[ICri_1b_first_index + subcomp + 1] = flow; } - // Last flow from InfectedCritical has to be divided between Recovered and Dead. + // Flow from InfectedCritical has to be divided between Recovered and Dead. // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. - // for 1a + // for InefectedCritical_1a: flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * y[ICri_1a_first_index + @@ -392,7 +394,9 @@ class Model LctStateGroup::template get_num_subcompartments() - 1] -= flow; dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get()[Group]) * flow; dydt[Di_a] = dydt[Di_a] + params.template get()[Group] * flow; - // for 1b + // Flow from InfectedCritical_1b has to be divided between Recovered_b and Dead_b. + // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. + // Outflow from InfectedCritical_1b: flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * (1 / params.template get()[Group]) * y[ICri_1b_first_index + diff --git a/cpp/models/lct_secir_2_diseases/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h index c237a909a0..fa44ddafbf 100644 --- a/cpp/models/lct_secir_2_diseases/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -37,7 +37,7 @@ namespace lsecir2d **********************************************************/ /** - * @brief Average time spent in the Exposed compartment for disease a. + * @brief Average time spent in the Exposed compartment for disease a in day unit. */ struct TimeExposed_a { using Type = Eigen::VectorX>; @@ -129,7 +129,7 @@ struct TransmissionProbabilityOnContact_a { }; /** - * @brief Average time spent in the Exposed compartment for disease b. + * @brief Average time spent in the Exposed compartment for disease b in day unit. */ struct TimeExposed_b { using Type = Eigen::VectorX>; diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index 1dadfc7905..07cf8d91d5 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -29,9 +29,7 @@ #include "memilio/data/analyze_result.h" #include "memilio/compartments/simulation.h" #include "load_test_data.h" - #include - #include #include "boost/numeric/odeint/stepper/runge_kutta_cash_karp54.hpp" @@ -595,6 +593,8 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) } } +// Run the model with more than one subcompartment for states E,I,C,H,U +// and calculate the TimeSeries with no subcompartments from the result TEST(TestLCTSecir2d, testSubcompartments) { using InfState = mio::lsecir2d::InfectionState; @@ -605,6 +605,7 @@ TEST(TestLCTSecir2d, testSubcompartments) ScalarType tmax = 1; ScalarType dt = 0.1; + // Initial population, split into subcompartments std::vector> init = { {200}, {0, 0}, {30, 10, 10}, {0, 0, 0}, {10, 10, 10}, {0, 0, 0}, {0}, {0}, {0, 0}, {30, 10, 10}, {0, 0, 0}, {10, 10, 10}, {0, 0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, @@ -695,44 +696,6 @@ class ModelTestLCTSecir2d : public testing::Test Model* model = nullptr; }; -/* -// Test compares a simulation with the result of a previous run stored in a.csv file. -// to do -TEST_F(ModelTestLCTSecir2d, compareWithPreviousRun) -{ - ScalarType tmax = 3; - mio::TimeSeries result = mio::simulate( - 0, tmax, 0.5, *model, - std::make_shared>()); - - // Compare subcompartments. - auto compare = load_test_data_csv("lct-secir-2d-subcompartments-compare.csv"); - - ASSERT_EQ(compare.size(), static_cast(result.get_num_time_points())); - for (size_t i = 0; i < compare.size(); i++) { - ASSERT_EQ(compare[i].size(), static_cast(result.get_num_elements()) + 1) << "at row " << i; - EXPECT_NEAR(result.get_time(i), compare[i][0], 1e-7) << "at row " << i; - for (size_t j = 1; j < compare[i].size(); j++) { - EXPECT_NEAR(result.get_value(i)[j - 1], compare[i][j], 1e-7) << " at row " << i; - } - } - - // Compare InfectionState compartments. - mio::TimeSeries population = model->calculate_compartments(result); - auto compare_population = load_test_data_csv("lct-secir-2d-compartments-compare.csv"); - - ASSERT_EQ(compare_population.size(), static_cast(population.get_num_time_points())); - for (size_t i = 0; i < compare_population.size(); i++) { - ASSERT_EQ(compare_population[i].size(), static_cast(population.get_num_elements()) + 1) - << "at row " << i; - EXPECT_NEAR(population.get_time(i), compare_population[i][0], 1e-7) << "at row " << i; - for (size_t j = 1; j < compare_population[i].size(); j++) { - EXPECT_NEAR(population.get_value(i)[j - 1], compare_population[i][j], 1e-7) << " at row " << i; - } - } -} -*/ - // Test calculate_compartments with a TimeSeries that has an incorrect number of elements. TEST_F(ModelTestLCTSecir2d, testCalculatePopWrongSize) { diff --git a/docs/source/cpp/lct.rst b/docs/source/cpp/lct.rst index 56cf4a8a98..807cae7b93 100644 --- a/docs/source/cpp/lct.rst +++ b/docs/source/cpp/lct.rst @@ -114,3 +114,4 @@ List of models :titlesonly: models/lsecir + models/lsecir2d diff --git a/docs/source/cpp/models/lsecir.rst b/docs/source/cpp/models/lsecir.rst index b818ffbef7..c7f3deefb7 100644 --- a/docs/source/cpp/models/lsecir.rst +++ b/docs/source/cpp/models/lsecir.rst @@ -1,7 +1,7 @@ LCT-based SECIR-type model ========================== -The LCT-SECIR module models and simulates an epidemic using an ODE-based approach while making use of the Linear Chain +The LCT-SECIR module models and simulates an epidemic using an ODE-based approach while making use of the Linear Chain Trick. This provides the option of Erlang distributed stay times in the compartments through the use of subcompartments. The model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical states are possible. The model assumes perfect immunity after recovery and is thus only suited for epidemic use cases. diff --git a/docs/source/cpp/models/lsecir2d.rst b/docs/source/cpp/models/lsecir2d.rst new file mode 100644 index 0000000000..3dbee34a62 --- /dev/null +++ b/docs/source/cpp/models/lsecir2d.rst @@ -0,0 +1,466 @@ +2 Diseases in LCT-based SECIR-type model +========================== + +| The LCT-SECIR-2-DISEASES model is an extension of the :doc:`model with one disease `. +| The model is ODE-based and uses the Linear Chain Trick to allow for more general Erlang distributed stay times in each compartment instead of just exponentially distributed stay times induced by basic ODE-based models. +| With the SECIR structure the model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical states are possible. +| For the two diseases :math:`a` and :math:`b` the model assumes no co-infection, a certain independence in the sense that prior infection with one disease does not affect the infection with the other disease (e.g. probability to get infected, time spend in each state, chances of recovery etc.), and perfect immunity after recovery for both diseases. + + +There are two possibilities for a susceptible individual (since we assume no co-infection): + 1. Get infected with disease a, then (if not dead) get infected with disease b + 2. Get infected with disease b, then (if not dead) get infected with disease a + +Each infection is simulated using a LCT-SECIR model. The states are labeled according to infection (first or second) and disease (:math:`a` or :math:`b`), +so the full model is given by the combination of infections :math:`1a`, :math:`2a`, :math:`1b`, and :math:`2b`. + +Below is a visualization of the infection states split into LCT-states and transitions without a stratification according to socisdemographic groups. + +.. image:: "" + :alt: tikz_lct-2d + +With infection states for :math:`i \in \{1,2\}, x \in \{a,b\}`: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Letter + - SECIR-State + - In the code + * - S + - `Susceptible` + - Susceptible + * - E + - `Exposed` + - Exposed_ix + * - C + - `Carrier` + - InfectedNoSymptoms_ix + * - I + - `Infected` + - InfectedSymptoms_ix + * - H + - `Hospitalized` + - InfectedSevere_ix + * - U + - `in Intensive Care Unit` + - InfectedCritical_ix + * - R + - `Recovered` + - Recovered_x, Recoverd_ab + * - D + - `Dead` + - Dead_x + +The compartments :math:`C, I` are infectious (red), the other compartments are considered to be not infectious (blue), +due to extensive isolation of the hospitalized individuals. + + +Infection States +---------------- + +The model contains the following list of **InfectionState**\s: + +.. code-block:: RST + + `Susceptible` + + `Exposed_1a` + `InfectedNoSymptoms_1a` + `InfectedSymptoms_1a` + `InfectedSevere_1a` + `InfectedCritical_1a` + + `Recovered_a` + `Dead_a` + + `Exposed_2a` + `InfectedNoSymptoms_2a` + `InfectedSymptoms_2a` + `InfectedSevere_2a` + `InfectedCritical_2a` + + `Exposed_1b` + `InfectedNoSymptoms_1b` + `InfectedSymptoms_1b` + `InfectedSevere_1b` + `InfectedCritical_1b` + + `Recovered_b` + `Dead_b` + + `Exposed_2b` + `InfectedNoSymptoms_2b` + `InfectedSymptoms_2b` + `InfectedSevere_2b` + `InfectedCritical_2b` + + `Recovered_ab` + +| It is possible to include subcompartments for the states `E`, `C`, `I`, `H`, and `U`, so compartments +| Exposed_1a, Exposed_2a, Exposed_1b, Exposed_2b, +| InfectedNoSymptoms_1a, InfectedNoSymptoms_2a, InfectedNoSymptoms_1b, InfectedNoSymptoms_2b, +| InfectedSymptoms_1a , InfectedSymptoms_2a, InfectedSymptoms_1b , InfectedSymptoms_2b, +| InfectedSevere_1a, InfectedSevere_2a , InfectedSevere_1b , InfectedSevere_2b, +| InfectedCritical_1a, InfectedCritical_2a, InfectedCritical_1b, and InfectedCritical_2b. + +The number of subcompartments can be set individually for each compartment. + + +Sociodemographic Stratification +------------------------------- + +In the LCT-SECIR-2D model, the population can be stratified by one sociodemographic dimension. +This dimension is denoted **Group**. It can be used for age groups as well as for other interpretations. +Different age groups can have different numbers of subcompartments. + +Parameters +---------- + +The model implements the following parameters: + +.. list-table:: + :header-rows: 1 + :widths: 20 20 60 + + * - Mathematical variable + - C++ variable name + - Description + * - :math:`\phi` + - ``ContactPatterns`` + - Average number of contacts for person per day, for multiple age groups this is a matrix. + * - :math:`\rho_a` + - ``TransmissionProbabilityOnContact_a`` + - Transmission risk for people located in the susceptible compartments for disease :math:`a`. + * - :math:`\rho_b` + - ``TransmissionProbabilityOnContact_b`` + - Transmission risk for people located in the susceptible compartments for disease :math:`b`. + * - :math:`\xi_{I_{NS}, a}` + - ``RelativeTransmissionNoSymptoms_a`` + - Proportion of nonsymptomatically infected people who are not isolated for disease :math:`a`. + * - :math:`\xi_{I_{NS}, b}` + - ``RelativeTransmissionNoSymptoms_b`` + - Proportion of nonsymptomatically infected people who are not isolated for disease :math:`b`. + * - :math:`\xi_{I_{Sy}, a}` + - ``RiskOfInfectionFromSymptomatic_a`` + - Proportion of infected people with symptoms who are not isolated for disease :math:`a`. + * - :math:`\xi_{I_{Sy}, b}` + - ``RiskOfInfectionFromSymptomatic_b`` + - Proportion of infected people with symptoms who are not isolated for disease :math:`b`. + * - :math:`N` + - ``m_N0`` + - Total population. + * - :math:`n_{E,1a}` + - Defined in ``LctStates`` + - Number of subcompartments of the Exposed_1a compartment. + * - :math:`n_{E,2a}` + - Defined in ``LctStates`` + - Number of subcompartments of the Exposed_2a compartment. + * - :math:`n_{E,1b}` + - Defined in ``LctStates`` + - Number of subcompartments of the Exposed_1b compartment. + * - :math:`n_{E,2b}` + - Defined in ``LctStates`` + - Number of subcompartments of the Exposed_2b compartment. + * - :math:`n_{NS,1a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedNoSymptoms_1a compartment. + * - :math:`n_{NS,2a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedNoSymptoms_2a compartment. + * - :math:`n_{NS,1b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedNoSymptoms_1b compartment. + * - :math:`n_{NS,2b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedNoSymptoms_2b compartment. + * - :math:`n_{Sy,1a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSymptoms_1a compartment. + * - :math:`n_{Sy,2a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSymptoms_2a compartment. + * - :math:`n_{Sy,1b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSymptoms_1b compartment. + * - :math:`n_{Sy,2b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSymptoms_2b compartment. + * - :math:`n_{Sev,1a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSevere_1a compartment. + * - :math:`n_{Sev,2a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSevere_2a compartment. + * - :math:`n_{Sev,1b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSevere_1b compartment. + * - :math:`n_{Sev,2b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedSevere_2b compartment. + * - :math:`n_{Cr,1a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedCritical_1a compartment. + * - :math:`n_{Cr,2a}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedCritical_2a compartment. + * - :math:`n_{Cr,1b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedCritical_1b compartment. + * - :math:`n_{Cr,2b}` + - Defined in ``LctStates`` + - Number of subcompartments of the InfectedCritical_2b compartment. + * - :math:`T_{E,a}` + - ``TimeExposed_a`` + - Average time in days an individual stays in the Exposed_1a or Exposed_2a compartment. + * - :math:`T_{E,b}` + - ``TimeExposed_b`` + - Average time in days an individual stays in the Exposed_1b or Exposed_2b compartment. + * - :math:`T_{I_{NS},a}` + - ``TimeInfectedNoSymptoms_a`` + - Average time in days an individual stays in the InfectedNoSymptomsa_1a or InfectedNoSymptoms_2a compartment. + * - :math:`T_{I_{NS},b}` + - ``TimeInfectedNoSymptoms_b`` + - Average time in days an individual stays in the InfectedNoSymptomsa_1b or InfectedNoSymptoms_2b compartment. + * - :math:`T_{I_{Sy},a}` + - ``TimeInfectedSymptoms_a`` + - Average time in days an individual stays in the InfectedSymptoms_1a or InfectedSymptoms_2a compartment. + * - :math:`T_{I_{Sy},b}` + - ``TimeInfectedSymptoms_b`` + - Average time in days an individual stays in the InfectedSymptoms_1b or InfectedSymptoms_2b compartment. + * - :math:`T_{I_{Sev},a}` + - ``TimeInfectedSevere_a`` + - Average time in days an individual stays in the InfectedSevere_1a or InfectedSevere_2a compartment. + * - :math:`T_{I_{Sev},b}` + - ``TimeInfectedSevere_b`` + - Average time in days an individual stays in the InfectedSevere_1b or InfectedSevere_2b compartment. + * - :math:`T_{I_{Cr},a}` + - ``TimeInfectedCritical_a`` + - Average time in days an individual stays in the InfectedCritical_1a or InfectedCritical_2a compartment. + * - :math:`T_{I_{Cr},b}` + - ``TimeInfectedCritical_b`` + - Average time in days an individual stays in the InfectedCritical_1b or InfectedCritical_2b compartment. + * - :math:`\mu_{I_{NS},a}^{R}` + - ``RecoveredPerInfectedNoSymptoms_a`` + - Probability of transition from compartment InfectedNoSymptoms_1a to Recovered_a or from InfectedNoSymptoms_2a to Recovered_ab. + * - :math:`\mu_{I_{NS},b}^{R}` + - ``RecoveredPerInfectedNoSymptoms_b`` + - Probability of transition from compartment InfectedNoSymptoms_1b to Recovered_b or from InfectedNoSymptoms_2b to Recovered_ab. + * - :math:`\mu_{I_{Sy},a}^{I_{Sev}}` + - ``SeverePerInfectedSymptoms_a`` + - Probability of transition from compartment InfectedSymptoms_1a to InfectedSevere_1a or from InfectedSymptoms_2a to InfectedSevere_2a. + * - :math:`\mu_{I_{Sy},b}^{I_{Sev}}` + - ``SeverePerInfectedSymptoms_b`` + - Probability of transition from compartment InfectedSymptoms_1b to InfectedSevere_1b or from InfectedSymptoms_2b to InfectedSevere_2b. + * - :math:`\mu_{I_{Sev},a}^{I_{Cr}}` + - ``CriticalPerSevere_a`` + - Probability of transition from compartment InfectedSevere_1a to InfectedCritical_1a or from InfectedSevere_2a to InfectedCritical_2a. + * - :math:`\mu_{I_{Sev},b}^{I_{Cr}}` + - ``CriticalPerSevere_b`` + - Probability of transition from compartment InfectedSevere_1b to InfectedCritical_1b or from InfectedSevere_2b to InfectedCritical_2b. + * - :math:`\mu_{I_{Cr},a}^{D}` + - ``DeathsPerCritical_a`` + - Probability of dying when in compartment InfectedCritical_1a or InfectedCritical_2a. + * - :math:`\mu_{I_{Cr},b}^{D}` + - ``DeathsPerCritical_b`` + - Probability of dying when in compartment InfectedCritical_1b or InfectedCritical_2b. + + +Initial conditions +------------------ + +To initialize the model, we start by defining the number of subcompartments for every compartment and constructing the model with it. + +.. code-block:: cpp + + constexpr size_t NumExposed_1a = 1, NumInfectedNoSymptoms_1a = 1, NumInfectedSymptoms_1a = 1, + NumInfectedSevere_1a = 1, NumInfectedCritical_1a = 1, NumExposed_2a = 1, + NumInfectedNoSymptoms_2a = 1, NumInfectedSymptoms_2a = 1, NumInfectedSevere_2a = 1, + NumInfectedCritical_2a = 1, NumExposed_1b = 1, NumInfectedNoSymptoms_1b = 1, + NumInfectedSymptoms_1b = 1, NumInfectedSevere_1b = 1, NumInfectedCritical_1b = 1, + NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 1, NumInfectedSymptoms_2b = 1, + NumInfectedSevere_2b = 1, NumInfectedCritical_2b = 1; + using InfState = mio::lsecir2d::InfectionState; + using LctState = mio::LctInfectionState< + InfState, 1, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, + NumInfectedCritical_1a, 1, 1, NumExposed_2a, NumInfectedNoSymptoms_2a, NumInfectedSymptoms_2a, + NumInfectedSevere_2a, NumInfectedCritical_2a, NumExposed_1b, NumInfectedNoSymptoms_1b, NumInfectedSymptoms_1b, + NumInfectedSevere_1b, NumInfectedCritical_1b, 1, 1, NumExposed_2b, NumInfectedNoSymptoms_2b, + NumInfectedSymptoms_2b, NumInfectedSevere_2b, NumInfectedCritical_2b, 1>; + using Model = mio::lsecir2d::Model; + Model model; + +For the simulation, we need initial values for all (sub)compartments. If we do not set the initial values manually, these are set to :math:`0` by default. + +We start with constructing a vector ``initial_populations`` that we will pass on to the model. It contains vectors for each compartment, +that contains a vector with initial values for the respective subcompartments. + +.. code-block:: cpp + + std::vector> initial_populations = { + {200}, {0, 0}, {30, 10, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0}, {0}, {0}, {0}, + {0, 0}, {10, 0}, {0, 0}, {0}, {10, 0}, {30, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0}, + {0}, {0}, {100}, {0, 0}, {0, 0}, {0, 0}, {0}, {0}}; + + +We assert that the vector has the correct size by checking that the number of `InfectionState`\s and the numbers of subcompartments are correct. + +.. code-block:: cpp + + if (initial_populations.size() != (size_t)InfState::Count) { + mio::log_error("The number of vectors in initial_populations does not match the number of InfectionStates."); + return 1; + } + if ((initial_populations[(size_t)InfState::Susceptible].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Exposed_1a].size() != NumExposed_1a) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_1a].size() != NumInfectedNoSymptoms_1a) || + (initial_populations[(size_t)InfState::InfectedSymptoms_1a].size() != NumInfectedSymptoms_1a) || + (initial_populations[(size_t)InfState::InfectedSevere_1a].size() != NumInfectedSevere_1a) || + (initial_populations[(size_t)InfState::InfectedCritical_1a].size() != NumInfectedCritical_1a) || + (initial_populations[(size_t)InfState::Exposed_2a].size() != NumExposed_2a) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_2a].size() != NumInfectedNoSymptoms_2a) || + (initial_populations[(size_t)InfState::InfectedSymptoms_2a].size() != NumInfectedSymptoms_2a) || + (initial_populations[(size_t)InfState::InfectedSevere_2a].size() != NumInfectedSevere_2a) || + (initial_populations[(size_t)InfState::InfectedCritical_2a].size() != NumInfectedCritical_2a) || + (initial_populations[(size_t)InfState::Recovered_a].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Dead_a].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Exposed_1b].size() != NumExposed_1b) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_1b].size() != NumInfectedNoSymptoms_1b) || + (initial_populations[(size_t)InfState::InfectedSymptoms_1b].size() != NumInfectedSymptoms_1b) || + (initial_populations[(size_t)InfState::InfectedSevere_1b].size() != NumInfectedSevere_1b) || + (initial_populations[(size_t)InfState::InfectedCritical_1b].size() != NumInfectedCritical_1b) || + (initial_populations[(size_t)InfState::Exposed_2b].size() != NumExposed_2b) || + (initial_populations[(size_t)InfState::InfectedNoSymptoms_2b].size() != NumInfectedNoSymptoms_2b) || + (initial_populations[(size_t)InfState::InfectedSymptoms_2b].size() != NumInfectedSymptoms_2b) || + (initial_populations[(size_t)InfState::InfectedSevere_2b].size() != NumInfectedSevere_2b) || + (initial_populations[(size_t)InfState::InfectedCritical_2b].size() != NumInfectedCritical_2b) || + (initial_populations[(size_t)InfState::Recovered_ab].size() != + LctState::get_num_subcompartments())) { + mio::log_error("The length of at least one vector in initial_populations does not match the related number of " + "subcompartments."); + return 1; + } + +The initial populations in the model are set via: + +.. code-block:: cpp + + std::vector flat_initial_populations; + for (auto&& vec : initial_populations) { + flat_initial_populations.insert(flat_initial_populations.end(), vec.begin(), vec.end()); + } + for (size_t i = 0; i < LctState::Count; i++) { + model.populations[i] = flat_initial_populations[i]; + } + +.. _Nonpharmaceutical Interventions: +Nonpharmaceutical Interventions +------------------------------- + +In the LCT-SECIR-2D model, nonpharmaceutical interventions (NPIs) are implemented through dampings in the contact matrix. +These dampings reduce the contact rates between different groups to simulate interventions. + +Basic dampings can be added to the contact matrix as follows: + +.. code-block:: cpp + + // Create a contact matrix with constant contact rate 10 (one age group). + mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + + // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. + contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); + +For age-resolved models, you can apply different dampings to different groups: + +.. code-block:: cpp + + // Create a contact matrix with constant contact rate 10 between all age groups + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(num_agegroups, num_agegroups, 10)); + + // Add a damping that reduces contacts within the same age group by 70% starting at day 5. + contact_matrix.add_damping(Eigen::VectorX::Constant(num_agegroups, 0.7).asDiagonal(), + mio::SimulationTime(5.)); + +Simulation +---------- + +We can simulate the model from :math:`t_0` to :math:`t_{\max}` with initial step size :math:`dt` as follows: + +.. code-block:: cpp + + ScalarType t0 = 0; + ScalarType tmax = 10; + ScalarType dt = 0.5; + mio::TimeSeries result = mio::simulate(t0, tmax, dt, model); + + +Output +------ + +The simulation result is stratefied by subcompartments. The function ``calculate_compartments()`` aggregates the subcompartments by `InfectionState`\s. + +.. code-block:: cpp + + mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); + +You can access the data in the `mio::TimeSeries` object as follows: + +.. code-block:: cpp + + // Get the number of time points. + auto num_points = static_cast(result.get_num_time_points()); + + // Access data at a specific time point. + Eigen::VectorX value_at_time_i = result.get_value(i); + ScalarType time_i = result.get_time(i); + + // Access the last time point. + Eigen::VectorX last_value = result.get_last_value(); + ScalarType last_time = result.get_last_time(); + + +You can print the simulation results as a formatted table: + +.. code-block:: cpp + + // Print results to console with default formatting. + result.print_table(); + + // Print with custom column labels and width, and custom number of decimals. + results.print_table({" S", " E1a", " C1a", " I1a", " H1a", " U1a", " Ra", + " Da", " E2a", " C2a", " I2a", " H2a", " U2a", " E1b", + " C1b", " I1b", " H1b", " U1b", " Rb", " Db", " E2b", + " C2b", " I2b", " H2b", " U2b", " Rab"}, + 6, 2); + +Additionally, you can export the results to a CSV file: + +.. code-block:: cpp + + // Export results to CSV with default settings. + result.export_csv("simulation_results.csv"); + + +Visualization +------------- + +To visualize the results of a simulation, you can use the Python package :doc:`m-plot <../../python/m-plot>` and its documentation. + + +Examples +-------- + +An example can be found at: + +- `examples/lct_secir_2_diseases.cpp `_ + + +Overview of the ``lsecir2d`` namespace: +----------------------------------------- + +.. doxygennamespace:: mio::lsecir2d From 1da5cf510e4ab2d5e53dff803aad714c269801ab Mon Sep 17 00:00:00 2001 From: an-jung Date: Fri, 22 Aug 2025 11:28:37 +0200 Subject: [PATCH 10/16] lct_infectionstate fix --- cpp/memilio/epidemiology/lct_infection_state.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cpp/memilio/epidemiology/lct_infection_state.h b/cpp/memilio/epidemiology/lct_infection_state.h index 8206ca4f50..4badc7747c 100644 --- a/cpp/memilio/epidemiology/lct_infection_state.h +++ b/cpp/memilio/epidemiology/lct_infection_state.h @@ -104,8 +104,8 @@ class LctInfectionState index = index + m_subcompartment_numbers[j]; } // number of subcompartments: - int num_subcomp = m_subcompartment_numbers[(size_t)State]; - compartments[i] = subcompartments.segment(index, num_subcomp).sum(); + size_t num_subcomp = m_subcompartment_numbers[(size_t)State]; + compartments[i] = subcompartments.segment(index, num_subcomp).sum(); } return compartments; From d7ce14a93d902517ded45d1ca22109ac3f90d681 Mon Sep 17 00:00:00 2001 From: an-jung Date: Fri, 29 Aug 2025 08:59:40 +0200 Subject: [PATCH 11/16] revert changes in lct_secir exmple, explain numbers in template --- cpp/examples/lct_secir.cpp | 29 ++++++++++++++------------- cpp/examples/lct_secir_2_diseases.cpp | 20 +++++++++++------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/cpp/examples/lct_secir.cpp b/cpp/examples/lct_secir.cpp index cc4ed51729..03278b1982 100644 --- a/cpp/examples/lct_secir.cpp +++ b/cpp/examples/lct_secir.cpp @@ -37,31 +37,31 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. - constexpr size_t NumExposed = 1, NumInfectedNoSymptoms = 1, NumInfectedSymptoms = 1, NumInfectedSevere = 1, - NumInfectedCritical = 1; + constexpr size_t NumExposed = 2, NumInfectedNoSymptoms = 3, NumInfectedSymptoms = 1, NumInfectedSevere = 1, + NumInfectedCritical = 5; using InfState = mio::lsecir::InfectionState; using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using Model = mio::lsecir::Model; Model model; // Variable defines whether the class Initializer is used to define an initial vector from flows or whether a manually // defined initial vector is used to initialize the LCT model. bool use_initializer_flows = false; - ScalarType tmax = 5; + ScalarType tmax = 10; // Set Parameters. - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; + model.parameters.get()[0] = 3.2; + model.parameters.get()[0] = 2.; + model.parameters.get()[0] = 5.8; + model.parameters.get()[0] = 9.5; + model.parameters.get()[0] = 7.1; - model.parameters.get()[0] = 0.1; + model.parameters.get()[0] = 0.05; mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); @@ -117,7 +117,8 @@ int main() // This method of defining the initial values using a vector of vectors is not necessary, but should remind you // how the entries of the initial value vector relate to the defined template parameters of the model or the number // of subcompartments. It is also possible to define the initial values directly. - std::vector> initial_populations = {{9000}, {1000}, {0}, {0}, {0}, {0}, {0}, {0}}; + std::vector> initial_populations = {{750}, {30, 20}, {20, 10, 10}, {50}, + {50}, {10, 10, 5, 3, 2}, {20}, {10}}; // Assert that initial_populations has the right shape. if (initial_populations.size() != (size_t)InfState::Count) { @@ -153,10 +154,10 @@ int main() } // Perform a simulation. - mio::TimeSeries result = mio::simulate(0, tmax, 0.1, model); + mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); // The simulation result is divided by subcompartments. // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); - interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 3); + interpolated_results.print_table({"S", "E", "C", "I", "H", "U", "R", "D "}, 12, 4); } diff --git a/cpp/examples/lct_secir_2_diseases.cpp b/cpp/examples/lct_secir_2_diseases.cpp index 5a258042e6..284582e71e 100644 --- a/cpp/examples/lct_secir_2_diseases.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -33,6 +33,7 @@ int main() // Simple example to demonstrate how to run a simulation using an LCT-SECIR-2-DISEASE model. // One single AgeGroup/Category member is used here. // Parameters, initial values and the number of subcompartments are not meant to represent a realistic scenario. + // The number of subcompartments can be chosen for most of the compartments: constexpr size_t NumExposed_1a = 2, NumInfectedNoSymptoms_1a = 3, NumInfectedSymptoms_1a = 3, NumInfectedSevere_1a = 3, NumInfectedCritical_1a = 2, NumExposed_2a = 1, NumInfectedNoSymptoms_2a = 2, NumInfectedSymptoms_2a = 2, NumInfectedSevere_2a = 2, @@ -40,13 +41,18 @@ int main() NumInfectedSymptoms_1b = 3, NumInfectedSevere_1b = 3, NumInfectedCritical_1b = 2, NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 2, NumInfectedSymptoms_2b = 2, NumInfectedSevere_2b = 2, NumInfectedCritical_2b = 1; - using InfState = mio::lsecir2d::InfectionState; - using LctState = mio::LctInfectionState< - InfState, 1, NumExposed_1a, NumInfectedNoSymptoms_1a, NumInfectedSymptoms_1a, NumInfectedSevere_1a, - NumInfectedCritical_1a, 1, 1, NumExposed_2a, NumInfectedNoSymptoms_2a, NumInfectedSymptoms_2a, - NumInfectedSevere_2a, NumInfectedCritical_2a, NumExposed_1b, NumInfectedNoSymptoms_1b, NumInfectedSymptoms_1b, - NumInfectedSevere_1b, NumInfectedCritical_1b, 1, 1, NumExposed_2b, NumInfectedNoSymptoms_2b, - NumInfectedSymptoms_2b, NumInfectedSevere_2b, NumInfectedCritical_2b, 1>; + // The compartment for Susceptible people and all compartments for Dead and Recovered people must have exactly one subcompartment: + constexpr size_t NumSusceptible = 1, NumDead_a = 1, NumDead_b = 1, NumRecovered_a = 1, NumRecovered_b = 1, + NumRecovered_ab = 1; + using InfState = mio::lsecir2d::InfectionState; + using LctState = + mio::LctInfectionState; using Model = mio::lsecir2d::Model; Model model; From ab75b1788aa52f00309314c0e66321999464d252 Mon Sep 17 00:00:00 2001 From: an-jung Date: Fri, 29 Aug 2025 09:23:59 +0200 Subject: [PATCH 12/16] CI fix in test_lct_secir_2_diseases --- cpp/tests/test_lct_secir_2_diseases.cpp | 32 +++++++------------------ 1 file changed, 8 insertions(+), 24 deletions(-) diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index 07cf8d91d5..ed5d8b8f38 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -166,13 +166,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) model_lct.parameters.get()[0] = 0.3; // Simulate - mio::TimeSeries result_lct2d = mio::simulate( - t0, tmax, dt, model_lct2d, - std::make_shared>()); + mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); - mio::TimeSeries result_lct = mio::simulate( - t0, tmax, dt, model_lct, - std::make_shared>()); + mio::TimeSeries result_lct = mio::simulate(t0, tmax, dt, model_lct); // Simulation results should be equal. // Compare LCT with infection 1a in LCT2D @@ -297,13 +293,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) model_lct.parameters.get()[0] = 0.3; // Simulate - mio::TimeSeries result_lct2d = mio::simulate( - t0, tmax, dt, model_lct2d, - std::make_shared>()); + mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); - mio::TimeSeries result_lct = mio::simulate( - t0, tmax, dt, model_lct, - std::make_shared>()); + mio::TimeSeries result_lct = mio::simulate(t0, tmax, dt, model_lct); // Simulation results should be equal. // Compare LCT with Infection 1b in LCT2D @@ -429,13 +421,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) model_lct.parameters.get()[0] = 0.3; // Simulate - mio::TimeSeries result_lct2d = mio::simulate( - t0, tmax, dt, model_lct2d, - std::make_shared>()); + mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); - mio::TimeSeries result_lct = mio::simulate( - t0, tmax, dt, model_lct, - std::make_shared>()); + mio::TimeSeries result_lct = mio::simulate(t0, tmax, dt, model_lct); // Simulation results should be equal. // Compare LCT with Infection 2a in LCT2D @@ -560,13 +548,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) model_lct.parameters.get()[0] = 0.3; // Simulate - mio::TimeSeries result_lct2d = mio::simulate( - t0, tmax, dt, model_lct2d, - std::make_shared>()); + mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); - mio::TimeSeries result_lct = mio::simulate( - t0, tmax, dt, model_lct, - std::make_shared>()); + mio::TimeSeries result_lct = mio::simulate(t0, tmax, dt, model_lct); // Simulation results should be equal. // Compare LCT with Infection 2b in LCT2D From e70e33921486601f4cd6f95011ab2564f9b81c6f Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 16 Oct 2025 10:50:32 +0200 Subject: [PATCH 13/16] apply stashes --- cpp/models/lct_secir/README.md | 2 +- cpp/models/lct_secir_2_diseases/README.md | 84 +------------------- cpp/models/lct_secir_2_diseases/model.h | 2 +- cpp/models/lct_secir_2_diseases/parameters.h | 54 +++++++------ cpp/tests/test_lct_secir_2_diseases.cpp | 11 +-- docs/source/cpp/models/lsecir2d.rst | 4 +- 6 files changed, 42 insertions(+), 115 deletions(-) diff --git a/cpp/models/lct_secir/README.md b/cpp/models/lct_secir/README.md index 155948877c..5277775aa4 100644 --- a/cpp/models/lct_secir/README.md +++ b/cpp/models/lct_secir/README.md @@ -1,5 +1,5 @@ # LCT SECIR model -This directory contains a model implementation based on an ODE formulation using the linear chain trick. +This directory contains a model implementation based on an ODE formulation using the Linear Chain Trick. To get started, check out the [official documentation](https://memilio.readthedocs.io/en/latest/cpp/models/lsecir.html) or the [code example](../../examples/lct_secir.cpp). \ No newline at end of file diff --git a/cpp/models/lct_secir_2_diseases/README.md b/cpp/models/lct_secir_2_diseases/README.md index 4e2e15c9e6..435d380ea3 100644 --- a/cpp/models/lct_secir_2_diseases/README.md +++ b/cpp/models/lct_secir_2_diseases/README.md @@ -1,83 +1,5 @@ # LCT SECIR TWO DISEASES model -This model describes infection with two independent (e.g. no co-infection) diseases a and b, based on the Linear Chain Trick. - -The Linear Chain Trick (LCT) provides the option to use Erlang-distributed stay times in the compartments through the use of subcompartments. -The normal ODE models have (possibly unrealistic) exponentially distributed stay times. -The LCT model can still be described by an ordinary differential equation system. - -For the concept of LCT models with one disease see -- Lena Plötzke, "Der Linear Chain Trick in der epidemiologischen Modellierung als Kompromiss zwischen gewöhnlichen und Integro-Differentialgleichungen", 2023. (https://elib.dlr.de/200381/, German only) -- P. J. Hurtado und A. S. Kirosingh, "Generalizations of the ‘Linear Chain Trick’: incorporating more flexible dwell time distributions into mean field ODE models“, 2019. (https://doi.org/10.1007/s00285-019-01412-w) - -For each infection there are the infection states Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere, InfectedCritical, Recovered and Dead -The infectious compartments are InfectedNoSymptoms and InfectedSymptoms, all other compartments are considered to be not infectious - -There are two possibilities for a susceptible individual (since we assume no co-infection): -1. Get infected with disease a, then (if not dead) get infected with disease b -2. Get infected with disease b, then (if not dead) get infected with disease a - -This leads to the following compartments: -- `Susceptible` ($S$), may get infected with a or b (-> Exposed_1a or Exposed_1b) at any time -Infection 1a: -- `Exposed_1a` ($E_{1a}$), becomes InfectedNoSymptoms_1a after some time -- `InfectedNoSymptoms_1a` ($I_{NS, 1a}_$), becomes InfectedSymptoms_1a or Recovered_a after some time -- `InfectedSymptoms_1a` ($I_{Sy, 1a}$), becomes InfectedSevere_1a or Recovered_a after some time -- `InfectedSevere_1a` ($I_{Sev, 1a}$), becomes InfectedCritical_1a or Recovered_a after some time -- `InfectedCritical_1a` ($I_{Cr, 1a}$), becomes Recovered_a or Dead_a after some time -- `Recovered_a` ($R_a$), immune to a, may get infected with b (-> Exposed_2b) at any time -- `Dead_a` ($D_a$), absorbing state -Infection 1b: -- `Exposed_1b` ($E_{1b}$), becomes InfectedNoSymptoms_1b after some time -- `InfectedNoSymptoms_1b` ($I_{NS, 1b}$), becomes InfectedSymptoms_1b or Recovered_1b after some time -- `InfectedSymptoms_1b` ($I_{Sy, 1b}$), becomes InfectedSevere_1b or Recovered_b after some time -- `InfectedSevere_1b` ($I_{Sev, 1b}$), becomes InfectedCritical_1b or Recovered_b after some time -- `InfectedCritical_1b` ($I_{Cr, 1b}$), becomes Recovered_b or Dead_b after some time -- `Recovered_b` ($R_b$), immune to b, may get infected with b (-> Exposed_2a) at any time -- `Dead_b` ($D_b$), absorbing state -Infection 2a: -- `Exposed_2a` ($E_{2a}$), becomes InfectedNoSympotoms_2a after some time -- `InfectedNoSymptoms_2a` ($I_{NS, 2a}$), becomes InfectedSymptoms_2a or Recovered_ab after some time -- `InfectedSymptoms_2a` ($I_{Sy, 2a}$), becomes InfectedSevere_2a or Recovered_ab after some time -- `InfectedSevere_2a` ($I_{Sev, 2a}$), becomes InfectedCritical_2a or Recovered_ab after some time -- `InfectedCritical_2a` ($I_{Cr, 2a}$), becomes Recovered_ab or Dead_a after some time -- `Recovered_ab` ($R_{ab}$), absorbing state, immune to a and b -Infection 2b: -- `Exposed_2b` ($E_{2b}$), becomes infected after some time -- `InfectedNoSymptoms_2b` ($I_{NS,2b}$), becomes InfectedSymptoms_2b or Recovered_ab after some time -- `InfectedSymptoms_2b` ($I_{Sy,2b}$), becomes InfectedSevere_2b or Recovered_ab after some time -- `InfectedSevere_2b` ($I_{Sev, 2b}$), becomes InfectedCritical_2b or Recovered_ab after some time -- `InfectedCritical_2b` ($I_{Cr, 2b}$), becomes Recovered_ab or Dead_b after some time - - -It is possible to include subcompartments for the five compartments Exposed, InfectedNoSymptoms, InfectedSymptoms, InfectedSevere and InfectedCritical. -You can divide the population according to different groups, e.g. AgeGroups or gender and choose parameters according to groups. - -The parameters depend on the disease (a or b), the number of subcompartments depends on the individual compartment (since it can be set independently). - - -| Mathematical variable | C++ variable name | Description | -|---------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------- | -| $\phi$ | `ContactPatterns` | Average number of contacts of a person per day. | -| $\rho_i$ | `TransmissionProbabilityOnContact_i` | Transmission risk for people located in the susceptible compartments, i in {a,b}. | -| $\xi_{I_{NS},i}$ | `RelativeTransmissionNoSymptoms_i` | Proportion of nonsymptomatically infected people who are not isolated, i in {a,b}. | -| $\xi_{I_{Sy},i}$ | `RiskOfInfectionFromSymptomatic_i` | Proportion of infected people with symptoms who are not isolated, i in {a,b}. | -| $n_{E,i}$ | Defined in `LctStates` | Number of subcompartments of the Exposed compartment, i in {1a, 2a, 1b,2b}. | -| $n_{NS,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedNoSymptoms compartment, i in {1a, 2a, 1b,2b}. | -| $n_{Sy,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSymptoms compartment, i in {1a, 2a, 1b,2b}. | -| $n_{Sev,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedSevere compartment, i in {1a, 2a, 1b,2b}. | -| $n_{Cr,i}$ | Defined in `LctStates` | Number of subcompartments of the InfectedCritical compartment, i in {1a, 2a, 1b,2b}. | -| $T_{E,i}$ | `TimeExposed` | Average time in days an individual stays in the Exposed compartment, i in {a,b}. | -| $T_{I_{NS},i}$ | `TimeInfectedNoSymptoms` | Average time in days an individual stays in the InfectedNoSymptoms compartment, i in {a,b}. | -| $T_{I_{Sy},i}$ | `TimeInfectedSymptoms` | Average time in days an individual stays in the InfectedSymptoms compartmen, i in {a,b}t. | -| $T_{I_{Sev},i}$ | `TimeInfectedSevere` | Average time in days an individual stays in the InfectedSevere compartment, i in {a,b}. | -| $T_{I_{Cr},i}$ | `TimeInfectedCritical` | Average time in days an individual stays in the InfectedCritical compartment, i in {a,b}. | -| $\mu_{I_{NS},i}^{R}$ | `RecoveredPerInfectedNoSymptoms` | Probability of transition from compartment InfectedNoSymptoms to Recovered, i in {a,b}. | -| $\mu_{I_{Sy},i}^{I_{Sev}}$ | `SeverePerInfectedSymptoms` | Probability of transition from compartment InfectedSymptoms to InfectedSevere, i in {a,b}. | -| $\mu_{I_{Sev},i}^{I_{Cr}}$ | `CriticalPerSevere` | Probability of transition from compartment InfectedSevere to InfectedCritical, i in {a,b}. | -| $\mu_{I_{Cr},i}^{D}$ | `DeathsPerCritical` | Probability of dying when in compartment InfectedCritical, i in {a,b}. | - - -## Examples - -A simple example can be found at [LCT2D minimal example](../../examples/lct_secir_2_diseases.cpp). +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. +To get started, check out the [official documentation](https://memilio.readthedocs.io/en/latest/cpp/models/lsecir2d.html) +or the [LCT2D minimal example](../../examples/lct_secir_2_diseases.cpp). diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index 4ebed88b47..60b210e4bf 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -1,7 +1,7 @@ /* * Copyright (C) 2020-2025 MEmilio * -* Authors: Annika Jungkalus, Lena Ploetzke +* Authors: Annika Jungklaus, Lena Ploetzke * * Contact: Martin J. Kuehn * diff --git a/cpp/models/lct_secir_2_diseases/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h index fa44ddafbf..258f221202 100644 --- a/cpp/models/lct_secir_2_diseases/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -36,8 +36,12 @@ namespace lsecir2d * Define Parameters of the LCT-SECIHURD-2-DISEASES model * **********************************************************/ +// Each paramater is a vector with length equal to the number of groups +// The contact patterns are given by a matrix with dimension (number of groups)*(number of groups) +// The default number of groups is 1 + /** - * @brief Average time spent in the Exposed compartment for disease a in day unit. + * @brief Average time spent in the Exposed compartment for disease a in day unit for each group. */ struct TimeExposed_a { using Type = Eigen::VectorX>; @@ -53,7 +57,7 @@ struct TimeExposed_a { /** * @brief Average time spent in the TimeInfectedNoSymptoms before developing - * symptoms or recover for disease a in day unit. + * symptoms or recovering for disease a in day unit for each group. */ struct TimeInfectedNoSymptoms_a { using Type = Eigen::VectorX>; @@ -69,7 +73,7 @@ struct TimeInfectedNoSymptoms_a { /** * @brief Average time spent in the TimeInfectedSymptoms before going to hospital - * or recover for disease a in day unit. + * or recovering for disease a in day unit for each group. */ struct TimeInfectedSymptoms_a { using Type = Eigen::VectorX>; @@ -84,7 +88,7 @@ struct TimeInfectedSymptoms_a { }; /** - * @brief Average time being in the Hospital before treated by ICU or recover for disease a in day unit. + * @brief Average time being in the Hospital before treated by ICU or recovering for disease a in day unit for each group. */ struct TimeInfectedSevere_a { using Type = Eigen::VectorX>; @@ -99,7 +103,7 @@ struct TimeInfectedSevere_a { }; /** - * @brief Average time treated by ICU before dead or recover for disease a in day unit. + * @brief Average time treated by ICU before dead or recovering for disease a in day unit for each group. */ struct TimeInfectedCritical_a { using Type = Eigen::VectorX>; @@ -114,7 +118,7 @@ struct TimeInfectedCritical_a { }; /** - * @brief Probability of getting infected from a contact for disease a. + * @brief Probability of getting infected from a contact for disease a for each group. */ struct TransmissionProbabilityOnContact_a { using Type = Eigen::VectorX>; @@ -129,7 +133,7 @@ struct TransmissionProbabilityOnContact_a { }; /** - * @brief Average time spent in the Exposed compartment for disease b in day unit. + * @brief Average time spent in the Exposed compartment for disease b in day unit for each group. */ struct TimeExposed_b { using Type = Eigen::VectorX>; @@ -145,7 +149,7 @@ struct TimeExposed_b { /** * @brief Average time spent in the TimeInfectedNoSymptoms before developing - * symptoms or recover for disease b in day unit. + * symptoms or recovering for disease b in day unit for each group. */ struct TimeInfectedNoSymptoms_b { using Type = Eigen::VectorX>; @@ -161,7 +165,7 @@ struct TimeInfectedNoSymptoms_b { /** * @brief Average time spent in the TimeInfectedSymptoms before going to hospital - * or recover for disease b in day unit. + * or recovering for disease b in day unit for each group. */ struct TimeInfectedSymptoms_b { using Type = Eigen::VectorX>; @@ -176,7 +180,7 @@ struct TimeInfectedSymptoms_b { }; /** - * @brief Average time being in the Hospital before treated by ICU or recover for disease b in day unit. + * @brief Average time being in the Hospital before treated by ICU or recovering for disease b in day unit for each group. */ struct TimeInfectedSevere_b { using Type = Eigen::VectorX>; @@ -191,7 +195,7 @@ struct TimeInfectedSevere_b { }; /** - * @brief Average time treated by ICU before dead or recover for disease b in day unit. + * @brief Average time treated by ICU before dead or recovering for disease b in day unit for each group. */ struct TimeInfectedCritical_b { using Type = Eigen::VectorX>; @@ -206,7 +210,7 @@ struct TimeInfectedCritical_b { }; /** - * @brief Probability of getting infected from a contact for disease b. + * @brief Probability of getting infected from a contact for disease b for each group. */ struct TransmissionProbabilityOnContact_b { using Type = Eigen::VectorX>; @@ -238,7 +242,7 @@ struct ContactPatterns { }; /** - * @brief The relative InfectedNoSymptoms infectability for disease a. + * @brief The relative InfectedNoSymptoms infectability for disease a for each group. */ struct RelativeTransmissionNoSymptoms_a { using Type = Eigen::VectorX>; @@ -253,7 +257,7 @@ struct RelativeTransmissionNoSymptoms_a { }; /** - * @brief The risk of infection from symptomatic cases for disease a. + * @brief The risk of infection from symptomatic cases for disease a for each group. */ struct RiskOfInfectionFromSymptomatic_a { using Type = Eigen::VectorX>; @@ -268,7 +272,7 @@ struct RiskOfInfectionFromSymptomatic_a { }; /** - * @brief The relative InfectedNoSymptoms infectability for disease b. + * @brief The relative InfectedNoSymptoms infectability for disease b for each group. */ struct RelativeTransmissionNoSymptoms_b { using Type = Eigen::VectorX>; @@ -283,7 +287,7 @@ struct RelativeTransmissionNoSymptoms_b { }; /** - * @brief The risk of infection from symptomatic cases for disease b. + * @brief The risk of infection from symptomatic cases for disease b for each group. */ struct RiskOfInfectionFromSymptomatic_b { using Type = Eigen::VectorX>; @@ -298,7 +302,7 @@ struct RiskOfInfectionFromSymptomatic_b { }; /** - * @brief The percentage of asymptomatic cases for disease a. + * @brief The percentage of asymptomatic cases for disease a for each group. */ struct RecoveredPerInfectedNoSymptoms_a { using Type = Eigen::VectorX>; @@ -313,7 +317,7 @@ struct RecoveredPerInfectedNoSymptoms_a { }; /** - * @brief The percentage of hospitalized patients per infected patients for disease a. + * @brief The percentage of hospitalized patients per infected patients for disease a for each group. */ struct SeverePerInfectedSymptoms_a { using Type = Eigen::VectorX>; @@ -328,7 +332,7 @@ struct SeverePerInfectedSymptoms_a { }; /** - * @brief The percentage of ICU patients per hospitalized patients for disease a. + * @brief The percentage of ICU patients per hospitalized patients for disease a for each group. */ struct CriticalPerSevere_a { using Type = Eigen::VectorX>; @@ -343,7 +347,7 @@ struct CriticalPerSevere_a { }; /** - * @brief The percentage of dead patients per ICU patients for disease a. + * @brief The percentage of dead patients per ICU patients for disease a for each group. */ struct DeathsPerCritical_a { using Type = Eigen::VectorX>; @@ -358,7 +362,7 @@ struct DeathsPerCritical_a { }; /** - * @brief The percentage of asymptomatic cases for disease b. + * @brief The percentage of asymptomatic cases for disease b for each group. */ struct RecoveredPerInfectedNoSymptoms_b { using Type = Eigen::VectorX>; @@ -373,7 +377,7 @@ struct RecoveredPerInfectedNoSymptoms_b { }; /** - * @brief The percentage of hospitalized patients per infected patients for disease b. + * @brief The percentage of hospitalized patients per infected patients for disease b for each group. */ struct SeverePerInfectedSymptoms_b { using Type = Eigen::VectorX>; @@ -388,7 +392,7 @@ struct SeverePerInfectedSymptoms_b { }; /** - * @brief The percentage of ICU patients per hospitalized patients for disease b. + * @brief The percentage of ICU patients per hospitalized patients for disease b for each group. */ struct CriticalPerSevere_b { using Type = Eigen::VectorX>; @@ -403,7 +407,7 @@ struct CriticalPerSevere_b { }; /** - * @brief The percentage of dead patients per ICU patients for disease b. + * @brief The percentage of dead patients per ICU patients for disease b for each group. */ struct DeathsPerCritical_b { using Type = Eigen::VectorX>; @@ -418,7 +422,7 @@ struct DeathsPerCritical_b { }; /** - * @brief The start day in the LCT-SECIR-2-DISEASES model. + * @brief The start day in the LCT-SECIR-2-DISEASES model. * The start day defines in which season the simulation is started. * If the start day is 180 and simulation takes place from t0=0 to * tmax=100 the days 180 to 280 of the year are simulated. diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index ed5d8b8f38..462a793049 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -61,7 +61,7 @@ TEST(TestLCTSecir2d, simulateDefault) EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); ScalarType sum_pop = init.sum(); for (Eigen::Index i = 0; i < result.get_num_time_points(); i++) { - EXPECT_NEAR(sum_pop, result[i].sum(), 1e-5); // check that total pop. is constant + EXPECT_NEAR(sum_pop, result[i].sum(), 1e-5); // check that total pop is constant } } @@ -83,15 +83,16 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) ScalarType tmax = 5; ScalarType dt = 0.1; - // Initialization vector for lct2d model. + // Initialization vector for LCT2D model. + // For the first infection the initialization vectors are the same for both models Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState2d::Count, 0); - init_lct2d[0] = 200; // lct and lct2d use different infection states - init_lct2d[3] = 50; // make sure initial pop. is in the same compartments for lct and lct2d + init_lct2d[0] = 200; + init_lct2d[3] = 50; init_lct2d[5] = 30; // Define LCT2D model. Model_2d model_lct2d; - //Set initial values + //Set initial values. for (size_t i = 0; i < LctState2d::Count; i++) { model_lct2d.populations[i] = init_lct2d[i]; } diff --git a/docs/source/cpp/models/lsecir2d.rst b/docs/source/cpp/models/lsecir2d.rst index 3dbee34a62..f05717c6f2 100644 --- a/docs/source/cpp/models/lsecir2d.rst +++ b/docs/source/cpp/models/lsecir2d.rst @@ -4,7 +4,7 @@ | The LCT-SECIR-2-DISEASES model is an extension of the :doc:`model with one disease `. | The model is ODE-based and uses the Linear Chain Trick to allow for more general Erlang distributed stay times in each compartment instead of just exponentially distributed stay times induced by basic ODE-based models. | With the SECIR structure the model is particularly suited for pathogens with pre- or asymptomatic infection states and when severe or critical states are possible. -| For the two diseases :math:`a` and :math:`b` the model assumes no co-infection, a certain independence in the sense that prior infection with one disease does not affect the infection with the other disease (e.g. probability to get infected, time spend in each state, chances of recovery etc.), and perfect immunity after recovery for both diseases. +| For the two diseases or variants of one disease :math:`a` and :math:`b` the model assumes no co-infection, a certain independence in the sense that prior infection with one disease does not affect the infection with the other disease (e.g. probability to get infected, time spend in each state, chances of recovery etc.), and perfect immunity after recovery for both diseases. There are two possibilities for a susceptible individual (since we assume no co-infection): @@ -14,7 +14,7 @@ There are two possibilities for a susceptible individual (since we assume no co- Each infection is simulated using a LCT-SECIR model. The states are labeled according to infection (first or second) and disease (:math:`a` or :math:`b`), so the full model is given by the combination of infections :math:`1a`, :math:`2a`, :math:`1b`, and :math:`2b`. -Below is a visualization of the infection states split into LCT-states and transitions without a stratification according to socisdemographic groups. +Below is a visualization of the infection states split into LCT-states and transitions without a stratification according to sociodemographic groups. .. image:: "" :alt: tikz_lct-2d From 7bcab8b19b9c43b4488a1f69d79f1efba274bafb Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 16 Oct 2025 16:09:36 +0200 Subject: [PATCH 14/16] template FP, other comments --- cpp/examples/lct_secir_2_diseases.cpp | 59 +- .../lct_secir_2_diseases/infection_state.h | 53 -- cpp/models/lct_secir_2_diseases/model.h | 267 +++--- cpp/models/lct_secir_2_diseases/parameters.h | 195 +++-- cpp/tests/test_lct_secir_2_diseases.cpp | 798 +++++++++--------- 5 files changed, 683 insertions(+), 689 deletions(-) diff --git a/cpp/examples/lct_secir_2_diseases.cpp b/cpp/examples/lct_secir_2_diseases.cpp index 284582e71e..4877cf3b35 100644 --- a/cpp/examples/lct_secir_2_diseases.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -46,50 +46,51 @@ int main() NumRecovered_ab = 1; using InfState = mio::lsecir2d::InfectionState; using LctState = - mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + using Model = mio::lsecir2d::Model; Model model; ScalarType tmax = 10; // Set Parameters. - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; - model.parameters.get()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; + model.parameters.get>()[0] = 3.; - model.parameters.get()[0] = 0.1; - model.parameters.get()[0] = 0.1; + model.parameters.get>()[0] = 0.1; + model.parameters.get>()[0] = 0.1; - mio::ContactMatrixGroup& contact_matrix = model.parameters.get(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); + mio::ContactMatrixGroup& contact_matrix = + model.parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(1, 1, 10)); // From SimulationTime 5, the contact pattern is reduced to 30% of the initial value. - contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(5.)); - model.parameters.get()[0] = 0.7; - model.parameters.get()[0] = 0.7; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.09; - model.parameters.get()[0] = 0.09; - model.parameters.get()[0] = 0.2; - model.parameters.get()[0] = 0.2; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.25; - model.parameters.get()[0] = 0.3; - model.parameters.get()[0] = 0.3; + model.parameters.get>()[0] = 0.7; + model.parameters.get>()[0] = 0.7; + model.parameters.get>()[0] = 0.25; + model.parameters.get>()[0] = 0.25; + model.parameters.get>()[0] = 0.09; + model.parameters.get>()[0] = 0.09; + model.parameters.get>()[0] = 0.2; + model.parameters.get>()[0] = 0.2; + model.parameters.get>()[0] = 0.25; + model.parameters.get>()[0] = 0.25; + model.parameters.get>()[0] = 0.3; + model.parameters.get>()[0] = 0.3; // Simple example how to initialize model without flows. // Define the initial values with the distribution of the population into subcompartments. diff --git a/cpp/models/lct_secir_2_diseases/infection_state.h b/cpp/models/lct_secir_2_diseases/infection_state.h index 92ef39c442..d33ade8b8f 100644 --- a/cpp/models/lct_secir_2_diseases/infection_state.h +++ b/cpp/models/lct_secir_2_diseases/infection_state.h @@ -67,59 +67,6 @@ enum class InfectionState Count = 26 }; -/** - * @brief The InfectionTransition enum describes the possible - * transitions of the infectious state of persons. - */ -enum class InfectionTransition -{ - // first infection with a - SusceptibleToExposed_1a = 0, - Exposed_1aToInfectedNoSymptoms_1a = 1, - InfectedNoSymptoms_1aToInfectedSymptoms_1a = 2, - InfectedNoSymptoms_1aToRecovered_a = 3, - InfectedSymptoms_1aToInfectedSevere_1a = 4, - InfectedSymptoms_1aToRecovered_a = 5, - InfectedSevere_1aToInfectedCritical_1a = 6, - InfectedSevere_1aToRecovered_a = 7, - InfectedCritical_1aToDead_a = 8, - InfectedCritical_1aToRecovered_a = 9, - // second infection with a - Recovered_bToExposed_2a = 10, - Exposed_2aToInfectedNoSymptoms_2a = 11, - InfectedNoSymptoms_2aToInfectedSymptoms_2a = 12, - InfectedNoSymptoms_2aToRecovered_ab = 13, - InfectedSymptoms_2aToInfectedSevere_2a = 14, - InfectedSymptoms_2aToRecovered_ab = 15, - InfectedSevere_2aToInfectedCritical_2a = 16, - InfectedSevere_2aToRecovered_ab = 17, - InfectedCritical_2aToDead_a = 18, - InfectedCritical_2aToRecovered_ab = 19, - // first infection with b - SusceptibleToExposed_1b = 20, - Exposed_1bToInfectedNoSymptoms_1b = 21, - InfectedNoSymptoms_1bToInfectedSymptoms_1b = 22, - InfectedNoSymptoms_1bToRecovered_b = 23, - InfectedSymptoms_1bToInfectedSevere_1b = 24, - InfectedSymptoms_1bToRecovered_b = 25, - InfectedSevere_1bToInfectedCritical_1b = 26, - InfectedSevere_1bToRecovered_b = 27, - InfectedCritical_1bToDead_b = 28, - InfectedCritical_1bToRecovered_b = 29, - // second infection with b - Recovered_aToExposed_2b = 30, - Exposed_2bToInfectedNoSymptoms_2b = 31, - InfectedNoSymptoms_2bToInfectedSymptoms_2b = 32, - InfectedNoSymptoms_2bToRecovered_ab = 33, - InfectedSymptoms_2bToInfectedSevere_2b = 34, - InfectedSymptoms_2bToRecovered_ab = 35, - InfectedSevere_2bToInfectedCritical_2b = 36, - InfectedSevere_2bToRecovered_ab = 37, - InfectedCritical_2bToDead_b = 38, - InfectedCritical_2bToRecovered_ab = 39, - Count = 40 -}; - } // namespace lsecir2d } // namespace mio diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index 60b210e4bf..64c84466a5 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -46,13 +46,12 @@ namespace lsecir2d * This is because the number of subcompartments can be different for each group. * Therefore, the number of LctStates also determines the number of groups. */ -template -class Model - : public CompartmentalModel, Parameters> +template +class Model : public CompartmentalModel, Parameters> { public: using LctStatesGroups = TypeList; - using Base = CompartmentalModel, Parameters>; + using Base = CompartmentalModel, Parameters>; using typename Base::ParameterSet; using typename Base::Populations; static size_t constexpr num_groups = sizeof...(LctStates); @@ -84,9 +83,8 @@ class Model * @param[in] t The current time. * @param[out] dydt A reference to the calculated output. */ - void get_derivatives(Eigen::Ref> pop, - Eigen::Ref> y, ScalarType t, - Eigen::Ref> dydt) const override + void get_derivatives(Eigen::Ref> pop, Eigen::Ref> y, FP t, + Eigen::Ref> dydt) const override { // Vectors are sorted such that we first have all InfectionState%s for AgeGroup 0, // afterwards all for AgeGroup 1 and so on. @@ -106,18 +104,18 @@ class Model * @return Result of the simulation divided in infection states without subcompartments. * Returns TimeSeries with values -1 if calculation is not possible. */ - TimeSeries calculate_compartments(const TimeSeries& subcompartments_ts) const + TimeSeries calculate_compartments(const TimeSeries& subcompartments_ts) const { Eigen::Index count_InfStates = (Eigen::Index)InfectionState::Count; Eigen::Index num_compartments = count_InfStates * num_groups; - TimeSeries compartments_ts(num_compartments); + TimeSeries compartments_ts(num_compartments); if (!(this->populations.get_num_compartments() == (size_t)subcompartments_ts.get_num_elements())) { log_error("Result does not match InfectionState of the Model."); - Eigen::VectorX wrong_size = Eigen::VectorX::Constant(num_compartments, -1); + Eigen::VectorX wrong_size = Eigen::VectorX::Constant(num_compartments, -1); compartments_ts.add_time_point(-1, wrong_size); return compartments_ts; } - Eigen::VectorX compartments(num_compartments); + Eigen::VectorX compartments(num_compartments); for (Eigen::Index timepoint = 0; timepoint < subcompartments_ts.get_num_time_points(); ++timepoint) { compress_vector(subcompartments_ts[timepoint], compartments); compartments_ts.add_time_point(subcompartments_ts.get_time(timepoint), compartments); @@ -148,8 +146,7 @@ class Model * @param[out] compartments Reference to the vector where the output is stored. */ template - void compress_vector(const Eigen::VectorX& subcompartments, - Eigen::VectorX& compartments) const + void compress_vector(const Eigen::VectorX& subcompartments, Eigen::VectorX& compartments) const { static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); using LctStateGroup = type_at_index_t; @@ -183,16 +180,15 @@ class Model * @param[out] dydt A reference to the calculated output. */ template - void get_derivatives_impl(Eigen::Ref> pop, - Eigen::Ref> y, ScalarType t, - Eigen::Ref> dydt) const + void get_derivatives_impl(Eigen::Ref> pop, Eigen::Ref> y, FP t, + Eigen::Ref> dydt) const { static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid."); using LctStateGroup = type_at_index_t; size_t first_index_group = this->populations.template get_first_index_of_group(); auto params = this->parameters; - ScalarType flow = 0; + FP flow = 0; // Indices of first subcompartment of the InfectionState for the group in the vectors. size_t Ei_1a_first_index = @@ -247,8 +243,7 @@ class Model double part_b = 0.; // part of people getting infected with disease b interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); // split flow - double div_part_both = - ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); + double div_part_both = ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); // Start with derivatives of First Infections (X_1a, X_1b) // Calculate derivative of the Exposed_1x compartments, split flow from Susceptible. @@ -259,8 +254,8 @@ class Model // Variable flow stores the value of the flow from one subcompartment to the next one. // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ei_1a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[Ei_1a_first_index + subcomp]; // Subtract flow from dydt[Ei_1a_first_index + subcomp] and add to next subcompartment. dydt[Ei_1a_first_index + subcomp] -= flow; dydt[Ei_1a_first_index + subcomp + 1] = flow; @@ -272,8 +267,8 @@ class Model // Variable flow stores the value of the flow from one subcompartment to the next one. // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1b_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ei_1b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[Ei_1b_first_index + subcomp]; // Subtract flow from dydt[Ei_1b_first_index + subcomp] and add to next subcompartment. dydt[Ei_1b_first_index + subcomp] -= flow; dydt[Ei_1b_first_index + subcomp + 1] = flow; @@ -284,9 +279,8 @@ class Model for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = - (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[INSi_1a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[INSi_1a_first_index + subcomp]; dydt[INSi_1a_first_index + subcomp] -= flow; dydt[INSi_1a_first_index + subcomp + 1] = flow; } @@ -295,9 +289,8 @@ class Model for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = - (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[INSi_1b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[INSi_1b_first_index + subcomp]; dydt[INSi_1b_first_index + subcomp] -= flow; dydt[INSi_1b_first_index + subcomp + 1] = flow; } @@ -305,80 +298,80 @@ class Model // Calculate derivative of the InfectedSymptoms_1a compartment. // Flow from last (sub-) compartment of C_1a must be split between // the first subcompartment of InfectedSymptoms_1a and Recovered_a. - dydt[Ri_a] = dydt[ISyi_1a_first_index] * params.template get()[Group]; + dydt[Ri_a] = dydt[ISyi_1a_first_index] * params.template get>()[Group]; dydt[ISyi_1a_first_index] = - dydt[ISyi_1a_first_index] * (1 - params.template get()[Group]); + dydt[ISyi_1a_first_index] * (1 - params.template get>()[Group]); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISyi_1a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISyi_1a_first_index + subcomp]; dydt[ISyi_1a_first_index + subcomp] -= flow; dydt[ISyi_1a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSymptoms_1b compartment. // Flow from last (sub-) compartment of C_1b must be split between // the first subcompartment of InfectedSymptoms_1b and Recovered_b. - dydt[Ri_b] = dydt[ISyi_1b_first_index] * params.template get()[Group]; + dydt[Ri_b] = dydt[ISyi_1b_first_index] * params.template get>()[Group]; dydt[ISyi_1b_first_index] = - dydt[ISyi_1b_first_index] * (1 - params.template get()[Group]); + dydt[ISyi_1b_first_index] * (1 - params.template get>()[Group]); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISyi_1b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISyi_1b_first_index + subcomp]; dydt[ISyi_1b_first_index + subcomp] -= flow; dydt[ISyi_1b_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSevere_1a compartment. // again split the flow from the last subcompartment of I_1a - dydt[Ri_a] += dydt[ISevi_1a_first_index] * (1 - params.template get()[Group]); + dydt[Ri_a] += dydt[ISevi_1a_first_index] * (1 - params.template get>()[Group]); dydt[ISevi_1a_first_index] = - dydt[ISevi_1a_first_index] * params.template get()[Group]; + dydt[ISevi_1a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISevi_1a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISevi_1a_first_index + subcomp]; dydt[ISevi_1a_first_index + subcomp] -= flow; dydt[ISevi_1a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSevere_1b compartment. // again split the flow from the last subcompartment of I_1b - dydt[Ri_b] += dydt[ISevi_1b_first_index] * (1 - params.template get()[Group]); + dydt[Ri_b] += dydt[ISevi_1b_first_index] * (1 - params.template get>()[Group]); dydt[ISevi_1b_first_index] = - dydt[ISevi_1b_first_index] * params.template get()[Group]; + dydt[ISevi_1b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISevi_1b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISevi_1b_first_index + subcomp]; dydt[ISevi_1b_first_index + subcomp] -= flow; dydt[ISevi_1b_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedCritical compartment. // again split flow from last subcompartment of H_1a between R_a and U_1a - dydt[Ri_a] += dydt[ICri_1a_first_index] * (1 - params.template get()[Group]); - dydt[ICri_1a_first_index] = dydt[ICri_1a_first_index] * params.template get()[Group]; + dydt[Ri_a] += dydt[ICri_1a_first_index] * (1 - params.template get>()[Group]); + dydt[ICri_1a_first_index] = dydt[ICri_1a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments() - 1; subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ICri_1a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_1a_first_index + subcomp]; dydt[ICri_1a_first_index + subcomp] -= flow; dydt[ICri_1a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedCritical compartment. // again split flow from last subcompartment of H_1b between R_b and U_1b - dydt[Ri_b] += dydt[ICri_1b_first_index] * (1 - params.template get()[Group]); - dydt[ICri_1b_first_index] = dydt[ICri_1b_first_index] * params.template get()[Group]; + dydt[Ri_b] += dydt[ICri_1b_first_index] * (1 - params.template get>()[Group]); + dydt[ICri_1b_first_index] = dydt[ICri_1b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments() - 1; subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ICri_1b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_1b_first_index + subcomp]; dydt[ICri_1b_first_index + subcomp] -= flow; dydt[ICri_1b_first_index + subcomp + 1] = flow; } @@ -386,25 +379,25 @@ class Model // Flow from InfectedCritical has to be divided between Recovered and Dead. // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. // for InefectedCritical_1a: - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_1a_first_index + LctStateGroup::template get_num_subcompartments() - 1]; dydt[ICri_1a_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; - dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get()[Group]) * flow; - dydt[Di_a] = dydt[Di_a] + params.template get()[Group] * flow; + dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get>()[Group]) * flow; + dydt[Di_a] = dydt[Di_a] + params.template get>()[Group] * flow; // Flow from InfectedCritical_1b has to be divided between Recovered_b and Dead_b. // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. // Outflow from InfectedCritical_1b: - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_1b_first_index + LctStateGroup::template get_num_subcompartments() - 1]; dydt[ICri_1b_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; - dydt[Ri_b] = dydt[Ri_b] + (1 - params.template get()[Group]) * flow; - dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; + dydt[Ri_b] = dydt[Ri_b] + (1 - params.template get>()[Group]) * flow; + dydt[Di_b] = dydt[Di_b] + params.template get>()[Group] * flow; // Second Infection (X_2a, X_2b) // outflow from Recovered, people getting infected for the second time @@ -423,8 +416,8 @@ class Model // Variable flow stores the value of the flow from one subcompartment to the next one. // Ei_2a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_2a_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ei_2a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[Ei_2a_first_index + subcomp]; // Subtract flow from dydt[Ei_2a_first_index + subcomp] and add to next subcompartment. dydt[Ei_2a_first_index + subcomp] -= flow; dydt[Ei_2a_first_index + subcomp + 1] = flow; @@ -436,8 +429,8 @@ class Model // Variable flow stores the value of the flow from one subcompartment to the next one. // Ei_2b_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_2b_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[Ei_2b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[Ei_2b_first_index + subcomp]; // Subtract flow from dydt[Ei_2b_first_index + subcomp] and add to next subcompartment. dydt[Ei_2b_first_index + subcomp] -= flow; dydt[Ei_2b_first_index + subcomp + 1] = flow; @@ -447,9 +440,8 @@ class Model for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = - (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[INSi_2a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[INSi_2a_first_index + subcomp]; dydt[INSi_2a_first_index + subcomp] -= flow; dydt[INSi_2a_first_index + subcomp + 1] = flow; } @@ -457,9 +449,8 @@ class Model for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = - (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[INSi_2b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[INSi_2b_first_index + subcomp]; dydt[INSi_2b_first_index + subcomp] -= flow; dydt[INSi_2b_first_index + subcomp + 1] = flow; } @@ -467,80 +458,80 @@ class Model // Calculate derivative of the InfectedSymptoms_2a compartment. // Flow from last (sub-) compartment of C_2a must be split between // the first subcompartment of InfectedSymptoms_2a and Recovered_ab. - dydt[Ri_ab] += dydt[ISyi_2a_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ISyi_2a_first_index] * params.template get>()[Group]; dydt[ISyi_2a_first_index] = - dydt[ISyi_2a_first_index] * (1 - params.template get()[Group]); + dydt[ISyi_2a_first_index] * (1 - params.template get>()[Group]); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISyi_2a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISyi_2a_first_index + subcomp]; dydt[ISyi_2a_first_index + subcomp] -= flow; dydt[ISyi_2a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSymptoms_2b compartment. // Flow from last (sub-) compartment of C_2b must be split between // the first subcompartment of InfectedSymptoms_2b and Recovered_ab. - dydt[Ri_ab] += dydt[ISyi_2b_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ISyi_2b_first_index] * params.template get>()[Group]; dydt[ISyi_2b_first_index] = - dydt[ISyi_2b_first_index] * (1 - params.template get()[Group]); + dydt[ISyi_2b_first_index] * (1 - params.template get>()[Group]); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISyi_2b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISyi_2b_first_index + subcomp]; dydt[ISyi_2b_first_index + subcomp] -= flow; dydt[ISyi_2b_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSevere_2a compartment. // again split the flow from the last subcompartment of I_2a - dydt[Ri_ab] += dydt[ISevi_2a_first_index] * (1 - params.template get()[Group]); + dydt[Ri_ab] += dydt[ISevi_2a_first_index] * (1 - params.template get>()[Group]); dydt[ISevi_2a_first_index] = - dydt[ISevi_2a_first_index] * params.template get()[Group]; + dydt[ISevi_2a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISevi_2a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISevi_2a_first_index + subcomp]; dydt[ISevi_2a_first_index + subcomp] -= flow; dydt[ISevi_2a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedSevere compartment. // again split the flow from the last subcompartment of I_2b - dydt[Ri_ab] += dydt[ISevi_2b_first_index] * (1 - params.template get()[Group]); + dydt[Ri_ab] += dydt[ISevi_2b_first_index] * (1 - params.template get>()[Group]); dydt[ISevi_2b_first_index] = - dydt[ISevi_2b_first_index] * params.template get()[Group]; + dydt[ISevi_2b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ISevi_2b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ISevi_2b_first_index + subcomp]; dydt[ISevi_2b_first_index + subcomp] -= flow; dydt[ISevi_2b_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedCritical compartment. // again split flow from last subcompartment of H_2a between R_ab and U_2a - dydt[Ri_ab] += dydt[ICri_2a_first_index] * (1 - params.template get()[Group]); - dydt[ICri_2a_first_index] = dydt[ICri_2a_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ICri_2a_first_index] * (1 - params.template get>()[Group]); + dydt[ICri_2a_first_index] = dydt[ICri_2a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments() - 1; subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ICri_2a_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_2a_first_index + subcomp]; dydt[ICri_2a_first_index + subcomp] -= flow; dydt[ICri_2a_first_index + subcomp + 1] = flow; } // Calculate derivative of the InfectedCritical compartment. // again split flow from last subcompartment of H_1b between R_b and U_1b - dydt[Ri_ab] += dydt[ICri_2b_first_index] * (1 - params.template get()[Group]); - dydt[ICri_2b_first_index] = dydt[ICri_2b_first_index] * params.template get()[Group]; + dydt[Ri_ab] += dydt[ICri_2b_first_index] * (1 - params.template get>()[Group]); + dydt[ICri_2b_first_index] = dydt[ICri_2b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments() - 1; subcomp++) { - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * y[ICri_2b_first_index + subcomp]; + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_2b_first_index + subcomp]; dydt[ICri_2b_first_index + subcomp] -= flow; dydt[ICri_2b_first_index + subcomp + 1] = flow; } @@ -548,23 +539,23 @@ class Model // Last flow from InfectedCritical has to be divided between Recovered and Dead. // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. // for 2a - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_2a_first_index + LctStateGroup::template get_num_subcompartments() - 1]; dydt[ICri_2a_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; - dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; - dydt[Di_a] = dydt[Di_a] + params.template get()[Group] * flow; + dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get>()[Group]) * flow; + dydt[Di_a] = dydt[Di_a] + params.template get>()[Group] * flow; // for 2b - flow = (ScalarType)LctStateGroup::template get_num_subcompartments() * - (1 / params.template get()[Group]) * + flow = (FP)LctStateGroup::template get_num_subcompartments() * + (1 / params.template get>()[Group]) * y[ICri_2b_first_index + LctStateGroup::template get_num_subcompartments() - 1]; dydt[ICri_2b_first_index + LctStateGroup::template get_num_subcompartments() - 1] -= flow; - dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get()[Group]) * flow; - dydt[Di_b] = dydt[Di_b] + params.template get()[Group] * flow; + dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get>()[Group]) * flow; + dydt[Di_b] = dydt[Di_b] + params.template get>()[Group] * flow; // Function call for next group if applicable. if constexpr (Group + 1 < num_groups) { @@ -589,18 +580,18 @@ class Model * @param[in] which_disease Index for which infected people cause the outflow (0 = a, 1 = b, 2 = a and b). */ template - void interact(Eigen::Ref> pop, Eigen::Ref> y, - ScalarType t, Eigen::Ref> dydt, double* part_a, double* part_b, - size_t compartment_index, int which_disease) const + void interact(Eigen::Ref> pop, Eigen::Ref> y, FP t, + Eigen::Ref> dydt, double* part_a, double* part_b, size_t compartment_index, + int which_disease) const { static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0), "The template parameters Group1 & Group2 should be valid."); - using LctStateGroup2 = type_at_index_t; - ScalarType infectedNoSymptoms_2_a = 0; - ScalarType infectedSymptoms_2_a = 0; - ScalarType infectedNoSymptoms_2_b = 0; - ScalarType infectedSymptoms_2_b = 0; - auto params = this->parameters; + using LctStateGroup2 = type_at_index_t; + FP infectedNoSymptoms_2_a = 0; + FP infectedSymptoms_2_a = 0; + FP infectedNoSymptoms_2_b = 0; + FP infectedSymptoms_2_b = 0; + auto params = this->parameters; size_t first_index_group2 = this->populations.template get_first_index_of_group(); @@ -645,51 +636,51 @@ class Model LctStateGroup2::template get_num_subcompartments()) .sum(); // Size of the Subpopulation Group2 without dead people. - double N_2 = pop.segment(first_index_group2, LctStateGroup2::Count).sum(); // sum over all compartments - N_2 = N_2 - pop.segment(LctStateGroup2::template get_first_index(), 1).sum() - + FP N_2 = pop.segment(first_index_group2, LctStateGroup2::Count).sum(); // sum over all compartments + N_2 = N_2 - pop.segment(LctStateGroup2::template get_first_index(), 1).sum() - pop.segment(LctStateGroup2::template get_first_index(), 1) .sum(); // all people minus dead people - const double divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; - ScalarType season_val = 1 + params.template get() * - sin(3.141592653589793 * ((params.template get() + t) / 182.5 + 0.5)); + const FP divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; + FP season_val = 1 + params.template get>() * + sin(3.141592653589793 * ((params.template get>() + t) / 182.5 + 0.5)); if (which_disease == 0) { // disease a dydt[compartment_index] += -y[compartment_index] * divN_2 * season_val * - params.template get().get_cont_freq_mat().get_matrix_at(t)( + params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * - params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_a + - params.template get()[Group2] * infectedSymptoms_2_a); + params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_a + + params.template get>()[Group2] * infectedSymptoms_2_a); } else if (which_disease == 1) { // disease b dydt[compartment_index] += -y[compartment_index] * divN_2 * season_val * - params.template get().get_cont_freq_mat().get_matrix_at(t)( + params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * - params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_b + - params.template get()[Group2] * infectedSymptoms_2_b); + params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_b + + params.template get>()[Group2] * infectedSymptoms_2_b); } else if (which_disease == 2) { // both diseases dydt[compartment_index] += -y[compartment_index] * divN_2 * season_val * - params.template get().get_cont_freq_mat().get_matrix_at(t)( + params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * - (params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_a + - params.template get()[Group2] * infectedSymptoms_2_a) + - params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_b + - params.template get()[Group2] * infectedSymptoms_2_b)); + (params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_a + + params.template get>()[Group2] * infectedSymptoms_2_a) + + params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_b + + params.template get>()[Group2] * infectedSymptoms_2_b)); } // To split the outflow from S between E_1a and E_1b: - *part_a += params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_a + - params.template get()[Group2] * infectedSymptoms_2_a); - *part_b += params.template get()[Group1] * - (params.template get()[Group2] * infectedNoSymptoms_2_b + - params.template get()[Group2] * infectedSymptoms_2_b); + *part_a += params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_a + + params.template get>()[Group2] * infectedSymptoms_2_a); + *part_b += params.template get>()[Group1] * + (params.template get>()[Group2] * infectedNoSymptoms_2_b + + params.template get>()[Group2] * infectedSymptoms_2_b); if constexpr (Group2 + 1 < num_groups) { interact(pop, y, t, dydt, part_a, part_b, compartment_index, which_disease); diff --git a/cpp/models/lct_secir_2_diseases/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h index 258f221202..48f3201c31 100644 --- a/cpp/models/lct_secir_2_diseases/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -22,6 +22,7 @@ #define LCT_SECIR_2_DISEASES_PARAMS_H #include "memilio/config.h" +#include "memilio/math/eigen.h" #include "memilio/utils/parameter_set.h" #include "memilio/utils/logging.h" #include "memilio/utils/uncertain_value.h" @@ -43,8 +44,9 @@ namespace lsecir2d /** * @brief Average time spent in the Exposed compartment for disease a in day unit for each group. */ +template struct TimeExposed_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -59,8 +61,9 @@ struct TimeExposed_a { * @brief Average time spent in the TimeInfectedNoSymptoms before developing * symptoms or recovering for disease a in day unit for each group. */ +template struct TimeInfectedNoSymptoms_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -75,8 +78,9 @@ struct TimeInfectedNoSymptoms_a { * @brief Average time spent in the TimeInfectedSymptoms before going to hospital * or recovering for disease a in day unit for each group. */ +template struct TimeInfectedSymptoms_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -90,8 +94,9 @@ struct TimeInfectedSymptoms_a { /** * @brief Average time being in the Hospital before treated by ICU or recovering for disease a in day unit for each group. */ +template struct TimeInfectedSevere_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -105,8 +110,9 @@ struct TimeInfectedSevere_a { /** * @brief Average time treated by ICU before dead or recovering for disease a in day unit for each group. */ +template struct TimeInfectedCritical_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -120,8 +126,9 @@ struct TimeInfectedCritical_a { /** * @brief Probability of getting infected from a contact for disease a for each group. */ +template struct TransmissionProbabilityOnContact_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -135,8 +142,9 @@ struct TransmissionProbabilityOnContact_a { /** * @brief Average time spent in the Exposed compartment for disease b in day unit for each group. */ +template struct TimeExposed_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -151,8 +159,9 @@ struct TimeExposed_b { * @brief Average time spent in the TimeInfectedNoSymptoms before developing * symptoms or recovering for disease b in day unit for each group. */ +template struct TimeInfectedNoSymptoms_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -167,8 +176,9 @@ struct TimeInfectedNoSymptoms_b { * @brief Average time spent in the TimeInfectedSymptoms before going to hospital * or recovering for disease b in day unit for each group. */ +template struct TimeInfectedSymptoms_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -182,8 +192,9 @@ struct TimeInfectedSymptoms_b { /** * @brief Average time being in the Hospital before treated by ICU or recovering for disease b in day unit for each group. */ +template struct TimeInfectedSevere_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -197,8 +208,9 @@ struct TimeInfectedSevere_b { /** * @brief Average time treated by ICU before dead or recovering for disease b in day unit for each group. */ +template struct TimeInfectedCritical_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -212,8 +224,9 @@ struct TimeInfectedCritical_b { /** * @brief Probability of getting infected from a contact for disease b for each group. */ +template struct TransmissionProbabilityOnContact_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -227,12 +240,15 @@ struct TransmissionProbabilityOnContact_b { /** * @brief The contact patterns within the society are modelled using an UncertainContactMatrix. */ +template struct ContactPatterns { - using Type = UncertainContactMatrix; - static Type get_default(size_t size = 1) // no age groups + using Type = UncertainContactMatrix; + + static Type get_default(size_t size) { - mio::ContactMatrixGroup contact_matrix(1, (Eigen::Index)size); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant((Eigen::Index)size, (Eigen::Index)size, 10.)); + mio::ContactMatrixGroup contact_matrix(1, (Eigen::Index)size); + contact_matrix[0] = + mio::ContactMatrix(Eigen::MatrixX::Constant((Eigen::Index)size, (Eigen::Index)size, 10.)); return Type(contact_matrix); } static std::string name() @@ -244,8 +260,9 @@ struct ContactPatterns { /** * @brief The relative InfectedNoSymptoms infectability for disease a for each group. */ +template struct RelativeTransmissionNoSymptoms_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -259,8 +276,9 @@ struct RelativeTransmissionNoSymptoms_a { /** * @brief The risk of infection from symptomatic cases for disease a for each group. */ +template struct RiskOfInfectionFromSymptomatic_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -274,8 +292,9 @@ struct RiskOfInfectionFromSymptomatic_a { /** * @brief The relative InfectedNoSymptoms infectability for disease b for each group. */ +template struct RelativeTransmissionNoSymptoms_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -289,8 +308,9 @@ struct RelativeTransmissionNoSymptoms_b { /** * @brief The risk of infection from symptomatic cases for disease b for each group. */ +template struct RiskOfInfectionFromSymptomatic_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 1.); @@ -304,8 +324,9 @@ struct RiskOfInfectionFromSymptomatic_b { /** * @brief The percentage of asymptomatic cases for disease a for each group. */ +template struct RecoveredPerInfectedNoSymptoms_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -319,8 +340,9 @@ struct RecoveredPerInfectedNoSymptoms_a { /** * @brief The percentage of hospitalized patients per infected patients for disease a for each group. */ +template struct SeverePerInfectedSymptoms_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -334,8 +356,9 @@ struct SeverePerInfectedSymptoms_a { /** * @brief The percentage of ICU patients per hospitalized patients for disease a for each group. */ +template struct CriticalPerSevere_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -349,8 +372,9 @@ struct CriticalPerSevere_a { /** * @brief The percentage of dead patients per ICU patients for disease a for each group. */ +template struct DeathsPerCritical_a { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.1); @@ -364,8 +388,9 @@ struct DeathsPerCritical_a { /** * @brief The percentage of asymptomatic cases for disease b for each group. */ +template struct RecoveredPerInfectedNoSymptoms_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -379,8 +404,9 @@ struct RecoveredPerInfectedNoSymptoms_b { /** * @brief The percentage of hospitalized patients per infected patients for disease b for each group. */ +template struct SeverePerInfectedSymptoms_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -394,8 +420,9 @@ struct SeverePerInfectedSymptoms_b { /** * @brief The percentage of ICU patients per hospitalized patients for disease b for each group. */ +template struct CriticalPerSevere_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.5); @@ -409,8 +436,9 @@ struct CriticalPerSevere_b { /** * @brief The percentage of dead patients per ICU patients for disease b for each group. */ +template struct DeathsPerCritical_b { - using Type = Eigen::VectorX>; + using Type = Eigen::VectorX>; static Type get_default(size_t size = 1) { return Type::Constant(size, 1, 0.1); @@ -427,8 +455,9 @@ struct DeathsPerCritical_b { * If the start day is 180 and simulation takes place from t0=0 to * tmax=100 the days 180 to 280 of the year are simulated. */ +template struct StartDay { - using Type = ScalarType; + using Type = FP; static Type get_default(size_t) { return 0.; @@ -444,8 +473,9 @@ struct StartDay { * The seasonality is given as (1+k*sin()) where the sine * curve is below one in summer and above one in winter. */ +template struct Seasonality { - using Type = ScalarType; + using Type = FP; static Type get_default(size_t) { return 0.; @@ -456,20 +486,23 @@ struct Seasonality { } }; +template using ParametersBase = - ParameterSet; + ParameterSet, TimeInfectedNoSymptoms_a, TimeInfectedSymptoms_a, TimeInfectedSevere_a, + TimeInfectedCritical_a, TimeExposed_b, TimeInfectedNoSymptoms_b, + TimeInfectedSymptoms_b, TimeInfectedSevere_b, TimeInfectedCritical_b, + TransmissionProbabilityOnContact_a, TransmissionProbabilityOnContact_b, ContactPatterns, + RelativeTransmissionNoSymptoms_a, RiskOfInfectionFromSymptomatic_a, + RecoveredPerInfectedNoSymptoms_a, SeverePerInfectedSymptoms_a, CriticalPerSevere_a, + DeathsPerCritical_a, RelativeTransmissionNoSymptoms_b, RiskOfInfectionFromSymptomatic_b, + RecoveredPerInfectedNoSymptoms_b, SeverePerInfectedSymptoms_b, CriticalPerSevere_b, + DeathsPerCritical_b, StartDay, Seasonality>; /** * @brief Parameters of an LCT-SECIR-2-DISEASES model. */ -class Parameters : public ParametersBase +template +class Parameters : public ParametersBase { public: /** @@ -477,7 +510,7 @@ class Parameters : public ParametersBase * @param num_groups The number of groups considered in the LCT2D model. */ Parameters(size_t num_groups) - : ParametersBase(num_groups) + : ParametersBase(num_groups) , m_num_groups{num_groups} { } @@ -494,145 +527,149 @@ class Parameters : public ParametersBase bool check_constraints() const { for (size_t i = 0; i < m_num_groups; ++i) { - if (this->get() < 0.0 || this->get() > 0.5) { + if (this->template get>() < 0.0 || this->template get>() > 0.5) { log_warning("Constraint check: Parameter Seasonality should lie between {:0.4f} and {:.4f}", 0.0, 0.5); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeExposed_a is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeExposed_b is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedNoSymptoms_a is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedNoSymptoms_b is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedSymptoms_a is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedSymptoms_b is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedSevere_a is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedSevere_b is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedCritical_a is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 1.0) { + if (this->template get>()[i] < 1.0) { log_error("Constraint check: Parameter TimeInfectedCritical_b is smaller than {:.4f}", 1.0); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter TransmissionProbabilityOnContact_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter TransmissionProbabilityOnContact_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RelativeTransmissionNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RiskOfInfectionFromSymptomatic_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter RecoveredPerInfectedNoSymptoms_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter SeverePerInfectedSymptoms_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || - this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter SeverePerInfectedSymptoms_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter CriticalPerSevere_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter CriticalPerSevere_b smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter DeathsPerCritical_a smaller {:d} or larger {:d}", 0, 1); return true; } - if (this->get()[i] < 0.0 || this->get()[i] > 1.0) { + if (this->template get>()[i] < 0.0 || + this->template get>()[i] > 1.0) { log_error("Constraint check: Parameter DeathsPerCritical_b smaller {:d} or larger {:d}", 0, 1); return true; } @@ -642,9 +679,9 @@ class Parameters : public ParametersBase } private: - Parameters(ParametersBase&& base) - : ParametersBase(std::move(base)) - , m_num_groups(this->template get().get_cont_freq_mat().get_num_groups()) + Parameters(ParametersBase&& base) + : ParametersBase(std::move(base)) + , m_num_groups(this->template get>().get_cont_freq_mat().get_num_groups()) { } @@ -658,7 +695,7 @@ class Parameters : public ParametersBase template static IOResult deserialize(IOContext& io) { - BOOST_OUTCOME_TRY(auto&& base, ParametersBase::deserialize(io)); + BOOST_OUTCOME_TRY(auto&& base, ParametersBase::deserialize(io)); return success(Parameters(std::move(base))); } }; diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index 462a793049..67e3c3ba72 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -36,10 +36,10 @@ // Test confirms that default construction of an LCT2D model works. TEST(TestLCTSecir2d, simulateDefault) { - using InfState = mio::lsecir2d::InfectionState; - using LctState = - mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + using InfState = mio::lsecir2d::InfectionState; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 1; ScalarType dt = 0.1; @@ -71,13 +71,13 @@ TEST(TestLCTSecir2d, simulateDefault) TEST(TestLCTSecir2d, compareWithLCTSecir1) { using InfState2d = mio::lsecir2d::InfectionState; - using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; using InfState_lct = mio::lsecir::InfectionState; - using LctState_lct = mio::LctInfectionState; - using Model_lct = mio::lsecir::Model; + using LctState_lct = mio::LctInfectionState; + using Model_lct = mio::lsecir::Model; ScalarType t0 = 0; ScalarType tmax = 5; @@ -98,38 +98,39 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) } // Set Parameters. - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - - model_lct2d.parameters.get()[0] = 0.05; - model_lct2d.parameters.get()[0] = 0.; - - mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get() = 50; - model_lct2d.parameters.get() = 0.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + + model_lct2d.parameters.get>()[0] = 0.05; + model_lct2d.parameters.get>()[0] = 0.; + + mio::ContactMatrixGroup& contact_matrix_lct2d = + model_lct2d.parameters.get>(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>() = 50; + model_lct2d.parameters.get>() = 0.1; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState_lct::Count, 0); @@ -145,26 +146,27 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) } // Set Parameters. - model_lct.parameters.get()[0] = 3.2; - model_lct.parameters.get()[0] = 2; - model_lct.parameters.get()[0] = 5.8; - model_lct.parameters.get()[0] = 9.5; - model_lct.parameters.get()[0] = 7.1; - - model_lct.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct.parameters.get()[0] = 0.7; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get() = 50; - model_lct.parameters.get() = 0.1; - model_lct.parameters.get()[0] = 0.09; - model_lct.parameters.get()[0] = 0.2; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get()[0] = 0.3; + model_lct.parameters.get>()[0] = 3.2; + model_lct.parameters.get>()[0] = 2; + model_lct.parameters.get>()[0] = 5.8; + model_lct.parameters.get>()[0] = 9.5; + model_lct.parameters.get>()[0] = 7.1; + + model_lct.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = + model_lct.parameters.get>(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get>()[0] = 0.7; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>() = 50; + model_lct.parameters.get>() = 0.1; + model_lct.parameters.get>()[0] = 0.09; + model_lct.parameters.get>()[0] = 0.2; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>()[0] = 0.3; // Simulate mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); @@ -200,9 +202,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) TEST(TestLCTSecir2d, compareWithLCTSecir2) { using InfState2d = mio::lsecir2d::InfectionState; - using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 5; ScalarType dt = 0.1; @@ -221,42 +223,43 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) } // Set Parameters. - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - - model_lct2d.parameters.get()[0] = 0.; - model_lct2d.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get() = 50; - model_lct2d.parameters.get() = 0.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + + model_lct2d.parameters.get>()[0] = 0.; + model_lct2d.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct2d = + model_lct2d.parameters.get>(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>() = 50; + model_lct2d.parameters.get>() = 0.1; using InfState = mio::lsecir::InfectionState; - using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir::Model; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); @@ -272,26 +275,27 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) } // Set Parameters. - model_lct.parameters.get()[0] = 3.2; - model_lct.parameters.get()[0] = 2; - model_lct.parameters.get()[0] = 5.8; - model_lct.parameters.get()[0] = 9.5; - model_lct.parameters.get()[0] = 7.1; - - model_lct.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct.parameters.get()[0] = 0.7; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get() = 50; - model_lct.parameters.get() = 0.1; - model_lct.parameters.get()[0] = 0.09; - model_lct.parameters.get()[0] = 0.2; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get()[0] = 0.3; + model_lct.parameters.get>()[0] = 3.2; + model_lct.parameters.get>()[0] = 2; + model_lct.parameters.get>()[0] = 5.8; + model_lct.parameters.get>()[0] = 9.5; + model_lct.parameters.get>()[0] = 7.1; + + model_lct.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = + model_lct.parameters.get>(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get>()[0] = 0.7; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>() = 50; + model_lct.parameters.get>() = 0.1; + model_lct.parameters.get>()[0] = 0.09; + model_lct.parameters.get>()[0] = 0.2; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>()[0] = 0.3; // Simulate mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); @@ -327,13 +331,13 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) TEST(TestLCTSecir2d, compareWithLCTSecir3) { using InfState_2d = mio::lsecir2d::InfectionState; - using LctState_2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using LctState_2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; using InfState_lct = mio::lsecir::InfectionState; - using LctState_lct = mio::LctInfectionState; - using Model_lct = mio::lsecir::Model; + using LctState_lct = mio::LctInfectionState; + using Model_lct = mio::lsecir::Model; ScalarType t0 = 0; ScalarType tmax = 5; @@ -353,38 +357,39 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) } // Set Parameters. - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - - model_lct2d.parameters.get()[0] = 0.05; - model_lct2d.parameters.get()[0] = 0.; - - mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get() = 50; - model_lct2d.parameters.get() = 0.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + + model_lct2d.parameters.get>()[0] = 0.05; + model_lct2d.parameters.get>()[0] = 0.; + + mio::ContactMatrixGroup& contact_matrix_lct2d = + model_lct2d.parameters.get>(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>() = 50; + model_lct2d.parameters.get>() = 0.1; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState_lct::Count, 0); @@ -400,26 +405,27 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) } // Set Parameters. - model_lct.parameters.get()[0] = 3.2; - model_lct.parameters.get()[0] = 2; - model_lct.parameters.get()[0] = 5.8; - model_lct.parameters.get()[0] = 9.5; - model_lct.parameters.get()[0] = 7.1; - - model_lct.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct.parameters.get()[0] = 0.7; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get() = 50; - model_lct.parameters.get() = 0.1; - model_lct.parameters.get()[0] = 0.09; - model_lct.parameters.get()[0] = 0.2; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get()[0] = 0.3; + model_lct.parameters.get>()[0] = 3.2; + model_lct.parameters.get>()[0] = 2; + model_lct.parameters.get>()[0] = 5.8; + model_lct.parameters.get>()[0] = 9.5; + model_lct.parameters.get>()[0] = 7.1; + + model_lct.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = + model_lct.parameters.get>(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get>()[0] = 0.7; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>() = 50; + model_lct.parameters.get>() = 0.1; + model_lct.parameters.get>()[0] = 0.09; + model_lct.parameters.get>()[0] = 0.2; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>()[0] = 0.3; // Simulate mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); @@ -455,9 +461,9 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) TEST(TestLCTSecir2d, compareWithLCTSecir4) { using InfState2d = mio::lsecir2d::InfectionState; - using LctState2d = mio::LctInfectionState; - using Model_2d = mio::lsecir2d::Model; + using LctState2d = mio::LctInfectionState; + using Model_2d = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 5; ScalarType dt = 0.1; @@ -476,42 +482,43 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) } // Set Parameters. - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - model_lct2d.parameters.get()[0] = 3.2; - model_lct2d.parameters.get()[0] = 2; - model_lct2d.parameters.get()[0] = 5.8; - model_lct2d.parameters.get()[0] = 9.5; - model_lct2d.parameters.get()[0] = 7.1; - - model_lct2d.parameters.get()[0] = 0.; - model_lct2d.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct2d = model_lct2d.parameters.get(); - contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get()[0] = 0.7; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.09; - model_lct2d.parameters.get()[0] = 0.2; - model_lct2d.parameters.get()[0] = 0.25; - model_lct2d.parameters.get()[0] = 0.3; - model_lct2d.parameters.get() = 50; - model_lct2d.parameters.get() = 0.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + model_lct2d.parameters.get>()[0] = 3.2; + model_lct2d.parameters.get>()[0] = 2; + model_lct2d.parameters.get>()[0] = 5.8; + model_lct2d.parameters.get>()[0] = 9.5; + model_lct2d.parameters.get>()[0] = 7.1; + + model_lct2d.parameters.get>()[0] = 0.; + model_lct2d.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct2d = + model_lct2d.parameters.get>(); + contact_matrix_lct2d[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct2d[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>()[0] = 0.7; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.09; + model_lct2d.parameters.get>()[0] = 0.2; + model_lct2d.parameters.get>()[0] = 0.25; + model_lct2d.parameters.get>()[0] = 0.3; + model_lct2d.parameters.get>() = 50; + model_lct2d.parameters.get>() = 0.1; using InfState = mio::lsecir::InfectionState; - using LctState = mio::LctInfectionState; - using Model = mio::lsecir::Model; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir::Model; // Initialization vector for LCT model. Eigen::VectorX init_lct = Eigen::VectorX::Constant((Eigen::Index)InfState::Count, 0); @@ -527,26 +534,27 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) } // Set Parameters. - model_lct.parameters.get()[0] = 3.2; - model_lct.parameters.get()[0] = 2; - model_lct.parameters.get()[0] = 5.8; - model_lct.parameters.get()[0] = 9.5; - model_lct.parameters.get()[0] = 7.1; - - model_lct.parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix_lct = model_lct.parameters.get(); - contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(2, 2, 10)); - contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); - - model_lct.parameters.get()[0] = 0.7; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get() = 50; - model_lct.parameters.get() = 0.1; - model_lct.parameters.get()[0] = 0.09; - model_lct.parameters.get()[0] = 0.2; - model_lct.parameters.get()[0] = 0.25; - model_lct.parameters.get()[0] = 0.3; + model_lct.parameters.get>()[0] = 3.2; + model_lct.parameters.get>()[0] = 2; + model_lct.parameters.get>()[0] = 5.8; + model_lct.parameters.get>()[0] = 9.5; + model_lct.parameters.get>()[0] = 7.1; + + model_lct.parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix_lct = + model_lct.parameters.get>(); + contact_matrix_lct[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(2, 2, 10)); + contact_matrix_lct[0].add_damping(0.7, mio::SimulationTime(2.)); + + model_lct.parameters.get>()[0] = 0.7; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>() = 50; + model_lct.parameters.get>() = 0.1; + model_lct.parameters.get>()[0] = 0.09; + model_lct.parameters.get>()[0] = 0.2; + model_lct.parameters.get>()[0] = 0.25; + model_lct.parameters.get>()[0] = 0.3; // Simulate mio::TimeSeries result_lct2d = mio::simulate(t0, tmax, dt, model_lct2d); @@ -582,10 +590,10 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) // and calculate the TimeSeries with no subcompartments from the result TEST(TestLCTSecir2d, testSubcompartments) { - using InfState = mio::lsecir2d::InfectionState; - using LctState = - mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + using InfState = mio::lsecir2d::InfectionState; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 1; ScalarType dt = 0.1; @@ -596,6 +604,10 @@ TEST(TestLCTSecir2d, testSubcompartments) {30, 10, 10}, {0, 0, 0}, {10, 10, 10}, {0, 0, 0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0}, {0}, {0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0}}; + // Initial population without subcompartments + std::vector init_no_subcompartments = {200, 0, 50, 0, 30, 0, 0, 0, 0, 50, 0, 30, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + Model model; // Transfer the initial values in initial_populations to the model. @@ -611,7 +623,11 @@ TEST(TestLCTSecir2d, testSubcompartments) mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); auto interpolated_results = mio::interpolate_simulation_result(population_no_subcompartments); - EXPECT_NEAR(result.get_last_time(), tmax, 1e-10); + // Compare the values of compartments at time 0 after using calculate_compartments + // to the initial values without subcompartments + for (size_t i = 0; i < 26; i++) { + EXPECT_NEAR(population_no_subcompartments.get_value(0)[i], init_no_subcompartments[i], 1e-10); + } } // Model setup to compare result with a previous output. @@ -619,9 +635,9 @@ class ModelTestLCTSecir2d : public testing::Test { public: using InfState = mio::lsecir2d::InfectionState; - using LctState = - mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + using LctState = mio::LctInfectionState; + using Model = mio::lsecir2d::Model; protected: virtual void SetUp() @@ -641,35 +657,36 @@ class ModelTestLCTSecir2d : public testing::Test } // Set parameters. - model->parameters.get()[0] = 3.2; - model->parameters.get()[0] = 2; - model->parameters.get()[0] = 5.8; - model->parameters.get()[0] = 9.5; - model->parameters.get()[0] = 7.1; - model->parameters.get()[0] = 0.05; - model->parameters.get()[0] = 3.2; - model->parameters.get()[0] = 2; - model->parameters.get()[0] = 5.8; - model->parameters.get()[0] = 9.5; - model->parameters.get()[0] = 7.1; - model->parameters.get()[0] = 0.05; - - mio::ContactMatrixGroup& contact_matrix = model->parameters.get(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); - contact_matrix[0].add_damping(0.7, mio::SimulationTime(2.)); - - model->parameters.get()[0] = 0.7; - model->parameters.get()[0] = 0.25; - model->parameters.get()[0] = 0.09; - model->parameters.get()[0] = 0.2; - model->parameters.get()[0] = 0.25; - model->parameters.get()[0] = 0.3; - model->parameters.get()[0] = 0.7; - model->parameters.get()[0] = 0.25; - model->parameters.get()[0] = 0.09; - model->parameters.get()[0] = 0.2; - model->parameters.get()[0] = 0.25; - model->parameters.get()[0] = 0.3; + model->parameters.get>()[0] = 3.2; + model->parameters.get>()[0] = 2; + model->parameters.get>()[0] = 5.8; + model->parameters.get>()[0] = 9.5; + model->parameters.get>()[0] = 7.1; + model->parameters.get>()[0] = 0.05; + model->parameters.get>()[0] = 3.2; + model->parameters.get>()[0] = 2; + model->parameters.get>()[0] = 5.8; + model->parameters.get>()[0] = 9.5; + model->parameters.get>()[0] = 7.1; + model->parameters.get>()[0] = 0.05; + + mio::ContactMatrixGroup& contact_matrix = + model->parameters.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(1, 1, 10)); + contact_matrix[0].add_damping(0.7, mio::SimulationTime(2.)); + + model->parameters.get>()[0] = 0.7; + model->parameters.get>()[0] = 0.25; + model->parameters.get>()[0] = 0.09; + model->parameters.get>()[0] = 0.2; + model->parameters.get>()[0] = 0.25; + model->parameters.get>()[0] = 0.3; + model->parameters.get>()[0] = 0.7; + model->parameters.get>()[0] = 0.25; + model->parameters.get>()[0] = 0.09; + model->parameters.get>()[0] = 0.2; + model->parameters.get>()[0] = 0.25; + model->parameters.get>()[0] = 0.3; } virtual void TearDown() @@ -711,171 +728,172 @@ TEST(TestLCTSecir2d, testConstraintsParameters) mio::set_log_level(mio::LogLevel::off); // Check for exceptions of parameters. - mio::lsecir2d::Parameters parameters_lct2d(1); - parameters_lct2d.get()[0] = 0; - parameters_lct2d.get()[0] = 3.1; - parameters_lct2d.get()[0] = 6.1; - parameters_lct2d.get()[0] = 11.1; - parameters_lct2d.get()[0] = 17.1; - parameters_lct2d.get()[0] = 0.01; - parameters_lct2d.get()[0] = 3.1; - parameters_lct2d.get()[0] = 3.1; - parameters_lct2d.get()[0] = 6.1; - parameters_lct2d.get()[0] = 11.1; - parameters_lct2d.get()[0] = 17.1; - parameters_lct2d.get()[0] = 0.01; - mio::ContactMatrixGroup& contact_matrix = parameters_lct2d.get(); - contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixXd::Constant(1, 1, 10)); - - parameters_lct2d.get()[0] = 1; - parameters_lct2d.get()[0] = 1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 1; - parameters_lct2d.get()[0] = 1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; - parameters_lct2d.get()[0] = 0.1; + mio::lsecir2d::Parameters parameters_lct2d(1); + parameters_lct2d.get>()[0] = 0; + parameters_lct2d.get>()[0] = 3.1; + parameters_lct2d.get>()[0] = 6.1; + parameters_lct2d.get>()[0] = 11.1; + parameters_lct2d.get>()[0] = 17.1; + parameters_lct2d.get>()[0] = 0.01; + parameters_lct2d.get>()[0] = 3.1; + parameters_lct2d.get>()[0] = 3.1; + parameters_lct2d.get>()[0] = 6.1; + parameters_lct2d.get>()[0] = 11.1; + parameters_lct2d.get>()[0] = 17.1; + parameters_lct2d.get>()[0] = 0.01; + mio::ContactMatrixGroup& contact_matrix = + parameters_lct2d.get>(); + contact_matrix[0] = mio::ContactMatrix(Eigen::MatrixX::Constant(1, 1, 10)); + + parameters_lct2d.get>()[0] = 1; + parameters_lct2d.get>()[0] = 1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 1; + parameters_lct2d.get>()[0] = 1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; // Check improper TimeExposed. bool constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get>()[0] = 3.1; - parameters_lct2d.get()[0] = 0.1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get>()[0] = 3.1; // Check TimeInfectedNoSymptoms. - parameters_lct2d.get()[0] = 0.1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get>()[0] = 3.1; - parameters_lct2d.get()[0] = 0.1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 3.1; + parameters_lct2d.get>()[0] = 3.1; // Check TimeInfectedSymptoms. - parameters_lct2d.get()[0] = -0.1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -0.1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 6.1; + parameters_lct2d.get>()[0] = 6.1; - parameters_lct2d.get()[0] = -0.1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -0.1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 6.1; + parameters_lct2d.get>()[0] = 6.1; // Check TimeInfectedSevere. - parameters_lct2d.get()[0] = 0.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 11.1; + parameters_lct2d.get>()[0] = 11.1; - parameters_lct2d.get()[0] = 0.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 11.1; + parameters_lct2d.get>()[0] = 11.1; // Check TimeInfectedCritical. - parameters_lct2d.get()[0] = 0.; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 17.1; + parameters_lct2d.get>()[0] = 17.1; - parameters_lct2d.get()[0] = 0.; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 0.; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 17.1; + parameters_lct2d.get>()[0] = 17.1; // Check TransmissionProbabilityOnContact. - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.01; + parameters_lct2d.get>()[0] = 0.01; - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.01; + parameters_lct2d.get>()[0] = 0.01; // Check RelativeTransmissionNoSymptoms. - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 1; + parameters_lct2d.get>()[0] = 1; - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 1; + parameters_lct2d.get>()[0] = 1; // Check RiskOfInfectionFromSymptomatic. - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 1; + parameters_lct2d.get>()[0] = 1; - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 1; + parameters_lct2d.get>()[0] = 1; // Check RecoveredPerInfectedNoSymptoms. - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; - parameters_lct2d.get()[0] = 1.5; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = 1.5; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; // Check SeverePerInfectedSymptoms. - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; // Check CriticalPerSevere. - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; // Check DeathsPerCritical. - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; - parameters_lct2d.get()[0] = -1; - constraint_check = parameters_lct2d.check_constraints(); + parameters_lct2d.get>()[0] = -1; + constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.get()[0] = 0.1; + parameters_lct2d.get>()[0] = 0.1; // Check Seasonality. - parameters_lct2d.set(1); + parameters_lct2d.set>(1); constraint_check = parameters_lct2d.check_constraints(); EXPECT_TRUE(constraint_check); - parameters_lct2d.set(0.1); + parameters_lct2d.set>(0.1); // Check with correct parameters. constraint_check = parameters_lct2d.check_constraints(); @@ -894,54 +912,54 @@ TEST(TestLCTSecir2d, testConstraintsModel) using InfState = mio::lsecir2d::InfectionState; // Check for improper number of subcompartments for Susceptible. - using LctStatewrongSusceptibles = - mio::LctInfectionState; - using ModelwrongSusceptibles = mio::lsecir2d::Model; + using LctStatewrongSusceptibles = mio::LctInfectionState; + using ModelwrongSusceptibles = mio::lsecir2d::Model; ModelwrongSusceptibles modelwrongSusceptibles; bool constraint_check = modelwrongSusceptibles.check_constraints(); EXPECT_TRUE(constraint_check); // Check for improper number of subcompartments for Recovered. - using LctStatewrongRecovered_a = - mio::LctInfectionState; - using ModelwrongRecovered_a = mio::lsecir2d::Model; + using LctStatewrongRecovered_a = mio::LctInfectionState; + using ModelwrongRecovered_a = mio::lsecir2d::Model; ModelwrongRecovered_a modelwrongRecovered_a; constraint_check = modelwrongRecovered_a.check_constraints(); EXPECT_TRUE(constraint_check); - using LctStatewrongRecovered_b = - mio::LctInfectionState; - using ModelwrongRecovered_b = mio::lsecir2d::Model; + using LctStatewrongRecovered_b = mio::LctInfectionState; + using ModelwrongRecovered_b = mio::lsecir2d::Model; ModelwrongRecovered_b modelwrongRecovered_b; constraint_check = modelwrongRecovered_b.check_constraints(); EXPECT_TRUE(constraint_check); - using LctStatewrongRecovered_ab = - mio::LctInfectionState; - using ModelwrongRecovered_ab = mio::lsecir2d::Model; + using LctStatewrongRecovered_ab = mio::LctInfectionState; + using ModelwrongRecovered_ab = mio::lsecir2d::Model; ModelwrongRecovered_ab modelwrongRecovered_ab; constraint_check = modelwrongRecovered_ab.check_constraints(); EXPECT_TRUE(constraint_check); // Check for improper number of subcompartments for Dead. - using LctStatewrongDead_a = - mio::LctInfectionState; - using ModelwrongDead_a = mio::lsecir2d::Model; + using LctStatewrongDead_a = mio::LctInfectionState; + using ModelwrongDead_a = mio::lsecir2d::Model; ModelwrongDead_a modelwrongDead_a; constraint_check = modelwrongDead_a.check_constraints(); EXPECT_TRUE(constraint_check); - using LctStatewrongDead_b = - mio::LctInfectionState; - using ModelwrongDead_b = mio::lsecir2d::Model; + using LctStatewrongDead_b = mio::LctInfectionState; + using ModelwrongDead_b = mio::lsecir2d::Model; ModelwrongDead_b modelwrongDead_b; constraint_check = modelwrongDead_b.check_constraints(); EXPECT_TRUE(constraint_check); // Check with a negative number in the initial population distribution. - using LctStatevalid = - mio::LctInfectionState; - using Model = mio::lsecir2d::Model; + using LctStatevalid = mio::LctInfectionState; + using Model = mio::lsecir2d::Model; Model model; model.populations[0] = -1000; constraint_check = model.check_constraints(); From ef6ce8af96a05d8a69da099781b2d16971dd116d Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 23 Oct 2025 12:50:49 +0200 Subject: [PATCH 15/16] default num_groups comment --- cpp/models/lct_secir_2_diseases/parameters.h | 48 ++++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/cpp/models/lct_secir_2_diseases/parameters.h b/cpp/models/lct_secir_2_diseases/parameters.h index 48f3201c31..160d8dae28 100644 --- a/cpp/models/lct_secir_2_diseases/parameters.h +++ b/cpp/models/lct_secir_2_diseases/parameters.h @@ -47,7 +47,7 @@ namespace lsecir2d template struct TimeExposed_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -64,7 +64,7 @@ struct TimeExposed_a { template struct TimeInfectedNoSymptoms_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -81,7 +81,7 @@ struct TimeInfectedNoSymptoms_a { template struct TimeInfectedSymptoms_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -97,7 +97,7 @@ struct TimeInfectedSymptoms_a { template struct TimeInfectedSevere_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -113,7 +113,7 @@ struct TimeInfectedSevere_a { template struct TimeInfectedCritical_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -129,7 +129,7 @@ struct TimeInfectedCritical_a { template struct TransmissionProbabilityOnContact_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -145,7 +145,7 @@ struct TransmissionProbabilityOnContact_a { template struct TimeExposed_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -162,7 +162,7 @@ struct TimeExposed_b { template struct TimeInfectedNoSymptoms_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -179,7 +179,7 @@ struct TimeInfectedNoSymptoms_b { template struct TimeInfectedSymptoms_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -195,7 +195,7 @@ struct TimeInfectedSymptoms_b { template struct TimeInfectedSevere_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -211,7 +211,7 @@ struct TimeInfectedSevere_b { template struct TimeInfectedCritical_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -227,7 +227,7 @@ struct TimeInfectedCritical_b { template struct TransmissionProbabilityOnContact_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -263,7 +263,7 @@ struct ContactPatterns { template struct RelativeTransmissionNoSymptoms_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -279,7 +279,7 @@ struct RelativeTransmissionNoSymptoms_a { template struct RiskOfInfectionFromSymptomatic_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -295,7 +295,7 @@ struct RiskOfInfectionFromSymptomatic_a { template struct RelativeTransmissionNoSymptoms_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -311,7 +311,7 @@ struct RelativeTransmissionNoSymptoms_b { template struct RiskOfInfectionFromSymptomatic_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 1.); } @@ -327,7 +327,7 @@ struct RiskOfInfectionFromSymptomatic_b { template struct RecoveredPerInfectedNoSymptoms_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -343,7 +343,7 @@ struct RecoveredPerInfectedNoSymptoms_a { template struct SeverePerInfectedSymptoms_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -359,7 +359,7 @@ struct SeverePerInfectedSymptoms_a { template struct CriticalPerSevere_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -375,7 +375,7 @@ struct CriticalPerSevere_a { template struct DeathsPerCritical_a { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.1); } @@ -391,7 +391,7 @@ struct DeathsPerCritical_a { template struct RecoveredPerInfectedNoSymptoms_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -407,7 +407,7 @@ struct RecoveredPerInfectedNoSymptoms_b { template struct SeverePerInfectedSymptoms_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -423,7 +423,7 @@ struct SeverePerInfectedSymptoms_b { template struct CriticalPerSevere_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.5); } @@ -439,7 +439,7 @@ struct CriticalPerSevere_b { template struct DeathsPerCritical_b { using Type = Eigen::VectorX>; - static Type get_default(size_t size = 1) + static Type get_default(size_t size) { return Type::Constant(size, 1, 0.1); } From dd6b40d5746cce1f295b7e4b7f10bac6fcdd1e74 Mon Sep 17 00:00:00 2001 From: an-jung Date: Thu, 6 Nov 2025 16:07:58 +0100 Subject: [PATCH 16/16] some name changes, CI will fail --- cpp/examples/lct_secir_2_diseases.cpp | 28 +-- .../lct_secir_2_diseases/infection_state.h | 8 +- cpp/models/lct_secir_2_diseases/model.h | 193 +++++++++--------- cpp/tests/test_lct_secir_2_diseases.cpp | 50 ++--- 4 files changed, 145 insertions(+), 134 deletions(-) diff --git a/cpp/examples/lct_secir_2_diseases.cpp b/cpp/examples/lct_secir_2_diseases.cpp index 4877cf3b35..75c8116d05 100644 --- a/cpp/examples/lct_secir_2_diseases.cpp +++ b/cpp/examples/lct_secir_2_diseases.cpp @@ -42,21 +42,21 @@ int main() NumExposed_2b = 1, NumInfectedNoSymptoms_2b = 2, NumInfectedSymptoms_2b = 2, NumInfectedSevere_2b = 2, NumInfectedCritical_2b = 1; // The compartment for Susceptible people and all compartments for Dead and Recovered people must have exactly one subcompartment: - constexpr size_t NumSusceptible = 1, NumDead_a = 1, NumDead_b = 1, NumRecovered_a = 1, NumRecovered_b = 1, - NumRecovered_ab = 1; - using InfState = mio::lsecir2d::InfectionState; + constexpr size_t NumSusceptible = 1, NumDead_a = 1, NumDead_b = 1, NumRecovered_1a = 1, NumRecovered_1b = 1, + NumRecovered_2ab = 1; + using InfState = mio::lsecir2d::InfectionState; using LctState = mio::LctInfectionState; + NumInfectedSevere_2b, NumInfectedCritical_2b, NumRecovered_2ab>; using Model = mio::lsecir2d::Model; Model model; - ScalarType tmax = 10; + ScalarType tmax = 5; // Set Parameters. model.parameters.get>()[0] = 3.; @@ -119,8 +119,8 @@ int main() (initial_populations[(size_t)InfState::InfectedSymptoms_2a].size() != NumInfectedSymptoms_2a) || (initial_populations[(size_t)InfState::InfectedSevere_2a].size() != NumInfectedSevere_2a) || (initial_populations[(size_t)InfState::InfectedCritical_2a].size() != NumInfectedCritical_2a) || - (initial_populations[(size_t)InfState::Recovered_a].size() != - LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Recovered_1a].size() != + LctState::get_num_subcompartments()) || (initial_populations[(size_t)InfState::Dead_a].size() != LctState::get_num_subcompartments()) || (initial_populations[(size_t)InfState::Exposed_1b].size() != NumExposed_1b) || @@ -128,13 +128,17 @@ int main() (initial_populations[(size_t)InfState::InfectedSymptoms_1b].size() != NumInfectedSymptoms_1b) || (initial_populations[(size_t)InfState::InfectedSevere_1b].size() != NumInfectedSevere_1b) || (initial_populations[(size_t)InfState::InfectedCritical_1b].size() != NumInfectedCritical_1b) || + (initial_populations[(size_t)InfState::Recovered_1b].size() != + LctState::get_num_subcompartments()) || + (initial_populations[(size_t)InfState::Dead_b].size() != + LctState::get_num_subcompartments()) || (initial_populations[(size_t)InfState::Exposed_2b].size() != NumExposed_2b) || (initial_populations[(size_t)InfState::InfectedNoSymptoms_2b].size() != NumInfectedNoSymptoms_2b) || (initial_populations[(size_t)InfState::InfectedSymptoms_2b].size() != NumInfectedSymptoms_2b) || (initial_populations[(size_t)InfState::InfectedSevere_2b].size() != NumInfectedSevere_2b) || (initial_populations[(size_t)InfState::InfectedCritical_2b].size() != NumInfectedCritical_2b) || - (initial_populations[(size_t)InfState::Recovered_ab].size() != - LctState::get_num_subcompartments())) { + (initial_populations[(size_t)InfState::Recovered_2ab].size() != + LctState::get_num_subcompartments())) { mio::log_error("The length of at least one vector in initial_populations does not match the related number of " "subcompartments."); return 1; @@ -149,7 +153,7 @@ int main() } // Perform a simulation. - mio::TimeSeries result = mio::simulate(0, tmax, 0.5, model); + mio::TimeSeries result = mio::simulate(0, tmax, 0.1, model); // The simulation result is divided by subcompartments. // We call the function calculate_compartments to get a result according to the InfectionStates. mio::TimeSeries population_no_subcompartments = model.calculate_compartments(result); diff --git a/cpp/models/lct_secir_2_diseases/infection_state.h b/cpp/models/lct_secir_2_diseases/infection_state.h index d33ade8b8f..bc134e7cc8 100644 --- a/cpp/models/lct_secir_2_diseases/infection_state.h +++ b/cpp/models/lct_secir_2_diseases/infection_state.h @@ -40,7 +40,7 @@ enum class InfectionState InfectedSymptoms_1a = 3, InfectedSevere_1a = 4, InfectedCritical_1a = 5, - Recovered_a = 6, + Recovered_1a = 6, Dead_a = 7, // second infection with disease a Exposed_2a = 8, @@ -54,7 +54,7 @@ enum class InfectionState InfectedSymptoms_1b = 15, InfectedSevere_1b = 16, InfectedCritical_1b = 17, - Recovered_b = 18, + Recovered_1b = 18, Dead_b = 19, // second infection with disease b Exposed_2b = 20, @@ -63,8 +63,8 @@ enum class InfectionState InfectedSevere_2b = 23, InfectedCritical_2b = 24, // Recovered from both diseases - Recovered_ab = 25, - Count = 26 + Recovered_2ab = 25, + Count = 26 }; } // namespace lsecir2d diff --git a/cpp/models/lct_secir_2_diseases/model.h b/cpp/models/lct_secir_2_diseases/model.h index 64c84466a5..1923418fb9 100644 --- a/cpp/models/lct_secir_2_diseases/model.h +++ b/cpp/models/lct_secir_2_diseases/model.h @@ -187,7 +187,7 @@ class Model : public CompartmentalModel; size_t first_index_group = this->populations.template get_first_index_of_group(); - auto params = this->parameters; + const auto& params = this->parameters; FP flow = 0; // Indices of first subcompartment of the InfectionState for the group in the vectors. @@ -231,28 +231,28 @@ class Model : public CompartmentalModel(); size_t ICri_2b_first_index = first_index_group + LctStateGroup::template get_first_index(); - size_t Ri_a = first_index_group + LctStateGroup::template get_first_index(); - size_t Ri_b = first_index_group + LctStateGroup::template get_first_index(); - size_t Ri_ab = first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_a = first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_b = first_index_group + LctStateGroup::template get_first_index(); + size_t Ri_ab = first_index_group + LctStateGroup::template get_first_index(); size_t Di_a = first_index_group + LctStateGroup::template get_first_index(); size_t Di_b = first_index_group + LctStateGroup::template get_first_index(); // Calculate derivative of the Susceptible compartment. - // outflow generated by disease a and disease b both - double part_a = 0.; // part of people getting infected with disease a - double part_b = 0.; // part of people getting infected with disease b - interact(pop, y, t, dydt, &part_a, &part_b, first_index_group, 2); - // split flow - double div_part_both = ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); - - // Start with derivatives of First Infections (X_1a, X_1b) + // The outflow is generated by disease a and disease b both. + FP part_a = 0.; // Part of people getting infected with disease a. + FP part_b = 0.; // Part of people getting infected with disease b. + interact(pop, y, t, dydt, &part_a, &part_b, 2); + // Split flow. + FP div_part_both = ((part_a + part_b) < Limits::zero_tolerance()) ? 0.0 : 1.0 / (part_a + part_b); + + // Start with derivatives of first infections (X_1a, X_1b) // Calculate derivative of the Exposed_1x compartments, split flow from Susceptible. // Exposed 1 a: dydt[Ei_1a_first_index] = -dydt[first_index_group] * part_a * div_part_both; for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { // Variable flow stores the value of the flow from one subcompartment to the next one. - // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1a_first_index + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed_1a and Ei_1a_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. flow = (FP)LctStateGroup::template get_num_subcompartments() * (1 / params.template get>()[Group]) * y[Ei_1a_first_index + subcomp]; @@ -265,7 +265,7 @@ class Model : public CompartmentalModel(); subcomp++) { // Variable flow stores the value of the flow from one subcompartment to the next one. - // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_1b_first_index + // Ei_1a_first_index + subcomp is always the index of a (sub-)compartment of Exposed_1a and Ei_1b_first_index // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms. flow = (FP)LctStateGroup::template get_num_subcompartments() * (1 / params.template get>()[Group]) * y[Ei_1b_first_index + subcomp]; @@ -275,7 +275,7 @@ class Model : public CompartmentalModel(); subcomp++) { @@ -285,7 +285,7 @@ class Model : public CompartmentalModel(); subcomp++) { @@ -296,8 +296,8 @@ class Model : public CompartmentalModel>()[Group]; dydt[ISyi_1a_first_index] = dydt[ISyi_1a_first_index] * (1 - params.template get>()[Group]); @@ -310,8 +310,8 @@ class Model : public CompartmentalModel>()[Group]; dydt[ISyi_1b_first_index] = dydt[ISyi_1b_first_index] * (1 - params.template get>()[Group]); @@ -325,7 +325,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ISevi_1a_first_index] = dydt[ISevi_1a_first_index] * params.template get>()[Group]; @@ -338,7 +338,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ISevi_1b_first_index] = dydt[ISevi_1b_first_index] * params.template get>()[Group]; @@ -351,8 +351,8 @@ class Model : public CompartmentalModel>()[Group]); dydt[ICri_1a_first_index] = dydt[ICri_1a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; @@ -363,8 +363,8 @@ class Model : public CompartmentalModel>()[Group]); dydt[ICri_1b_first_index] = dydt[ICri_1b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; @@ -376,9 +376,9 @@ class Model : public CompartmentalModel() * (1 / params.template get>()[Group]) * y[ICri_1a_first_index + @@ -387,8 +387,6 @@ class Model : public CompartmentalModel() - 1] -= flow; dydt[Ri_a] = dydt[Ri_a] + (1 - params.template get>()[Group]) * flow; dydt[Di_a] = dydt[Di_a] + params.template get>()[Group] * flow; - // Flow from InfectedCritical_1b has to be divided between Recovered_b and Dead_b. - // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered. // Outflow from InfectedCritical_1b: flow = (FP)LctStateGroup::template get_num_subcompartments() * (1 / params.template get>()[Group]) * @@ -399,17 +397,15 @@ class Model : public CompartmentalModel>()[Group]) * flow; dydt[Di_b] = dydt[Di_b] + params.template get>()[Group] * flow; - // Second Infection (X_2a, X_2b) - // outflow from Recovered, people getting infected for the second time + // Second Infection: + // Outflow from Recovered_1a and Recovered_1b (people getting infected for the second time). double temp_Ra = dydt[Ri_a]; - interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_a, - 1); // outflow from R_a is only affected by b, no age groups + interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, 1); // Outflow from Recovered_1a is only affected by b. double temp_Rb = dydt[Ri_b]; - interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, Ri_b, - 0); // outflow from R_b is only affected by a, no age groups + interact<0, 0>(pop, y, t, dydt, &part_a, &part_b, 0); // Outflow from Recovered_1b is only affected by a. - // Calculate derivative of the Exposed_2i compartments - // Exposed 2 a + // Calculate derivative of the Exposed_2i compartments: + // Exposed_2a: dydt[Ei_2a_first_index] = -(dydt[Ri_b] - temp_Rb); for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments(); subcomp++) { @@ -422,7 +418,7 @@ class Model : public CompartmentalModel(); subcomp++) { @@ -456,8 +452,8 @@ class Model : public CompartmentalModel>()[Group]; dydt[ISyi_2a_first_index] = dydt[ISyi_2a_first_index] * (1 - params.template get>()[Group]); @@ -470,8 +466,8 @@ class Model : public CompartmentalModel>()[Group]; dydt[ISyi_2b_first_index] = dydt[ISyi_2b_first_index] * (1 - params.template get>()[Group]); @@ -485,7 +481,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ISevi_2a_first_index] = dydt[ISevi_2a_first_index] * params.template get>()[Group]; @@ -498,7 +494,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ISevi_2b_first_index] = dydt[ISevi_2b_first_index] * params.template get>()[Group]; @@ -512,7 +508,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ICri_2a_first_index] = dydt[ICri_2a_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; @@ -524,7 +520,7 @@ class Model : public CompartmentalModel>()[Group]); dydt[ICri_2b_first_index] = dydt[ICri_2b_first_index] * params.template get>()[Group]; for (size_t subcomp = 0; @@ -538,7 +534,7 @@ class Model : public CompartmentalModel() * (1 / params.template get>()[Group]) * y[ICri_2a_first_index + @@ -547,7 +543,7 @@ class Model : public CompartmentalModel() - 1] -= flow; dydt[Ri_ab] = dydt[Ri_ab] + (1 - params.template get>()[Group]) * flow; dydt[Di_a] = dydt[Di_a] + params.template get>()[Group] * flow; - // for 2b + // Outflow from InfectedCritical_2b: flow = (FP)LctStateGroup::template get_num_subcompartments() * (1 / params.template get>()[Group]) * y[ICri_2b_first_index + @@ -576,27 +572,26 @@ class Model : public CompartmentalModel void interact(Eigen::Ref> pop, Eigen::Ref> y, FP t, - Eigen::Ref> dydt, double* part_a, double* part_b, size_t compartment_index, - int which_disease) const + Eigen::Ref> dydt, double* part_a, double* part_b, int relevant_disease) const { static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0), "The template parameters Group1 & Group2 should be valid."); - using LctStateGroup2 = type_at_index_t; - FP infectedNoSymptoms_2_a = 0; - FP infectedSymptoms_2_a = 0; - FP infectedNoSymptoms_2_b = 0; - FP infectedSymptoms_2_b = 0; - auto params = this->parameters; + using LctStateGroup1 = type_at_index_t; + using LctStateGroup2 = type_at_index_t; + FP InfectedNoSymptoms_Group2_a = 0; + FP InfectedSymptoms_Group2_a = 0; + FP InfectedNoSymptoms_Group2_b = 0; + FP InfectedSymptoms_Group2_b = 0; + const auto& params = this->parameters; size_t first_index_group2 = this->populations.template get_first_index_of_group(); // Calculate sum of all subcompartments for InfectedNoSymptoms for disease a of Group2. - infectedNoSymptoms_2_a = + InfectedNoSymptoms_Group2_a = pop.segment(first_index_group2 + LctStateGroup2::template get_first_index(), LctStateGroup2::template get_num_subcompartments()) @@ -606,7 +601,7 @@ class Model : public CompartmentalModel()) .sum(); // Calculate sum of all subcompartments for InfectedSymptoms for disease a of Group2. - infectedSymptoms_2_a = + InfectedSymptoms_Group2_a = pop.segment(first_index_group2 + LctStateGroup2::template get_first_index(), LctStateGroup2::template get_num_subcompartments()) @@ -616,7 +611,7 @@ class Model : public CompartmentalModel()) .sum(); // Calculate sum of all subcompartments for InfectedNoSymptoms for disease b of Group2. - infectedNoSymptoms_2_b = + InfectedNoSymptoms_Group2_b = pop.segment(first_index_group2 + LctStateGroup2::template get_first_index(), LctStateGroup2::template get_num_subcompartments()) @@ -626,7 +621,7 @@ class Model : public CompartmentalModel()) .sum(); // Calculate sum of all subcompartments for InfectedSymptoms for disease b of Group2. - infectedSymptoms_2_b = + InfectedSymptoms_Group2_b = pop.segment(first_index_group2 + LctStateGroup2::template get_first_index(), LctStateGroup2::template get_num_subcompartments()) @@ -635,55 +630,67 @@ class Model : public CompartmentalModel(), LctStateGroup2::template get_num_subcompartments()) .sum(); - // Size of the Subpopulation Group2 without dead people. - FP N_2 = pop.segment(first_index_group2, LctStateGroup2::Count).sum(); // sum over all compartments - N_2 = N_2 - pop.segment(LctStateGroup2::template get_first_index(), 1).sum() - - pop.segment(LctStateGroup2::template get_first_index(), 1) - .sum(); // all people minus dead people - const FP divN_2 = (N_2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_2; - FP season_val = 1 + params.template get>() * + // Size of the subpopulation Group2 without dead people. + FP N_Group2 = pop.segment(first_index_group2, LctStateGroup2::Count).sum(); // Sum over all compartments. + N_Group2 = N_Group2 - pop.segment(LctStateGroup2::template get_first_index(), 1).sum() - + pop.segment(LctStateGroup2::template get_first_index(), 1) + .sum(); // All people minus dead people. + const FP div_N_Group2 = (N_Group2 < Limits::zero_tolerance()) ? 0.0 : 1.0 / N_Group2; + FP season_val = 1 + params.template get>() * sin(3.141592653589793 * ((params.template get>() + t) / 182.5 + 0.5)); - if (which_disease == 0) { // disease a + // Index of the first compartment (Susceptible) for the current group (Group1). + size_t first_index_group1 = this->populations.template get_first_index_of_group(); + + if (relevant_disease == 0) { // Disease a. + // Get index for compartment Recovered_1b and calculate outflow driven by disease a. + size_t compartment_index = + first_index_group1 + LctStateGroup1::template get_first_index(); dydt[compartment_index] += - -y[compartment_index] * divN_2 * season_val * + -y[compartment_index] * div_N_Group2 * season_val * params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_a + - params.template get>()[Group2] * infectedSymptoms_2_a); + (params.template get>()[Group2] * InfectedNoSymptoms_Group2_a + + params.template get>()[Group2] * InfectedSymptoms_Group2_a); } - else if (which_disease == 1) { // disease b + else if (relevant_disease == 1) { // Disease b. + // Get index for compartment Recovered_1a and calculate outflow driven by disease b. + size_t compartment_index = + first_index_group1 + LctStateGroup1::template get_first_index(); dydt[compartment_index] += - -y[compartment_index] * divN_2 * season_val * + -y[compartment_index] * div_N_Group2 * season_val * params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_b + - params.template get>()[Group2] * infectedSymptoms_2_b); + (params.template get>()[Group2] * InfectedNoSymptoms_Group2_b + + params.template get>()[Group2] * InfectedSymptoms_Group2_b); } - else if (which_disease == 2) { // both diseases + else if (relevant_disease == 2) { // Both diseases drive outflow from Susceptible compartment. + size_t compartment_index = first_index_group1; dydt[compartment_index] += - -y[compartment_index] * divN_2 * season_val * + -y[compartment_index] * div_N_Group2 * season_val * params.template get>().get_cont_freq_mat().get_matrix_at(SimulationTime(t))( static_cast(Group1), static_cast(Group2)) * (params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_a + - params.template get>()[Group2] * infectedSymptoms_2_a) + + (params.template get>()[Group2] * + InfectedNoSymptoms_Group2_a + + params.template get>()[Group2] * InfectedSymptoms_Group2_a) + params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_b + - params.template get>()[Group2] * infectedSymptoms_2_b)); + (params.template get>()[Group2] * + InfectedNoSymptoms_Group2_b + + params.template get>()[Group2] * InfectedSymptoms_Group2_b)); } - // To split the outflow from S between E_1a and E_1b: + // To split the outflow from Susceptible between Exposed_1a and Exposed_1b. *part_a += params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_a + - params.template get>()[Group2] * infectedSymptoms_2_a); + (params.template get>()[Group2] * InfectedNoSymptoms_Group2_a + + params.template get>()[Group2] * InfectedSymptoms_Group2_a); *part_b += params.template get>()[Group1] * - (params.template get>()[Group2] * infectedNoSymptoms_2_b + - params.template get>()[Group2] * infectedSymptoms_2_b); + (params.template get>()[Group2] * InfectedNoSymptoms_Group2_b + + params.template get>()[Group2] * InfectedSymptoms_Group2_b); if constexpr (Group2 + 1 < num_groups) { - interact(pop, y, t, dydt, part_a, part_b, compartment_index, which_disease); + interact(pop, y, t, dydt, part_a, part_b, relevant_disease); } } @@ -705,9 +712,9 @@ class Model : public CompartmentalModel() != 1 || - LctStateGroup::template get_num_subcompartments() != 1 || - LctStateGroup::template get_num_subcompartments() != 1) { + if (LctStateGroup::template get_num_subcompartments() != 1 || + LctStateGroup::template get_num_subcompartments() != 1 || + LctStateGroup::template get_num_subcompartments() != 1) { log_warning("Constraint check: The number of subcompartments for Recovered of group {} should be one!", Group); return true; diff --git a/cpp/tests/test_lct_secir_2_diseases.cpp b/cpp/tests/test_lct_secir_2_diseases.cpp index 67e3c3ba72..223d3783ea 100644 --- a/cpp/tests/test_lct_secir_2_diseases.cpp +++ b/cpp/tests/test_lct_secir_2_diseases.cpp @@ -192,7 +192,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir1) EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSevere], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_1a], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Recovered], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_a], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_1a], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Dead], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_a], 1e-5); } @@ -321,13 +321,13 @@ TEST(TestLCTSecir2d, compareWithLCTSecir2) EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSevere], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_1b], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Recovered], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_b], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_1b], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Dead], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_b], 1e-5); } } -// 3. Test: Second infection with disease a (transmission prob. of disease b = 0, start in Recovered_b)*/ +// 3. Test: Second infection with disease a (transmission prob. of disease b = 0, start in Recovered_1b)*/ TEST(TestLCTSecir2d, compareWithLCTSecir3) { using InfState_2d = mio::lsecir2d::InfectionState; @@ -341,7 +341,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) ScalarType t0 = 0; ScalarType tmax = 5; - ScalarType dt = 0.1; + ScalarType dt = 1; // Initialization vector for lct2d model. Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState_2d::Count, 0); @@ -439,7 +439,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Susceptible], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_b], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_1b], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Exposed], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_2a], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedNoSymptoms], @@ -451,13 +451,13 @@ TEST(TestLCTSecir2d, compareWithLCTSecir3) EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::InfectedSevere], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_2a], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Recovered], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_ab], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_2ab], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState_lct::Dead], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_a], 1e-5); } } -// 4. Test: Second infection with disease b (transmission prob. of disease a = 0, start in Recovered_a)*/ +// 4. Test: Second infection with disease b (transmission prob. of disease a = 0, start in Recovered_1a)*/ TEST(TestLCTSecir2d, compareWithLCTSecir4) { using InfState2d = mio::lsecir2d::InfectionState; @@ -466,7 +466,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) using Model_2d = mio::lsecir2d::Model; ScalarType t0 = 0; ScalarType tmax = 5; - ScalarType dt = 0.1; + ScalarType dt = 1; // Initialization vector for lct2d model. Eigen::VectorX init_lct2d = Eigen::VectorX::Constant((Eigen::Index)InfState2d::Count, 0); @@ -568,7 +568,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) EXPECT_NEAR(result_lct.get_time(i), result_lct2d.get_time(i), 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Susceptible], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_a], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_1a], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Exposed], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Exposed_2b], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedNoSymptoms], @@ -580,7 +580,7 @@ TEST(TestLCTSecir2d, compareWithLCTSecir4) EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::InfectedSevere], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::InfectedSevere_2b], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Recovered], - result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_ab], 1e-5); + result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Recovered_2ab], 1e-5); EXPECT_NEAR(result_lct[i][(Eigen::Index)InfState::Dead], result_lct2d[i][(Eigen::Index)mio::lsecir2d::InfectionState::Dead_b], 1e-5); } @@ -920,25 +920,25 @@ TEST(TestLCTSecir2d, testConstraintsModel) EXPECT_TRUE(constraint_check); // Check for improper number of subcompartments for Recovered. - using LctStatewrongRecovered_a = mio::LctInfectionState; - using ModelwrongRecovered_a = mio::lsecir2d::Model; - ModelwrongRecovered_a modelwrongRecovered_a; - constraint_check = modelwrongRecovered_a.check_constraints(); + using LctStatewrongRecovered_1a = mio::LctInfectionState; + using ModelwrongRecovered_1a = mio::lsecir2d::Model; + ModelwrongRecovered_1a modelwrongRecovered_1a; + constraint_check = modelwrongRecovered_1a.check_constraints(); EXPECT_TRUE(constraint_check); - using LctStatewrongRecovered_b = mio::LctInfectionState; - using ModelwrongRecovered_b = mio::lsecir2d::Model; - ModelwrongRecovered_b modelwrongRecovered_b; - constraint_check = modelwrongRecovered_b.check_constraints(); + using LctStatewrongRecovered_1b = mio::LctInfectionState; + using ModelwrongRecovered_1b = mio::lsecir2d::Model; + ModelwrongRecovered_1b modelwrongRecovered_1b; + constraint_check = modelwrongRecovered_1b.check_constraints(); EXPECT_TRUE(constraint_check); - using LctStatewrongRecovered_ab = mio::LctInfectionState; - using ModelwrongRecovered_ab = mio::lsecir2d::Model; - ModelwrongRecovered_ab modelwrongRecovered_ab; - constraint_check = modelwrongRecovered_ab.check_constraints(); + using LctStatewrongRecovered_2ab = mio::LctInfectionState; + using ModelwrongRecovered_2ab = mio::lsecir2d::Model; + ModelwrongRecovered_2ab modelwrongRecovered_2ab; + constraint_check = modelwrongRecovered_2ab.check_constraints(); EXPECT_TRUE(constraint_check); // Check for improper number of subcompartments for Dead.