model.h Source File

CPP API: model.h Source File
ode_secir/model.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 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 
29 #include "ode_secir/parameters.h"
30 #include "memilio/math/smoother.h"
33 
34 #include <numbers>
35 #include <complex>
36 
37 namespace mio
38 {
39 namespace osecir
40 {
41 
42 // Create template specializations for the age resolved
43 // SECIHURD model
44 // clang-format off
60 // clang-format on
61 
62 template <typename FP>
63 class Model : public FlowModel<FP, InfectionState, Populations<FP, AgeGroup, InfectionState>, Parameters<FP>, Flows>
64 {
66 
67 public:
68  using typename Base::ParameterSet;
69  using typename Base::Populations;
70 
71  Model(const Populations& pop, const ParameterSet& params)
72  : Base(pop, params)
73  {
74  }
75 
76  Model(int num_agegroups)
78  ParameterSet(mio::AgeGroup(num_agegroups)))
79  {
80  }
81 
82  void get_flows(Eigen::Ref<const Eigen::VectorX<FP>> pop, Eigen::Ref<const Eigen::VectorX<FP>> y, FP t,
83  Eigen::Ref<Eigen::VectorX<FP>> flows) const override
84  {
85  auto const& params = this->parameters;
86  AgeGroup n_agegroups = params.get_num_groups();
87 
88  ContactMatrixGroup<FP> const& contact_matrix = params.template get<ContactPatterns<FP>>();
89 
90  FP icu_occupancy = 0.0;
91  FP test_and_trace_required = 0.0;
92  for (AgeGroup i = 0; i < n_agegroups; ++i) {
93  test_and_trace_required += (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) /
94  params.template get<TimeInfectedNoSymptoms<FP>>()[i] *
95  this->populations.get_from(pop, {i, InfectionState::InfectedNoSymptoms});
96  icu_occupancy += this->populations.get_from(pop, {i, InfectionState::InfectedCritical});
97  }
98 
99  for (AgeGroup i = 0; i < n_agegroups; i++) {
100 
101  size_t Si = this->populations.get_flat_index({i, InfectionState::Susceptible});
102  size_t Ei = this->populations.get_flat_index({i, InfectionState::Exposed});
103  size_t INSi = this->populations.get_flat_index({i, InfectionState::InfectedNoSymptoms});
104  size_t INSCi = this->populations.get_flat_index({i, InfectionState::InfectedNoSymptomsConfirmed});
105  size_t ISyi = this->populations.get_flat_index({i, InfectionState::InfectedSymptoms});
106  size_t ISyCi = this->populations.get_flat_index({i, InfectionState::InfectedSymptomsConfirmed});
107  size_t ISevi = this->populations.get_flat_index({i, InfectionState::InfectedSevere});
108  size_t ICri = this->populations.get_flat_index({i, InfectionState::InfectedCritical});
109 
110  for (AgeGroup j = 0; j < n_agegroups; j++) {
111  size_t Sj = this->populations.get_flat_index({j, InfectionState::Susceptible});
112  size_t Ej = this->populations.get_flat_index({j, InfectionState::Exposed});
113  size_t INSj = this->populations.get_flat_index({j, InfectionState::InfectedNoSymptoms});
114  size_t ISyj = this->populations.get_flat_index({j, InfectionState::InfectedSymptoms});
115  size_t ISevj = this->populations.get_flat_index({j, InfectionState::InfectedSevere});
116  size_t ICrj = this->populations.get_flat_index({j, InfectionState::InfectedCritical});
117  size_t Rj = this->populations.get_flat_index({j, InfectionState::Recovered});
118 
119  //symptomatic are less well quarantined when testing and tracing is overwhelmed so they infect more people
120  FP riskFromInfectedSymptomatic =
121  smoother_cosine<FP>(test_and_trace_required, params.template get<TestAndTraceCapacity<FP>>(),
122  params.template get<TestAndTraceCapacity<FP>>() *
123  params.template get<TestAndTraceCapacityMaxRisk<FP>>(),
124  params.template get<RiskOfInfectionFromSymptomatic<FP>>()[j],
125  params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[j]);
126 
127  // effective contact rate by contact rate between groups i and j and damping j
128  FP season_val = (1 + params.template get<Seasonality<FP>>() *
129  sin(std::numbers::pi_v<ScalarType> *
130  ((params.template get<StartDay<FP>>() + t) / 182.5 + 0.5)));
131  FP cont_freq_eff =
132  season_val * contact_matrix.get_matrix_at(SimulationTime<FP>(t))(
133  static_cast<Eigen::Index>((size_t)i), static_cast<Eigen::Index>((size_t)j));
134  FP Nj =
135  pop[Sj] + pop[Ej] + pop[INSj] + pop[ISyj] + pop[ISevj] + pop[ICrj] + pop[Rj]; // without died people
136  const FP divNj = (Nj < Limits<FP>::zero_tolerance()) ? FP(0.0) : FP(1.0 / Nj);
137  FP dummy_S = y[Si] * cont_freq_eff * divNj *
138  params.template get<TransmissionProbabilityOnContact<FP>>()[i] *
139  (params.template get<RelativeTransmissionNoSymptoms<FP>>()[j] * pop[INSj] +
140  riskFromInfectedSymptomatic * pop[ISyj]);
141 
142  // Susceptible -> Exposed
143  flows[this->template get_flat_flow_index<InfectionState::Susceptible, InfectionState::Exposed>({i})] +=
144  dummy_S;
145  }
146 
147  // ICU capacity shortage is close
148  FP criticalPerSevereAdjusted = smoother_cosine<FP>(
149  icu_occupancy, 0.90 * params.template get<ICUCapacity<FP>>(), params.template get<ICUCapacity<FP>>(),
150  params.template get<CriticalPerSevere<FP>>()[i], 0.0);
151 
152  FP deathsPerSevereAdjusted = params.template get<CriticalPerSevere<FP>>()[i] - criticalPerSevereAdjusted;
153 
154  // Exposed -> InfectedNoSymptoms
155  flows[this->template get_flat_flow_index<InfectionState::Exposed, InfectionState::InfectedNoSymptoms>(
156  {i})] = (1.0 / params.template get<TimeExposed<FP>>()[i]) * y[Ei];
157 
158  // InfectedNoSymptoms -> InfectedSymptoms / Recovered
161  (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) *
162  (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSi];
163  flows[this->template get_flat_flow_index<InfectionState::InfectedNoSymptoms, InfectionState::Recovered>(
164  {i})] = params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i] *
165  (1.0 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSi];
166 
167  // InfectedNoSymptomsConfirmed -> InfectedSymptomsConfirmed / Recovered
170  (1.0 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) *
171  (1.0 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSCi];
174  params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i] *
175  (1.0 / params.template get<TimeInfectedNoSymptoms<FP>>()[i]) * y[INSCi];
176 
177  // InfectedSymptoms -> InfectedSevere / Recovered
178  flows[this->template get_flat_flow_index<InfectionState::InfectedSymptoms, InfectionState::InfectedSevere>(
179  {i})] = params.template get<SeverePerInfectedSymptoms<FP>>()[i] /
180  params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyi];
181  flows[this->template get_flat_flow_index<InfectionState::InfectedSymptoms, InfectionState::Recovered>(
182  {i})] = (1 - params.template get<SeverePerInfectedSymptoms<FP>>()[i]) /
183  params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyi];
184 
185  // InfectedSymptomsConfirmed -> InfectedSevere / Recovered
188  params.template get<SeverePerInfectedSymptoms<FP>>()[i] /
189  params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyCi];
192  (1.0 - params.template get<SeverePerInfectedSymptoms<FP>>()[i]) /
193  params.template get<TimeInfectedSymptoms<FP>>()[i] * y[ISyCi];
194 
195  // InfectedSevere -> InfectedCritical / Recovered / Dead
196  flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::InfectedCritical>(
197  {i})] = criticalPerSevereAdjusted / params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
198  flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::Recovered>({i})] =
199  (1.0 - params.template get<CriticalPerSevere<FP>>()[i]) /
200  params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
201  flows[this->template get_flat_flow_index<InfectionState::InfectedSevere, InfectionState::Dead>({i})] =
202  deathsPerSevereAdjusted / params.template get<TimeInfectedSevere<FP>>()[i] * y[ISevi];
203 
204  // InfectedCritical -> Dead / Recovered
205  flows[this->template get_flat_flow_index<InfectionState::InfectedCritical, InfectionState::Dead>({i})] =
206  params.template get<DeathsPerCritical<FP>>()[i] / params.template get<TimeInfectedCritical<FP>>()[i] *
207  y[ICri];
208  flows[this->template get_flat_flow_index<InfectionState::InfectedCritical, InfectionState::Recovered>(
209  {i})] = (1.0 - params.template get<DeathsPerCritical<FP>>()[i]) /
210  params.template get<TimeInfectedCritical<FP>>()[i] * y[ICri];
211  }
212  }
213 
218  template <class IOContext>
219  void serialize(IOContext& io) const
220  {
221  auto obj = io.create_object("Model");
222  obj.add_element("Parameters", this->parameters);
223  obj.add_element("Populations", this->populations);
224  }
225 
230  template <class IOContext>
231  static IOResult<Model> deserialize(IOContext& io)
232  {
233  auto obj = io.expect_object("Model");
234  auto par = obj.expect_element("Parameters", Tag<ParameterSet>{});
235  auto pop = obj.expect_element("Populations", Tag<Populations>{});
236  return apply(
237  io,
238  [](auto&& par_, auto&& pop_) {
239  return Model{pop_, par_};
240  },
241  par, pop);
242  }
243 };
244 
245 //forward declaration, see below.
246 template <typename FP, class BaseT = mio::Simulation<FP, Model<FP>>>
247 class Simulation;
248 
257 template <typename FP, class Base = mio::Simulation<FP, Model<FP>>>
258 FP get_infections_relative(const Simulation<FP, Base>& model, FP t, const Eigen::Ref<const Eigen::VectorX<FP>>& y);
259 
265 template <typename FP, class BaseT>
266 class Simulation : public BaseT
267 {
268 public:
275  Simulation(mio::osecir::Model<FP> const& model, FP t0 = 0., FP dt = 0.1)
276  : BaseT(model, t0, dt)
277  , m_t_last_npi_check(t0)
278  {
279  }
280 
288  Eigen::Ref<Eigen::VectorX<FP>> advance(FP tmax)
289  {
290  using std::min;
291  auto& t_end_dyn_npis = this->get_model().parameters.get_end_dynamic_npis();
292  auto& dyn_npis = this->get_model().parameters.template get<DynamicNPIsInfectedSymptoms<FP>>();
293  auto& contact_patterns = this->get_model().parameters.template get<ContactPatterns<FP>>();
294 
295  FP delay_npi_implementation; // delay which is needed to implement a NPI that is criterion-dependent
296  FP t = BaseT::get_result().get_last_time();
297  const FP dt = dyn_npis.get_thresholds().size() > 0 ? FP(dyn_npis.get_interval().get()) : FP(tmax);
298 
299  while (t < tmax) {
300  FP dt_eff = min<FP>(dt, tmax - t);
301  dt_eff = min<FP>(dt_eff, m_t_last_npi_check + dt - t);
302 
303  BaseT::advance(t + dt_eff);
304  if (t > 0) {
305  delay_npi_implementation =
306  this->get_model().parameters.template get<DynamicNPIsImplementationDelay<FP>>();
307  }
308  else { // DynamicNPIs for t=0 are 'misused' to be from-start NPIs. I.e., do not enforce delay.
309  delay_npi_implementation = 0;
310  }
311  t = t + dt_eff;
312 
313  if (dyn_npis.get_thresholds().size() > 0) {
314  if (floating_point_greater_equal<FP>(t, m_t_last_npi_check + dt)) {
315  if (t < t_end_dyn_npis) {
316  auto inf_rel = get_infections_relative<FP>(*this, t, this->get_result().get_last_value()) *
317  dyn_npis.get_base_value();
318  auto exceeded_threshold = dyn_npis.get_max_exceeded_threshold(inf_rel);
319  if (exceeded_threshold != dyn_npis.get_thresholds().end() &&
320  (exceeded_threshold->first > m_dynamic_npi.first ||
321  t > FP(m_dynamic_npi.second))) { //old npi was weaker or is expired
322 
323  auto t_start = SimulationTime<FP>(t + delay_npi_implementation);
324  auto t_end = t_start + SimulationTime<FP>(FP(dyn_npis.get_duration()));
325  m_dynamic_npi = std::make_pair(exceeded_threshold->first, t_end);
326  implement_dynamic_npis<FP>(contact_patterns.get_cont_freq_mat(), exceeded_threshold->second,
327  t_start, t_end, [](auto& g) {
328  return make_contact_damping_matrix(g);
329  });
330  }
331  }
332  m_t_last_npi_check = t;
333  }
334  }
335  else {
336  m_t_last_npi_check = t;
337  }
338  }
339 
340  return this->get_result().get_last_value();
341  }
342 
343 private:
345  std::pair<FP, SimulationTime<FP>> m_dynamic_npi = {-std::numeric_limits<FP>::max(), mio::SimulationTime<FP>(0)};
346 };
347 
360 template <typename FP>
361 inline auto simulate(FP t0, FP tmax, FP dt, const Model<FP>& model,
362  std::unique_ptr<OdeIntegratorCore<FP>>&& integrator_core = nullptr)
363 {
364  return mio::simulate<FP, Model<FP>, Simulation<FP>>(t0, tmax, dt, model, std::move(integrator_core));
365 }
366 
379 template <typename FP>
380 inline auto simulate_flows(FP t0, FP tmax, FP dt, const Model<FP>& model,
381  std::unique_ptr<OdeIntegratorCore<FP>>&& integrator_core = nullptr)
382 {
383  return mio::simulate_flows<FP, Model<FP>, Simulation<FP, mio::FlowSimulation<FP, Model<FP>>>>(
384  t0, tmax, dt, model, std::move(integrator_core));
385 }
386 
387 //see declaration above.
388 template <typename FP, class Base>
389 FP get_infections_relative(const Simulation<FP, Base>& sim, FP /* t*/, const Eigen::Ref<const Eigen::VectorX<FP>>& y)
390 {
391  FP sum_inf = 0;
392  for (auto i = AgeGroup(0); i < sim.get_model().parameters.get_num_groups(); ++i) {
393  sum_inf += sim.get_model().populations.get_from(y, {i, InfectionState::InfectedSymptoms});
394  }
395  FP inf_rel = sum_inf / sim.get_model().populations.get_total();
396 
397  return inf_rel;
398 }
399 
408 template <typename FP, class Base>
410 {
411  using std::abs;
412  using std::sin;
413 
414  if (!(t_idx < static_cast<size_t>(sim.get_result().get_num_time_points()))) {
415  return mio::failure(mio::StatusCode::OutOfRange, "t_idx is not a valid index for the TimeSeries");
416  }
417 
418  auto const& params = sim.get_model().parameters;
419  const size_t num_groups = (size_t)sim.get_model().parameters.get_num_groups();
420  //The infected compartments in the SECIR Model are: Exposed, Carrier, Infected, Hospitalized, ICU in respective agegroups
421  const size_t num_infected_compartments = 5;
422  const size_t total_infected_compartments = num_infected_compartments * num_groups;
423  const FP pi = std::numbers::pi_v<ScalarType>;
424 
425  //F encodes new Infections and V encodes transition times in the next-generation matrix calculation of R_t
426  Eigen::MatrixX<FP> F(total_infected_compartments, total_infected_compartments);
427  Eigen::MatrixX<FP> V(total_infected_compartments, total_infected_compartments);
428  F = Eigen::MatrixX<FP>::Zero(total_infected_compartments,
429  total_infected_compartments); //Initialize matrices F and V with zeroes
430  V = Eigen::MatrixX<FP>::Zero(total_infected_compartments, total_infected_compartments);
431 
432  FP test_and_trace_required = 0.0;
433  FP icu_occupancy = 0.0;
434  for (auto i = AgeGroup(0); i < (mio::AgeGroup)num_groups; ++i) {
435  test_and_trace_required +=
436  (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[i]) /
437  params.template get<TimeInfectedNoSymptoms<FP>>()[i] *
438  sim.get_result().get_value(
439  t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedNoSymptoms})];
440  icu_occupancy += sim.get_result().get_value(
441  t_idx)[sim.get_model().populations.get_flat_index({i, InfectionState::InfectedCritical})];
442  }
443 
444  FP season_val =
445  (1 +
446  params.template get<Seasonality<FP>>() *
447  sin(pi *
448  ((sim.get_model().parameters.template get<StartDay<FP>>() + sim.get_result().get_time(t_idx)) / 182.5 +
449  0.5)));
450  ContactMatrixGroup<FP> const& contact_matrix = sim.get_model().parameters.template get<ContactPatterns<FP>>();
451 
452  Eigen::MatrixX<FP> cont_freq_eff(num_groups, num_groups);
453  Eigen::MatrixX<FP> riskFromInfectedSymptomatic_derivatives(num_groups, num_groups);
454  Eigen::VectorX<FP> divN(num_groups);
455  Eigen::VectorX<FP> riskFromInfectedSymptomatic(num_groups);
456 
457  for (mio::AgeGroup k = 0; k < (mio::AgeGroup)num_groups; k++) {
458  FP temp = sim.get_result().get_value(
459  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Susceptible})] +
460  sim.get_result().get_value(
461  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Exposed})] +
462  sim.get_result().get_value(
463  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedNoSymptoms})] +
464  sim.get_result().get_value(
465  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSymptoms})] +
466  sim.get_result().get_value(
467  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedSevere})] +
468  sim.get_result().get_value(
469  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::InfectedCritical})] +
470  sim.get_result().get_value(
471  t_idx)[sim.get_model().populations.get_flat_index({k, InfectionState::Recovered})];
472  if (temp < Limits<FP>::zero_tolerance()) {
473  temp = 1.0;
474  }
475  divN[(size_t)k] = 1 / temp;
476 
477  riskFromInfectedSymptomatic[(size_t)k] = smoother_cosine<FP>(
478  test_and_trace_required, params.template get<TestAndTraceCapacity<FP>>(),
479  (params.template get<TestAndTraceCapacity<FP>>()) * params.template get<TestAndTraceCapacityMaxRisk<FP>>(),
480  params.template get<RiskOfInfectionFromSymptomatic<FP>>()[k],
481  params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[k]);
482 
483  for (mio::AgeGroup l = 0; l < (mio::AgeGroup)num_groups; l++) {
484  if (test_and_trace_required < params.template get<TestAndTraceCapacity<FP>>() ||
485  test_and_trace_required > params.template get<TestAndTraceCapacityMaxRisk<FP>>() *
486  params.template get<TestAndTraceCapacity<FP>>()) {
487  riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) = 0;
488  }
489  else {
490  riskFromInfectedSymptomatic_derivatives((size_t)k, (size_t)l) =
491  -0.5 * pi *
492  (params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>()[k] -
493  params.template get<RiskOfInfectionFromSymptomatic<FP>>()[k]) /
494  (4 * params.template get<TestAndTraceCapacity<FP>>()) *
495  (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[l]) /
496  params.template get<TimeInfectedNoSymptoms<FP>>()[l] *
497  sin(pi / (4 * params.template get<TestAndTraceCapacity<FP>>()) *
498  (test_and_trace_required - params.template get<TestAndTraceCapacity<FP>>()));
499  }
500  }
501 
502  for (Eigen::Index l = 0; l < (Eigen::Index)num_groups; l++) {
503  cont_freq_eff(l, (size_t)k) =
504  season_val * contact_matrix.get_matrix_at(SimulationTime<FP>(static_cast<FP>(t_idx)))(
505  static_cast<Eigen::Index>((size_t)l), static_cast<Eigen::Index>((size_t)k));
506  }
507  }
508 
509  //Check criterion if matrix V will be invertible by checking if subblock J is invertible
510  Eigen::MatrixX<FP> J(num_groups, num_groups);
511  J = Eigen::MatrixX<FP>::Zero(num_groups, num_groups);
512  for (size_t i = 0; i < num_groups; i++) {
513  J(i, i) = 1 / (params.template get<TimeInfectedCritical<FP>>()[(mio::AgeGroup)i]);
514 
515  if (!(icu_occupancy < 0.9 * params.template get<ICUCapacity<FP>>() ||
516  icu_occupancy > params.template get<ICUCapacity<FP>>())) {
517  for (size_t j = 0; j < num_groups; j++) {
518  J(i, j) -= sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
519  {(mio::AgeGroup)i, InfectionState::InfectedSevere})] /
520  params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i] * 5 *
521  params.template get<CriticalPerSevere<FP>>()[(mio::AgeGroup)i] * pi /
522  (params.template get<ICUCapacity<FP>>()) *
523  sin(pi / (0.1 * params.template get<ICUCapacity<FP>>()) *
524  (icu_occupancy - 0.9 * params.template get<ICUCapacity<FP>>()));
525  }
526  }
527  }
528 
529  //Check, if J is invertible
530  if (J.determinant() == 0) {
531  return mio::failure(mio::StatusCode::UnknownError, "Matrix V is not invertible");
532  }
533 
534  //Initialize the matrix F
535  for (size_t i = 0; i < num_groups; i++) {
536  for (size_t j = 0; j < num_groups; j++) {
537 
538  FP temp = 0;
539  for (Eigen::Index k = 0; k < (Eigen::Index)num_groups; k++) {
540  temp += cont_freq_eff(i, k) *
541  sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
542  {(mio::AgeGroup)k, InfectionState::InfectedSymptoms})] *
543  riskFromInfectedSymptomatic_derivatives(k, j) * divN[k];
544  }
545 
546  F(i, j + num_groups) =
547  sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
548  {(mio::AgeGroup)i, InfectionState::Susceptible})] *
550  (cont_freq_eff(i, j) * params.template get<RelativeTransmissionNoSymptoms<FP>>()[(mio::AgeGroup)j] *
551  divN[(size_t)j] +
552  temp);
553  }
554 
555  for (size_t j = 0; j < num_groups; j++) {
556  F(i, j + 2 * num_groups) = sim.get_result().get_value(t_idx)[sim.get_model().populations.get_flat_index(
557  {(mio::AgeGroup)i, InfectionState::Susceptible})] *
559  cont_freq_eff(i, j) * riskFromInfectedSymptomatic[(size_t)j] * divN[(size_t)j];
560  }
561  }
562 
563  //Initialize the matrix V
564  for (Eigen::Index i = 0; i < (Eigen::Index)num_groups; i++) {
565  FP criticalPerSevereAdjusted = smoother_cosine<FP>(
566  icu_occupancy, 0.90 * params.template get<ICUCapacity<FP>>(), params.template get<ICUCapacity<FP>>(),
567  params.template get<CriticalPerSevere<FP>>()[(mio::AgeGroup)i], 0);
568 
569  V(i, i) = 1 / params.template get<TimeExposed<FP>>()[(mio::AgeGroup)i];
570  V(i + num_groups, i) = -1 / params.template get<TimeExposed<FP>>()[(mio::AgeGroup)i];
571  V(i + num_groups, i + num_groups) = 1 / params.template get<TimeInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i];
572  V(i + 2 * num_groups, i + num_groups) =
573  -(1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i]) /
574  params.template get<TimeInfectedNoSymptoms<FP>>()[(mio::AgeGroup)i];
575  V(i + 2 * num_groups, i + 2 * num_groups) =
576  (1 / params.template get<TimeInfectedSymptoms<FP>>()[(mio::AgeGroup)i]);
577  V(i + 3 * num_groups, i + 2 * num_groups) =
578  -params.template get<SeverePerInfectedSymptoms<FP>>()[(mio::AgeGroup)i] /
579  params.template get<TimeInfectedSymptoms<FP>>()[(mio::AgeGroup)i];
580  V(i + 3 * num_groups, i + 3 * num_groups) =
581  1 / (params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i]);
582  V(i + 4 * num_groups, i + 3 * num_groups) =
583  -criticalPerSevereAdjusted / (params.template get<TimeInfectedSevere<FP>>()[(mio::AgeGroup)i]);
584 
585  for (size_t j = 0; j < num_groups; j++) {
586  V(i + 4 * num_groups, j + 4 * num_groups) = J(i, j);
587  }
588  }
589 
590  V = V.inverse();
591 
592  Eigen::MatrixX<ScalarType> NextGenMatrix(num_infected_compartments * num_groups, 5 * num_groups);
593  NextGenMatrix.noalias() = F * V;
594 
595  // Compute the largest eigenvalue in absolute value
596  Eigen::ComplexEigenSolver<Eigen::MatrixX<ScalarType>> ces;
597  ces.compute(NextGenMatrix);
598  FP rho = ces.eigenvalues().cwiseAbs().maxCoeff();
599 
600  return mio::success(rho);
601 }
602 
610 template <typename FP, class Base>
611 Eigen::VectorX<FP> get_reproduction_numbers(const Simulation<FP, Base>& sim)
612 {
613  Eigen::VectorX<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<ScalarType>((size_t)i, sim).value();
616  }
617  return temp;
618 }
619 
627 template <typename FP, class Base>
629 {
630  if (t_value < sim.get_result().get_time(0) || t_value > sim.get_result().get_last_time()) {
632  "Cannot interpolate reproduction number outside computed horizon of the TimeSeries");
633  }
634 
635  if (t_value == sim.get_result().get_time(0)) {
636  return mio::success(get_reproduction_number((size_t)0, sim).value());
637  }
638 
639  const auto& times = sim.get_result().get_times();
640 
641  auto time_late = std::distance(times.begin(), std::lower_bound(times.begin(), times.end(), t_value));
642 
643  FP y1 = get_reproduction_number(static_cast<size_t>(time_late - 1), sim).value();
644  FP y2 = get_reproduction_number(static_cast<size_t>(time_late), sim).value();
645 
646  auto result = linear_interpolation(t_value, sim.get_result().get_time(time_late - 1),
647  sim.get_result().get_time(time_late), y1, y2);
648  return mio::success(static_cast<FP>(result));
649 }
650 
662 template <typename FP, class Base = mio::Simulation<FP, Model<FP>>>
663 auto get_mobility_factors(const Simulation<FP, Base>& sim, FP /*t*/, const Eigen::Ref<const Eigen::VectorX<FP>>& y)
664 {
665  auto& params = sim.get_model().parameters;
666  //parameters as arrays
667  auto&& p_asymp = params.template get<RecoveredPerInfectedNoSymptoms<FP>>().array().template cast<FP>();
668  auto&& p_inf = params.template get<RiskOfInfectionFromSymptomatic<FP>>().array().template cast<FP>();
669  auto&& p_inf_max = params.template get<MaxRiskOfInfectionFromSymptomatic<FP>>().array().template cast<FP>();
670  //slice of InfectedNoSymptoms
671  auto y_INS = slice(y, {Eigen::Index(InfectionState::InfectedNoSymptoms),
672  Eigen::Index(size_t(params.get_num_groups())), Eigen::Index(InfectionState::Count)});
673 
674  //compute isolation, same as infection risk from main model
675  auto test_and_trace_required =
676  ((1 - p_asymp) / params.template get<TimeInfectedNoSymptoms<FP>>().array().template cast<FP>() * y_INS.array())
677  .sum();
678  auto test_and_trace_capacity = FP(params.template get<TestAndTraceCapacity<FP>>());
679  auto test_and_trace_capacity_max_risk = FP(params.template get<TestAndTraceCapacityMaxRisk<FP>>());
680  auto riskFromInfectedSymptomatic = smoother_cosine<FP>(test_and_trace_required, test_and_trace_capacity,
681  test_and_trace_capacity * test_and_trace_capacity_max_risk,
682  p_inf.matrix(), p_inf_max.matrix());
683 
684  //set factor for infected
685  auto factors = Eigen::VectorX<FP>::Ones(y.rows()).eval();
686  slice(factors, {Eigen::Index(InfectionState::InfectedSymptoms), Eigen::Index(size_t(params.get_num_groups())),
687  Eigen::Index(InfectionState::Count)})
688  .array() = riskFromInfectedSymptomatic;
689  return factors;
690 }
691 
692 template <typename FP, class Base = mio::Simulation<FP, Model<FP>>>
693 auto test_commuters(Simulation<FP, Base>& sim, Eigen::Ref<Eigen::VectorX<FP>> mobile_population, FP time)
694 {
695  auto& model = sim.get_model();
696  FP nondetection = 1.0;
697  if (time >= model.parameters.get_start_commuter_detection() &&
698  time < model.parameters.get_end_commuter_detection()) {
699  nondetection = model.parameters.get_commuter_nondetection();
700  }
701  for (auto i = AgeGroup(0); i < model.parameters.get_num_groups(); ++i) {
702  auto INSi = model.populations.get_flat_index({i, InfectionState::InfectedNoSymptoms});
703  auto INSCi = model.populations.get_flat_index({i, InfectionState::InfectedNoSymptomsConfirmed});
704  auto ISyi = model.populations.get_flat_index({i, InfectionState::InfectedSymptoms});
705  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  sim.get_result().get_last_value()[INSi] -= mobile_population[INSi] * (1 - nondetection);
709  sim.get_result().get_last_value()[INSCi] += mobile_population[INSi] * (1 - nondetection);
710  sim.get_result().get_last_value()[ISyi] -= mobile_population[ISyi] * (1 - nondetection);
711  sim.get_result().get_last_value()[ISyCi] += mobile_population[ISyi] * (1 - nondetection);
712 
713  //reduce the number of commuters
714  mobile_population[ISyi] *= nondetection;
715  mobile_population[INSi] *= nondetection;
716  }
717 }
718 
719 } // namespace osecir
720 } // namespace mio
721 
722 #endif // ODESECIR_MODEL_H
represents a collection of contact frequency matrices that whose sum is the total number of contacts.
Definition: contact_matrix.h:536
A FlowModel is a CompartmentalModel defined by the flows between compartments.
Definition: flow_model.h:74
constexpr size_t get_flat_flow_index() const
A flat index into an array of flows (as computed by get_flows), if the only used category in Pop is C...
Definition: flow_model.h:201
Interface class defining the integration step used in a SystemIntegrator.
Definition: integrator.h:48
double simulation time.
Definition: damping.h:58
Run the Simulation in discrete steps, evolve the Model and report results.
Definition: models/abm/simulation.h:37
Model & get_model()
Get the Model that this Simulation evolves.
Definition: models/abm/simulation.h:91
Definition: ode_secir/model.h:64
Model(const Populations &pop, const ParameterSet &params)
Definition: ode_secir/model.h:71
Model(int num_agegroups)
Definition: ode_secir/model.h:76
void get_flows(Eigen::Ref< const Eigen::VectorX< FP >> pop, Eigen::Ref< const Eigen::VectorX< FP >> y, FP t, Eigen::Ref< Eigen::VectorX< FP >> flows) const override
Definition: ode_secir/model.h:82
static IOResult< Model > deserialize(IOContext &io)
deserialize an object of this class.
Definition: ode_secir/model.h:231
void serialize(IOContext &io) const
serialize this.
Definition: ode_secir/model.h:219
Parameters of an age-resolved SECIR/SECIHURD model.
Definition: ode_secir/parameters.h:399
specialization of compartment model simulation for secir models.
Definition: ode_secir/model.h:267
Eigen::Ref< Eigen::VectorX< FP > > advance(FP tmax)
advance simulation to tmax.
Definition: ode_secir/model.h:288
Simulation(mio::osecir::Model< FP > const &model, FP t0=0., FP dt=0.1)
construct a simulation.
Definition: ode_secir/model.h:275
FP m_t_last_npi_check
Definition: ode_secir/model.h:344
ad::internal::unary_intermediate< AD_TAPE_REAL, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::operations::ad_sin< AD_TAPE_REAL > > sin(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x1)
Definition: ad.hpp:913
static min_max_return_type< ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > >::type min(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &a, const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &b)
Definition: ad.hpp:2599
ad::internal::unary_intermediate< AD_TAPE_REAL, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::operations::ad_fabs< AD_TAPE_REAL > > abs(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x1)
Definition: ad.hpp:1144
static min_max_return_type< ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > >::type max(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &a, const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &b)
Definition: ad.hpp:2596
trait_value< T >::RETURN_TYPE & value(T &x)
Definition: ad.hpp:3308
Eigen::VectorX< FP > get_reproduction_numbers(const Simulation< FP, Base > &sim)
Computes the reproduction number for all time points of the Model output obtained by the Simulation.
Definition: ode_secir/model.h:611
IOResult< FP > get_reproduction_number(FP t_value, const Simulation< FP, Base > &sim)
Computes the reproduction number at a given time point of the Simulation.
Definition: ode_secir/model.h:628
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
std::vector< TimeSeries< FP > > simulate_flows(FP t0, FP tmax, FP dt, Model const &model, std::unique_ptr< OdeIntegratorCore< FP >> &&integrator_core=nullptr)
Run a FlowSimulation of a FlowModel.
Definition: flow_simulation.h:111
FP get_infections_relative(const SimulationNode< FP, Sim > &node, FP t, const Eigen::Ref< const Eigen::VectorX< FP >> &y)
get the percantage of infected people of the total population in the node If dynamic NPIs are enabled...
Definition: metapopulation_mobility_instant.h:484
auto failure(const IOStatus &s)
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:380
boost::outcome_v2::in_place_type_t< T > Tag
Type that is used for overload resolution.
Definition: io.h:407
auto i
Definition: io.h:809
details::ApplyResultT< F, T... > apply(IOContext &io, F f, const IOResult< T > &... rs)
Evaluate a function with zero or more unpacked IOResults as arguments.
Definition: io.h:481
auto linear_interpolation(const X &x_eval, const X &x_1, const X &x_2, const V &y1, const V &y2)
Linear interpolation between two data values.
Definition: interpolation.h:44
auto slice(V &&v, Seq< Eigen::Index > elems)
take a regular slice of a row or column vector.
Definition: eigen_util.h:107
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:359
TimeSeries< FP > simulate(FP t0, FP tmax, FP dt, Model const &model, std::unique_ptr< OdeIntegratorCore< FP >> &&integrator_core=nullptr)
Run a Simulation of a CompartmentalModel.
Definition: memilio/compartments/simulation.h:100
auto get_mobility_factors(const SimulationNode< FP, Sim > &node, FP t, const Eigen::Ref< const Eigen::VectorX< FP >> &y)
Get an additional mobility factor.
Definition: metapopulation_mobility_instant.h:509
constexpr std::tuple_element< I, std::tuple< Index< CategoryTags >... > >::type & get(Index< CategoryTags... > &i) noexcept
Retrieves the Index (by reference) at the Ith position of a MultiIndex.
Definition: index.h:294
boost::outcome_v2::unchecked< T, IOStatus > IOResult
Value-or-error type for operations that return a value but can fail.
Definition: io.h:353
void test_commuters(SimulationNode< FP, Sim > &node, Eigen::Ref< Eigen::VectorX< FP >> mobile_population, FP time)
Test persons when moving from their source node.
Definition: metapopulation_mobility_instant.h:533
The AgeGroup struct is used as a dynamically sized tag for all age dependent categories.
Definition: age_group.h:32
Populations populations
Definition: compartmental_model.h:156
mio::Populations< FP, AgeGroup, InfectionState > Populations
Definition: compartmental_model.h:62
ParameterSet parameters
Definition: compartmental_model.h:157
A Flow defines a possible transition between two Compartments in a FlowModel.
Definition: flow.h:33
Type specific limits for floating-points.
Definition: memilio/config.h:59
Collection of types. Each type is mapped to an index of type size_t.
Definition: type_list.h:32
the percentage of ICU patients per hospitalized patients in the SECIR model
Definition: ode_secir/parameters.h:276
the icu capacity in the SECIR model
Definition: ode_secir/parameters.h:80
the percentage of asymptomatic cases in the SECIR model
Definition: ode_secir/parameters.h:212
the risk of infection from symptomatic cases in the SECIR model
Definition: ode_secir/parameters.h:228
Multiplier for the test and trace capacity to determine when it is considered overloaded.
Definition: ode_secir/parameters.h:372
capacity to test and trace contacts of infected for quarantine per day.
Definition: ode_secir/parameters.h:356
the (mean) time in day unit for asymptomatic cases that are infectious but have not yet developed sym...
Definition: ode_secir/parameters.h:113
the time people are 'simply' hospitalized before returning home in the SECIR model in day unit
Definition: ode_secir/parameters.h:147
probability of getting infected from a contact
Definition: ode_secir/parameters.h:180