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

          Line data    Source code
       1             : /*
       2             : * Copyright (C) 2020-2025 MEmilio
       3             : *
       4             : * Authors: Lena Ploetzke, Anna Wendler
       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             : #include "ide_secir/parameters_io.h"
      22             : #include "memilio/config.h"
      23             : #include "memilio/epidemiology/age_group.h"
      24             : #include <cstddef>
      25             : #include <iostream>
      26             : 
      27             : #ifdef MEMILIO_HAS_JSONCPP
      28             : 
      29             : #include "ide_secir/model.h"
      30             : #include "ide_secir/infection_state.h"
      31             : #include "memilio/math/eigen.h"
      32             : #include "memilio/io/epi_data.h"
      33             : #include "memilio/io/io.h"
      34             : #include "memilio/utils/date.h"
      35             : 
      36             : #include <string>
      37             : #include <cmath>
      38             : 
      39             : namespace mio
      40             : {
      41             : namespace isecir
      42             : {
      43             : 
      44           5 : IOResult<void> set_initial_flows(Model& model, ScalarType dt, std::string const& path, Date date,
      45             :                                  ScalarType scale_confirmed_cases)
      46             : {
      47             :     //--- Preparations ---
      48             :     // Try to get RKI data from path.
      49           5 :     BOOST_OUTCOME_TRY(auto&& rki_data, mio::read_confirmed_cases_data(path));
      50           5 :     auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
      51        2204 :         return a.date < b.date;
      52             :     });
      53           5 :     if (max_date_entry == rki_data.end()) {
      54           1 :         log_error("RKI data file is empty.");
      55           1 :         return failure(StatusCode::InvalidFileFormat, path + ", file is empty.");
      56             :     }
      57           4 :     auto max_date = max_date_entry->date;
      58           4 :     if (max_date < date) {
      59           1 :         log_error("Specified date does not exist in RKI data.");
      60           1 :         return failure(StatusCode::OutOfRange, path + ", specified date does not exist in RKI data.");
      61             :     }
      62           3 :     auto min_date_entry = std::min_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
      63        1653 :         return a.date < b.date;
      64             :     });
      65           3 :     auto min_date       = min_date_entry->date;
      66             : 
      67             :     // Get (global) support_max to determine how many flows in the past we have to compute.
      68           3 :     ScalarType global_support_max         = model.get_global_support_max(dt);
      69           3 :     Eigen::Index global_support_max_index = Eigen::Index(std::ceil(global_support_max / dt));
      70             : 
      71             :     // Get the number of AgeGroups.
      72           3 :     const size_t num_age_groups = ConfirmedCasesDataEntry::age_group_names.size();
      73             : 
      74             :     // m_transitions should be empty at the beginning.
      75             : 
      76           3 :     if (model.m_transitions.get_num_time_points() > 0) {
      77           2 :         model.m_transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
      78             :     }
      79           3 :     if (model.m_populations.get_time(0) != 0) {
      80           1 :         model.m_populations.remove_last_time_point();
      81           2 :         model.m_populations.add_time_point<Eigen::VectorXd>(
      82           2 :             0, TimeSeries<ScalarType>::Vector::Constant((int)InfectionState::Count * num_age_groups, 0));
      83             :     }
      84             : 
      85             :     // The first time we need is -4 * global_support_max.
      86           3 :     Eigen::Index start_shift = 4 * global_support_max_index;
      87             :     // The last time needed is dependent on the mean stay time in the Exposed compartment and
      88             :     // the mean stay time of asymptomatic individuals in InfectedNoSymptoms.
      89             :     // The mean stay time in a compartment may be dependent on the AgeGroup.
      90           3 :     CustomIndexArray<ScalarType, AgeGroup> mean_ExposedToInfectedNoSymptoms =
      91           3 :         CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
      92           3 :     CustomIndexArray<ScalarType, AgeGroup> mean_InfectedNoSymptomsToInfectedSymptoms =
      93           3 :         CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
      94           3 :     CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSymptomsToInfectedSevere =
      95           3 :         CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
      96           3 :     CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSevereToInfectedCritical =
      97           3 :         CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
      98           3 :     CustomIndexArray<ScalarType, AgeGroup> mean_InfectedCriticalToDead =
      99           3 :         CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
     100           3 :     Eigen::Index last_time_index_needed = 0;
     101             : 
     102          21 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
     103             :         // Set the Dead compartment to 0 so that RKI data can be added correctly.
     104          18 :         int Di                     = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     105          18 :         model.m_populations[0][Di] = 0;
     106             : 
     107          18 :         mean_ExposedToInfectedNoSymptoms[group] =
     108             :             model.parameters
     109          18 :                 .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms)]
     110          18 :                 .get_mean(dt);
     111          18 :         mean_InfectedNoSymptomsToInfectedSymptoms[group] =
     112             :             model.parameters
     113          18 :                 .get<TransitionDistributions>()[group]
     114          18 :                                                [Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms)]
     115          18 :                 .get_mean(dt);
     116          18 :         mean_InfectedSymptomsToInfectedSevere[group] =
     117             :             model.parameters
     118          18 :                 .get<TransitionDistributions>()[group]
     119          18 :                                                [Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere)]
     120          18 :                 .get_mean(dt);
     121          18 :         mean_InfectedSevereToInfectedCritical[group] =
     122             :             model.parameters
     123          18 :                 .get<TransitionDistributions>()[group]
     124          18 :                                                [Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical)]
     125          18 :                 .get_mean(dt);
     126          18 :         mean_InfectedCriticalToDead[group] =
     127             :             model.parameters
     128          18 :                 .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::InfectedCriticalToDead)]
     129          18 :                 .get_mean(dt);
     130          18 :         if (last_time_index_needed <
     131          18 :             Eigen::Index(std::ceil(
     132          18 :                 (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt))) {
     133           3 :             last_time_index_needed = Eigen::Index(std::ceil(
     134           3 :                 (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
     135             :         }
     136             :     }
     137             : 
     138             :     // Create TimeSeries with zeros. The index of time zero is start_shift.
     139          66 :     for (Eigen::Index i = -start_shift; i <= last_time_index_needed; i++) {
     140             :         // Add time point.
     141         126 :         model.m_transitions.add_time_point(
     142         126 :             i * dt, TimeSeries<ScalarType>::Vector::Constant((size_t)InfectionTransition::Count * num_age_groups, 0.));
     143             :     }
     144             : 
     145             :     // Setting the entries in m_total_confirmed_cases to zero before overwriting it with the RKI data.
     146           3 :     model.m_total_confirmed_cases = CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
     147             :     //--- Calculate the flow InfectedNoSymptomsToInfectedSymptoms using the RKI data and store in the m_transitions object.---
     148           3 :     ScalarType min_offset_needed = std::ceil(
     149           3 :         model.m_transitions.get_time(0) -
     150             :         1); // Need -1 if first time point is integer and just the floor value if not, therefore use ceil and -1
     151           3 :     ScalarType max_offset_needed = std::ceil(model.m_transitions.get_last_time());
     152           3 :     bool min_offset_needed_avail = false;
     153           3 :     bool max_offset_needed_avail = false;
     154             : 
     155             :     // Go through the entries of rki_data and check if date is needed for calculation. Confirmed cases are scaled.
     156             :     // Define dummy variables to store the first and the last index of the TimeSeries where the considered entry of
     157             :     // rki_data is potentially needed.
     158           3 :     Eigen::Index idx_needed_first = 0;
     159           3 :     Eigen::Index idx_needed_last  = 0;
     160           3 :     ScalarType time_idx           = 0;
     161        1659 :     for (auto&& entry : rki_data) {
     162        1656 :         int offset     = get_offset_in_days(entry.date, date);
     163        1656 :         AgeGroup group = entry.age_group;
     164        1656 :         if ((offset >= min_offset_needed) && (offset <= max_offset_needed)) {
     165         150 :             if (offset == min_offset_needed) {
     166          12 :                 min_offset_needed_avail = true;
     167             :             }
     168         150 :             if (offset == max_offset_needed) {
     169          12 :                 max_offset_needed_avail = true;
     170             :             }
     171             :             // Smallest index for which the entry is needed.
     172         150 :             idx_needed_first =
     173         150 :                 Eigen::Index(std::max(std::floor((offset - model.m_transitions.get_time(0) - 1) / dt), 0.));
     174             :             // Biggest index for which the entry is needed.
     175         150 :             idx_needed_last = Eigen::Index(std::min(std::ceil((offset - model.m_transitions.get_time(0) + 1) / dt),
     176         300 :                                                     double(model.m_transitions.get_num_time_points() - 1)));
     177             : 
     178         300 :             int INStISyi = model.get_transition_flat_index(
     179         150 :                 Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), group);
     180             : 
     181         804 :             for (Eigen::Index i = idx_needed_first; i <= idx_needed_last; i++) {
     182             : 
     183         654 :                 time_idx = model.m_transitions.get_time(i);
     184         654 :                 if (offset == int(std::floor(time_idx))) {
     185         528 :                     model.m_transitions[i][INStISyi] +=
     186         264 :                         (1 - (time_idx - std::floor(time_idx))) * scale_confirmed_cases * entry.num_confirmed;
     187             :                 }
     188         654 :                 if (offset == int(std::ceil(time_idx))) {
     189         528 :                     model.m_transitions[i][INStISyi] +=
     190         264 :                         (time_idx - std::floor(time_idx)) * scale_confirmed_cases * entry.num_confirmed;
     191             :                 }
     192         654 :                 if (offset == int(std::floor(time_idx - dt))) {
     193         528 :                     model.m_transitions[i][INStISyi] -=
     194         264 :                         (1 - (time_idx - dt - std::floor(time_idx - dt))) * scale_confirmed_cases * entry.num_confirmed;
     195             :                 }
     196         654 :                 if (offset == int(std::ceil(time_idx - dt))) {
     197         528 :                     model.m_transitions[i][INStISyi] -=
     198         264 :                         (time_idx - dt - std::floor(time_idx - dt)) * scale_confirmed_cases * entry.num_confirmed;
     199             :                 }
     200             :             }
     201             : 
     202             :             // Compute Dead by shifting RKI data according to mean stay times.
     203             :             // This is done because the RKI reports death with the date of positive test instead of the date of deaths.
     204         150 :             int Di = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
     205         300 :             if (offset ==
     206         300 :                 std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
     207         150 :                            mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) {
     208          12 :                 model.m_populations[0][Di] +=
     209          12 :                     (1 -
     210          12 :                      (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
     211          12 :                       mean_InfectedCriticalToDead[group] -
     212          12 :                       std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
     213          12 :                                  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group]))) *
     214          12 :                     entry.num_deaths;
     215             :             }
     216         300 :             if (offset ==
     217         300 :                 std::ceil(-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
     218         150 :                           mean_InfectedCriticalToDead[group])) {
     219          12 :                 model.m_populations[0][Di] +=
     220          12 :                     (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
     221          12 :                      mean_InfectedCriticalToDead[group] -
     222          12 :                      std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
     223          12 :                                 mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) *
     224          12 :                     entry.num_deaths;
     225             :             }
     226             : 
     227         150 :             if (offset == 0) {
     228          18 :                 model.m_total_confirmed_cases[group] = scale_confirmed_cases * entry.num_confirmed;
     229             :             }
     230             :         }
     231             :     }
     232             : 
     233           3 :     if (!max_offset_needed_avail) {
     234           1 :         log_error("Necessary range of dates needed to compute initial values does not exist in RKI data.");
     235           1 :         return failure(StatusCode::OutOfRange, path + ", necessary range of dates does not exist in RKI data.");
     236             :     }
     237           2 :     if (!min_offset_needed_avail) {
     238           1 :         std::string min_date_string =
     239           7 :             std::to_string(min_date.day) + "." + std::to_string(min_date.month) + "." + std::to_string(min_date.year);
     240             :         // Get first date that is needed.
     241           1 :         mio::Date min_offset_date          = offset_date_by_days(date, int(min_offset_needed));
     242           6 :         std::string min_offset_date_string = std::to_string(min_offset_date.day) + "." +
     243           5 :                                              std::to_string(min_offset_date.month) + "." +
     244           3 :                                              std::to_string(min_offset_date.year);
     245           4 :         log_warning("RKI data is needed from " + min_offset_date_string +
     246           3 :                     " to compute initial values. RKI data is only available from " + min_date_string +
     247             :                     ". Missing dates were set to 0.");
     248           1 :     }
     249             : 
     250             :     //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms. ---
     251             :     // Set m_support_max_vector and m_derivative_vector in the model which is needed for the following computations.
     252           2 :     model.set_transitiondistributions_support_max(dt);
     253           2 :     model.set_transitiondistributions_derivative(dt);
     254             : 
     255          14 :     for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
     256             :         //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms. ---
     257             :         // Compute flow InfectedSymptomsToInfectedSevere for -3 * global_support_max, ..., 0.
     258         168 :         for (Eigen::Index i = -3 * global_support_max_index; i <= 0; i++) {
     259         156 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere),
     260             :                                Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), dt,
     261             :                                i + start_shift, group);
     262             :         }
     263             :         // Compute flow InfectedSevereToInfectedCritical for -2 * global_support_max, ..., 0.
     264         120 :         for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
     265         108 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical),
     266             :                                Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
     267             :                                group);
     268             :         }
     269             :         // Compute flows from InfectedSymptoms, InfectedSevere and InfectedCritical to Recovered and
     270             :         // flow InfectedCriticalToDead for -global_support_max, ..., 0.
     271          72 :         for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
     272             :             // Compute flow InfectedSymptomsToRecovered.
     273          60 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToRecovered),
     274             :                                Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), dt,
     275             :                                i + start_shift, group);
     276             :             // Compute flow InfectedSevereToRecovered.
     277          60 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToRecovered),
     278             :                                Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
     279             :                                group);
     280             :             // Compute flow InfectedCriticalToRecovered.
     281          60 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToRecovered),
     282             :                                Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
     283             :                                group);
     284             :             // Compute flow InfectedCriticalToDead.
     285          60 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToDead),
     286             :                                Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
     287             :                                group);
     288             :         }
     289             : 
     290             :         //--- Calculate the remaining flows. ---
     291             :         // Compute flow ExposedToInfectedNoSymptoms for -2 * global_support_max, ..., 0.
     292             :         // Use mean value of the TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
     293             : 
     294          12 :         Eigen::Index index_shift_mean = Eigen::Index(std::round(mean_InfectedNoSymptomsToInfectedSymptoms[group] / dt));
     295             :         int EtINSi =
     296          12 :             model.get_transition_flat_index(Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), group);
     297          24 :         int INStISyi = model.get_transition_flat_index(
     298          12 :             Eigen::Index(InfectionTransition::InfectedNoSymptomsToInfectedSymptoms), group);
     299         120 :         for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
     300         108 :             model.m_transitions[i + start_shift][EtINSi] =
     301         216 :                 (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
     302         216 :                          InfectionTransition::InfectedNoSymptomsToInfectedSymptoms)]) *
     303         216 :                 model.m_transitions[i + start_shift + index_shift_mean][INStISyi];
     304             :         }
     305             : 
     306             :         // Compute flow SusceptibleToExposed for -global_support_max, ..., 0.
     307             :         // Use mean values of the TransitionDistribution ExposedToInfectedNoSymptoms and of the
     308             :         // TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
     309          12 :         index_shift_mean = Eigen::Index(std::round(
     310          12 :             (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
     311          12 :         int StEi = model.get_transition_flat_index(Eigen::Index(InfectionTransition::SusceptibleToExposed), group);
     312             : 
     313          72 :         for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
     314          60 :             model.m_transitions[i + start_shift][StEi] =
     315         120 :                 (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
     316         120 :                          InfectionTransition::InfectedNoSymptomsToInfectedSymptoms)]) *
     317         120 :                 model.m_transitions[i + start_shift + index_shift_mean][INStISyi];
     318             :         }
     319             : 
     320             :         // InfectedNoSymptomsToRecovered for -global_support_max, ..., 0.
     321             :         // If we previously calculated the transition ExposedToInfectedNoSymptoms, we can calculate this transition
     322             :         // using the standard formula.
     323          72 :         for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
     324          60 :             model.compute_flow(Eigen::Index(InfectionTransition::InfectedNoSymptomsToRecovered),
     325             :                                Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), dt, i + start_shift,
     326             :                                group);
     327             :         }
     328             :     }
     329             : 
     330             :     // At the end of the calculation, delete all time points that are not required for the simulation.
     331           2 :     auto transition_copy(model.m_transitions);
     332           2 :     model.m_transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
     333          12 :     for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
     334          10 :         model.m_transitions.add_time_point(i * dt, transition_copy.get_value(i + start_shift));
     335             :     }
     336             : 
     337           4 :     return mio::success();
     338           5 : }
     339             : 
     340             : } // namespace isecir
     341             : } // namespace mio
     342             : 
     343             : #endif // MEMILIO_HAS_JSONCPP

Generated by: LCOV version 1.14