LCOV - code coverage report
Current view: top level - models/ide_secir - model.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 348 348 100.0 %
Date: 2025-01-17 12:16:22 Functions: 18 18 100.0 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2025 MEmilio
       3             : *
       4             : * Authors: Anna Wendler, Lena Ploetzke, Martin J. Kuehn
       5             : *
       6             : * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
       7             : *
       8             : * Licensed under the Apache License, Version 2.0 (the "License");
       9             : * you may not use this file except in compliance with the License.
      10             : * You may obtain a copy of the License at
      11             : *
      12             : *     http://www.apache.org/licenses/LICENSE-2.0
      13             : *
      14             : * Unless required by applicable law or agreed to in writing, software
      15             : * distributed under the License is distributed on an "AS IS" BASIS,
      16             : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      17             : * See the License for the specific language governing permissions and
      18             : * limitations under the License.
      19             : */
      20             : #include "ide_secir/model.h"
      21             : #include "ide_secir/parameters.h"
      22             : #include "infection_state.h"
      23             : #include "memilio/config.h"
      24             : #include "memilio/epidemiology/age_group.h"
      25             : #include "memilio/utils/custom_index_array.h"
      26             : #include "memilio/utils/logging.h"
      27             : #include "memilio/math/eigen.h"
      28             : 
      29             : #include "vector"
      30             : #include <algorithm>
      31             : #include <cstddef>
      32             : 
      33             : namespace mio
      34             : {
      35             : namespace isecir
      36             : {
      37             : 
      38          21 : Model::Model(TimeSeries<ScalarType>&& init, CustomIndexArray<ScalarType, AgeGroup> N_init,
      39             :              CustomIndexArray<ScalarType, AgeGroup> deaths, size_t num_agegroups,
      40          21 :              CustomIndexArray<ScalarType, AgeGroup> total_confirmed_cases)
      41          21 :     : parameters{Parameters(AgeGroup(num_agegroups))}
      42          21 :     , m_transitions{std::move(init)}
      43          21 :     , m_populations{TimeSeries<ScalarType>(Eigen::Index(InfectionState::Count) * num_agegroups)}
      44          21 :     , m_total_confirmed_cases{total_confirmed_cases}
      45          21 :     , m_N{N_init}
      46          42 :     , m_num_agegroups{num_agegroups}
      47             : 
      48             : {
      49          21 :     if (m_transitions.get_num_time_points() > 0) {
      50             :         // Add first time point in m_populations according to last time point in m_transitions which is where we start
      51             :         // the simulation.
      52          60 :         m_populations.add_time_point<Eigen::VectorX<ScalarType>>(
      53          20 :             m_transitions.get_last_time(),
      54          40 :             TimeSeries<ScalarType>::Vector::Constant((size_t)InfectionState::Count * m_num_agegroups, 0.));
      55             :     }
      56             :     else {
      57             :         // Initialize m_populations with zero as the first point of time if no data is provided for the transitions.
      58             :         // This can happen for example in the case of initialization with real data.
      59           2 :         m_populations.add_time_point<Eigen::VectorX<ScalarType>>(
      60           2 :             0, TimeSeries<ScalarType>::Vector::Constant((size_t)InfectionState::Count * m_num_agegroups, 0.));
      61             :     }
      62             : 
      63             :     // Set deaths at simulation start time t0.
      64          54 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); group++) {
      65          33 :         int Di                             = get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
      66          33 :         m_populations[Eigen::Index(0)][Di] = deaths[group];
      67             :     }
      68          21 : }
      69             : 
      70          10 : bool Model::check_constraints(ScalarType dt) const
      71             : {
      72             : 
      73          10 :     if (!((size_t)m_transitions.get_num_elements() == (size_t)InfectionTransition::Count * m_num_agegroups)) {
      74           1 :         log_error("A variable given for model construction is not valid. Number of elements in transition vector "
      75             :                   "does not match the required number.");
      76           1 :         return true;
      77             :     }
      78             : 
      79          19 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
      80             : 
      81          98 :         for (int i = 0; i < (int)InfectionState::Count; i++) {
      82          88 :             int index = get_state_flat_index(i, group);
      83          88 :             if (m_populations[0][index] < 0) {
      84           1 :                 log_error("Initialization failed. Initial values for populations are less than zero.");
      85           1 :                 return true;
      86             :             }
      87             :         }
      88             :     }
      89             : 
      90             :     // It may be possible to run the simulation with fewer time points, but this number ensures that it is possible.
      91           8 :     if (m_transitions.get_num_time_points() < (Eigen::Index)std::ceil(get_global_support_max(dt) / dt)) {
      92           1 :         log_error("Initialization failed. Not enough time points for transitions given before start of "
      93             :                   "simulation.");
      94           1 :         return true;
      95             :     }
      96             : 
      97          15 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
      98             : 
      99          83 :         for (int i = 0; i < m_transitions.get_num_time_points(); i++) {
     100         816 :             for (int j = 0; j < (int)InfectionTransition::Count; j++) {
     101         742 :                 int index = get_transition_flat_index(j, group);
     102         742 :                 if (m_transitions[i][index] < 0) {
     103           1 :                     log_error("Initialization failed. One or more initial value for transitions is less than zero.");
     104           1 :                     return true;
     105             :                 }
     106             :             }
     107             :         }
     108             :     }
     109           6 :     if (m_transitions.get_last_time() != m_populations.get_last_time()) {
     110           1 :         log_error("Last time point of TimeSeries for transitions does not match last time point of "
     111             :                   "TimeSeries for "
     112             :                   "compartments. Both of these time points have to agree for a sensible simulation.");
     113           1 :         return true;
     114             :     }
     115             : 
     116           5 :     if (m_populations.get_num_time_points() != 1) {
     117           1 :         log_error("The TimeSeries for the compartments contains more than one time point. It is unclear how to "
     118             :                   "initialize.");
     119           1 :         return true;
     120             :     }
     121             : 
     122           4 :     return parameters.check_constraints();
     123             : }
     124             : 
     125             : // Note that this function computes the global_support_max via the get_support_max() method and does not make use
     126             : // of the vector m_transitiondistributions_support_max. This is because the global_support_max is already used in
     127             : // check_constraints and we cannot ensure that the vector has already been computed when checking for constraints
     128             : // (which usually happens before setting the initial flows and simulating).
     129          11 : ScalarType Model::get_global_support_max(ScalarType dt) const
     130             : {
     131          11 :     ScalarType global_support_max     = 0.;
     132          11 :     ScalarType global_support_max_new = 0.;
     133          39 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     134          56 :         global_support_max_new = std::max(
     135          28 :             {parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::ExposedToInfectedNoSymptoms]
     136          28 :                  .get_support_max(dt, m_tol),
     137             :              parameters
     138          28 :                  .get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms]
     139          28 :                  .get_support_max(dt, m_tol),
     140          28 :              parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedNoSymptomsToRecovered]
     141          28 :                  .get_support_max(dt, m_tol),
     142             :              parameters
     143          28 :                  .get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedSymptomsToInfectedSevere]
     144          28 :                  .get_support_max(dt, m_tol),
     145          28 :              parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedSymptomsToRecovered]
     146          28 :                  .get_support_max(dt, m_tol),
     147             :              parameters
     148          28 :                  .get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedSevereToInfectedCritical]
     149          28 :                  .get_support_max(dt, m_tol),
     150          28 :              parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedSevereToRecovered]
     151          28 :                  .get_support_max(dt, m_tol),
     152          28 :              parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedCriticalToDead]
     153          28 :                  .get_support_max(dt, m_tol),
     154          28 :              parameters.get<TransitionDistributions>()[group][(int)InfectionTransition::InfectedCriticalToRecovered]
     155          28 :                  .get_support_max(dt, m_tol)});
     156          28 :         if (global_support_max_new > global_support_max) {
     157          11 :             global_support_max = global_support_max_new;
     158             :         }
     159             :     }
     160          11 :     return global_support_max;
     161             : }
     162             : 
     163             : // ---- Functionality to calculate the sizes of the compartments for time t0. ----
     164          85 : void Model::compute_compartment_from_flows(ScalarType dt, Eigen::Index idx_InfectionState, AgeGroup group,
     165             :                                            Eigen::Index idx_IncomingFlow, int idx_TransitionDistribution1,
     166             :                                            int idx_TransitionDistribution2)
     167             : {
     168             : 
     169          85 :     ScalarType sum       = 0;
     170          85 :     ScalarType calc_time = 0;
     171             :     // Determine relevant calculation area and corresponding index.
     172          85 :     if ((1 - parameters.get<TransitionProbabilities>()[group][idx_TransitionDistribution1]) > 0) {
     173          59 :         calc_time = std::max(m_transitiondistributions_support_max[group][idx_TransitionDistribution1],
     174          59 :                              m_transitiondistributions_support_max[group][idx_TransitionDistribution2]);
     175             :     }
     176             :     else {
     177          26 :         calc_time = m_transitiondistributions_support_max[group][idx_TransitionDistribution1];
     178             :     }
     179             : 
     180          85 :     Eigen::Index calc_time_index = (Eigen::Index)std::ceil(calc_time / dt) - 1;
     181             : 
     182          85 :     Eigen::Index num_time_points = m_transitions.get_num_time_points();
     183             : 
     184             :     // Index referring to m_transitions.
     185          85 :     int flow_index = get_transition_flat_index(idx_IncomingFlow, (size_t)group);
     186             :     // Index referring to m_populations.
     187          85 :     int state_index = get_state_flat_index(idx_InfectionState, (size_t)group);
     188             : 
     189         208 :     for (Eigen::Index i = num_time_points - 1 - calc_time_index; i < num_time_points - 1; i++) {
     190             : 
     191         123 :         ScalarType state_age = (num_time_points - 1 - i) * dt;
     192             : 
     193         123 :         sum += (parameters.get<TransitionProbabilities>()[group][idx_TransitionDistribution1] *
     194         123 :                     parameters.get<TransitionDistributions>()[group][idx_TransitionDistribution1].eval(state_age) +
     195         123 :                 (1 - parameters.get<TransitionProbabilities>()[group][idx_TransitionDistribution1]) *
     196         123 :                     parameters.get<TransitionDistributions>()[group][idx_TransitionDistribution2].eval(state_age)) *
     197         123 :                m_transitions[i + 1][flow_index];
     198             :     }
     199             : 
     200          85 :     m_populations.get_last_value()[state_index] = sum;
     201          85 : }
     202             : 
     203          15 : void Model::initial_compute_compartments_infection(ScalarType dt)
     204             : {
     205             :     // We need to compute the initial compartments for every AgeGroup.
     206          32 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     207             :         // Exposed
     208          17 :         compute_compartment_from_flows(dt, Eigen::Index(InfectionState::Exposed), group,
     209             :                                        Eigen::Index(InfectionTransition::SusceptibleToExposed),
     210             :                                        (int)InfectionTransition::ExposedToInfectedNoSymptoms);
     211             :         // InfectedNoSymptoms
     212          17 :         compute_compartment_from_flows(dt, Eigen::Index(InfectionState::InfectedNoSymptoms), group,
     213             :                                        Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms),
     214             :                                        (int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms,
     215             :                                        (int)InfectionTransition::InfectedNoSymptomsToRecovered);
     216             :         // InfectedSymptoms
     217          17 :         compute_compartment_from_flows(dt, Eigen::Index(InfectionState::InfectedSymptoms), group,
     218             :                                        Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms),
     219             :                                        (int)InfectionTransition::InfectedSymptomsToInfectedSevere,
     220             :                                        (int)InfectionTransition::InfectedSymptomsToRecovered);
     221             :         // InfectedSevere
     222          17 :         compute_compartment_from_flows(dt, Eigen::Index(InfectionState::InfectedSevere), group,
     223             :                                        Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere),
     224             :                                        (int)InfectionTransition::InfectedSevereToInfectedCritical,
     225             :                                        (int)InfectionTransition::InfectedSevereToRecovered);
     226             :         // InfectedCritical
     227          17 :         compute_compartment_from_flows(dt, Eigen::Index(InfectionState::InfectedCritical), group,
     228             :                                        Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical),
     229             :                                        (int)InfectionTransition::InfectedCriticalToDead,
     230             :                                        (int)InfectionTransition::InfectedCriticalToRecovered);
     231             :     }
     232          15 : }
     233             : 
     234          15 : void Model::initial_compute_compartments(ScalarType dt)
     235             : {
     236             :     // The initialization method only affects the Susceptible and Recovered compartments.
     237             :     // It is possible to calculate the sizes of the other compartments in advance because only the initial values of
     238             :     // the flows are used.
     239          15 :     initial_compute_compartments_infection(dt);
     240             : 
     241             :     // We store in two Booleans if there are Susceptibles or Recovered given for every age group.
     242          15 :     bool susceptibles_given = true;
     243          16 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     244          15 :         int Si = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     245          15 :         if (m_populations[Eigen::Index(0)][Si] < 1e-12) {
     246          14 :             susceptibles_given = false;
     247          14 :             break;
     248             :         }
     249             :     }
     250          15 :     bool recovered_given = true;
     251          16 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     252          15 :         int Ri = get_state_flat_index(Eigen::Index(InfectionState::Recovered), group);
     253          15 :         if (m_populations[Eigen::Index(0)][Ri] < 1e-12) {
     254          14 :             recovered_given = false;
     255          14 :             break;
     256             :         }
     257             :     }
     258             : 
     259             :     // We check which Initialization method we want to use.
     260          21 :     if (!(m_total_confirmed_cases == CustomIndexArray<ScalarType, AgeGroup>()) &&
     261           6 :         std::all_of(m_total_confirmed_cases.begin(), m_total_confirmed_cases.end(), [](ScalarType x) {
     262           6 :             return x > 1e-12;
     263             :         })) {
     264           1 :         m_initialization_method = 1;
     265           2 :         for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     266           1 :             int Si    = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     267           1 :             int Ei    = get_state_flat_index(Eigen::Index(InfectionState::Exposed), group);
     268           1 :             int INSi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedNoSymptoms), group);
     269           1 :             int ISyi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedSymptoms), group);
     270           1 :             int ISevi = get_state_flat_index(Eigen::Index(InfectionState::InfectedSevere), group);
     271           1 :             int ICri  = get_state_flat_index(Eigen::Index(InfectionState::InfectedCritical), group);
     272           1 :             int Ri    = get_state_flat_index(Eigen::Index(InfectionState::Recovered), group);
     273           1 :             int Di    = get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     274             :             // The scheme of the ODE model for initialization is applied here.
     275           4 :             m_populations[Eigen::Index(0)][Ri] = m_total_confirmed_cases[group] - m_populations[Eigen::Index(0)][ISyi] -
     276           3 :                                                  m_populations[Eigen::Index(0)][ISevi] -
     277           3 :                                                  m_populations[Eigen::Index(0)][ICri] -
     278           2 :                                                  m_populations[Eigen::Index(0)][Eigen::Index(InfectionState::Dead)];
     279             : 
     280           1 :             m_populations[Eigen::Index(0)][Si] =
     281           3 :                 m_N[group] - m_populations[Eigen::Index(0)][Ei] - m_populations[Eigen::Index(0)][INSi] -
     282           4 :                 m_populations[Eigen::Index(0)][ISyi] - m_populations[Eigen::Index(0)][ISevi] -
     283           4 :                 m_populations[Eigen::Index(0)][ICri] - m_populations[Eigen::Index(0)][Ri] -
     284           2 :                 m_populations[Eigen::Index(0)][Di];
     285             :         }
     286             :     }
     287             : 
     288          14 :     else if (susceptibles_given) {
     289             :         // Take initialized value for Susceptibles if value can't be calculated via the standard formula.
     290           1 :         m_initialization_method = 2;
     291             :         // R; need an initial value for R, therefore do not calculate via compute_recovered()
     292           2 :         for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     293           1 :             int Si    = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     294           1 :             int Ei    = get_state_flat_index(Eigen::Index(InfectionState::Exposed), group);
     295           1 :             int INSi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedNoSymptoms), group);
     296           1 :             int ISyi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedSymptoms), group);
     297           1 :             int ISevi = get_state_flat_index(Eigen::Index(InfectionState::InfectedSevere), group);
     298           1 :             int ICri  = get_state_flat_index(Eigen::Index(InfectionState::InfectedCritical), group);
     299           1 :             int Ri    = get_state_flat_index(Eigen::Index(InfectionState::Recovered), group);
     300           1 :             int Di    = get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     301           1 :             m_populations[Eigen::Index(0)][Ri] =
     302           3 :                 m_N[group] - m_populations[Eigen::Index(0)][Si] - m_populations[Eigen::Index(0)][Ei] -
     303           4 :                 m_populations[Eigen::Index(0)][INSi] - m_populations[Eigen::Index(0)][ISyi] -
     304           4 :                 m_populations[Eigen::Index(0)][ISevi] - m_populations[Eigen::Index(0)][ICri] -
     305           2 :                 m_populations[Eigen::Index(0)][Di];
     306             :         }
     307             :     }
     308          13 :     else if (recovered_given) {
     309             :         // If value for Recovered is initialized and standard method is not applicable (i.e., no value for total infections
     310             :         // or Susceptibles given directly), calculate Susceptibles via other compartments.
     311           1 :         m_initialization_method = 3;
     312           2 :         for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     313           1 :             int Si    = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     314           1 :             int Ei    = get_state_flat_index(Eigen::Index(InfectionState::Exposed), group);
     315           1 :             int INSi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedNoSymptoms), group);
     316           1 :             int ISyi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedSymptoms), group);
     317           1 :             int ISevi = get_state_flat_index(Eigen::Index(InfectionState::InfectedSevere), group);
     318           1 :             int ICri  = get_state_flat_index(Eigen::Index(InfectionState::InfectedCritical), group);
     319           1 :             int Ri    = get_state_flat_index(Eigen::Index(InfectionState::Recovered), group);
     320           1 :             int Di    = get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     321           1 :             m_populations[Eigen::Index(0)][Si] =
     322           3 :                 m_N[group] - m_populations[Eigen::Index(0)][Ei] - m_populations[Eigen::Index(0)][INSi] -
     323           4 :                 m_populations[Eigen::Index(0)][ISyi] - m_populations[Eigen::Index(0)][ISevi] -
     324           4 :                 m_populations[Eigen::Index(0)][ICri] - m_populations[Eigen::Index(0)][Ri] -
     325           2 :                 m_populations[Eigen::Index(0)][Di];
     326             :         }
     327             :     }
     328             :     else {
     329             :         // Compute Susceptibles at t0 and m_forceofinfection at time t0-dt as initial values for discretization scheme.
     330             :         // Use m_forceofinfection at t0-dt to be consistent with further calculations of S (see compute_susceptibles()),
     331             :         // where also the value of m_forceofinfection for the previous timestep is used.
     332          12 :         compute_forceofinfection(dt, true);
     333          12 :         if (std::all_of(m_forceofinfection.begin(), m_forceofinfection.end(), [](ScalarType x) {
     334          14 :                 return x > 1e-12;
     335             :             })) {
     336          10 :             m_initialization_method = 4;
     337          22 :             for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     338          12 :                 int Si    = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     339          12 :                 int Ei    = get_state_flat_index(Eigen::Index(InfectionState::Exposed), group);
     340          12 :                 int INSi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedNoSymptoms), group);
     341          12 :                 int ISyi  = get_state_flat_index(Eigen::Index(InfectionState::InfectedSymptoms), group);
     342          12 :                 int ISevi = get_state_flat_index(Eigen::Index(InfectionState::InfectedSevere), group);
     343          12 :                 int ICri  = get_state_flat_index(Eigen::Index(InfectionState::InfectedCritical), group);
     344          12 :                 int Ri    = get_state_flat_index(Eigen::Index(InfectionState::Recovered), group);
     345          12 :                 int Di    = get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     346             : 
     347          12 :                 int StEi = get_transition_flat_index(Eigen::Index(InfectionTransition::SusceptibleToExposed), group);
     348             :                 /* Attention: With an inappropriate combination of parameters and initial conditions, it can happen that S 
     349             :                 becomes greater than N when this formula is used. In this case, at least one compartment is initialized 
     350             :                 with a number less than zero, so that a log_error is thrown.
     351             :                 However, this initialization method is consistent with the numerical solver of the model equations,
     352             :                 so it may sometimes make sense to use this method. */
     353          24 :                 m_populations[Eigen::Index(0)][Si] =
     354          24 :                     m_transitions.get_last_value()[StEi] / (dt * m_forceofinfection[group]);
     355             : 
     356             :                 // Recovered; calculated as the difference between the total population and the sum of the other
     357             :                 // compartment sizes.
     358          12 :                 m_populations[Eigen::Index(0)][Ri] =
     359          36 :                     m_N[group] - m_populations[Eigen::Index(0)][Si] - m_populations[Eigen::Index(0)][Ei] -
     360          48 :                     m_populations[Eigen::Index(0)][INSi] - m_populations[Eigen::Index(0)][ISyi] -
     361          48 :                     m_populations[Eigen::Index(0)][ISevi] - m_populations[Eigen::Index(0)][ICri] -
     362          24 :                     m_populations[Eigen::Index(0)][Di];
     363             :             }
     364             :         }
     365             :         else {
     366           2 :             m_initialization_method = -1;
     367           2 :             log_error("Error occured while initializing compartments: Force of infection is evaluated to 0 and neither "
     368             :                       "Susceptibles nor Recovered or total confirmed cases for time 0 were set. One of them should be "
     369             :                       "larger 0.");
     370             :         }
     371             :     }
     372             :     // Verify that the initialization produces an appropriate result.
     373             :     // Another check would be if the sum of the compartments is equal to N, but in all initialization methods one of
     374             :     // the compartments is initialized via N - the others.
     375             :     // This also means that if a compartment is greater than N, we will always have one or more compartments less than
     376             :     // zero.
     377             :     // Check if all compartments are non negative.
     378          32 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     379         153 :         for (Eigen::Index i = 0; i < (Eigen::Index)InfectionState::Count; i++) {
     380         136 :             int idx = get_state_flat_index(i, group);
     381         136 :             if (m_populations[0][idx] < 0) {
     382           1 :                 m_initialization_method = -2;
     383           1 :                 log_error(
     384             :                     "Initialization failed. One or more initial values for populations are less than zero. It may "
     385             :                     "help to use a different method for initialization.");
     386             :             }
     387             :         }
     388             :     }
     389             : 
     390             :     // Compute m_forceofinfection at time t0 needed for further simulation.
     391          15 :     compute_forceofinfection(dt);
     392          15 : }
     393             : 
     394             : // ---- Functionality for the iterations of a simulation. ----
     395         108 : void Model::compute_susceptibles(ScalarType dt)
     396             : {
     397             :     // We need to compute to compute the Susceptibles in every AgeGroup.
     398         226 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     399         118 :         Eigen::Index num_time_points = m_populations.get_num_time_points();
     400             :         // Using number of Susceptibles from previous time step and force of infection from previous time step:
     401             :         // Compute current number of Susceptibles and store Susceptibles in m_populations.
     402         118 :         int Si = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     403         236 :         m_populations.get_last_value()[Si] =
     404         236 :             m_populations[num_time_points - 2][Si] / (1 + dt * m_forceofinfection[group]);
     405             :     }
     406         108 : }
     407             : 
     408        1626 : void Model::compute_flow(Eigen::Index idx_InfectionTransitions, Eigen::Index idx_IncomingFlow, ScalarType dt,
     409             :                          Eigen::Index current_time_index, AgeGroup group)
     410             : {
     411        1626 :     ScalarType sum = 0;
     412             :     /* In order to satisfy TransitionDistribution(dt*i) = 0 for all i >= k, k is determined by the maximum support of 
     413             :     the distribution.
     414             :     Since we are using a backwards difference scheme to compute the derivative, we have that the
     415             :     derivative of TransitionDistribution(dt*i) = 0 for all i >= k+1.
     416             : 
     417             :     Hence calc_time_index goes until std::ceil(support_max/dt) since for std::ceil(support_max/dt)+1 all terms are 
     418             :     already zero. 
     419             :     This needs to be adjusted if we are changing the finite difference scheme */
     420             :     Eigen::Index calc_time_index =
     421        1626 :         (Eigen::Index)std::ceil(m_transitiondistributions_support_max[group][idx_InfectionTransitions] / dt);
     422             : 
     423        1626 :     int transition_idx = get_transition_flat_index(idx_InfectionTransitions, size_t(group));
     424        7336 :     for (Eigen::Index i = current_time_index - calc_time_index; i < current_time_index; i++) {
     425             :         // (current_time_index - i)  is the index corresponding to time the individuals have already spent in this state.
     426        5710 :         sum += m_transitiondistributions_derivative[group][idx_InfectionTransitions][current_time_index - i] *
     427        5710 :                m_transitions[i + 1][idx_IncomingFlow];
     428             :     }
     429             : 
     430        1626 :     m_transitions.get_value(current_time_index)[transition_idx] =
     431        1626 :         (-dt) * parameters.get<TransitionProbabilities>()[group][idx_InfectionTransitions] * sum;
     432        1626 : }
     433             : 
     434        1062 : void Model::compute_flow(Eigen::Index idx_InfectionTransitions, Eigen::Index idx_IncomingFlow, ScalarType dt,
     435             :                          AgeGroup group)
     436             : {
     437        1062 :     Eigen::Index current_time_index = m_transitions.get_num_time_points() - 1;
     438        1062 :     compute_flow(idx_InfectionTransitions, idx_IncomingFlow, dt, current_time_index, group);
     439        1062 : }
     440             : 
     441         108 : void Model::flows_current_timestep(ScalarType dt)
     442             : {
     443             :     // Compute flows for every AgeGroup.
     444         226 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     445         118 :         int StEi = get_transition_flat_index(Eigen::Index(InfectionTransition::SusceptibleToExposed), group);
     446         118 :         int Si   = get_state_flat_index(Eigen::Index(InfectionState::Susceptible), group);
     447             : 
     448             :         // Calculate flow SusceptibleToExposed with force of infection from previous time step and Susceptibles from current time step.
     449         118 :         m_transitions.get_last_value()[StEi] = dt * m_forceofinfection[group] * m_populations.get_last_value()[Si];
     450             : 
     451             :         // Calculate all other flows with compute_flow.
     452             :         // Flow from Exposed to InfectedNoSymptoms
     453         118 :         compute_flow(Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms),
     454             :                      Eigen::Index(InfectionTransition::SusceptibleToExposed), dt, group);
     455             :         // Flow from InfectedNoSymptoms to InfectedSymptoms
     456         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms),
     457             :                      Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), dt, group);
     458             :         // Flow from InfectedNoSymptoms to Recovered
     459         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedNoSymptomsToRecovered),
     460             :                      Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), dt, group);
     461             :         // Flow from InfectedSymptoms to InfectedSevere
     462         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere),
     463             :                      Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), dt, group);
     464             :         // Flow from InfectedSymptoms to Recovered
     465         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToRecovered),
     466             :                      Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), dt, group);
     467             :         // Flow from InfectedSevere to InfectedCritical
     468         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical),
     469             :                      Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, group);
     470             :         // Flow from InfectedSevere to Recovered
     471         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToRecovered),
     472             :                      Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, group);
     473             :         // Flow from InfectedCritical to Dead
     474         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToDead),
     475             :                      Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, group);
     476             :         // Flow from InfectedCritical to Recovered
     477         118 :         compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToRecovered),
     478             :                      Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, group);
     479             :     }
     480         108 : }
     481             : 
     482         826 : void Model::update_compartment_from_flow(InfectionState infectionState,
     483             :                                          std::vector<InfectionTransition> const& IncomingFlows,
     484             :                                          std::vector<InfectionTransition> const& OutgoingFlows, AgeGroup group)
     485             : {
     486         826 :     int state_idx = get_state_flat_index(Eigen::Index(infectionState), group);
     487             : 
     488         826 :     Eigen::Index num_time_points   = m_populations.get_num_time_points();
     489         826 :     ScalarType updated_compartment = m_populations[num_time_points - 2][state_idx];
     490        2006 :     for (const InfectionTransition& inflow : IncomingFlows) {
     491        1180 :         int inflow_idx = get_transition_flat_index(Eigen::Index(inflow), group);
     492        1180 :         updated_compartment += m_transitions.get_last_value()[inflow_idx];
     493             :     }
     494        1888 :     for (const InfectionTransition& outflow : OutgoingFlows) {
     495        1062 :         int outflow_idx = get_transition_flat_index(Eigen::Index(outflow), group);
     496        1062 :         updated_compartment -= m_transitions.get_last_value()[outflow_idx];
     497             :     }
     498         826 :     m_populations.get_last_value()[state_idx] = updated_compartment;
     499         826 : }
     500             : 
     501         108 : void Model::update_compartments()
     502             : {
     503             :     // Update compartments for every AgeGroup.
     504         226 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     505             :         // Exposed
     506         118 :         update_compartment_from_flow(InfectionState::Exposed, {InfectionTransition::SusceptibleToExposed},
     507             :                                      {InfectionTransition::ExposedToInfectedNoSymptoms}, group);
     508             : 
     509             :         // InfectedNoSymptoms
     510         118 :         update_compartment_from_flow(InfectionState::InfectedNoSymptoms,
     511             :                                      {InfectionTransition::ExposedToInfectedNoSymptoms},
     512             :                                      {InfectionTransition::InfectedNoSymptomsToInfectedSymptoms,
     513             :                                       InfectionTransition::InfectedNoSymptomsToRecovered},
     514             :                                      group);
     515             : 
     516             :         // InfectedSymptoms
     517         118 :         update_compartment_from_flow(
     518             :             InfectionState::InfectedSymptoms, {InfectionTransition::InfectedNoSymptomsToInfectedSymptoms},
     519             :             {InfectionTransition::InfectedSymptomsToInfectedSevere, InfectionTransition::InfectedSymptomsToRecovered},
     520             :             group);
     521             : 
     522             :         // InfectedSevere
     523         118 :         update_compartment_from_flow(
     524             :             InfectionState::InfectedSevere, {InfectionTransition::InfectedSymptomsToInfectedSevere},
     525             :             {InfectionTransition::InfectedSevereToInfectedCritical, InfectionTransition::InfectedSevereToRecovered},
     526             :             group);
     527             : 
     528             :         // InfectedCritical
     529         118 :         update_compartment_from_flow(
     530             :             InfectionState::InfectedCritical, {InfectionTransition::InfectedSevereToInfectedCritical},
     531             :             {InfectionTransition::InfectedCriticalToDead, InfectionTransition::InfectedCriticalToRecovered}, group);
     532             : 
     533             :         // Recovered
     534         354 :         update_compartment_from_flow(InfectionState::Recovered,
     535             :                                      {
     536             :                                          InfectionTransition::InfectedNoSymptomsToRecovered,
     537             :                                          InfectionTransition::InfectedSymptomsToRecovered,
     538             :                                          InfectionTransition::InfectedSevereToRecovered,
     539             :                                          InfectionTransition::InfectedCriticalToRecovered,
     540             :                                      },
     541         236 :                                      std::vector<InfectionTransition>(), group);
     542             : 
     543             :         // Dead
     544         354 :         update_compartment_from_flow(InfectionState::Dead, {InfectionTransition::InfectedCriticalToDead},
     545         236 :                                      std::vector<InfectionTransition>(), group);
     546             :     }
     547         108 : }
     548             : 
     549         135 : void Model::compute_forceofinfection(ScalarType dt, bool initialization)
     550             : {
     551             : 
     552         135 :     m_forceofinfection = CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(m_num_agegroups), 0.);
     553             :     // We compute the force of infection for every AgeGroup.
     554             : 
     555         284 :     for (AgeGroup i = AgeGroup(0); i < AgeGroup(m_num_agegroups); ++i) {
     556             : 
     557             :         Eigen::Index num_time_points;
     558             :         ScalarType current_time;
     559             : 
     560         149 :         if (initialization) {
     561             :             // Determine m_forceofinfection at time t0-dt which is the penultimate timepoint in m_transitions.
     562          14 :             num_time_points = m_transitions.get_num_time_points() - 1;
     563             :             // Get time of penultimate timepoint in m_transitions.
     564          14 :             current_time = m_transitions.get_time(num_time_points - 1);
     565             :         }
     566             :         else {
     567             :             // Determine m_forceofinfection for current last time in m_transitions.
     568         135 :             num_time_points = m_transitions.get_num_time_points();
     569         135 :             current_time    = m_transitions.get_last_time();
     570             :         }
     571             :         //We compute the Season Value.
     572             :         ScalarType season_val =
     573             :             1 +
     574         149 :             parameters.get<Seasonality>() *
     575         149 :                 sin(3.141592653589793 * (std::fmod((parameters.get<StartDay>() + current_time), 365.0) / 182.5 + 0.5));
     576             :         // To include contacts between all age groups we sum over all age groups.
     577         340 :         for (AgeGroup j = AgeGroup(0); j < AgeGroup(m_num_agegroups); ++j) {
     578             :             // Determine the relevant calculation area = union of the supports of the relevant transition distributions.
     579         955 :             ScalarType calc_time = std::max(
     580         191 :                 {m_transitiondistributions_support_max[j]
     581         191 :                                                       [(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms],
     582         191 :                  m_transitiondistributions_support_max[j][(int)InfectionTransition::InfectedNoSymptomsToRecovered],
     583         191 :                  m_transitiondistributions_support_max[j][(int)InfectionTransition::InfectedSymptomsToInfectedSevere],
     584         191 :                  m_transitiondistributions_support_max[j][(int)InfectionTransition::InfectedSymptomsToRecovered]});
     585             :             // Corresponding index.
     586             :             // Need calc_time_index timesteps in sum,
     587             :             // subtract 1 because in the last summand all TransitionDistributions evaluate to 0 (by definition of support_max).
     588         191 :             Eigen::Index calc_time_index = (Eigen::Index)std::ceil(calc_time / dt) - 1;
     589             : 
     590         191 :             int Dj     = get_state_flat_index(Eigen::Index(InfectionState::Dead), j);
     591         191 :             int ICrtDj = get_transition_flat_index(Eigen::Index(InfectionTransition::InfectedCriticalToDead), j);
     592             : 
     593         191 :             int EtINSj = get_transition_flat_index(Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), j);
     594             :             int INStISyj =
     595         191 :                 get_transition_flat_index(Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), j);
     596             : 
     597             :             // We store the number of deaths for every AgeGroup.
     598             :             ScalarType deaths_j;
     599         191 :             if (initialization) {
     600             :                 // Determine the number of individuals in Dead compartment at time t0-dt.
     601          20 :                 deaths_j = m_populations[Eigen::Index(0)][Dj] - m_transitions.get_last_value()[ICrtDj];
     602             :             }
     603             :             else {
     604         171 :                 deaths_j = m_populations.get_last_value()[Dj];
     605             :             }
     606             : 
     607         191 :             ScalarType sum = 0;
     608             : 
     609             :             // Sum over all relevant time points.
     610         573 :             for (Eigen::Index l = num_time_points - 1 - calc_time_index; l < num_time_points - 1; l++) {
     611         382 :                 Eigen::Index state_age_index = num_time_points - 1 - l;
     612         382 :                 ScalarType state_age         = state_age_index * dt;
     613         382 :                 sum += season_val * parameters.get<TransmissionProbabilityOnContact>()[i].eval(state_age) *
     614        1146 :                        parameters.get<ContactPatterns>().get_cont_freq_mat().get_matrix_at(current_time)(
     615         764 :                            static_cast<Eigen::Index>((size_t)i), static_cast<Eigen::Index>((size_t)j)) *
     616         382 :                        (m_transitiondistributions_in_forceofinfection[j][0][num_time_points - l - 1] *
     617         764 :                             m_transitions[l + 1][EtINSj] *
     618         382 :                             parameters.get<RelativeTransmissionNoSymptoms>()[j].eval(state_age) +
     619         382 :                         m_transitiondistributions_in_forceofinfection[j][1][num_time_points - l - 1] *
     620         382 :                             m_transitions[l + 1][INStISyj] *
     621         382 :                             parameters.get<RiskOfInfectionFromSymptomatic>()[j].eval(state_age));
     622             :             }
     623             :             const double divNj =
     624         191 :                 (m_N[j] - deaths_j < Limits<ScalarType>::zero_tolerance()) ? 0.0 : 1.0 / (m_N[j] - deaths_j);
     625         191 :             m_forceofinfection[i] += divNj * sum;
     626             :         }
     627             :     }
     628         135 : }
     629             : 
     630             : // ---- Functionality to set vectors with necessary information regarding TransitionDistributions. ----
     631          17 : void Model::set_transitiondistributions_support_max(ScalarType dt)
     632             : {
     633          68 :     m_transitiondistributions_support_max = CustomIndexArray<std::vector<ScalarType>, AgeGroup>(
     634          68 :         AgeGroup(m_num_agegroups), std::vector<ScalarType>((int)InfectionTransition::Count, 0.));
     635             :     // We do not consider the transition SusceptibleToExposed as it is not needed in the computations.
     636          46 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     637         290 :         for (int transition = 1; transition < (int)InfectionTransition::Count; transition++) {
     638         261 :             m_transitiondistributions_support_max[group][transition] =
     639         522 :                 parameters.get<TransitionDistributions>()[AgeGroup(group)][transition].get_support_max(dt, m_tol);
     640             :         }
     641             :     }
     642          17 : }
     643             : 
     644          17 : void Model::set_transitiondistributions_derivative(ScalarType dt)
     645             : {
     646          68 :     m_transitiondistributions_derivative = CustomIndexArray<std::vector<std::vector<ScalarType>>, AgeGroup>(
     647          34 :         AgeGroup(m_num_agegroups),
     648          51 :         std::vector<std::vector<ScalarType>>((int)InfectionTransition::Count, std::vector<ScalarType>(1, 0.)));
     649             :     // We do not consider the transition SusceptibleToExposed as it is not needed in the computations.
     650          46 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     651         290 :         for (int transition = 1; transition < (int)InfectionTransition::Count; transition++) {
     652             :             Eigen::Index support_max_index =
     653         261 :                 (Eigen::Index)std::ceil(m_transitiondistributions_support_max[group][transition] / dt);
     654             :             // Create vec_derivative that contains the value of the approximated derivative for all necessary time points.
     655             :             // Here, we evaluate the derivative at time points t_0, ..., t_{support_max_index}.
     656         522 :             std::vector<ScalarType> vec_derivative(support_max_index + 1, 0.);
     657             : 
     658        1321 :             for (Eigen::Index i = 0; i <= support_max_index; i++) {
     659             :                 // Compute state_age for considered index.
     660        1060 :                 ScalarType state_age = (ScalarType)i * dt;
     661             :                 // Compute derivative.
     662        1060 :                 vec_derivative[i] =
     663        1060 :                     (parameters.get<TransitionDistributions>()[group][transition].eval(state_age) -
     664        1060 :                      parameters.get<TransitionDistributions>()[group][transition].eval(state_age - dt)) /
     665        1060 :                     dt;
     666             :             }
     667         261 :             m_transitiondistributions_derivative[group][transition] = vec_derivative;
     668         261 :         }
     669             :     }
     670          17 : }
     671             : 
     672          15 : void Model::set_transitiondistributions_in_forceofinfection(ScalarType dt)
     673             : {
     674          60 :     m_transitiondistributions_in_forceofinfection = CustomIndexArray<std::vector<std::vector<ScalarType>>, AgeGroup>(
     675          60 :         AgeGroup(m_num_agegroups), std::vector<std::vector<ScalarType>>(2, std::vector<ScalarType>(1, 0.)));
     676             :     // Relevant transitions for force of infection term.
     677          15 :     std::vector<std::vector<int>> relevant_transitions = {
     678             :         {(int)InfectionTransition::InfectedNoSymptomsToInfectedSymptoms,
     679             :          (int)InfectionTransition::InfectedNoSymptomsToRecovered},
     680             :         {(int)InfectionTransition::InfectedSymptomsToInfectedSevere,
     681         105 :          (int)InfectionTransition::InfectedSymptomsToRecovered}};
     682             : 
     683             :     // Determine the relevant calculation area = union of the supports of the relevant transition distributions.
     684             : 
     685          32 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(m_num_agegroups); ++group) {
     686          68 :         ScalarType calc_time = std::max({m_transitiondistributions_support_max[group][relevant_transitions[0][0]],
     687          17 :                                          m_transitiondistributions_support_max[group][relevant_transitions[0][1]],
     688          17 :                                          m_transitiondistributions_support_max[group][relevant_transitions[1][0]],
     689          17 :                                          m_transitiondistributions_support_max[group][relevant_transitions[1][1]]});
     690             :         // Corresponding index.
     691             :         // Need to evaluate survival functions at t_0, ..., t_{calc_time_index} for computation of force of infection,
     692             :         // subtract 1 because in the last summand all TransitionDistributions evaluate to 0 (by definition of support_max).
     693          17 :         Eigen::Index calc_time_index = (Eigen::Index)std::ceil(calc_time / dt) - 1;
     694             :         // Compute contributions from survival functions and transition probabilities starting in InfectedNoSymptoms and
     695             :         // InfectedSymptoms, respectively, on force of infection term.
     696          51 :         for (int contribution = 0; contribution < 2; contribution++) {
     697          68 :             std::vector<ScalarType> vec_contribution_to_foi(calc_time_index + 1, 0.);
     698         116 :             for (Eigen::Index i = 0; i <= calc_time_index; i++) {
     699             :                 // Compute state_age for all indices from t_0, ..., t_{calc_time_index}.
     700          82 :                 ScalarType state_age = i * dt;
     701          82 :                 vec_contribution_to_foi[i] =
     702          82 :                     parameters.get<TransitionProbabilities>()[group][relevant_transitions[contribution][0]] *
     703          82 :                         parameters.get<TransitionDistributions>()[group][relevant_transitions[contribution][0]].eval(
     704          82 :                             state_age) +
     705          82 :                     parameters.get<TransitionProbabilities>()[group][relevant_transitions[contribution][1]] *
     706          82 :                         parameters.get<TransitionDistributions>()[group][relevant_transitions[contribution][1]].eval(
     707             :                             state_age);
     708             :             }
     709             : 
     710          34 :             m_transitiondistributions_in_forceofinfection[group][contribution] = vec_contribution_to_foi;
     711          34 :         }
     712             :     }
     713          30 : }
     714             : 
     715             : } // namespace isecir
     716             : } // namespace mio

Generated by: LCOV version 1.14