parameters_io.h Source File

CPP API: parameters_io.h Source File
models/ide_secir/parameters_io.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Lena Ploetzke, Anna Wendler
5 *
6 * Contact: Martin J. Kuehn <Martin.Kuehn@DLR.de>
7 *
8 * Licensed under the Apache License, Version 2.0 (the "License");
9 * you may not use this file except in compliance with the License.
10 * You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing, software
15 * distributed under the License is distributed on an "AS IS" BASIS,
16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17 * See the License for the specific language governing permissions and
18 * limitations under the License.
19 */
20 #ifndef IDE_INITIALFLOWS_H
21 #define IDE_INITIALFLOWS_H
22 
23 #include "memilio/config.h"
24 
25 #ifdef MEMILIO_HAS_JSONCPP
26 
27 #include "ide_secir/model.h"
29 
31 #include "memilio/io/epi_data.h"
32 #include "memilio/io/io.h"
33 #include "memilio/utils/date.h"
34 #include "memilio/utils/logging.h"
35 
36 #include <string>
37 
38 namespace mio
39 {
40 namespace isecir
41 {
42 
78 template <typename EntryType>
79 IOResult<void> set_initial_flows(Model& model, const ScalarType dt, const std::vector<EntryType> rki_data,
80  const Date date, const CustomIndexArray<ScalarType, AgeGroup> scale_confirmed_cases)
81 {
82  // Check if scale_confirmed_cases has the right size (= number of age groups).
83  assert(model.get_num_agegroups() == (size_t)scale_confirmed_cases.size());
84  // Check if the correct EntryType was used.
85  if constexpr (std::is_same_v<EntryType, ConfirmedCasesDataEntry>) {
86  assert(model.get_num_agegroups() == (size_t)EntryType::age_group_names.size());
87  }
88  else {
89  assert(model.get_num_agegroups() == 1);
90  }
91 
92  //--- Preparations ---
93  auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
94  return a.date < b.date;
95  });
96  if (max_date_entry == rki_data.end()) {
97  log_error("RKI data file is empty.");
98  return failure(StatusCode::InvalidFileFormat, "RKI data file is empty.");
99  }
100  auto max_date = max_date_entry->date;
101  if (max_date < date) {
102  log_error("Specified date does not exist in RKI data.");
103  return failure(StatusCode::OutOfRange, "Specified date does not exist in RKI data.");
104  }
105 
106  // Get (global) support_max to determine how many flows in the past we have to compute.
107  ScalarType global_support_max = model.get_global_support_max(dt);
108  Eigen::Index global_support_max_index = Eigen::Index(std::ceil(global_support_max / dt));
109 
110  // Get the number of AgeGroups.
111  const size_t num_age_groups = model.get_num_agegroups();
112 
113  // m_transitions should be empty at the beginning.
114  if (model.transitions.get_num_time_points() > 0) {
115  model.transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
116  }
117  if (model.populations.get_time(0) != 0) {
118  model.populations.remove_last_time_point();
119  model.populations.add_time_point<Eigen::VectorXd>(
120  0, TimeSeries<ScalarType>::Vector::Constant((int)InfectionState::Count * num_age_groups, 0));
121  }
122 
123  // The first time we need is -4 * global_support_max because we need values for
124  // InfectedNoSymptomsToInfectedSymptoms on this time window to compute all consecutive transitions on the time
125  // window from -global_support_max to 0.
126  Eigen::Index start_shift = 4 * global_support_max_index;
127  // The last time needed is dependent on the mean stay time in the Exposed compartment and
128  // the mean stay time of asymptomatic individuals in InfectedNoSymptoms.
129  // The mean stay time in a compartment may be dependent on the AgeGroup.
130  CustomIndexArray<ScalarType, AgeGroup> mean_ExposedToInfectedNoSymptoms =
131  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
132  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedNoSymptomsToInfectedSymptoms =
133  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
134  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSymptomsToInfectedSevere =
135  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
136  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSevereToInfectedCritical =
137  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
138  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedCriticalToDead =
139  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
140  Eigen::Index last_time_index_needed = 0;
141 
142  for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
143  // Set the Dead compartment to 0 so that RKI data can be added correctly.
144  int Di = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
145  model.populations[0][Di] = 0;
146 
147  mean_ExposedToInfectedNoSymptoms[group] =
148  model.parameters
149  .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms)]
150  .get_mean(dt);
151  mean_InfectedNoSymptomsToInfectedSymptoms[group] =
152  model.parameters
153  .get<TransitionDistributions>()[group]
155  .get_mean(dt);
156  mean_InfectedSymptomsToInfectedSevere[group] =
157  model.parameters
158  .get<TransitionDistributions>()[group]
160  .get_mean(dt);
161  mean_InfectedSevereToInfectedCritical[group] =
162  model.parameters
163  .get<TransitionDistributions>()[group]
165  .get_mean(dt);
166  mean_InfectedCriticalToDead[group] =
167  model.parameters
168  .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::InfectedCriticalToDead)]
169  .get_mean(dt);
170  if (last_time_index_needed <
171  Eigen::Index(std::ceil(
172  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt))) {
173  last_time_index_needed = Eigen::Index(std::ceil(
174  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
175  }
176  }
177 
178  // Create TimeSeries with zeros. The index of time zero is start_shift.
179  for (Eigen::Index i = -start_shift; i <= last_time_index_needed; i++) {
180  // Add time point.
181  model.transitions.add_time_point(
182  i * dt, TimeSeries<ScalarType>::Vector::Constant((size_t)InfectionTransition::Count * num_age_groups, 0.));
183  }
184 
185  // Setting the entries in m_total_confirmed_cases to zero before overwriting it with the RKI data.
186  model.total_confirmed_cases = CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
187  //--- Calculate the flow InfectedNoSymptomsToInfectedSymptoms using the RKI data and store in the m_transitions object.---
188  ScalarType min_offset_needed = std::ceil(
189  model.transitions.get_time(0) -
190  1); // Need -1 if first time point is integer and just the floor value if not, therefore use ceil and -1
191  ScalarType max_offset_needed = std::ceil(model.transitions.get_last_time());
192  bool min_offset_needed_avail = false;
193  bool max_offset_needed_avail = false;
194 
195  // Go through the entries of rki_data and check if date is needed for calculation. Confirmed cases are scaled.
196  // Define dummy variables to store the first and the last index of the TimeSeries where the considered entry of
197  // rki_data is potentially needed.
198  Eigen::Index idx_needed_first = 0;
199  Eigen::Index idx_needed_last = 0;
200  ScalarType time_idx = 0;
201 
202  for (auto&& entry : rki_data) {
203  int offset = get_offset_in_days(entry.date, date);
204 
205  // Get the index regarding the age group.
206  // If we don't have age resolution and use EntryType=ConfirmedCasesNoAge, the index is set to 1.
207  // If we consider multiple age groups and use EntryType=ConfirmedCasesDataEntry, it is determined accordingly.
208  AgeGroup group = AgeGroup(0);
209  if constexpr (std::is_same_v<EntryType, ConfirmedCasesDataEntry>) {
210  group = entry.age_group;
211  }
212 
213  if ((offset >= min_offset_needed) && (offset <= max_offset_needed)) {
214  if (offset == min_offset_needed) {
215  min_offset_needed_avail = true;
216  }
217  if (offset == max_offset_needed) {
218  max_offset_needed_avail = true;
219  }
220  // Smallest index for which the entry is needed.
221  idx_needed_first =
222  Eigen::Index(std::max(std::floor((offset - model.transitions.get_time(0) - 1) / dt), 0.));
223  // Biggest index for which the entry is needed.
224  idx_needed_last = Eigen::Index(std::min(std::ceil((offset - model.transitions.get_time(0) + 1) / dt),
225  ScalarType(model.transitions.get_num_time_points() - 1)));
226 
227  int INStISyi = model.get_transition_flat_index(
229 
230  for (Eigen::Index i = idx_needed_first; i <= idx_needed_last; i++) {
231 
232  time_idx = model.transitions.get_time(i);
233  if (offset == int(std::floor(time_idx))) {
234  model.transitions[i][INStISyi] +=
235  (1 - (time_idx - std::floor(time_idx))) * scale_confirmed_cases[group] * entry.num_confirmed;
236  }
237  if (offset == int(std::ceil(time_idx))) {
238  model.transitions[i][INStISyi] +=
239  (time_idx - std::floor(time_idx)) * scale_confirmed_cases[group] * entry.num_confirmed;
240  }
241  if (offset == int(std::floor(time_idx - dt))) {
242  model.transitions[i][INStISyi] -= (1 - (time_idx - dt - std::floor(time_idx - dt))) *
243  scale_confirmed_cases[group] * entry.num_confirmed;
244  }
245  if (offset == int(std::ceil(time_idx - dt))) {
246  model.transitions[i][INStISyi] -= (time_idx - dt - std::floor(time_idx - dt)) *
247  scale_confirmed_cases[group] * entry.num_confirmed;
248  }
249  }
250 
251  // Compute Dead by shifting RKI data according to mean stay times.
252  // This is done because the RKI reports death with the date of positive test instead of the date of deaths.
253  int Di = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
254  if (offset ==
255  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
256  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) {
257  model.populations[0][Di] +=
258  (1 -
259  (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
260  mean_InfectedCriticalToDead[group] -
261  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
262  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group]))) *
263  entry.num_deaths;
264  }
265  if (offset ==
266  std::ceil(-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
267  mean_InfectedCriticalToDead[group])) {
268  model.populations[0][Di] +=
269  (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
270  mean_InfectedCriticalToDead[group] -
271  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
272  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) *
273  entry.num_deaths;
274  }
275 
276  if (offset == 0) {
277  model.total_confirmed_cases[group] = scale_confirmed_cases[group] * entry.num_confirmed;
278  }
279  }
280  }
281 
282  if (!max_offset_needed_avail) {
283  log_error("Necessary range of dates needed to compute initial values does not exist in RKI data.");
284  return failure(StatusCode::OutOfRange, "Necessary range of dates does not exist in RKI data.");
285  }
286  if (!min_offset_needed_avail) {
287  auto min_date_entry = std::min_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
288  return a.date < b.date;
289  });
290  auto min_date = min_date_entry->date;
291  // Get first date that is needed.
292  mio::Date min_offset_date = offset_date_by_days(date, int(min_offset_needed));
293  log_warning("RKI data is needed from {} to compute initial values. RKI data is only available from {}. Missing "
294  "dates were set to 0.",
295  min_offset_date, min_date);
296  }
297 
298  //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms (that were set above using rki_data). ---
299  // Set m_support_max_vector and m_derivative_vector in the model which is needed for the following computations.
300  model.set_transitiondistributions_support_max(dt);
301  model.set_transitiondistributions_derivative(dt);
302 
303  for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
304  //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms. ---
305  // Compute flow InfectedSymptomsToInfectedSevere for -3 * global_support_max, ..., 0.
306  for (Eigen::Index i = -3 * global_support_max_index; i <= 0; i++) {
307  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere),
309  i + start_shift, group);
310  }
311  // Compute flow InfectedSevereToInfectedCritical for -2 * global_support_max, ..., 0.
312  for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
313  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical),
314  Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
315  group);
316  }
317  // Compute flows from InfectedSymptoms, InfectedSevere and InfectedCritical to Recovered and
318  // flow InfectedCriticalToDead for -global_support_max, ..., 0.
319  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
320  // Compute flow InfectedSymptomsToRecovered.
321  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToRecovered),
323  i + start_shift, group);
324  // Compute flow InfectedSevereToRecovered.
325  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToRecovered),
326  Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
327  group);
328  // Compute flow InfectedCriticalToRecovered.
329  model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToRecovered),
330  Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
331  group);
332  // Compute flow InfectedCriticalToDead.
333  model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToDead),
334  Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
335  group);
336  }
337 
338  //--- Calculate the remaining flows. ---
339  // Compute flow ExposedToInfectedNoSymptoms for -2 * global_support_max, ..., 0.
340  // Use mean value of the TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
341 
342  Eigen::Index index_shift_mean = Eigen::Index(std::round(mean_InfectedNoSymptomsToInfectedSymptoms[group] / dt));
343  int EtINSi =
344  model.get_transition_flat_index(Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), group);
345  int INStISyi = model.get_transition_flat_index(
347  for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
348  model.transitions[i + start_shift][EtINSi] =
349  (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
351  model.transitions[i + start_shift + index_shift_mean][INStISyi];
352  }
353 
354  // Compute flow SusceptibleToExposed for -global_support_max, ..., 0.
355  // Use mean values of the TransitionDistribution ExposedToInfectedNoSymptoms and of the
356  // TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
357  index_shift_mean = Eigen::Index(std::round(
358  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
359  int StEi = model.get_transition_flat_index(Eigen::Index(InfectionTransition::SusceptibleToExposed), group);
360 
361  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
362  model.transitions[i + start_shift][StEi] =
363  (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
365  model.transitions[i + start_shift + index_shift_mean][INStISyi];
366  }
367 
368  // InfectedNoSymptomsToRecovered for -global_support_max, ..., 0.
369  // If we previously calculated the transition ExposedToInfectedNoSymptoms, we can calculate this transition
370  // using the standard formula.
371  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
372  model.compute_flow(Eigen::Index(InfectionTransition::InfectedNoSymptomsToRecovered),
373  Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), dt, i + start_shift,
374  group);
375  }
376  }
377 
378  // At the end of the calculation, delete all time points that are not required for the simulation.
379  auto transition_copy(model.transitions);
380  model.transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
381  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
382  model.transitions.add_time_point(i * dt, transition_copy.get_value(i + start_shift));
383  }
384 
385  return mio::success();
386 }
387 
388 } // namespace isecir
389 } // namespace mio
390 
391 #endif // MEMILIO_HAS_JSONCPP
392 
393 #endif // IDE_INITIALFLOWS_H
double ScalarType
Configuration of memilio library.
Definition: memilio/config.h:30
static double floor(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x)
Definition: ad.hpp:2451
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
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
static double ceil(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x)
Definition: ad.hpp:2449
IOResult< void > set_initial_flows(Model &model, const ScalarType dt, const std::vector< EntryType > rki_data, const Date date, const CustomIndexArray< ScalarType, AgeGroup > scale_confirmed_cases)
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
auto failure(const IOStatus &s)
Create an object that is implicitly convertible to an error IOResult<T>.
Definition: io.h:380
auto i
Definition: io.h:809
void log_warning(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:112
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:359
int get_offset_in_days(Date date1, Date date2)
Computes the offset in days given two dates: first date minus second date.
Definition: date.h:310
void log_error(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:100
Date offset_date_by_days(Date date, int offset_days)
Computes the new date corresponding to a given date and a offset in days.
Definition: date.h:242
Simple date representation as year, month, and day.
Definition: date.h:50