LCOV - code coverage report
Current view: top level - models/ode_secir - model.h (source / functions) Hit Total Coverage
Test: coverage.info Lines: 347 348 99.7 %
Date: 2024-11-18 12:45:26 Functions: 21 22 95.5 %

          Line data    Source code
       1             : /* 
       2             : * Copyright (C) 2020-2024 MEmilio
       3             : *
       4             : * Authors: Daniel Abele, Jan Kleinert, 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             : #ifndef ODESECIR_MODEL_H
      21             : #define ODESECIR_MODEL_H
      22             : 
      23             : #include "memilio/compartments/flow_model.h"
      24             : #include "memilio/compartments/simulation.h"
      25             : #include "memilio/compartments/flow_simulation.h"
      26             : #include "memilio/epidemiology/populations.h"
      27             : #include "ode_secir/analyze_result.h"
      28             : #include "ode_secir/infection_state.h"
      29             : #include "ode_secir/parameters.h"
      30             : #include "memilio/math/smoother.h"
      31             : #include "memilio/math/eigen_util.h"
      32             : #include "memilio/math/interpolation.h"
      33             : 
      34             : namespace mio
      35             : {
      36             : namespace osecir
      37             : {
      38             : 
      39             : // Create template specializations for the age resolved
      40             : // SECIHURD model
      41             : // clang-format off
      42             : using Flows = TypeList<Flow<InfectionState::Susceptible,                 InfectionState::Exposed>,
      43             :                        Flow<InfectionState::Exposed,                     InfectionState::InfectedNoSymptoms>,
      44             :                        Flow<InfectionState::InfectedNoSymptoms,          InfectionState::InfectedSymptoms>,
      45             :                        Flow<InfectionState::InfectedNoSymptoms,          InfectionState::Recovered>,
      46             :                        Flow<InfectionState::InfectedNoSymptomsConfirmed, InfectionState::InfectedSymptomsConfirmed>,
      47             :                        Flow<InfectionState::InfectedNoSymptomsConfirmed, InfectionState::Recovered>,
      48             :                        Flow<InfectionState::InfectedSymptoms,            InfectionState::InfectedSevere>,
      49             :                        Flow<InfectionState::InfectedSymptoms,            InfectionState::Recovered>,
      50             :                        Flow<InfectionState::InfectedSymptomsConfirmed,   InfectionState::InfectedSevere>,
      51             :                        Flow<InfectionState::InfectedSymptomsConfirmed,   InfectionState::Recovered>,
      52             :                        Flow<InfectionState::InfectedSevere,              InfectionState::InfectedCritical>,
      53             :                        Flow<InfectionState::InfectedSevere,              InfectionState::Recovered>,
      54             :                        Flow<InfectionState::InfectedSevere,              InfectionState::Dead>,
      55             :                        Flow<InfectionState::InfectedCritical,            InfectionState::Dead>,
      56             :                        Flow<InfectionState::InfectedCritical,            InfectionState::Recovered>>;
      57             : // clang-format on
      58             : 
      59             : template <typename FP = ScalarType>
      60             : class Model : public FlowModel<FP, InfectionState, Populations<FP, AgeGroup, InfectionState>, Parameters<FP>, Flows>
      61             : {
      62             :     using Base = FlowModel<FP, InfectionState, mio::Populations<FP, AgeGroup, InfectionState>, Parameters<FP>, Flows>;
      63             : 
      64             : public:
      65             :     using typename Base::ParameterSet;
      66             :     using typename Base::Populations;
      67             : 
      68         371 :     Model(const Populations& pop, const ParameterSet& params)
      69         371 :         : Base(pop, params)
      70             :     {
      71         371 :     }
      72             : 
      73         362 :     Model(int num_agegroups)
      74         724 :         : Model(Populations({mio::AgeGroup(num_agegroups), InfectionState::Count}),
      75        1086 :                 ParameterSet(mio::AgeGroup(num_agegroups)))
      76             :     {
      77         362 :     }
      78             : 
      79      411453 :     void get_flows(Eigen::Ref<const Vector<FP>> pop, Eigen::Ref<const Vector<FP>> y, FP t,
      80             :                    Eigen::Ref<Vector<FP>> flows) const override
      81             :     {
      82      411453 :         auto const& params   = this->parameters;
      83      411453 :         AgeGroup n_agegroups = params.get_num_groups();
      84             : 
      85      411453 :         ContactMatrixGroup const& contact_matrix = params.template get<ContactPatterns<FP>>();
      86             : 
      87      411453 :         auto icu_occupancy           = 0.0;
      88      411453 :         auto test_and_trace_required = 0.0;
      89      835098 :         for (auto i = AgeGroup(0); i < n_agegroups; ++i) {
      90      847290 :             test_and_trace_required += (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) /
      91      423645 :                                        params.template get<TimeInfectedNoSymptoms<FP>>()[i] *
      92      423645 :                                        this->populations.get_from(pop, {i, InfectionState::InfectedNoSymptoms});
      93      423645 :             icu_occupancy += this->populations.get_from(pop, {i, InfectionState::InfectedCritical});
      94             :         }
      95             : 
      96      835098 :         for (auto i = AgeGroup(0); i < n_agegroups; i++) {
      97             : 
      98      423645 :             size_t Si    = this->populations.get_flat_index({i, InfectionState::Susceptible});
      99      423645 :             size_t Ei    = this->populations.get_flat_index({i, InfectionState::Exposed});
     100      423645 :             size_t INSi  = this->populations.get_flat_index({i, InfectionState::InfectedNoSymptoms});
     101      423645 :             size_t INSCi = this->populations.get_flat_index({i, InfectionState::InfectedNoSymptomsConfirmed});
     102      423645 :             size_t ISyi  = this->populations.get_flat_index({i, InfectionState::InfectedSymptoms});
     103      423645 :             size_t ISyCi = this->populations.get_flat_index({i, InfectionState::InfectedSymptomsConfirmed});
     104      423645 :             size_t ISevi = this->populations.get_flat_index({i, InfectionState::InfectedSevere});
     105      423645 :             size_t ICri  = this->populations.get_flat_index({i, InfectionState::InfectedCritical});
     106             : 
     107      883866 :             for (auto j = AgeGroup(0); j < n_agegroups; j++) {
     108      460221 :                 size_t Sj    = this->populations.get_flat_index({j, InfectionState::Susceptible});
     109      460221 :                 size_t Ej    = this->populations.get_flat_index({j, InfectionState::Exposed});
     110      460221 :                 size_t INSj  = this->populations.get_flat_index({j, InfectionState::InfectedNoSymptoms});
     111      460221 :                 size_t ISyj  = this->populations.get_flat_index({j, InfectionState::InfectedSymptoms});
     112      460221 :                 size_t ISevj = this->populations.get_flat_index({j, InfectionState::InfectedSevere});
     113      460221 :                 size_t ICrj  = this->populations.get_flat_index({j, InfectionState::InfectedCritical});
     114      460221 :                 size_t Rj    = this->populations.get_flat_index({j, InfectionState::Recovered});
     115             : 
     116             :                 //symptomatic are less well quarantined when testing and tracing is overwhelmed so they infect more people
     117             :                 auto riskFromInfectedSymptomatic =
     118     1840884 :                     smoother_cosine(test_and_trace_required, params.template get<TestAndTraceCapacity<FP>>(),
     119      920442 :                                     params.template get<TestAndTraceCapacity<FP>>() *
     120      460221 :                                         params.template get<TestAndTraceCapacityMaxRisk<FP>>(),
     121      460221 :                                     params.template get<RiskOfInfectionFromSymptomatic<FP>>()[j],
     122      460221 :                                     params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[j]);
     123             : 
     124             :                 // effective contact rate by contact rate between groups i and j and damping j
     125      460221 :                 ScalarType season_val =
     126      460221 :                     (1 + params.template get<Seasonality<FP>>() *
     127      460221 :                              sin(3.141592653589793 * ((params.template get<StartDay>() + t) / 182.5 + 0.5)));
     128      460221 :                 ScalarType cont_freq_eff =
     129      920442 :                     season_val * contact_matrix.get_matrix_at(t)(static_cast<Eigen::Index>((size_t)i),
     130      460221 :                                                                  static_cast<Eigen::Index>((size_t)j));
     131      460221 :                 ScalarType Nj =
     132      460221 :                     pop[Sj] + pop[Ej] + pop[INSj] + pop[ISyj] + pop[ISevj] + pop[ICrj] + pop[Rj]; // without died people
     133      460221 :                 const ScalarType divNj = (Nj < Limits<ScalarType>::zero_tolerance()) ? 0.0 : 1.0 / Nj;
     134      920442 :                 ScalarType dummy_S     = y[Si] * cont_freq_eff * divNj *
     135      460221 :                                      params.template get<TransmissionProbabilityOnContact<FP>>()[i] *
     136      460221 :                                      (params.template get<RelativeTransmissionNoSymptoms<FP>>()[j] * pop[INSj] +
     137      460221 :                                       riskFromInfectedSymptomatic * pop[ISyj]);
     138             : 
     139             :                 // Susceptible -> Exposed
     140      460221 :                 flows[this->template get_flat_flow_index<InfectionState::Susceptible, InfectionState::Exposed>({i})] +=
     141             :                     dummy_S;
     142             :             }
     143             : 
     144             :             // ICU capacity shortage is close
     145     1270935 :             ScalarType criticalPerSevereAdjusted = smoother_cosine(
     146      847290 :                 icu_occupancy, 0.90 * params.template get<ICUCapacity<FP>>(), params.template get<ICUCapacity<FP>>(),
     147      423645 :                 params.template get<CriticalPerSevere<FP>>()[i], 0);
     148             : 
     149      423645 :             ScalarType deathsPerSevereAdjusted =
     150      423645 :                 params.template get<CriticalPerSevere<FP>>()[i] - criticalPerSevereAdjusted;
     151             : 
     152             :             // Exposed -> InfectedNoSymptoms
     153      423645 :             flows[this->template get_flat_flow_index<InfectionState::Exposed, InfectionState::InfectedNoSymptoms>(
     154      847290 :                 {i})] = (1 / params.template get<TimeExposed<FP>>()[i]) * y[Ei];
     155             : 
     156             :             // InfectedNoSymptoms -> InfectedSymptoms / Recovered
     157      847290 :             flows[this->template get_flat_flow_index<InfectionState::InfectedNoSymptoms,
     158      423645 :                                                      InfectionState::InfectedSymptoms>({i})] =
     159      423645 :                 (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) *
     160      423645 :                 (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSi];
     161      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedNoSymptoms, InfectionState::Recovered>(
     162      847290 :                 {i})] = params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i] *
     163      423645 :                         (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSi];
     164             : 
     165             :             // InfectedNoSymptomsConfirmed -> InfectedSymptomsConfirmed / Recovered
     166      847290 :             flows[this->template get_flat_flow_index<InfectionState::InfectedNoSymptomsConfirmed,
     167      423645 :                                                      InfectionState::InfectedSymptomsConfirmed>({i})] =
     168      423645 :                 (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) *
     169      423645 :                 (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSCi];
     170      847290 :             flows[this->template get_flat_flow_index<InfectionState::InfectedNoSymptomsConfirmed,
     171      423645 :                                                      InfectionState::Recovered>({i})] =
     172      423645 :                 params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i] *
     173      423645 :                 (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSCi];
     174             : 
     175             :             // InfectedSymptoms -> InfectedSevere / Recovered
     176      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSymptoms, InfectionState::InfectedSevere>(
     177     1270935 :                 {i})] = params.template get<SeverePerInfectedSymptoms<FP>>()[i] /
     178      847290 :                         params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyi];
     179      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSymptoms, InfectionState::Recovered>(
     180     1270935 :                 {i})] = (1 - params.template get<SeverePerInfectedSymptoms<FP>>()[i]) /
     181      847290 :                         params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyi];
     182             : 
     183             :             // InfectedSymptomsConfirmed -> InfectedSevere / Recovered
     184      847290 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSymptomsConfirmed,
     185      423645 :                                                      InfectionState::InfectedSevere>({i})] =
     186      847290 :                 params.template get<SeverePerInfectedSymptoms<FP>>()[i] /
     187      847290 :                 params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyCi];
     188      847290 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSymptomsConfirmed,
     189      423645 :                                                      InfectionState::Recovered>({i})] =
     190      847290 :                 (1 - params.template get<SeverePerInfectedSymptoms<FP>>()[i]) /
     191      847290 :                 params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyCi];
     192             : 
     193             :             // InfectedSevere -> InfectedCritical / Recovered / Dead
     194      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::InfectedCritical>(
     195      847290 :                 {i})] = criticalPerSevereAdjusted / params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
     196      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::Recovered>({i})] =
     197      847290 :                 (1 - params.template get<CriticalPerSevere<FP>>()[i]) /
     198      847290 :                 params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
     199      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::Dead>({i})] =
     200      423645 :                 deathsPerSevereAdjusted / params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
     201             : 
     202             :             // InfectedCritical -> Dead / Recovered
     203      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedCritical, InfectionState::Dead>({i})] =
     204      423645 :                 params.template get<DeathsPerCritical<FP>>()[i] / params.template get<TimeInfectedCritical<FP>>()[i] *
     205      423645 :                 y[ICri];
     206      423645 :             flows[this->template get_flat_flow_index<InfectionState::InfectedCritical, InfectionState::Recovered>(
     207     1270935 :                 {i})] = (1 - params.template get<DeathsPerCritical<FP>>()[i]) /
     208      847290 :                         params.template get<TimeInfectedCritical<FP>>()[i] * y[ICri];
     209             :         }
     210      411453 :     }
     211             : 
     212             :     /**
     213             :      * serialize this. 
     214             :      * @see mio::serialize
     215             :      */
     216             :     template <class IOContext>
     217          23 :     void serialize(IOContext& io) const
     218             :     {
     219          69 :         auto obj = io.create_object("Model");
     220          23 :         obj.add_element("Parameters", this->parameters);
     221          23 :         obj.add_element("Populations", this->populations);
     222          46 :     }
     223             : 
     224             :     /**
     225             :      * deserialize an object of this class.
     226             :      * @see mio::deserialize
     227             :      */
     228             :     template <class IOContext>
     229           9 :     static IOResult<Model> deserialize(IOContext& io)
     230             :     {
     231          27 :         auto obj = io.expect_object("Model");
     232          27 :         auto par = obj.expect_element("Parameters", Tag<ParameterSet>{});
     233          27 :         auto pop = obj.expect_element("Populations", Tag<Populations>{});
     234             :         return apply(
     235             :             io,
     236           9 :             [](auto&& par_, auto&& pop_) {
     237           9 :                 return Model{pop_, par_};
     238             :             },
     239          18 :             par, pop);
     240           9 :     }
     241             : };
     242             : 
     243             : //forward declaration, see below.
     244             : template <typename FP = ScalarType, class BaseT = mio::Simulation<FP, Model<FP>>>
     245             : class Simulation;
     246             : 
     247             : /**
     248             :  * get percentage of infections per total population.
     249             :  * @param model the compartment model with initial values.
     250             :  * @param t current simulation time.
     251             :  * @param y current value of compartments.
     252             :  * @tparam FP floating point type, e.g., double.
     253             :  * @tparam Base simulation type that uses a secir compartment model. see Simulation.
     254             :  */
     255             : template <typename FP = ScalarType, class Base = mio::Simulation<FP, Model<FP>>>
     256             : double get_infections_relative(const Simulation<FP, Base>& model, FP t, const Eigen::Ref<const Vector<FP>>& y);
     257             : 
     258             : /**
     259             :  * specialization of compartment model simulation for secir models.
     260             :  * @tparam FP floating point type, e.g., double.
     261             :  * @tparam BaseT simulation type that uses a secir compartment model. default mio::Simulation. For testing purposes only!
     262             :  */
     263             : template <typename FP, class BaseT>
     264             : class Simulation : public BaseT
     265             : {
     266             : public:
     267             :     /**
     268             :      * construct a simulation.
     269             :      * @param model the model to simulate.
     270             :      * @param t0 start time
     271             :      * @param dt time steps
     272             :      */
     273         319 :     Simulation(mio::osecir::Model<FP> const& model, FP t0 = 0., FP dt = 0.1)
     274             :         : BaseT(model, t0, dt)
     275         319 :         , m_t_last_npi_check(t0)
     276             :     {
     277         319 :     }
     278             : 
     279             :     /**
     280             :      * @brief advance simulation to tmax.
     281             :      * Overwrites Simulation::advance and includes a check for dynamic NPIs in regular intervals.
     282             :      * @see Simulation::advance
     283             :      * @param tmax next stopping point of simulation
     284             :      * @return value at tmax
     285             :      */
     286         265 :     Eigen::Ref<Vector<FP>> advance(FP tmax)
     287             :     {
     288         265 :         auto& t_end_dyn_npis   = this->get_model().parameters.get_end_dynamic_npis();
     289         265 :         auto& dyn_npis         = this->get_model().parameters.template get<DynamicNPIsInfectedSymptoms<FP>>();
     290         265 :         auto& contact_patterns = this->get_model().parameters.template get<ContactPatterns<FP>>();
     291             : 
     292             :         FP delay_npi_implementation; // delay which is needed to implement a NPI that is criterion-dependent
     293         265 :         auto t        = BaseT::get_result().get_last_time();
     294         265 :         const auto dt = dyn_npis.get_thresholds().size() > 0 ? dyn_npis.get_interval().get() : tmax;
     295             : 
     296         530 :         while (t < tmax) {
     297         265 :             auto dt_eff = std::min({dt, tmax - t, m_t_last_npi_check + dt - t});
     298             : 
     299         265 :             BaseT::advance(t + dt_eff);
     300         265 :             if (t > 0) {
     301          86 :                 delay_npi_implementation =
     302          86 :                     this->get_model().parameters.template get<DynamicNPIsImplementationDelay<FP>>();
     303             :             }
     304             :             else { // DynamicNPIs for t=0 are 'misused' to be from-start NPIs. I.e., do not enforce delay.
     305         179 :                 delay_npi_implementation = 0;
     306             :             }
     307         265 :             t = t + dt_eff;
     308             : 
     309         265 :             if (dyn_npis.get_thresholds().size() > 0) {
     310          35 :                 if (floating_point_greater_equal(t, m_t_last_npi_check + dt)) {
     311          35 :                     if (t < t_end_dyn_npis) {
     312          70 :                         auto inf_rel = get_infections_relative<FP>(*this, t, this->get_result().get_last_value()) *
     313          35 :                                        dyn_npis.get_base_value();
     314          35 :                         auto exceeded_threshold = dyn_npis.get_max_exceeded_threshold(inf_rel);
     315          63 :                         if (exceeded_threshold != dyn_npis.get_thresholds().end() &&
     316          28 :                             (exceeded_threshold->first > m_dynamic_npi.first ||
     317           0 :                              t > ScalarType(m_dynamic_npi.second))) { //old npi was weaker or is expired
     318             : 
     319          28 :                             auto t_start  = SimulationTime(t + delay_npi_implementation);
     320          28 :                             auto t_end    = t_start + SimulationTime(ScalarType(dyn_npis.get_duration()));
     321          28 :                             m_dynamic_npi = std::make_pair(exceeded_threshold->first, t_end);
     322          28 :                             implement_dynamic_npis(contact_patterns.get_cont_freq_mat(), exceeded_threshold->second,
     323          28 :                                                    t_start, t_end, [](auto& g) {
     324          28 :                                                        return make_contact_damping_matrix(g);
     325             :                                                    });
     326             :                         }
     327             :                     }
     328          35 :                     m_t_last_npi_check = t;
     329             :                 }
     330             :             }
     331             :             else {
     332         230 :                 m_t_last_npi_check = t;
     333             :             }
     334             :         }
     335             : 
     336         265 :         return this->get_result().get_last_value();
     337             :     }
     338             : 
     339             : private:
     340             :     double m_t_last_npi_check;
     341             :     std::pair<double, SimulationTime> m_dynamic_npi = {-std::numeric_limits<double>::max(), mio::SimulationTime(0)};
     342             : };
     343             : 
     344             : /**
     345             :  * @brief Specialization of simulate for SECIR models using Simulation.
     346             :  *
     347             :  * @tparam FP floating point type, e.g., double.
     348             :  * @param[in] t0 start time.
     349             :  * @param[in] tmax end time.
     350             :  * @param[in] dt time step.
     351             :  * @param[in] model SECIR model to simulate.
     352             :  * @param[in] integrator optional integrator, uses rk45 if nullptr.
     353             :  * 
     354             :  * @return Returns the result of the simulation.
     355             :  */
     356             : template <typename FP = ScalarType>
     357         137 : inline auto simulate(FP t0, FP tmax, FP dt, const Model<FP>& model,
     358             :                      std::shared_ptr<IntegratorCore<FP>> integrator = nullptr)
     359             : {
     360         137 :     return mio::simulate<FP, Model<FP>, Simulation<>>(t0, tmax, dt, model, integrator);
     361             : }
     362             : 
     363             : /**
     364             :  * @brief Specialization of simulate for SECIR models using the FlowSimulation.
     365             :  * 
     366             :  * @tparam FP floating point type, e.g., double.
     367             :  * @param[in] t0 start time.
     368             :  * @param[in] tmax end time.
     369             :  * @param[in] dt time step.
     370             :  * @param[in] model SECIR model to simulate.
     371             :  * @param[in] integrator optional integrator, uses rk45 if nullptr.
     372             :  * 
     373             :  * @return Returns the result of the Flowsimulation.
     374             :  */
     375             : template <typename FP = ScalarType>
     376             : inline auto simulate_flows(FP t0, FP tmax, FP dt, const Model<FP>& model,
     377             :                            std::shared_ptr<IntegratorCore<FP>> integrator = nullptr)
     378             : {
     379             :     return mio::simulate_flows<FP, Model<FP>, Simulation<FP, mio::FlowSimulation<FP, Model<FP>>>>(t0, tmax, dt, model,
     380             :                                                                                                   integrator);
     381             : }
     382             : 
     383             : //see declaration above.
     384             : template <typename FP, class Base>
     385          44 : double get_infections_relative(const Simulation<FP, Base>& sim, FP /* t*/, const Eigen::Ref<const Vector<FP>>& y)
     386             : {
     387          44 :     double sum_inf = 0;
     388         106 :     for (auto i = AgeGroup(0); i < sim.get_model().parameters.get_num_groups(); ++i) {
     389          62 :         sum_inf += sim.get_model().populations.get_from(y, {i, InfectionState::InfectedSymptoms});
     390             :     }
     391          44 :     auto inf_rel = sum_inf / sim.get_model().populations.get_total();
     392             : 
     393          44 :     return inf_rel;
     394             : }
     395             : 
     396             : /**
     397             : *@brief Computes the reproduction number at a given index time of the Model output obtained by the Simulation.
     398             : *@param t_idx The index time at which the reproduction number is computed.
     399             : *@param sim The simulation holding the SECIR model
     400             : *@tparam Base simulation type that uses a SECIR compartment model. see Simulation.
     401             : *@returns The computed reproduction number at the provided index time.
     402             : */
     403             : 
     404             : template <typename FP, class Base>
     405         144 : IOResult<FP> get_reproduction_number(size_t t_idx, const Simulation<FP, Base>& sim)
     406             : {
     407             : 
     408         144 :     if (!(t_idx < static_cast<size_t>(sim.get_result().get_num_time_points()))) {
     409           9 :         return mio::failure(mio::StatusCode::OutOfRange, "t_idx is not a valid index for the TimeSeries");
     410             :     }
     411             : 
     412         135 :     auto const& params      = sim.get_model().parameters;
     413         135 :     const size_t num_groups = (size_t)sim.get_model().parameters.get_num_groups();
     414             :     //The infected compartments in the SECIR Model are: Exposed, Carrier, Infected, Hospitalized, ICU in respective agegroups
     415         135 :     const size_t num_infected_compartments   = 5;
     416         135 :     const size_t total_infected_compartments = num_infected_compartments * num_groups;
     417         135 :     const double pi                          = std::acos(-1);
     418             : 
     419             :     //F encodes new Infections and V encodes transition times in the next-generation matrix calculation of R_t
     420         135 :     Eigen::MatrixXd F(total_infected_compartments, total_infected_compartments);
     421         135 :     Eigen::MatrixXd V(total_infected_compartments, total_infected_compartments);
     422         135 :     F = Eigen::MatrixXd::Zero(total_infected_compartments,
     423             :                               total_infected_compartments); //Initialize matrices F and V with zeroes
     424         135 :     V = Eigen::MatrixXd::Zero(total_infected_compartments, total_infected_compartments);
     425             : 
     426         135 :     auto test_and_trace_required = 0.0;
     427         135 :     auto icu_occupancy           = 0.0;
     428         540 :     for (auto i = AgeGroup(0); i < (mio::AgeGroup)num_groups; ++i) {
     429         405 :         test_and_trace_required +=
     430         810 :             (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) /
     431         810 :             params.template get<TimeInfectedNoSymptoms<FP>>()[i] *
     432         405 :             sim.get_result().get_value(
     433         405 :                 t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedNoSymptoms})];
     434         405 :         icu_occupancy += sim.get_result().get_value(
     435         810 :             t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedCritical})];
     436             :     }
     437             : 
     438         135 :     double season_val =
     439         135 :         (1 + params.template get<Seasonality<FP>>() *
     440         135 :                  sin(pi *
     441         135 :                      ((sim.get_model().parameters.template get<StartDay>() + sim.get_result().get_time(t_idx)) / 182.5 +
     442             :                       0.5)));
     443         135 :     ContactMatrixGroup const& contact_matrix = sim.get_model().parameters.template get<ContactPatterns<FP>>();
     444             : 
     445         135 :     Eigen::MatrixXd cont_freq_eff(num_groups, num_groups);
     446         135 :     Eigen::MatrixXd riskFromInfectedSymptomatic_derivatives(num_groups, num_groups);
     447         135 :     Eigen::VectorXd divN(num_groups);
     448         135 :     Eigen::VectorXd riskFromInfectedSymptomatic(num_groups);
     449             : 
     450         540 :     for (mio::AgeGroup k = 0; k < (mio::AgeGroup)num_groups; k++) {
     451         405 :         double temp = sim.get_result().get_value(
     452         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Susceptible})] +
     453         405 :                       sim.get_result().get_value(
     454         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Exposed})] +
     455         405 :                       sim.get_result().get_value(
     456         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedNoSymptoms})] +
     457         405 :                       sim.get_result().get_value(
     458         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSymptoms})] +
     459         405 :                       sim.get_result().get_value(
     460         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSevere})] +
     461         405 :                       sim.get_result().get_value(
     462         810 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedCritical})] +
     463         405 :                       sim.get_result().get_value(
     464         405 :                           t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Recovered})];
     465         405 :         if (temp == 0) {
     466          18 :             temp = 1;
     467             :         }
     468         405 :         divN[(size_t)k] = 1 / temp;
     469             : 
     470        2025 :         riskFromInfectedSymptomatic[(size_t)k] = smoother_cosine(
     471         405 :             test_and_trace_required, params.template get<TestAndTraceCapacity<FP>>(),
     472         810 :             (params.template get<TestAndTraceCapacity<FP>>()) * params.template get<TestAndTraceCapacityMaxRisk<FP>>(),
     473         405 :             params.template get<RiskOfInfectionFromSymptomatic<FP>>()[k],
     474         405 :             params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[k]);
     475             : 
     476        1620 :         for (mio::AgeGroup l = 0; l < (mio::AgeGroup)num_groups; l++) {
     477        1377 :             if (test_and_trace_required < params.template get<TestAndTraceCapacity<FP>>() ||
     478         324 :                 test_and_trace_required > params.template get<TestAndTraceCapacityMaxRisk<FP>>() *
     479         162 :                                               params.template get<TestAndTraceCapacity<FP>>()) {
     480        1134 :                 riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) = 0;
     481             :             }
     482             :             else {
     483          81 :                 riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) =
     484          81 :                     -0.5 * pi *
     485         162 :                     (params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[k] -
     486          81 :                      params.template get<RiskOfInfectionFromSymptomatic<FP>>()[k]) /
     487          81 :                     (4 * params.template get<TestAndTraceCapacity<FP>>()) *
     488         162 :                     (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[l]) /
     489          81 :                     params.template get<TimeInfectedNoSymptoms<FP>>()[l] *
     490          81 :                     std::sin(pi / (4 * params.template get<TestAndTraceCapacity<FP>>()) *
     491          81 :                              (test_and_trace_required - params.template get<TestAndTraceCapacity<FP>>()));
     492             :             }
     493             :         }
     494             : 
     495        1620 :         for (Eigen::Index l = 0; l < (Eigen::Index)num_groups; l++) {
     496        2430 :             cont_freq_eff(l, (size_t)k) =
     497        3645 :                 season_val * contact_matrix.get_matrix_at(static_cast<double>(t_idx))(
     498        1215 :                                  static_cast<Eigen::Index>((size_t)l), static_cast<Eigen::Index>((size_t)k));
     499             :         }
     500             :     }
     501             : 
     502             :     //Check criterion if matrix V will be invertible by checking if subblock J is invertible
     503         135 :     Eigen::MatrixXd J(num_groups, num_groups);
     504         135 :     J = Eigen::MatrixXd::Zero(num_groups, num_groups);
     505         540 :     for (size_t i = 0; i < num_groups; i++) {
     506         405 :         J(i, i) = 1 / (params.template get<TimeInfectedCritical<FP>>()[(mio::AgeGroup)i]);
     507             : 
     508         432 :         if (!(icu_occupancy < 0.9 * params.template get<ICUCapacity<FP>>() ||
     509          27 :               icu_occupancy > (double)(params.template get<ICUCapacity<FP>>()))) {
     510         108 :             for (size_t j = 0; j < num_groups; j++) {
     511         405 :                 J(i, j) -= sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
     512         243 :                                {(mio::AgeGroup)i, InfectionState::InfectedSevere})] /
     513         243 :                            params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i] * 5 *
     514         243 :                            params.template get<CriticalPerSevere<FP>>()[(mio::AgeGroup)i] * pi /
     515         162 :                            (params.template get<ICUCapacity<FP>>()) *
     516          81 :                            std::sin(pi / (0.1 * params.template get<ICUCapacity<FP>>()) *
     517          81 :                                     (icu_occupancy - 0.9 * params.template get<ICUCapacity<FP>>()));
     518             :             }
     519             :         }
     520             :     }
     521             : 
     522             :     //Check, if J is invertible
     523         135 :     if (J.determinant() == 0) {
     524           9 :         return mio::failure(mio::StatusCode::UnknownError, "Matrix V is not invertible");
     525             :     }
     526             : 
     527             :     //Initialize the matrix F
     528         504 :     for (size_t i = 0; i < num_groups; i++) {
     529        1512 :         for (size_t j = 0; j < num_groups; j++) {
     530             : 
     531        1134 :             double temp = 0;
     532        4536 :             for (Eigen::Index k = 0; k < (Eigen::Index)num_groups; k++) {
     533        3402 :                 temp += cont_freq_eff(i, k) *
     534       13608 :                         sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
     535        6804 :                             {(mio::AgeGroup)k, InfectionState::InfectedSymptoms})] *
     536        6804 :                         riskFromInfectedSymptomatic_derivatives(k, j) * divN[k];
     537             :             }
     538             : 
     539        2268 :             F(i, j + num_groups) =
     540        4536 :                 sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
     541        3402 :                     {(mio::AgeGroup)i, InfectionState::Susceptible})] *
     542        2268 :                 params.template get<TransmissionProbabilityOnContact<FP>>()[(mio::AgeGroup)i] *
     543        2268 :                 (cont_freq_eff(i, j) * params.template get<RelativeTransmissionNoSymptoms<FP>>()[(mio::AgeGroup)j] *
     544        1134 :                      divN[(size_t)j] +
     545             :                  temp);
     546             :         }
     547             : 
     548        1512 :         for (size_t j = 0; j < num_groups; j++) {
     549        5670 :             F(i, j + 2 * num_groups) = sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
     550        3402 :                                            {(mio::AgeGroup)i, InfectionState::Susceptible})] *
     551        2268 :                                        params.template get<TransmissionProbabilityOnContact<FP>>()[(mio::AgeGroup)i] *
     552        1134 :                                        cont_freq_eff(i, j) * riskFromInfectedSymptomatic[(size_t)j] * divN[(size_t)j];
     553             :         }
     554             :     }
     555             : 
     556             :     //Initialize the matrix V
     557         504 :     for (Eigen::Index i = 0; i < (Eigen::Index)num_groups; i++) {
     558        1134 :         double criticalPerSevereAdjusted = smoother_cosine(
     559         756 :             icu_occupancy, 0.90 * params.template get<ICUCapacity<FP>>(), params.template get<ICUCapacity<FP>>(),
     560         756 :             params.template get<CriticalPerSevere<FP>>()[(mio::AgeGroup)i], 0);
     561             : 
     562         378 :         V(i, i)                           = 1 / params.template get<TimeExposed<FP>>()[(mio::AgeGroup)i];
     563         378 :         V(i + num_groups, i)              = -1 / params.template get<TimeExposed<FP>>()[(mio::AgeGroup)i];
     564         378 :         V(i + num_groups, i + num_groups) = 1 / params.template get<TimeInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i];
     565         378 :         V(i + 2 * num_groups, i + num_groups) =
     566        1134 :             -(1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i]) /
     567         756 :             params.template get<TimeInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i];
     568         378 :         V(i + 2 * num_groups, i + 2 * num_groups) =
     569         756 :             (1 / params.template get<TimeInfectedSymptoms<FP>>()[(mio::AgeGroup)i]);
     570         378 :         V(i + 3 * num_groups, i + 2 * num_groups) =
     571        1134 :             -params.template get<SeverePerInfectedSymptoms<FP>>()[(mio::AgeGroup)i] /
     572         756 :             params.template get<TimeInfectedSymptoms<FP>>()[(mio::AgeGroup)i];
     573         378 :         V(i + 3 * num_groups, i + 3 * num_groups) =
     574         756 :             1 / (params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i]);
     575         378 :         V(i + 4 * num_groups, i + 3 * num_groups) =
     576         756 :             -criticalPerSevereAdjusted / (params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i]);
     577             : 
     578        1512 :         for (size_t j = 0; j < num_groups; j++) {
     579        1134 :             V(i + 4 * num_groups, j + 4 * num_groups) = J(i, j);
     580             :         }
     581             :     }
     582             : 
     583         126 :     V = V.inverse();
     584             : 
     585             :     //Compute F*V
     586         126 :     Eigen::MatrixXd NextGenMatrix(num_infected_compartments * num_groups, 5 * num_groups);
     587         126 :     NextGenMatrix = F * V;
     588             : 
     589             :     //Compute the largest eigenvalue in absolute value
     590         126 :     Eigen::ComplexEigenSolver<Eigen::MatrixXd> ces;
     591             : 
     592         126 :     ces.compute(NextGenMatrix);
     593         126 :     const Eigen::VectorXcd eigen_vals = ces.eigenvalues();
     594             : 
     595         126 :     Eigen::VectorXd eigen_vals_abs;
     596         126 :     eigen_vals_abs.resize(eigen_vals.size());
     597             : 
     598        2016 :     for (int i = 0; i < eigen_vals.size(); i++) {
     599        1890 :         eigen_vals_abs[i] = std::abs(eigen_vals[i]);
     600             :     }
     601         126 :     return mio::success(eigen_vals_abs.maxCoeff());
     602         135 : }
     603             : /**
     604             : *@brief Computes the reproduction number for all time points of the Model output obtained by the Simulation.
     605             : *@param sim The Model Simulation.
     606             : *@tparam Base simulation type that uses a SECIR compartment model. see Simulation.
     607             : *@returns Eigen::Vector containing all reproduction numbers
     608             : */
     609             : 
     610             : template <typename FP, class Base>
     611             : Vector<FP> get_reproduction_numbers(const Simulation<FP, Base>& sim)
     612             : {
     613             :     Vector<FP> temp(sim.get_result().get_num_time_points());
     614             :     for (int i = 0; i < sim.get_result().get_num_time_points(); i++) {
     615             :         temp[i] = get_reproduction_number((size_t)i, sim).value();
     616             :     }
     617             :     return temp;
     618             : }
     619             : 
     620             : /**
     621             : *@brief @brief Computes the reproduction number at a given time point of the Simulation. If the particular time point is not part of the output, a linearly interpolated value is returned.
     622             : *@param t_value The time point at which the reproduction number should be computed.
     623             : *@param sim The Model Simulation.
     624             : *@tparam Base simulation type that uses a SECIR compartment model. see Simulation.
     625             : *@returns The computed reproduction number at the provided time point, potentially using linear interpolation.
     626             : */
     627             : template <class Base>
     628          54 : IOResult<ScalarType> get_reproduction_number(ScalarType t_value, const Simulation<Base>& sim)
     629             : {
     630          54 :     if (t_value < sim.get_result().get_time(0) || t_value > sim.get_result().get_last_time()) {
     631             :         return mio::failure(mio::StatusCode::OutOfRange,
     632          18 :                             "Cannot interpolate reproduction number outside computed horizon of the TimeSeries");
     633             :     }
     634             : 
     635          36 :     if (t_value == sim.get_result().get_time(0)) {
     636           9 :         return mio::success(get_reproduction_number((size_t)0, sim).value());
     637             :     }
     638             : 
     639          27 :     const auto& times = sim.get_result().get_times();
     640             : 
     641          27 :     auto time_late = std::distance(times.begin(), std::lower_bound(times.begin(), times.end(), t_value));
     642             : 
     643          27 :     ScalarType y1 = get_reproduction_number(static_cast<size_t>(time_late - 1), sim).value();
     644          27 :     ScalarType y2 = get_reproduction_number(static_cast<size_t>(time_late), sim).value();
     645             : 
     646          27 :     auto result = linear_interpolation(t_value, sim.get_result().get_time(time_late - 1),
     647          27 :                                        sim.get_result().get_time(time_late), y1, y2);
     648          27 :     return mio::success(static_cast<ScalarType>(result));
     649             : }
     650             : 
     651             : /**
     652             :  * Get mobility factors.
     653             :  * Used by mobility graph simulation.
     654             :  * Like infection risk, mobility of infected individuals is reduced if they are well isolated.
     655             :  * @param model the compartment model with initial values.
     656             :  * @param t current simulation time.
     657             :  * @param y current value of compartments.
     658             :  * @return vector expression, same size as y, with mobility factors per compartment.
     659             :  * @tparam FP floating point type, e.g., double.
     660             :  * @tparam Base simulation type that uses a secir compartment model; see Simulation.
     661             :  */
     662             : template <typename FP = ScalarType, class Base = mio::Simulation<Model<FP>, FP>>
     663          75 : auto get_mobility_factors(const Simulation<Base>& sim, FP /*t*/, const Eigen::Ref<const Vector<FP>>& y)
     664             : {
     665          75 :     auto& params = sim.get_model().parameters;
     666             :     //parameters as arrays
     667          75 :     auto&& p_asymp   = params.template get<RecoveredPerInfectedNoSymptoms<FP>>().array().template cast<FP>();
     668          75 :     auto&& p_inf     = params.template get<RiskOfInfectionFromSymptomatic<FP>>().array().template cast<FP>();
     669          75 :     auto&& p_inf_max = params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>().array().template cast<FP>();
     670             :     //slice of InfectedNoSymptoms
     671         150 :     auto y_INS = slice(y, {Eigen::Index(InfectionState::InfectedNoSymptoms),
     672         150 :                            Eigen::Index(size_t(params.get_num_groups())), Eigen::Index(InfectionState::Count)});
     673             : 
     674             :     //compute isolation, same as infection risk from main model
     675          75 :     auto test_and_trace_required =
     676         225 :         ((1 - p_asymp) / params.template get<TimeInfectedNoSymptoms<FP>>().array().template cast<FP>() * y_INS.array())
     677         300 :             .sum();
     678          75 :     auto test_and_trace_capacity          = double(params.template get<TestAndTraceCapacity<FP>>());
     679          75 :     auto test_and_trace_capacity_max_risk = double(params.template get<TestAndTraceCapacityMaxRisk<FP>>());
     680          75 :     auto riskFromInfectedSymptomatic =
     681             :         smoother_cosine(test_and_trace_required, test_and_trace_capacity,
     682             :                         test_and_trace_capacity * test_and_trace_capacity_max_risk, p_inf.matrix(), p_inf_max.matrix());
     683             : 
     684             :     //set factor for infected
     685          75 :     auto factors = Eigen::VectorXd::Ones(y.rows()).eval();
     686         225 :     slice(factors, {Eigen::Index(InfectionState::InfectedSymptoms), Eigen::Index(size_t(params.get_num_groups())),
     687             :                     Eigen::Index(InfectionState::Count)})
     688         150 :         .array() = riskFromInfectedSymptomatic;
     689         150 :     return factors;
     690          75 : }
     691             : 
     692             : template <typename FP = ScalarType, class Base = mio::Simulation<Model<FP>, FP>>
     693           9 : auto test_commuters(Simulation<FP, Base>& sim, Eigen::Ref<Vector<FP>> mobile_population, FP time)
     694             : {
     695           9 :     auto& model       = sim.get_model();
     696           9 :     auto nondetection = 1.0;
     697          18 :     if (time >= model.parameters.get_start_commuter_detection() &&
     698           9 :         time < model.parameters.get_end_commuter_detection()) {
     699           9 :         nondetection = (double)model.parameters.get_commuter_nondetection();
     700             :     }
     701          27 :     for (auto i = AgeGroup(0); i < model.parameters.get_num_groups(); ++i) {
     702          18 :         auto INSi  = model.populations.get_flat_index({i, InfectionState::InfectedNoSymptoms});
     703          18 :         auto INSCi = model.populations.get_flat_index({i, InfectionState::InfectedNoSymptomsConfirmed});
     704          18 :         auto ISyi  = model.populations.get_flat_index({i, InfectionState::InfectedSymptoms});
     705          18 :         auto ISyCi = model.populations.get_flat_index({i, InfectionState::InfectedSymptomsConfirmed});
     706             : 
     707             :         //put detected commuters in their own compartment so they don't contribute to infections in their home node
     708          18 :         sim.get_result().get_last_value()[INSi] -= mobile_population[INSi] * (1 - nondetection);
     709          18 :         sim.get_result().get_last_value()[INSCi] += mobile_population[INSi] * (1 - nondetection);
     710          18 :         sim.get_result().get_last_value()[ISyi] -= mobile_population[ISyi] * (1 - nondetection);
     711          18 :         sim.get_result().get_last_value()[ISyCi] += mobile_population[ISyi] * (1 - nondetection);
     712             : 
     713             :         //reduce the number of commuters
     714          18 :         mobile_population[ISyi] *= nondetection;
     715          18 :         mobile_population[INSi] *= nondetection;
     716             :     }
     717           9 : }
     718             : 
     719             : } // namespace osecir
     720             : } // namespace mio
     721             : 
     722             : #endif // ODESECIR_MODEL_H

Generated by: LCOV version 1.14