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 
81 template <typename EntryType>
82 IOResult<void> set_initial_flows(Model& model, const ScalarType dt, const std::vector<EntryType> rki_data,
83  const Date date, const CustomIndexArray<ScalarType, AgeGroup> scale_confirmed_cases)
84 {
85  // Check if scale_confirmed_cases has the right size (= number of age groups).
86  assert(model.get_num_agegroups() == (size_t)scale_confirmed_cases.size());
87  // Check if the correct EntryType was used.
88  if constexpr (std::is_same_v<EntryType, ConfirmedCasesDataEntry>) {
89  assert(model.get_num_agegroups() == (size_t)EntryType::age_group_names.size());
90  }
91  else {
92  assert(model.get_num_agegroups() == 1);
93  }
94 
95  //--- Preparations ---
96  auto max_date_entry = std::max_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
97  return a.date < b.date;
98  });
99  if (max_date_entry == rki_data.end()) {
100  log_error("RKI data file is empty.");
101  return failure(StatusCode::InvalidFileFormat, "RKI data file is empty.");
102  }
103  auto max_date = max_date_entry->date;
104  if (max_date < date) {
105  log_error("Specified date does not exist in RKI data.");
106  return failure(StatusCode::OutOfRange, "Specified date does not exist in RKI data.");
107  }
108 
109  // Get (global) support_max to determine how many flows in the past we have to compute.
110  ScalarType global_support_max = model.get_global_support_max(dt);
111  Eigen::Index global_support_max_index = Eigen::Index(std::ceil(global_support_max / dt));
112 
113  // Get the number of AgeGroups.
114  const size_t num_age_groups = model.get_num_agegroups();
115 
116  // m_transitions should be empty at the beginning.
117  if (model.transitions.get_num_time_points() > 0) {
118  model.transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
119  }
120  if (model.populations.get_time(0) != 0) {
121  model.populations.remove_last_time_point();
122  model.populations.add_time_point<Eigen::VectorXd>(
123  0, TimeSeries<ScalarType>::Vector::Constant((int)InfectionState::Count * num_age_groups, 0));
124  }
125 
126  // The first time we need is -4 * global_support_max because we need values for
127  // InfectedNoSymptomsToInfectedSymptoms on this time window to compute all consecutive transitions on the time
128  // window from -global_support_max to 0.
129  Eigen::Index start_shift = 4 * global_support_max_index;
130  // The last time needed is dependent on the mean stay time in the Exposed compartment and
131  // the mean stay time of asymptomatic individuals in InfectedNoSymptoms.
132  // The mean stay time in a compartment may be dependent on the AgeGroup.
133  CustomIndexArray<ScalarType, AgeGroup> mean_ExposedToInfectedNoSymptoms =
134  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
135  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedNoSymptomsToInfectedSymptoms =
136  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
137  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSymptomsToInfectedSevere =
138  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
139  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedSevereToInfectedCritical =
140  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
141  CustomIndexArray<ScalarType, AgeGroup> mean_InfectedCriticalToDead =
142  CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
143  Eigen::Index last_time_index_needed = 0;
144 
145  for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
146  // Set the Dead compartment to 0 so that RKI data can be added correctly.
147  int Di = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
148  model.populations[0][Di] = 0;
149 
150  mean_ExposedToInfectedNoSymptoms[group] =
151  model.parameters
152  .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms)]
153  .get_mean(dt);
154  mean_InfectedNoSymptomsToInfectedSymptoms[group] =
155  model.parameters
156  .get<TransitionDistributions>()[group]
158  .get_mean(dt);
159  mean_InfectedSymptomsToInfectedSevere[group] =
160  model.parameters
161  .get<TransitionDistributions>()[group]
163  .get_mean(dt);
164  mean_InfectedSevereToInfectedCritical[group] =
165  model.parameters
166  .get<TransitionDistributions>()[group]
168  .get_mean(dt);
169  mean_InfectedCriticalToDead[group] =
170  model.parameters
171  .get<TransitionDistributions>()[group][Eigen::Index(InfectionTransition::InfectedCriticalToDead)]
172  .get_mean(dt);
173  if (last_time_index_needed <
174  Eigen::Index(std::ceil(
175  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt))) {
176  last_time_index_needed = Eigen::Index(std::ceil(
177  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
178  }
179  }
180 
181  // Create TimeSeries with zeros. The index of time zero is start_shift.
182  for (Eigen::Index i = -start_shift; i <= last_time_index_needed; i++) {
183  // Add time point.
184  model.transitions.add_time_point(
185  i * dt, TimeSeries<ScalarType>::Vector::Constant((size_t)InfectionTransition::Count * num_age_groups, 0.));
186  }
187 
188  // Setting the entries in m_total_confirmed_cases to zero before overwriting it with the RKI data.
189  model.total_confirmed_cases = CustomIndexArray<ScalarType, AgeGroup>(AgeGroup(num_age_groups), 0.);
190  //--- Calculate the flow InfectedNoSymptomsToInfectedSymptoms using the RKI data and store in the m_transitions object.---
191  ScalarType min_offset_needed = std::ceil(
192  model.transitions.get_time(0) -
193  1); // Need -1 if first time point is integer and just the floor value if not, therefore use ceil and -1
194  ScalarType max_offset_needed = std::ceil(model.transitions.get_last_time());
195  bool min_offset_needed_avail = false;
196  bool max_offset_needed_avail = false;
197 
198  // Go through the entries of rki_data and check if date is needed for calculation. Confirmed cases are scaled.
199  // Define dummy variables to store the first and the last index of the TimeSeries where the considered entry of
200  // rki_data is potentially needed.
201  Eigen::Index idx_needed_first = 0;
202  Eigen::Index idx_needed_last = 0;
203  ScalarType time_idx = 0;
204 
205  for (auto&& entry : rki_data) {
206  int offset = get_offset_in_days(entry.date, date);
207 
208  // Get the index regarding the age group.
209  // If we don't have age resolution and use EntryType=ConfirmedCasesNoAge, the index is set to 1.
210  // If we consider multiple age groups and use EntryType=ConfirmedCasesDataEntry, it is determined accordingly.
211  AgeGroup group = AgeGroup(0);
212  if constexpr (std::is_same_v<EntryType, ConfirmedCasesDataEntry>) {
213  group = entry.age_group;
214  }
215 
216  if ((offset >= min_offset_needed) && (offset <= max_offset_needed)) {
217  if (offset == min_offset_needed) {
218  min_offset_needed_avail = true;
219  }
220  if (offset == max_offset_needed) {
221  max_offset_needed_avail = true;
222  }
223  // Smallest index for which the entry is needed.
224  idx_needed_first =
225  Eigen::Index(std::max(std::floor((offset - model.transitions.get_time(0) - 1) / dt), 0.));
226  // Biggest index for which the entry is needed.
227  idx_needed_last = Eigen::Index(std::min(std::ceil((offset - model.transitions.get_time(0) + 1) / dt),
228  ScalarType(model.transitions.get_num_time_points() - 1)));
229 
230  int INStISyi = model.get_transition_flat_index(
232 
233  for (Eigen::Index i = idx_needed_first; i <= idx_needed_last; i++) {
234 
235  time_idx = model.transitions.get_time(i);
236  if (offset == int(std::floor(time_idx))) {
237  model.transitions[i][INStISyi] +=
238  (1 - (time_idx - std::floor(time_idx))) * scale_confirmed_cases[group] * entry.num_confirmed;
239  }
240  if (offset == int(std::ceil(time_idx))) {
241  model.transitions[i][INStISyi] +=
242  (time_idx - std::floor(time_idx)) * scale_confirmed_cases[group] * entry.num_confirmed;
243  }
244  if (offset == int(std::floor(time_idx - dt))) {
245  model.transitions[i][INStISyi] -= (1 - (time_idx - dt - std::floor(time_idx - dt))) *
246  scale_confirmed_cases[group] * entry.num_confirmed;
247  }
248  if (offset == int(std::ceil(time_idx - dt))) {
249  model.transitions[i][INStISyi] -= (time_idx - dt - std::floor(time_idx - dt)) *
250  scale_confirmed_cases[group] * entry.num_confirmed;
251  }
252  }
253 
254  // Compute Dead by shifting RKI data according to mean stay times.
255  // This is done because the RKI reports death with the date of positive test instead of the date of deaths.
256  int Di = model.get_state_flat_index(Eigen::Index(InfectionState::Dead), group);
257  if (offset ==
258  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
259  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) {
260  model.populations[0][Di] +=
261  (1 -
262  (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
263  mean_InfectedCriticalToDead[group] -
264  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
265  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group]))) *
266  entry.num_deaths;
267  }
268  if (offset ==
269  std::ceil(-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
270  mean_InfectedCriticalToDead[group])) {
271  model.populations[0][Di] +=
272  (-mean_InfectedSymptomsToInfectedSevere[group] - mean_InfectedSevereToInfectedCritical[group] -
273  mean_InfectedCriticalToDead[group] -
274  std::floor(-mean_InfectedSymptomsToInfectedSevere[group] -
275  mean_InfectedSevereToInfectedCritical[group] - mean_InfectedCriticalToDead[group])) *
276  entry.num_deaths;
277  }
278 
279  if (offset == 0) {
280  model.total_confirmed_cases[group] = scale_confirmed_cases[group] * entry.num_confirmed;
281  }
282  }
283  }
284 
285  if (!max_offset_needed_avail) {
286  log_error("Necessary range of dates needed to compute initial values does not exist in RKI data.");
287  return failure(StatusCode::OutOfRange, "Necessary range of dates does not exist in RKI data.");
288  }
289  if (!min_offset_needed_avail) {
290  auto min_date_entry = std::min_element(rki_data.begin(), rki_data.end(), [](auto&& a, auto&& b) {
291  return a.date < b.date;
292  });
293  auto min_date = min_date_entry->date;
294  // Get first date that is needed.
295  mio::Date min_offset_date = offset_date_by_days(date, int(min_offset_needed));
296  log_warning("RKI data is needed from {} to compute initial values. RKI data is only available from {}. Missing "
297  "dates were set to 0.",
298  min_offset_date, min_date);
299  }
300 
301  //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms (that were set above using rki_data). ---
302  // Set m_support_max_vector and m_derivative_vector in the model which is needed for the following computations.
303  model.set_transitiondistributions_support_max(dt);
304  model.set_transitiondistributions_derivative(dt);
305 
306  for (AgeGroup group = AgeGroup(0); group < AgeGroup(num_age_groups); group++) {
307  //--- Calculate the flows "after" InfectedNoSymptomsToInfectedSymptoms. ---
308  // Compute flow InfectedSymptomsToInfectedSevere for -3 * global_support_max, ..., 0.
309  for (Eigen::Index i = -3 * global_support_max_index; i <= 0; i++) {
310  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere),
312  i + start_shift, group);
313  }
314  // Compute flow InfectedSevereToInfectedCritical for -2 * global_support_max, ..., 0.
315  for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
316  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical),
317  Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
318  group);
319  }
320  // Compute flows from InfectedSymptoms, InfectedSevere and InfectedCritical to Recovered and
321  // flow InfectedCriticalToDead for -global_support_max, ..., 0.
322  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
323  // Compute flow InfectedSymptomsToRecovered.
324  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSymptomsToRecovered),
326  i + start_shift, group);
327  // Compute flow InfectedSevereToRecovered.
328  model.compute_flow(Eigen::Index(InfectionTransition::InfectedSevereToRecovered),
329  Eigen::Index(InfectionTransition::InfectedSymptomsToInfectedSevere), dt, i + start_shift,
330  group);
331  // Compute flow InfectedCriticalToRecovered.
332  model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToRecovered),
333  Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
334  group);
335  // Compute flow InfectedCriticalToDead.
336  model.compute_flow(Eigen::Index(InfectionTransition::InfectedCriticalToDead),
337  Eigen::Index(InfectionTransition::InfectedSevereToInfectedCritical), dt, i + start_shift,
338  group);
339  }
340 
341  //--- Calculate the remaining flows. ---
342  // Compute flow ExposedToInfectedNoSymptoms for -2 * global_support_max, ..., 0.
343  // Use mean value of the TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
344 
345  Eigen::Index index_shift_mean = Eigen::Index(std::round(mean_InfectedNoSymptomsToInfectedSymptoms[group] / dt));
346  int EtINSi =
347  model.get_transition_flat_index(Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), group);
348  int INStISyi = model.get_transition_flat_index(
350  for (Eigen::Index i = -2 * global_support_max_index; i <= 0; i++) {
351  model.transitions[i + start_shift][EtINSi] =
352  (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
354  model.transitions[i + start_shift + index_shift_mean][INStISyi];
355  }
356 
357  // Compute flow SusceptibleToExposed for -global_support_max, ..., 0.
358  // Use mean values of the TransitionDistribution ExposedToInfectedNoSymptoms and of the
359  // TransitionDistribution InfectedNoSymptomsToInfectedSymptoms for the calculation.
360  index_shift_mean = Eigen::Index(std::round(
361  (mean_ExposedToInfectedNoSymptoms[group] + mean_InfectedNoSymptomsToInfectedSymptoms[group]) / dt));
362  int StEi = model.get_transition_flat_index(Eigen::Index(InfectionTransition::SusceptibleToExposed), group);
363 
364  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
365  model.transitions[i + start_shift][StEi] =
366  (1 / model.parameters.get<TransitionProbabilities>()[group][Eigen::Index(
368  model.transitions[i + start_shift + index_shift_mean][INStISyi];
369  }
370 
371  // InfectedNoSymptomsToRecovered for -global_support_max, ..., 0.
372  // If we previously calculated the transition ExposedToInfectedNoSymptoms, we can calculate this transition
373  // using the standard formula.
374  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
375  model.compute_flow(Eigen::Index(InfectionTransition::InfectedNoSymptomsToRecovered),
376  Eigen::Index(InfectionTransition::ExposedToInfectedNoSymptoms), dt, i + start_shift,
377  group);
378  }
379  }
380 
381  // At the end of the calculation, delete all time points that are not required for the simulation.
382  auto transition_copy(model.transitions);
383  model.transitions = TimeSeries<ScalarType>(Eigen::Index(InfectionTransition::Count) * num_age_groups);
384  for (Eigen::Index i = -global_support_max_index; i <= 0; i++) {
385  model.transitions.add_time_point(i * dt, transition_copy.get_value(i + start_shift));
386  }
387 
388  return mio::success();
389 }
390 
391 } // namespace isecir
392 } // namespace mio
393 
394 #endif // MEMILIO_HAS_JSONCPP
395 
396 #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:381
auto i
Definition: io.h:810
void log_warning(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:126
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:360
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:114
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