model.h Source File

CPP API: model.h Source File
lct_secir/model.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Lena Ploetzke
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 
21 #ifndef LCT_SECIR_MODEL_H
22 #define LCT_SECIR_MODEL_H
23 
24 #include "lct_secir/parameters.h"
25 #include "lct_secir/infection_state.h"
29 #include "memilio/config.h"
31 #include "memilio/utils/logging.h"
32 #include "memilio/math/eigen.h"
35 
36 #include <numbers>
37 
38 namespace mio
39 {
40 namespace lsecir
41 {
53 template <typename FP, class... LctStates>
54 class Model : public CompartmentalModel<FP, InfectionState, LctPopulations<FP, LctStates...>, Parameters<FP>>
55 {
56 public:
57  using LctStatesGroups = TypeList<LctStates...>;
59  using typename Base::ParameterSet;
60  using typename Base::Populations;
61  static size_t constexpr num_groups = sizeof...(LctStates);
62  static_assert(num_groups >= 1, "The number of LctStates provided should be at least one.");
63 
67  {
68  }
69 
74  Model(const Populations& pop, const ParameterSet& params)
75  : Base(pop, params)
76  {
77  }
78 
90  void get_derivatives(Eigen::Ref<const Eigen::VectorX<FP>> pop, Eigen::Ref<const Eigen::VectorX<FP>> y, FP t,
91  Eigen::Ref<Eigen::VectorX<FP>> dydt) const override
92  {
93  // Vectors are sorted such that we first have all InfectionState%s for AgeGroup 0,
94  // afterwards all for AgeGroup 1 and so on.
95  dydt.setZero();
96  get_derivatives_impl(pop, y, t, dydt);
97  }
98 
111  TimeSeries<FP> calculate_compartments(const TimeSeries<FP>& subcompartments_ts) const
112  {
113  Eigen::Index count_InfStates = (Eigen::Index)InfectionState::Count;
114  Eigen::Index num_compartments = count_InfStates * num_groups;
115  TimeSeries<FP> compartments_ts(num_compartments);
116  if (!(this->populations.get_num_compartments() == (size_t)subcompartments_ts.get_num_elements())) {
117  log_error("Result does not match InfectionState of the Model.");
118  Eigen::VectorX<FP> wrong_size = Eigen::VectorX<FP>::Constant(num_compartments, -1);
119  compartments_ts.add_time_point(-1, wrong_size);
120  return compartments_ts;
121  }
122  Eigen::VectorX<FP> compartments(num_compartments);
123  for (Eigen::Index timepoint = 0; timepoint < subcompartments_ts.get_num_time_points(); ++timepoint) {
124  compress_vector(subcompartments_ts[timepoint], compartments);
125  compartments_ts.add_time_point(subcompartments_ts.get_time(timepoint), compartments);
126  }
127 
128  return compartments_ts;
129  }
130 
135  bool check_constraints() const
136  {
137 
138  return (check_constraints_impl() || this->parameters.check_constraints() ||
139  this->populations.check_constraints());
140  }
141 
142 private:
152  template <size_t Group = 0>
153  void compress_vector(const Eigen::VectorX<FP>& subcompartments, Eigen::VectorX<FP>& compartments) const
154  {
155  static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
156  using LctStateGroup = type_at_index_t<Group, LctStates...>;
157 
158  // Define first index of the group Group in a vector including all compartments without a resolution
159  // in subcompartments.
160  Eigen::Index count_InfStates = (Eigen::Index)InfectionState::Count;
161  Eigen::Index first_index_group_comps = Group * count_InfStates;
162 
163  // Use function from the LctState of the Group to calculate the vector without subcompartments
164  // using the corresponding vector with subcompartments.
165  compartments.segment(first_index_group_comps, count_InfStates) =
166  LctStateGroup::calculate_compartments(subcompartments.segment(
167  this->populations.template get_first_index_of_group<Group>(), LctStateGroup::Count));
168 
169  // Function call for next group if applicable.
170  if constexpr (Group + 1 < num_groups) {
171  compress_vector<Group + 1>(subcompartments, compartments);
172  }
173  }
174 
186  template <size_t Group = 0>
187  void get_derivatives_impl(Eigen::Ref<const Eigen::VectorX<FP>> pop, Eigen::Ref<const Eigen::VectorX<FP>> y, FP t,
188  Eigen::Ref<Eigen::VectorX<FP>> dydt) const
189  {
190  static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
191  using LctStateGroup = type_at_index_t<Group, LctStates...>;
192 
193  size_t first_index_group = this->populations.template get_first_index_of_group<Group>();
194  const auto& params = this->parameters;
195  FP flow = 0;
196 
197  // Indices of first subcompartment of the InfectionState for the group in the vectors.
198  size_t Ei_first_index = first_index_group + LctStateGroup::template get_first_index<InfectionState::Exposed>();
199  size_t INSi_first_index =
200  first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedNoSymptoms>();
201  size_t ISyi_first_index =
202  first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedSymptoms>();
203  size_t ISevi_first_index =
204  first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedSevere>();
205  size_t ICri_first_index =
206  first_index_group + LctStateGroup::template get_first_index<InfectionState::InfectedCritical>();
207  size_t Ri = first_index_group + LctStateGroup::template get_first_index<InfectionState::Recovered>();
208  size_t Di = first_index_group + LctStateGroup::template get_first_index<InfectionState::Dead>();
209 
210  // Calculate derivative of the Susceptible compartment.
211  interact<Group, 0>(pop, y, t, dydt);
212 
213  // Calculate derivative of the Exposed compartment.
214  dydt[Ei_first_index] = -dydt[first_index_group];
215  for (size_t subcomp = 0; subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::Exposed>();
216  subcomp++) {
217  // Variable flow stores the value of the flow from one subcompartment to the next one.
218  // Ei_first_index + subcomp is always the index of a (sub-)compartment of Exposed and Ei_first_index
219  // + subcomp + 1 can also be the index of the first (sub-)compartment of InfectedNoSymptoms.
220  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::Exposed>() *
221  (1 / params.template get<TimeExposed<FP>>()[Group]) * y[Ei_first_index + subcomp];
222  // Subtract flow from dydt[Ei_first_index + subcomp] and add to next subcompartment.
223  dydt[Ei_first_index + subcomp] -= flow;
224  dydt[Ei_first_index + subcomp + 1] = flow;
225  }
226 
227  // Calculate derivative of the InfectedNoSymptoms compartment.
228  for (size_t subcomp = 0;
229  subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>();
230  subcomp++) {
231  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>() *
232  (1 / params.template get<TimeInfectedNoSymptoms<FP>>()[Group]) * y[INSi_first_index + subcomp];
233  dydt[INSi_first_index + subcomp] -= flow;
234  dydt[INSi_first_index + subcomp + 1] = flow;
235  }
236 
237  // Calculate derivative of the InfectedSymptoms compartment.
238  // Flow from last (sub-) compartment of C must be split between
239  // the first subcompartment of InfectedSymptoms and Recovered.
240  dydt[Ri] = dydt[ISyi_first_index] * params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[Group];
241  dydt[ISyi_first_index] =
242  dydt[ISyi_first_index] * (1 - params.template get<RecoveredPerInfectedNoSymptoms<FP>>()[Group]);
243  for (size_t subcomp = 0;
244  subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>(); subcomp++) {
245  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSymptoms>() *
246  (1 / params.template get<TimeInfectedSymptoms<FP>>()[Group]) * y[ISyi_first_index + subcomp];
247  dydt[ISyi_first_index + subcomp] -= flow;
248  dydt[ISyi_first_index + subcomp + 1] = flow;
249  }
250 
251  // Calculate derivative of the InfectedSevere compartment.
252  dydt[Ri] += dydt[ISevi_first_index] * (1 - params.template get<SeverePerInfectedSymptoms<FP>>()[Group]);
253  dydt[ISevi_first_index] = dydt[ISevi_first_index] * params.template get<SeverePerInfectedSymptoms<FP>>()[Group];
254  for (size_t subcomp = 0;
255  subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>(); subcomp++) {
256  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedSevere>() *
257  (1 / params.template get<TimeInfectedSevere<FP>>()[Group]) * y[ISevi_first_index + subcomp];
258  dydt[ISevi_first_index + subcomp] -= flow;
259  dydt[ISevi_first_index + subcomp + 1] = flow;
260  }
261 
262  // Calculate derivative of the InfectedCritical compartment.
263  dydt[Ri] += dydt[ICri_first_index] * (1 - params.template get<CriticalPerSevere<FP>>()[Group]);
264  dydt[ICri_first_index] = dydt[ICri_first_index] * params.template get<CriticalPerSevere<FP>>()[Group];
265  for (size_t subcomp = 0;
266  subcomp < LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() - 1;
267  subcomp++) {
268  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() *
269  (1 / params.template get<TimeInfectedCritical<FP>>()[Group]) * y[ICri_first_index + subcomp];
270  dydt[ICri_first_index + subcomp] -= flow;
271  dydt[ICri_first_index + subcomp + 1] = flow;
272  }
273  // Last flow from InfectedCritical has to be divided between Recovered and Dead.
274  // Must be calculated separately in order not to overwrite the already calculated values ​​for Recovered.
275  flow = (FP)LctStateGroup::template get_num_subcompartments<InfectionState::InfectedCritical>() *
276  (1 / params.template get<TimeInfectedCritical<FP>>()[Group]) * y[Ri - 1];
277  dydt[Ri - 1] -= flow;
278  dydt[Ri] = dydt[Ri] + (1 - params.template get<DeathsPerCritical<FP>>()[Group]) * flow;
279  dydt[Di] = params.template get<DeathsPerCritical<FP>>()[Group] * flow;
280 
281  // Function call for next group if applicable.
282  if constexpr (Group + 1 < num_groups) {
283  get_derivatives_impl<Group + 1>(pop, y, t, dydt);
284  }
285  }
286 
298  template <size_t Group1, size_t Group2 = 0>
299  void interact(Eigen::Ref<const Eigen::VectorX<FP>> pop, Eigen::Ref<const Eigen::VectorX<FP>> y, FP t,
300  Eigen::Ref<Eigen::VectorX<FP>> dydt) const
301  {
302  static_assert((Group1 < num_groups) && (Group1 >= 0) && (Group2 < num_groups) && (Group2 >= 0),
303  "The template parameters Group1 & Group2 should be valid.");
304  using LctStateGroup2 = type_at_index_t<Group2, LctStates...>;
305  size_t Si_1 = this->populations.template get_first_index_of_group<Group1>();
306  FP infectedNoSymptoms_2 = 0;
307  FP infectedSymptoms_2 = 0;
308  auto params = this->parameters;
309 
310  size_t first_index_group2 = this->populations.template get_first_index_of_group<Group2>();
311 
312  // Calculate sum of all subcompartments for InfectedNoSymptoms of Group2.
313  infectedNoSymptoms_2 =
314  pop.segment(first_index_group2 +
315  LctStateGroup2::template get_first_index<InfectionState::InfectedNoSymptoms>(),
316  LctStateGroup2::template get_num_subcompartments<InfectionState::InfectedNoSymptoms>())
317  .sum();
318  // Calculate sum of all subcompartments for InfectedSymptoms of Group2.
319  infectedSymptoms_2 =
320  pop.segment(first_index_group2 +
321  LctStateGroup2::template get_first_index<InfectionState::InfectedSymptoms>(),
322  LctStateGroup2::template get_num_subcompartments<InfectionState::InfectedSymptoms>())
323  .sum();
324  // Size of the Subpopulation Group2 without dead people.
325  FP N_2 = pop.segment(first_index_group2, LctStateGroup2::Count - 1).sum();
326  const FP divN_2 = (N_2 < Limits<FP>::zero_tolerance()) ? 0.0 : 1.0 / N_2;
327  FP season_val = 1 + params.template get<Seasonality<FP>>() *
328  std::sin(std::numbers::pi_v<ScalarType> *
329  ((params.template get<StartDay<FP>>() + t) / 182.5 + 0.5));
330  dydt[Si_1] += -y[Si_1] * divN_2 * season_val *
331  params.template get<TransmissionProbabilityOnContact<FP>>()[Group1] *
332  params.template get<ContactPatterns<FP>>().get_cont_freq_mat().get_matrix_at(
333  SimulationTime<FP>(t))(static_cast<Eigen::Index>(Group1), static_cast<Eigen::Index>(Group2)) *
334  (params.template get<RelativeTransmissionNoSymptoms<FP>>()[Group2] * infectedNoSymptoms_2 +
335  params.template get<RiskOfInfectionFromSymptomatic<FP>>()[Group2] * infectedSymptoms_2);
336  // Function call for next interacting group if applicable.
337  if constexpr (Group2 + 1 < num_groups) {
338  interact<Group1, Group2 + 1>(pop, y, t, dydt);
339  }
340  }
341 
349  template <size_t Group = 0>
351  {
352  static_assert((Group < num_groups) && (Group >= 0), "The template parameter Group should be valid.");
353  using LctStateGroup = type_at_index_t<Group, LctStates...>;
354 
355  if (LctStateGroup::template get_num_subcompartments<InfectionState::Susceptible>() != 1) {
356  log_warning("Constraint check: The number of subcompartments for Susceptibles of group {} should be one!",
357  Group);
358  return true;
359  }
360  if (LctStateGroup::template get_num_subcompartments<InfectionState::Recovered>() != 1) {
361  log_warning("Constraint check: The number of subcompartments for Recovered of group {} should be one!",
362  Group);
363  return true;
364  }
365  if (LctStateGroup::template get_num_subcompartments<InfectionState::Dead>() != 1) {
366  log_warning("Constraint check: The number of subcompartments for Dead of group {} should be one!", Group);
367  return true;
368  }
369 
370  if constexpr (Group == num_groups - 1) {
371  return false;
372  }
373  else {
374  return check_constraints_impl<Group + 1>();
375  }
376  }
377 };
378 
379 } // namespace lsecir
380 } // namespace mio
381 
382 #endif // LCTSECIR_MODEL_H
A class template for compartment populations of LCT models.
Definition: lct_populations.h:57
size_t get_num_compartments() const
get_num_compartments Returns the number of compartments.
Definition: lct_populations.h:75
double simulation time.
Definition: damping.h:58
stores vectors of values at time points (or some other abstract variable) the value at each time poin...
Definition: time_series.h:58
Eigen::Index get_num_elements() const
number of elements of vector at each time point
Definition: time_series.h:205
Eigen::Index get_num_time_points() const
number of time points in the series
Definition: time_series.h:197
FP & get_time(Eigen::Index i)
time of time point at index i
Definition: time_series.h:272
Eigen::Ref< Vector > add_time_point()
add one uninitialized time point
Definition: time_series.h:221
Parameters of the simulation that are the same everywhere within the Model.
Definition: abm/parameters.h:764
bool check_constraints() const
Checks whether all Parameters satisfy their corresponding constraints and logs an error if constraint...
Definition: abm/parameters.h:793
Class that defines an LCT-SECIR model.
Definition: lct_secir/model.h:55
void compress_vector(const Eigen::VectorX< FP > &subcompartments, Eigen::VectorX< FP > &compartments) const
Converts a vector with subcompartments in a vector without subcompartments by summing up subcompartme...
Definition: lct_secir/model.h:153
bool check_constraints() const
Checks that the model satisfies all constraints (e.g.
Definition: lct_secir/model.h:135
void get_derivatives_impl(Eigen::Ref< const Eigen::VectorX< FP >> pop, Eigen::Ref< const Eigen::VectorX< FP >> y, FP t, Eigen::Ref< Eigen::VectorX< FP >> dydt) const
Evaluates the right-hand-side f of the ODE dydt = f(y, t) recursively for each group.
Definition: lct_secir/model.h:187
void interact(Eigen::Ref< const Eigen::VectorX< FP >> pop, Eigen::Ref< const Eigen::VectorX< FP >> y, FP t, Eigen::Ref< Eigen::VectorX< FP >> dydt) const
Calculates the derivative of the Susceptible compartment for Group1.
Definition: lct_secir/model.h:299
Model()
Default constructor.
Definition: lct_secir/model.h:65
TimeSeries< FP > calculate_compartments(const TimeSeries< FP > &subcompartments_ts) const
Cumulates a simulation result with subcompartments to produce a result that divides the population on...
Definition: lct_secir/model.h:111
void get_derivatives(Eigen::Ref< const Eigen::VectorX< FP >> pop, Eigen::Ref< const Eigen::VectorX< FP >> y, FP t, Eigen::Ref< Eigen::VectorX< FP >> dydt) const override
Evaluates the right-hand-side f of the ODE dydt = f(y, t).
Definition: lct_secir/model.h:90
bool check_constraints_impl() const
Checks whether LctState of a group satisfies all constraints.
Definition: lct_secir/model.h:350
Model(const Populations &pop, const ParameterSet &params)
Constructor using Populations and ParameterSet.
Definition: lct_secir/model.h:74
static constexpr size_t num_groups
Definition: lct_secir/model.h:61
Parameters of an LCT-SECIR model.
Definition: lct_secir/parameters.h:305
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
InfectionState
The InfectionState enum describes the basic categories for the infection state of persons.
Definition: lct_secir/infection_state.h:34
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
void log_warning(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:112
typename type_at_index< Index, Types... >::type type_at_index_t
The type at the Index-th position in the list Types.
Definition: metaprogramming.h:118
void log_error(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:100
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
CompartmentalModel is a template for a compartmental model for an array of initial populations and a ...
Definition: compartmental_model.h:59
LctPopulations< FP, LctStates... > Populations
Definition: compartmental_model.h:62
Collection of types. Each type is mapped to an index of type size_t.
Definition: type_list.h:32
The contact patterns within the society are modelled using an UncertainContactMatrix.
Definition: lct_secir/parameters.h:143
The risk of infection from symptomatic cases for each group in the SECIR model.
Definition: lct_secir/parameters.h:179
The start day in the LCT SECIR model.
Definition: lct_secir/parameters.h:262