LCOV - code coverage report
Current view: top level - models/lct_secir - model.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 162 162 100.0 %
Date: 2024-11-18 12:45:26 Functions: 103 243 42.4 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2024 MEmilio
       3             : *
       4             : * Authors: Lena Ploetzke
       5             : *
       6             : * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
       7             : *
       8             : * Licensed under the Apache License, Version 2.0 (the "License");
       9             : * you may not use this file except in compliance with the License.
      10             : * You may obtain a copy of the License at
      11             : *
      12             : *     http://www.apache.org/licenses/LICENSE-2.0
      13             : *
      14             : * Unless required by applicable law or agreed to in writing, software
      15             : * distributed under the License is distributed on an "AS IS" BASIS,
      16             : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      17             : * See the License for the specific language governing permissions and
      18             : * limitations under the License.
      19             : */
      20             : 
      21             : #ifndef LCT_SECIR_MODEL_H
      22             : #define LCT_SECIR_MODEL_H
      23             : 
      24             : #include "lct_secir/parameters.h"
      25             : #include "lct_secir/infection_state.h"
      26             : #include "memilio/compartments/compartmentalmodel.h"
      27             : #include "memilio/epidemiology/lct_populations.h"
      28             : #include "memilio/epidemiology/lct_infection_state.h"
      29             : #include "memilio/config.h"
      30             : #include "memilio/utils/time_series.h"
      31             : #include "memilio/utils/logging.h"
      32             : #include "memilio/math/eigen.h"
      33             : #include "memilio/utils/type_list.h"
      34             : #include "memilio/utils/metaprogramming.h"
      35             : namespace mio
      36             : {
      37             : namespace lsecir
      38             : {
      39             : /**
      40             :  * @brief Class that defines an LCT-SECIR model.
      41             :  *
      42             :  * @tparam LctStates The LCT model can work with any number of LctStates, where each LctState corresponds to a group,
      43             :  * e.g. one AgeGroup. The purpose of the LctStates is to define the number of subcompartments for each InfectionState.
      44             :  * If you do not want to divide the population into groups, just use one LctState.
      45             :  * If you want to divide the population according to more than one category, e.g. sex and age, 
      46             :  * you have to specify one LctState for each pair of groups, e.g. for (female, A00-A04), (female, A05-A14) etc. 
      47             :  * This is because the number of subcompartments can be different for each group.
      48             :  * Therefore, the number of LctStates also determines the number of groups. 
      49             :  */
      50             : template <class... LctStates>
      51             : class Model
      52             :     : public CompartmentalModel<ScalarType, InfectionState, LctPopulations<ScalarType, LctStates...>, Parameters>
      53             : {
      54             : public:
      55             :     using LctStatesGroups = TypeList<LctStates...>;
      56             :     using Base = CompartmentalModel<ScalarType, InfectionState, LctPopulations<ScalarType, LctStates...>, Parameters>;
      57             :     using typename Base::ParameterSet;
      58             :     using typename Base::Populations;
      59             :     static size_t constexpr num_groups = sizeof...(LctStates);
      60             :     static_assert(num_groups >= 1, "The number of LctStates provided should be at least one.");
      61             : 
      62             :     /// @brief Default constructor.
      63          19 :     Model()
      64          19 :         : Base(Populations(), ParameterSet(num_groups))
      65             :     {
      66          19 :     }
      67             : 
      68             :     /** @brief Constructor using Populations and ParameterSet.
      69             :      * @param[in] pop An instance of the Populations class.
      70             :      * @param[in] params Parameters used to be used in the simulation.
      71             :      */
      72             :     Model(const Populations& pop, const ParameterSet& params)
      73             :         : Base(pop, params)
      74             :     {
      75             :     }
      76             : 
      77             :     /**
      78             :      * @brief Evaluates the right-hand-side f of the ODE dydt = f(y, t).
      79             :      *
      80             :      * The LCT-SECIR model is defined through ordinary differential equations of the form dydt = f(y, t). 
      81             :      * y is a vector containing the number of individuals for each (sub-) compartment.
      82             :      * This function evaluates the right-hand-side f of the ODE and can be used in an ODE solver.
      83             :      * @param[in] pop The current state of the population in the geographic unit we are considering.
      84             :      * @param[in] y The current state of the model (or a subpopulation) as a flat array.
      85             :      * @param[in] t The current time.
      86             :      * @param[out] dydt A reference to the calculated output.
      87             :      */
      88         237 :     void get_derivatives(Eigen::Ref<const Vector<ScalarType>> pop, Eigen::Ref<const Vector<ScalarType>> y, ScalarType t,
      89             :                          Eigen::Ref<Vector<ScalarType>> dydt) const override
      90             :     {
      91             :         // Vectors are sorted such that we first have all InfectionState%s for AgeGroup 0,
      92             :         // afterwards all for AgeGroup 1 and so on.
      93         237 :         dydt.setZero();
      94         237 :         get_derivatives_impl(pop, y, t, dydt);
      95         237 :     }
      96             : 
      97             :     /**
      98             :      * @brief Cumulates a simulation result with subcompartments to produce a result that divides the population only
      99             :      *   into the infection states defined in InfectionState.
     100             :      *
     101             :      * If the model is used for simulation, we will get a result in form of a TimeSeries with infection states divided 
     102             :      * in subcompartments.
     103             :      * The function calculates a TimeSeries without subcompartments from another TimeSeries with subcompartments. 
     104             :      * This is done by summing up the numbers in the subcompartments.
     105             :      * @param[in] subcompartments_ts Result of a simulation with the model.
     106             :      * @return Result of the simulation divided in infection states without subcompartments. 
     107             :      *  Returns TimeSeries with values -1 if calculation is not possible.
     108             :      */
     109           5 :     TimeSeries<ScalarType> calculate_compartments(const TimeSeries<ScalarType>& subcompartments_ts) const
     110             :     {
     111           5 :         Eigen::Index count_InfStates  = (Eigen::Index)InfectionState::Count;
     112           5 :         Eigen::Index num_compartments = count_InfStates * num_groups;
     113           5 :         TimeSeries<ScalarType> compartments_ts(num_compartments);
     114           5 :         if (!(this->populations.get_num_compartments() == (size_t)subcompartments_ts.get_num_elements())) {
     115           2 :             log_error("Result does not match InfectionState of the Model.");
     116           1 :             Vector<ScalarType> wrong_size = Vector<ScalarType>::Constant(num_compartments, -1);
     117           1 :             compartments_ts.add_time_point(-1, wrong_size);
     118           1 :             return compartments_ts;
     119           1 :         }
     120           4 :         Vector<ScalarType> compartments(num_compartments);
     121          23 :         for (Eigen::Index timepoint = 0; timepoint < subcompartments_ts.get_num_time_points(); ++timepoint) {
     122          19 :             compress_vector(subcompartments_ts[timepoint], compartments);
     123          19 :             compartments_ts.add_time_point(subcompartments_ts.get_time(timepoint), compartments);
     124             :         }
     125             : 
     126           4 :         return compartments_ts;
     127           5 :     }
     128             : 
     129             :     /**
     130             :      * @brief Checks that the model satisfies all constraints (e.g. parameter or population constraints).
     131             :      * @return Returns true if one or more constraints are not satisfied, false otherwise.
     132             :      */
     133          12 :     bool check_constraints() const
     134             :     {
     135             : 
     136          21 :         return (check_constraints_impl() || this->parameters.check_constraints() ||
     137          21 :                 this->populations.check_constraints());
     138             :     }
     139             : 
     140             : private:
     141             :     /**
     142             :      * @brief Converts a vector with subcompartments in a vector without subcompartments 
     143             :      * by summing up subcompartment values.
     144             :      * This is done recursively for each group which corresponds to a slice of the vector.
     145             :      *
     146             :      * @tparam group The group specifying the slice of the vector being considered. 
     147             :      * @param[in] subcompartments The vector that should be converted.
     148             :      * @param[out] compartments Reference to the vector where the output is stored.
     149             :      */
     150             :     template <size_t Group = 0>
     151          32 :     void compress_vector(const Vector<ScalarType>& subcompartments, Vector<ScalarType>& compartments) const
     152             :     {
     153             :         static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
     154             :         using LctStateGroup = type_at_index_t<Group, LctStates...>;
     155             : 
     156             :         // Specify first indices of the vector slices considered.
     157             :         // First index of the group Group in an vector including all subcompartments.
     158          32 :         Eigen::Index first_index_group_subcomps = this->populations.template get_first_index_of_group<Group>();
     159          32 :         Eigen::Index count_InfStates            = (Eigen::Index)InfectionState::Count;
     160             :         // First index of the group Group in an vector including all compartments without a resolution
     161             :         // in subcompartments.
     162          32 :         Eigen::Index first_index_group_comps = Group * count_InfStates;
     163             : 
     164             :         // Use segment of the vector subcompartments of each InfectionState and sum up the values of subcompartments.
     165          32 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::Susceptible] =
     166             :             subcompartments[first_index_group_subcomps];
     167          64 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::Exposed] =
     168             :             subcompartments
     169          32 :                 .segment(first_index_group_subcomps +
     170          32 :                              LctStateGroup::template get_first_index<InfectionState::Exposed>(),
     171          32 :                          LctStateGroup::template get_num_subcompartments<InfectionState::Exposed>())
     172          64 :                 .sum();
     173          64 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::InfectedNoSymptoms] =
     174             :             subcompartments
     175          32 :                 .segment(first_index_group_subcomps +
     176          32 :                              LctStateGroup::template get_first_index<InfectionState::InfectedNoSymptoms>(),
     177          32 :                          LctStateGroup::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>())
     178          64 :                 .sum();
     179          64 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::InfectedSymptoms] =
     180             :             subcompartments
     181          32 :                 .segment(first_index_group_subcomps +
     182          32 :                              LctStateGroup::template get_first_index<InfectionState::InfectedSymptoms>(),
     183          32 :                          LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>())
     184          64 :                 .sum();
     185          64 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::InfectedSevere] =
     186             :             subcompartments
     187          32 :                 .segment(first_index_group_subcomps +
     188          32 :                              LctStateGroup::template get_first_index<InfectionState::InfectedSevere>(),
     189          32 :                          LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>())
     190          64 :                 .sum();
     191          64 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::InfectedCritical] =
     192             :             subcompartments
     193          32 :                 .segment(first_index_group_subcomps +
     194          32 :                              LctStateGroup::template get_first_index<InfectionState::InfectedCritical>(),
     195          32 :                          LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>())
     196          64 :                 .sum();
     197          32 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::Recovered] =
     198          64 :             subcompartments[first_index_group_subcomps +
     199          32 :                             LctStateGroup::template get_first_index<InfectionState::Recovered>()];
     200          32 :         compartments[first_index_group_comps + (Eigen::Index)InfectionState::Dead] =
     201          64 :             subcompartments[first_index_group_subcomps +
     202          32 :                             LctStateGroup::template get_first_index<InfectionState::Dead>()];
     203             :         // Function call for next group if applicable.
     204             :         if constexpr (Group + 1 < num_groups) {
     205          13 :             compress_vector<Group + 1>(subcompartments, compartments);
     206             :         }
     207          32 :     }
     208             : 
     209             :     /**
     210             :      * @brief Evaluates the right-hand-side f of the ODE dydt = f(y, t) recursively for each group.
     211             :      *
     212             :      * See also the function get_derivative.
     213             :      * For each group, one slice of the output vector is calculated.
     214             :      * @tparam group The group specifying the slice of the vector being considered.  
     215             :      * @param[in] pop The current state of the population in the geographic unit we are considering.
     216             :      * @param[in] y The current state of the model (or a subpopulation) as a flat array.
     217             :      * @param[in] t The current time.
     218             :      * @param[out] dydt A reference to the calculated output.
     219             :      */
     220             :     template <size_t Group = 0>
     221         264 :     void get_derivatives_impl(Eigen::Ref<const Vector<ScalarType>> pop, Eigen::Ref<const Vector<ScalarType>> y,
     222             :                               ScalarType t, Eigen::Ref<Vector<ScalarType>> dydt) const
     223             :     {
     224             :         static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
     225             :         using LctStateGroup = type_at_index_t<Group, LctStates...>;
     226             : 
     227         264 :         size_t first_index_group = this->populations.template get_first_index_of_group<Group>();
     228         264 :         auto params              = this->parameters;
     229         264 :         ScalarType flow          = 0;
     230             : 
     231             :         // Indices of first subcompartment of the InfectionState for the group in the vectors.
     232         264 :         size_t Ei_first_index = first_index_group + LctStateGroup::template get_first_index<InfectionState::Exposed>();
     233         264 :         size_t INSi_first_index =
     234         264 :             first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedNoSymptoms>();
     235         264 :         size_t ISyi_first_index =
     236         264 :             first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedSymptoms>();
     237         264 :         size_t ISevi_first_index =
     238         264 :             first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedSevere>();
     239         264 :         size_t ICri_first_index =
     240         264 :             first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedCritical>();
     241         264 :         size_t Ri = first_index_group + LctStateGroup::template get_first_index<InfectionState::Recovered>();
     242         264 :         size_t Di = first_index_group + LctStateGroup::template get_first_index<InfectionState::Dead>();
     243             : 
     244             :         // Calculate derivative of the Susceptible compartment.
     245         264 :         interact<Group, 0>(pop, y, t, dydt);
     246             : 
     247             :         // Calculate derivative of the Exposed compartment.
     248         264 :         dydt[Ei_first_index] = -dydt[first_index_group];
     249         653 :         for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::Exposed>();
     250             :              subcomp++) {
     251             :             // Variable flow stores the value of the flow from one subcompartment to the next one.
     252             :             // Ei_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_first_index
     253             :             // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms.
     254         389 :             flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::Exposed>() *
     255         389 :                    (1 / params.template get<TimeExposed>()[Group]) * y[Ei_first_index + subcomp];
     256             :             // Subtract flow from dydt[Ei_first_index + subcomp] and add to next subcompartment.
     257         389 :             dydt[Ei_first_index + subcomp] -= flow;
     258         389 :             dydt[Ei_first_index + subcomp + 1] = flow;
     259             :         }
     260             : 
     261             :         // Calculate derivative of the InfectedNoSymptoms compartment.
     262         264 :         for (size_t subcomp = 0;
     263         778 :              subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>();
     264             :              subcomp++) {
     265         514 :             flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>() *
     266         514 :                    (1 / params.template get<TimeInfectedNoSymptoms>()[Group]) * y[INSi_first_index + subcomp];
     267         514 :             dydt[INSi_first_index + subcomp] -= flow;
     268         514 :             dydt[INSi_first_index + subcomp + 1] = flow;
     269             :         }
     270             : 
     271             :         // Calculate derivative of the InfectedSymptoms compartment.
     272             :         // Flow from last (sub-) compartment of C must be split between
     273             :         // the first subcompartment of InfectedSymptoms and Recovered.
     274         264 :         dydt[Ri] = dydt[ISyi_first_index] * params.template get<RecoveredPerInfectedNoSymptoms>()[Group];
     275         264 :         dydt[ISyi_first_index] =
     276         264 :             dydt[ISyi_first_index] * (1 - params.template get<RecoveredPerInfectedNoSymptoms>()[Group]);
     277         264 :         for (size_t subcomp = 0;
     278         533 :              subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>(); subcomp++) {
     279         269 :             flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>() *
     280         269 :                    (1 / params.template get<TimeInfectedSymptoms>()[Group]) * y[ISyi_first_index + subcomp];
     281         269 :             dydt[ISyi_first_index + subcomp] -= flow;
     282         269 :             dydt[ISyi_first_index + subcomp + 1] = flow;
     283             :         }
     284             : 
     285             :         // Calculate derivative of the InfectedSevere compartment.
     286         264 :         dydt[Ri] += dydt[ISevi_first_index] * (1 - params.template get<SeverePerInfectedSymptoms>()[Group]);
     287         264 :         dydt[ISevi_first_index] = dydt[ISevi_first_index] * params.template get<SeverePerInfectedSymptoms>()[Group];
     288         264 :         for (size_t subcomp = 0;
     289         533 :              subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>(); subcomp++) {
     290         269 :             flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>() *
     291         269 :                    (1 / params.template get<TimeInfectedSevere>()[Group]) * y[ISevi_first_index + subcomp];
     292         269 :             dydt[ISevi_first_index + subcomp] -= flow;
     293         269 :             dydt[ISevi_first_index + subcomp + 1] = flow;
     294             :         }
     295             : 
     296             :         // Calculate derivative of the InfectedCritical compartment.
     297         264 :         dydt[Ri] += dydt[ICri_first_index] * (1 - params.template get<CriticalPerSevere>()[Group]);
     298         264 :         dydt[ICri_first_index] = dydt[ICri_first_index] * params.template get<CriticalPerSevere>()[Group];
     299         264 :         for (size_t subcomp = 0;
     300         749 :              subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() - 1;
     301             :              subcomp++) {
     302         485 :             flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() *
     303         485 :                    (1 / params.template get<TimeInfectedCritical>()[Group]) * y[ICri_first_index + subcomp];
     304         485 :             dydt[ICri_first_index + subcomp] -= flow;
     305         485 :             dydt[ICri_first_index + subcomp + 1] = flow;
     306             :         }
     307             :         // Last flow from InfectedCritical has to be divided between Recovered and Dead.
     308             :         // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered.
     309         264 :         flow = (ScalarType)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() *
     310         264 :                (1 / params.template get<TimeInfectedCritical>()[Group]) * y[Ri - 1];
     311         264 :         dydt[Ri - 1] -= flow;
     312         264 :         dydt[Ri] = dydt[Ri] + (1 - params.template get<DeathsPerCritical>()[Group]) * flow;
     313         264 :         dydt[Di] = params.template get<DeathsPerCritical>()[Group] * flow;
     314             : 
     315             :         // Function call for next group if applicable.
     316             :         if constexpr (Group + 1 < num_groups) {
     317          27 :             get_derivatives_impl<Group + 1>(pop, y, t, dydt);
     318             :         }
     319         528 :     }
     320             : 
     321             :     /**
     322             :      * @brief Calculates the derivative of the Susceptible compartment for Group1.
     323             :      *
     324             :      * This is done recursively by calculating the interaction terms with each group.
     325             :      * @tparam Group1 The group for which the derivative of the Susceptible compartment should be calculated. 
     326             :      * @tparam Group2 The group that Group1 interacts with. 
     327             :      * @param[in] pop The current state of the population in the geographic unit we are considering.
     328             :      * @param[in] y The current state of the model (or a subpopulation) as a flat array.
     329             :      * @param[in] t The current time.
     330             :      * @param[out] dydt A reference to the calculated output.
     331             :      */
     332             :     template <size_t Group1, size_t Group2 = 0>
     333         344 :     void interact(Eigen::Ref<const Vector<ScalarType>> pop, Eigen::Ref<const Vector<ScalarType>> y, ScalarType t,
     334             :                   Eigen::Ref<Vector<ScalarType>> dydt) const
     335             :     {
     336             :         static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0),
     337             :                       "The template parameters Group1 & Group2 should be valid.");
     338             :         using LctStateGroup2            = type_at_index_t<Group2, LctStates...>;
     339         344 :         size_t Si_1                     = this->populations.template get_first_index_of_group<Group1>();
     340         344 :         ScalarType infectedNoSymptoms_2 = 0;
     341         344 :         ScalarType infectedSymptoms_2   = 0;
     342         344 :         auto params                     = this->parameters;
     343             : 
     344         344 :         size_t first_index_group2 = this->populations.template get_first_index_of_group<Group2>();
     345             : 
     346             :         // Calculate sum of all subcompartments for InfectedNoSymptoms of Group2.
     347         344 :         infectedNoSymptoms_2 =
     348         344 :             pop.segment(first_index_group2 +
     349         344 :                             LctStateGroup2::template get_first_index<InfectionState::InfectedNoSymptoms>(),
     350         344 :                         LctStateGroup2::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>())
     351         344 :                 .sum();
     352             :         // Calculate sum of all subcompartments for InfectedSymptoms of Group2.
     353         344 :         infectedSymptoms_2 =
     354         344 :             pop.segment(first_index_group2 +
     355         344 :                             LctStateGroup2::template get_first_index<InfectionState::InfectedSymptoms>(),
     356         344 :                         LctStateGroup2::template get_num_subcompartments<InfectionState::InfectedSymptoms>())
     357         344 :                 .sum();
     358             :         // Size of the Subpopulation Group2 without dead people.
     359         344 :         double N_2            = pop.segment(first_index_group2, LctStateGroup2::Count - 1).sum();
     360         344 :         ScalarType season_val = 1 + params.template get<Seasonality>() *
     361         344 :                                         sin(3.141592653589793 * ((params.template get<StartDay>() + t) / 182.5 + 0.5));
     362         688 :         dydt[Si_1] += -y[Si_1] / N_2 * season_val * params.template get<TransmissionProbabilityOnContact>()[Group1] *
     363         688 :                       params.template get<ContactPatterns>().get_cont_freq_mat().get_matrix_at(t)(
     364         344 :                           static_cast<Eigen::Index>(Group1), static_cast<Eigen::Index>(Group2)) *
     365         344 :                       (params.template get<RelativeTransmissionNoSymptoms>()[Group2] * infectedNoSymptoms_2 +
     366         344 :                        params.template get<RiskOfInfectionFromSymptomatic>()[Group2] * infectedSymptoms_2);
     367             :         // Function call for next interacting group if applicable.
     368             :         if constexpr (Group2 + 1 < num_groups) {
     369          80 :             interact<Group1, Group2 + 1>(pop, y, t, dydt);
     370             :         }
     371         688 :     }
     372             : 
     373             :     /**
     374             :      * @brief Checks whether LctState of a group satisfies all constraints. 
     375             :      *  Recursively, it checks that all groups satisfy the constraints.
     376             :      *
     377             :      * @tparam group The group for which the constraints should be checked.
     378             :      * @return Returns true if one or more constraints are not satisfied, false otherwise.
     379             :      */
     380             :     template <size_t Group = 0>
     381          22 :     bool check_constraints_impl() const
     382             :     {
     383             :         static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
     384             :         using LctStateGroup = type_at_index_t<Group, LctStates...>;
     385             : 
     386          22 :         if (LctStateGroup::template get_num_subcompartments<InfectionState::Susceptible>() != 1) {
     387           2 :             log_warning("Constraint check: The number of subcompartments for Susceptibles of group {} should be one!",
     388             :                         Group);
     389           1 :             return true;
     390             :         }
     391          21 :         if (LctStateGroup::template get_num_subcompartments<InfectionState::Recovered>() != 1) {
     392           2 :             log_warning("Constraint check: The number of subcompartments for Recovered of group {} should be one!",
     393             :                         Group);
     394           1 :             return true;
     395             :         }
     396          20 :         if (LctStateGroup::template get_num_subcompartments<InfectionState::Dead>() != 1) {
     397           2 :             log_warning("Constraint check: The number of subcompartments for Dead of group {} should be one!", Group);
     398           1 :             return true;
     399             :         }
     400             : 
     401             :         if constexpr (Group == num_groups - 1) {
     402           9 :             return false;
     403             :         }
     404             :         else {
     405          10 :             return check_constraints_impl<Group + 1>();
     406             :         }
     407             :     }
     408             : };
     409             : 
     410             : } // namespace lsecir
     411             : } // namespace mio
     412             : 
     413             : #endif // LCTSECIR_MODEL_H

Generated by: LCOV version 1.14