LCOV - code coverage report
Current view: top level - models/lct_secir - initializer_flows.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 93 93 100.0 %
Date: 2025-01-17 12:16:22 Functions: 48 58 82.8 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2025 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 LCTSECIR_INITIALIZER_H
      22             : #define LCTSECIR_INITIALIZER_H
      23             : 
      24             : #include "memilio/config.h"
      25             : #include "lct_secir/model.h"
      26             : #include "lct_secir/infection_state.h"
      27             : #include "memilio/math/eigen.h"
      28             : #include "memilio/utils/time_series.h"
      29             : #include "memilio/math/floating_point.h"
      30             : #include "memilio/utils/logging.h"
      31             : #include "memilio/utils/metaprogramming.h"
      32             : #include "memilio/epidemiology/state_age_function.h"
      33             : 
      34             : namespace mio
      35             : {
      36             : namespace lsecir
      37             : {
      38             : 
      39             : /**
      40             :  * @brief Class that can be used to compute an initialization vector out of flows for an LCT Model 
      41             :  *  with division in groups.
      42             :  * 
      43             :  *  The initialization method is based on the fact that the LCT model is a special case of an IDE model with special 
      44             :  *  choices of stay time distributions (so-called Erlang distributions). Accordingly, the method for calculating 
      45             :  *  initial values for the compartments from given flows/transitions is taken from the IDE-SECIR model.
      46             :  *  We cannot use the functionality of the IDE model directly, as we have to calculate a division into sub-compartments
      47             :  *  and not only the sizes of the compartments.
      48             : *   See also the IDE-SECIR model for the general method and for a better understanding of flows/transitions.
      49             :  *
      50             :  * @tparam Model is expected to be an LCT-SECIR model defined in models/lct_secir/model.h.
      51             :  */
      52             : template <typename Model>
      53             : class Initializer
      54             : {
      55             : public:
      56             :     using LctStatesGroups = typename Model::LctStatesGroups;
      57             : 
      58             :     /**
      59             :      * @brief Constructs a new Initializer object.
      60             :      *
      61             :      * @param[in] flows Initializing TimeSeries with flows fitting to these defined in InfectionTransition. 
      62             :      *      For each group of m_model, InfectionTransition::Count entries are required.
      63             :      *      Timesteps should be equidistant and the values should be non-negative. 
      64             :      *      The time history has to be long enough so that it is possible to calculate the initial vector. 
      65             :      *      The length of the required time history depends on the Erlang densities used to compute the initial vector.
      66             :      * @param[in, out] model The LCT-SECIR model for which the initialization should be performed.
      67             :      */
      68          13 :     Initializer(TimeSeries<ScalarType>&& flows, Model& model)
      69          13 :         : m_flows(std::move(flows))
      70          13 :         , m_model(model)
      71             :     {
      72          13 :         m_dt = m_flows.get_time(1) - m_flows.get_time(0);
      73          13 :     }
      74             : 
      75             :     /**
      76             :      * @brief Core function of Initializer.
      77             :      *
      78             :      * Computes a vector that can be used for the initialization of an LCT model stratified by groups with the number 
      79             :      * of persons for each subcompartment for each group.
      80             :      * The initial value vector is updated in the model.
      81             :      *
      82             :      * @param[in] total_population The total size of the considered population.
      83             :      * @param[in] deaths Number of deceased people from the disease at time 0.
      84             :      * @param[in] total_confirmed_cases Total number of confirmed cases at time 0.
      85             :      * @return Returns true if one (or more) constraint(s) of the model, the initial flows 
      86             :      *      or the computed initial value vector are not satisfied, otherwise false. 
      87             :      */
      88          13 :     bool compute_initialization_vector(Eigen::VectorX<ScalarType> const& total_population,
      89             :                                        Eigen::VectorX<ScalarType> const& deaths,
      90             :                                        Eigen::VectorX<ScalarType> const& total_confirmed_cases)
      91             :     {
      92             : 
      93          13 :         Eigen::VectorX<ScalarType> init(m_model.populations.get_compartments());
      94             : 
      95          13 :         bool error = compute_initialization_vector_impl(init, total_population, deaths, total_confirmed_cases);
      96          13 :         if (error) {
      97           6 :             return true;
      98             :         }
      99             : 
     100             :         // Update initial value vector of the model.
     101         217 :         for (size_t i = 0; i < m_model.populations.get_num_compartments(); i++) {
     102         210 :             m_model.populations[i] = init[i];
     103             :         }
     104             : 
     105             :         // Check if constraints are fulfilled.
     106           7 :         return check_constraints();
     107          13 :     }
     108             : 
     109             :     /**
     110             :      * @brief Setter for the tolerance used to calculate the maximum support of ErlangDensity%s.
     111             :      *
     112             :      * @param[in] new_tol New tolerance.
     113             :      */
     114           3 :     void set_tol_for_support_max(ScalarType new_tol)
     115             :     {
     116           3 :         m_tol = new_tol;
     117           3 :     }
     118             : 
     119             : private:
     120             :     /**
     121             :      * @brief Implementation of the calculation of the initial value vector slice that corresponds to a specified group.
     122             :      *
     123             :      * Computes a vector that can be used for the initialization of an LCT model stratified by groups with the number 
     124             :      * of persons for each subcompartment. The groups are calculated recursively.
     125             :      *
     126             :      * @tparam Group The group for which the corresponding slice of the initial value vector is calculated.
     127             :      * @param[out] init The initial value vector under consideration.
     128             :      * @param[in] total_population The total size of the considered population.
     129             :      * @param[in] deaths Number of deceased people from the disease at time 0.
     130             :      * @param[in] total_confirmed_cases Total number of confirmed cases at time 0.
     131             :      * @return Returns true if one (or more) constraint(s) of the computed initial value vector are not satisfied,
     132             :      *       otherwise false. 
     133             :      */
     134             :     template <size_t Group = 0>
     135          22 :     bool compute_initialization_vector_impl(Eigen::VectorX<ScalarType>& init,
     136             :                                             Eigen::VectorX<ScalarType> const& total_population,
     137             :                                             Eigen::VectorX<ScalarType> const& deaths,
     138             :                                             Eigen::VectorX<ScalarType> const& total_confirmed_cases)
     139             :     {
     140             :         static_assert((Group < Model::num_groups) && (Group >= 0), "The template parameter Group should be valid.");
     141             :         using LctStateGroup            = type_at_index_t<Group, LctStatesGroups>;
     142          22 :         Eigen::Index first_index       = m_model.populations.template get_first_index_of_group<Group>();
     143          22 :         Eigen::Index first_index_flows = Group * (Eigen::Index)InfectionTransition::Count;
     144             : 
     145          22 :         bool error =
     146             :             // Exposed.
     147          44 :             (compute_compartment<InfectionState::Exposed, Group>(
     148             :                 init, first_index_flows + (Eigen::Index)InfectionTransition::SusceptibleToExposed,
     149          22 :                 1 / m_model.parameters.template get<TimeExposed>()[Group])) ||
     150             :             // InfectedNoSymptoms.
     151          42 :             (compute_compartment<InfectionState::InfectedNoSymptoms, Group>(
     152             :                 init, first_index_flows + (Eigen::Index)InfectionTransition::ExposedToInfectedNoSymptoms,
     153          21 :                 1 / m_model.parameters.template get<TimeInfectedNoSymptoms>()[Group])) ||
     154             :             // InfectedSymptoms.
     155          40 :             (compute_compartment<InfectionState::InfectedSymptoms, Group>(
     156             :                 init, first_index_flows + (Eigen::Index)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms,
     157          20 :                 1 / m_model.parameters.template get<TimeInfectedSymptoms>()[Group])) ||
     158             :             // InfectedSevere.
     159          38 :             (compute_compartment<InfectionState::InfectedSevere, Group>(
     160             :                 init, first_index_flows + (Eigen::Index)InfectionTransition::InfectedSymptomsToInfectedSevere,
     161          62 :                 1 / m_model.parameters.template get<TimeInfectedSevere>()[Group])) ||
     162             :             // InfectedCritical.
     163          36 :             (compute_compartment<InfectionState::InfectedCritical, Group>(
     164             :                 init, first_index_flows + (Eigen::Index)InfectionTransition::InfectedSevereToInfectedCritical,
     165          18 :                 1 / m_model.parameters.template get<TimeInfectedCritical>()[Group]));
     166          22 :         if (error) {
     167           5 :             return true;
     168             :         }
     169             : 
     170             :         // Recovered.
     171             :         // Number of recovered is equal to the cumulative number of confirmed cases minus the number of people
     172             :         // in the group who are infected at the moment.
     173          17 :         init[first_index + LctStateGroup::template get_first_index<InfectionState::Recovered>()] =
     174          17 :             total_confirmed_cases[Group] -
     175          51 :             init.segment(first_index + LctStateGroup::template get_first_index<InfectionState::InfectedSymptoms>(),
     176          17 :                          LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>() +
     177          17 :                              LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>() +
     178          17 :                              LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>())
     179          17 :                 .sum() -
     180             :             deaths[Group];
     181             : 
     182             :         // Susceptibles.
     183          17 :         init[first_index + LctStateGroup::template get_first_index<InfectionState::Susceptible>()] =
     184          17 :             total_population[Group] -
     185          34 :             init.segment(first_index + LctStateGroup::template get_first_index<InfectionState::Exposed>(),
     186             :                          LctStateGroup::Count - 2)
     187          17 :                 .sum() -
     188             :             deaths[Group];
     189             : 
     190             :         // Dead.
     191          17 :         init[first_index + LctStateGroup::template get_first_index<InfectionState::Dead>()] = deaths[Group];
     192             : 
     193          67 :         for (size_t state_idx : {LctStateGroup::template get_first_index<InfectionState::Susceptible>(),
     194             :                                  LctStateGroup::template get_first_index<InfectionState::Recovered>(),
     195             :                                  LctStateGroup::template get_first_index<InfectionState::Dead>()}) {
     196          51 :             if (floating_point_less(init[first_index + state_idx], 0.0, 1e-8)) {
     197           1 :                 log_error("Initialization failed. Values of total_confirmed_cases, deaths and total_population do not "
     198             :                           "match the specified values for the transitions, leading to at least one negative result.");
     199           1 :                 return true;
     200             :             }
     201             :         }
     202             :         // Function call for next group if applicable.
     203             :         if constexpr (Group + 1 < Model::num_groups) {
     204           9 :             return compute_initialization_vector_impl<Group + 1>(init, total_population, deaths, total_confirmed_cases);
     205             :         }
     206             :         else {
     207           7 :             return false;
     208             :         }
     209             :     }
     210             : 
     211             :     /**
     212             :      * @brief Checks constraints of the Initializer including checks for the model.
     213             :      * @return Returns true if one (or more) constraint(s) are not satisfied, otherwise false. 
     214             :      */
     215           7 :     bool check_constraints() const
     216             :     {
     217           7 :         if (!((Eigen::Index)InfectionTransition::Count * Model::num_groups == m_flows.get_num_elements())) {
     218           1 :             log_error("Initial condition size does not match subcompartments.");
     219           1 :             return true;
     220             :         }
     221             : 
     222           6 :         if (!(floating_point_equal(0., m_flows.get_last_time(), 1e-8, 1e-14))) {
     223           1 :             log_error("Last time point in flows has to be 0.");
     224           1 :             return true;
     225             :         }
     226             : 
     227         725 :         for (int i = 1; i < m_flows.get_num_time_points(); i++) {
     228         721 :             if (!(floating_point_equal(m_dt, m_flows.get_time(i) - m_flows.get_time(i - 1), 1e-8, 1e-14))) {
     229           1 :                 log_error("Time points in flows have to be equidistant.");
     230           1 :                 return true;
     231             :             }
     232             :         }
     233             : 
     234           4 :         if (!(m_dt < 1)) {
     235           1 :             log_warning("Step size was set very large. The result could be distorted.");
     236           1 :             return true;
     237             :         }
     238             : 
     239             :         // Check if model is valid and the calculated initial value vector is valid.
     240           3 :         return m_model.check_constraints();
     241             :     }
     242             : 
     243             :     /**
     244             :      * @brief Computes a slice of the initial value vector for each subcompartment of one InfectionState 
     245             :      *  for a specified group.
     246             :      *
     247             :      * @tparam Group The group for which the corresponding slice of the initial value vector is calculated.
     248             :      * @tparam State The InfectionState for which the corresponding slice of the initial value vector is calculated
     249             :      * @param[out] init The initial value vector under consideration.
     250             :      * @param[in] idx_incoming_flow Index of the flow which is relevant for the calculation, so the flow 
     251             :      *      to the InfectionState.
     252             :      * @param[in] transition_rate Specifies the transition rate of the InfectionState. 
     253             :      *      'This is equal to 1 / (expected time in the InfectionState).
     254             :      
     255             :      * @return Returns true if one (or more) constraint(s) of the computed slice of the initial value vector
     256             :      *      are not satisfied, otherwise false. 
     257             :      */
     258             :     template <InfectionState State, size_t Group>
     259         100 :     bool compute_compartment(Eigen::VectorX<ScalarType>& init, Eigen::Index idx_incoming_flow,
     260             :                              ScalarType transition_rate) const
     261             :     {
     262         100 :         size_t first_index = m_model.populations.template get_first_index_of_group<Group>() +
     263         100 :                              type_at_index_t<Group, LctStatesGroups>::template get_first_index<State>();
     264         100 :         size_t num_subcompartments = type_at_index_t<Group, LctStatesGroups>::template get_num_subcompartments<State>();
     265             : 
     266             :         // Initialize relevant density for the InfectionState.
     267             :         // For the first subcompartment a shape parameter of one is needed.
     268         100 :         ErlangDensity erlang(1, 1. / (num_subcompartments * transition_rate));
     269             : 
     270             :         // Initialize other relevant parameters.
     271         100 :         ScalarType calc_time{0};
     272         100 :         Eigen::Index calc_time_index{0};
     273         100 :         ScalarType state_age{0};
     274         100 :         ScalarType sum{0};
     275         100 :         Eigen::Index num_time_points = m_flows.get_num_time_points();
     276             : 
     277             :         // Calculate number of people in each subcompartment.
     278         328 :         for (size_t j = 0; j < num_subcompartments; j++) {
     279             :             // For subcompartment number j+1, shape parameter j+1 is needed.
     280         233 :             erlang.set_distribution_parameter((ScalarType)j + 1);
     281             : 
     282             :             // Determine relevant calculation area and corresponding index.
     283         233 :             calc_time       = erlang.get_support_max(m_dt, m_tol);
     284         233 :             calc_time_index = (Eigen::Index)std::ceil(calc_time / m_dt) - 1;
     285             : 
     286         233 :             if (num_time_points < calc_time_index) {
     287           1 :                 log_error("Initialization failed. Not enough time points for the transitions are given. More than {} "
     288             :                           "are needed but just {} are given.",
     289             :                           calc_time_index, num_time_points);
     290           1 :                 return true;
     291             :             }
     292             :             else {
     293             :                 // Approximate integral with non-standard scheme.
     294        8042 :                 for (Eigen::Index i = num_time_points - calc_time_index; i < num_time_points; i++) {
     295        7810 :                     state_age = (num_time_points - i) * m_dt;
     296        7810 :                     sum += erlang.eval(state_age) * m_flows[i][idx_incoming_flow];
     297             :                 }
     298         232 :                 init[first_index + j] = 1. / (num_subcompartments * transition_rate) * sum;
     299         232 :                 if (init[first_index + j] < 0) {
     300           4 :                     log_error(
     301             :                         "Initialization failed. Result for at least one subcompartment is less than zero. Please check "
     302             :                         "the input values.");
     303           4 :                     return true;
     304             :                 }
     305             :             }
     306         228 :             sum = 0;
     307             :         }
     308          95 :         return false;
     309         100 :     }
     310             : 
     311             :     TimeSeries<ScalarType> m_flows; ///< TimeSeries with the flows which are used to calculate the initial vector.
     312             :     Model& m_model; ///< The LCT-SECIR model for which the initialization should be performed.
     313             :     ScalarType m_dt{}; ///< Step size of the times in m_flows and time step for the approximation of the integral.
     314             :     ScalarType m_tol{1e-10}; ///< Tolerance used to calculate the maximum support of the ErlangDensity%s.
     315             : };
     316             : 
     317             : } // namespace lsecir
     318             : } // namespace mio
     319             : 
     320             : #endif

Generated by: LCOV version 1.14