analyze_result.h Source File

CPP API: analyze_result.h Source File
memilio/data/analyze_result.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Wadim Koslow, Daniel Abele, David Kerkmann, Sascha Korf
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 MEMILIO_DATA_ANALYZE_RESULT_H
21 #define MEMILIO_DATA_ANALYZE_RESULT_H
22 
23 #include "memilio/config.h"
24 #include "memilio/utils/logging.h"
28 #include "memilio/io/io.h"
29 
30 #include <cassert>
31 #include <vector>
32 
33 namespace mio
34 {
35 
49 template <typename FP>
50 TimeSeries<FP> interpolate_simulation_result(const TimeSeries<FP>& simulation_result,
51  const FP abs_tol = FP{100.} * Limits<FP>::zero_tolerance());
52 
61 template <typename FP>
62 TimeSeries<FP> interpolate_simulation_result(const TimeSeries<FP>& simulation_result,
63  const std::vector<FP>& interpolation_times);
64 
68 template <class T>
69 using InterpolateResultT = std::decay_t<decltype(interpolate_simulation_result(std::declval<T>()))>;
70 
77 template <class T>
78 std::vector<InterpolateResultT<T>> interpolate_ensemble_results(const std::vector<T>& ensemble_results)
79 {
80  std::vector<InterpolateResultT<T>> interpolated;
81  interpolated.reserve(ensemble_results.size());
82  std::transform(ensemble_results.begin(), ensemble_results.end(), std::back_inserter(interpolated), [](auto& run) {
83  return interpolate_simulation_result(run);
84  });
85  return interpolated;
86 }
87 
88 template <typename FP>
89 std::vector<std::vector<TimeSeries<FP>>> sum_nodes(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_result);
90 
99 template <typename FP>
100 std::vector<TimeSeries<FP>> ensemble_mean(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_results);
101 
113 template <typename FP>
114 std::vector<TimeSeries<FP>> ensemble_percentile(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_result, FP p);
121 template <typename FP, class Simulation>
122 std::vector<TimeSeries<FP>>
124 {
125  std::vector<TimeSeries<FP>> interpolated;
126  interpolated.reserve(graph_result.nodes().size());
127  std::transform(graph_result.nodes().begin(), graph_result.nodes().end(), std::back_inserter(interpolated),
128  [](auto& n) {
129  return interpolate_simulation_result(n.property.get_result());
130  });
131  return interpolated;
132 }
133 
142 template <typename FP>
143 FP result_distance_2norm(const std::vector<mio::TimeSeries<FP>>& result1,
144  const std::vector<mio::TimeSeries<FP>>& result2);
145 
156 template <typename FP, class InfectionState>
157 FP result_distance_2norm(const std::vector<mio::TimeSeries<FP>>& result1,
158  const std::vector<mio::TimeSeries<FP>>& result2, InfectionState compartment)
159 {
160  using std::sqrt;
161 
162  assert(result1.size() == result2.size());
163  assert(result1.size() > 0);
164  assert(result1[0].get_num_time_points() > 0);
165  assert(result1[0].get_num_elements() > 0);
166 
167  auto num_compartments = Eigen::Index(InfectionState::Count);
168  auto num_age_groups = result1[0].get_num_elements() / num_compartments;
169 
170  auto norm_sqr = 0.0;
171  for (auto iter_node1 = result1.begin(), iter_node2 = result2.begin(); iter_node1 < result1.end();
172  ++iter_node1, ++iter_node2) {
173  for (Eigen::Index time_idx = 0; time_idx < iter_node1->get_num_time_points(); ++time_idx) {
174  auto v1 = (*iter_node1)[time_idx];
175  auto v2 = (*iter_node2)[time_idx];
176  for (Eigen::Index age_idx = 0; age_idx < num_age_groups; ++age_idx) {
177  auto d1 = v1[age_idx * num_compartments + Eigen::Index(compartment)];
178  auto d2 = v2[age_idx * num_compartments + Eigen::Index(compartment)];
179  norm_sqr += (d1 - d2) * (d1 - d2);
180  }
181  }
182  }
183  return sqrt(norm_sqr);
184 }
185 
186 template <typename FP>
187 TimeSeries<FP> interpolate_simulation_result(const TimeSeries<FP>& simulation_result, const FP abs_tol)
188 {
189  assert(abs_tol < 1);
190  using std::ceil;
191  using std::floor;
192  const auto t0 = simulation_result.get_time(0);
193  const auto t_max = simulation_result.get_last_time();
194  // add an additional day, if the first time point is within tolerance of floor(t0)
195  const auto day0 = ceil(t0 - abs_tol);
196  // add an additional day, if the last time point is within tolerance of ceil(tmax)
197  const auto day_max = floor(t_max + abs_tol);
198 
199  // create interpolation_times vector with all days between day0 and day_max
200  std::vector<FP> tps(static_cast<int>(day_max) - static_cast<int>(day0) + 1);
201  std::iota(tps.begin(), tps.end(), day0);
202 
203  return interpolate_simulation_result<FP>(simulation_result, tps);
204 }
205 
206 template <typename FP>
208  const std::vector<FP>& interpolation_times)
209 {
210  assert(simulation_result.get_num_time_points() > 0 && "TimeSeries must not be empty.");
211 
212  assert(std::is_sorted(interpolation_times.begin(), interpolation_times.end()) &&
213  "Time points for interpolation have to be sorted in non-descending order.");
214 
215  if (interpolation_times.size() >= 2) {
216  assert((interpolation_times[1] > simulation_result.get_time(0) &&
217  interpolation_times.rbegin()[1] <= simulation_result.get_last_time()) &&
218  "All but the first and the last time point of interpolation have lie between simulation times (strictly "
219  "for lower boundary).");
220  }
221 
222  TimeSeries<FP> interpolated(simulation_result.get_num_elements());
223 
224  if (interpolation_times.size() == 0) {
225  return interpolated;
226  }
227 
228  size_t interp_idx = 0;
229  // add first time point of interpolation times in case it is smaller than the first time point of simulation_result
230  // this is used for the case that it equals the first time point of simulation up to tolerance
231  // this is necessary even if the tolerance is 0 due to the way the comparison in the loop is implemented (< and >=)
232  if (simulation_result.get_time(0) >= interpolation_times[0]) {
233  interpolated.add_time_point(interpolation_times[0], simulation_result[0]);
234  ++interp_idx;
235  }
236 
237  //interpolate between pair of time points that lie on either side of each interpolation point
238  for (Eigen::Index sim_idx = 0;
239  sim_idx < simulation_result.get_num_time_points() - 1 && interp_idx < interpolation_times.size();) {
240  //only go to next pair of time points if no time point is added.
241  //otherwise check the same time points again
242  //in case there is more than one interpolation point between the two time points
243  if (simulation_result.get_time(sim_idx) < interpolation_times[interp_idx] &&
244  simulation_result.get_time(sim_idx + 1) >= interpolation_times[interp_idx]) {
245  interpolated.add_time_point(
246  interpolation_times[interp_idx],
247  linear_interpolation<FP>(interpolation_times[interp_idx], simulation_result.get_time(sim_idx),
248  simulation_result.get_time(sim_idx + 1), simulation_result[sim_idx],
249  simulation_result[sim_idx + 1]));
250  ++interp_idx;
251  }
252  else {
253  ++sim_idx;
254  }
255  }
256 
257  // add last time point of interpolation times in case it is larger than the last time point of simulation_result
258  // this is used for the case that it equals the last time point of simulation up to tolerance
259  if (interp_idx < interpolation_times.size() &&
260  simulation_result.get_last_time() < interpolation_times[interp_idx]) {
261  interpolated.add_time_point(interpolation_times[interp_idx], simulation_result.get_last_value());
262  }
263 
264  return interpolated;
265 }
266 
267 template <typename FP>
268 std::vector<std::vector<TimeSeries<FP>>> sum_nodes(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_result)
269 {
270  auto num_runs = ensemble_result.size();
271  auto num_nodes = ensemble_result[0].size();
272  auto num_time_points = ensemble_result[0][0].get_num_time_points();
273  auto num_elements = ensemble_result[0][0].get_num_elements();
274 
275  std::vector<std::vector<TimeSeries<FP>>> sum_result(
276  num_runs, std::vector<TimeSeries<FP>>(1, TimeSeries<FP>::zero(num_time_points, num_elements)));
277 
278  for (size_t run = 0; run < num_runs; run++) {
279  for (Eigen::Index time = 0; time < num_time_points; time++) {
280  sum_result[run][0].get_time(time) = ensemble_result[run][0].get_time(time);
281  for (size_t node = 0; node < num_nodes; node++) {
282  sum_result[run][0][time] += ensemble_result[run][node][time];
283  }
284  }
285  }
286  return sum_result;
287 }
288 
289 template <typename FP>
290 std::vector<TimeSeries<FP>> ensemble_mean(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_result)
291 {
292  auto num_runs = ensemble_result.size();
293  auto num_nodes = ensemble_result[0].size();
294  auto num_time_points = ensemble_result[0][0].get_num_time_points();
295  auto num_elements = ensemble_result[0][0].get_num_elements();
296 
297  std::vector<TimeSeries<FP>> mean(num_nodes, TimeSeries<FP>::zero(num_time_points, num_elements));
298 
299  for (size_t run = 0; run < num_runs; run++) {
300  assert(ensemble_result[run].size() == num_nodes && "ensemble results not uniform.");
301  for (size_t node = 0; node < num_nodes; node++) {
302  assert(ensemble_result[run][node].get_num_time_points() == num_time_points &&
303  "ensemble results not uniform.");
304  for (Eigen::Index time = 0; time < num_time_points; time++) {
305  assert(ensemble_result[run][node].get_num_elements() == num_elements &&
306  "ensemble results not uniform.");
307  mean[node].get_time(time) = ensemble_result[run][node].get_time(time);
308  mean[node][time] += ensemble_result[run][node][time] / num_runs;
309  }
310  }
311  }
312 
313  return mean;
314 }
315 
316 template <typename FP>
317 std::vector<TimeSeries<FP>> ensemble_percentile(const std::vector<std::vector<TimeSeries<FP>>>& ensemble_result, FP p)
318 {
319  assert(p > 0.0 && p < 1.0 && "Invalid percentile value.");
320 
321  auto num_runs = ensemble_result.size();
322  auto num_nodes = ensemble_result[0].size();
323  auto num_time_points = ensemble_result[0][0].get_num_time_points();
324  auto num_elements = ensemble_result[0][0].get_num_elements();
325 
326  std::vector<TimeSeries<FP>> percentile(num_nodes, TimeSeries<FP>::zero(num_time_points, num_elements));
327 
328  std::vector<FP> single_element_ensemble(num_runs); //reused for each element
329  for (size_t node = 0; node < num_nodes; node++) {
330  for (Eigen::Index time = 0; time < num_time_points; time++) {
331  percentile[node].get_time(time) = ensemble_result[0][node].get_time(time);
332  for (Eigen::Index elem = 0; elem < num_elements; elem++) {
333  std::transform(ensemble_result.begin(), ensemble_result.end(), single_element_ensemble.begin(),
334  [=](auto& run) {
335  return run[node][time][elem];
336  });
337  std::sort(single_element_ensemble.begin(), single_element_ensemble.end());
338  percentile[node][time][elem] = single_element_ensemble[static_cast<size_t>(num_runs * p)];
339  }
340  }
341  }
342  return percentile;
343 }
344 
345 template <typename FP>
346 FP result_distance_2norm(const std::vector<mio::TimeSeries<FP>>& result1,
347  const std::vector<mio::TimeSeries<FP>>& result2)
348 {
349  using std::sqrt;
350  assert(result1.size() == result2.size());
351  assert(result1.size() > 0);
352  assert(result1[0].get_num_time_points() > 0);
353  assert(result1[0].get_num_elements() > 0);
354 
355  auto norm_sqr = 0.0;
356  for (auto iter_node1 = result1.begin(), iter_node2 = result2.begin(); iter_node1 < result1.end();
357  ++iter_node1, ++iter_node2) {
358  for (Eigen::Index time_idx = 0; time_idx < iter_node1->get_num_time_points(); ++time_idx) {
359  auto v1 = (*iter_node1)[time_idx];
360  auto v2 = (*iter_node2)[time_idx];
361  norm_sqr += ((v1 - v2).array() * (v1 - v2).array()).sum();
362  }
363  }
364  return sqrt(norm_sqr);
365 }
366 
376 template <class FP>
378  bool add_values = false)
379 {
380  if (ts1.get_num_elements() != ts2.get_num_elements()) {
381  log_error("TimeSeries have a different number of elements.");
383  }
384  if (!ts1.is_strictly_monotonic() || !ts2.is_strictly_monotonic()) {
385  log_error("TimeSeries need to have strictly monotonic time points to be merged.");
387  }
388  Eigen::Index t1_iterator = 0;
389  Eigen::Index t2_iterator = 0;
390  const Eigen::Index t1_size = ts1.get_num_time_points();
391  const Eigen::Index t2_size = ts2.get_num_time_points();
392  TimeSeries<FP> merged_ts(ts1.get_num_elements());
393  merged_ts.reserve(t1_size + t2_size);
394  // merge entries of both time series until one finishes
395  while (t1_iterator < t1_size && t2_iterator < t2_size) {
396  // check which ts has the smaller time at the current iterator, and merge it
397  if (ts1.get_time(t1_iterator) < ts2.get_time(t2_iterator)) {
398  merged_ts.add_time_point(ts1.get_time(t1_iterator), ts1.get_value(t1_iterator));
399  ++t1_iterator;
400  }
401  else if (ts1.get_time(t1_iterator) == ts2.get_time(t2_iterator)) {
402  merged_ts.add_time_point(ts1.get_time(t1_iterator), ts1.get_value(t1_iterator));
403  if (add_values) {
404  merged_ts.get_last_value() += ts2.get_value(t2_iterator);
405  }
406  else {
407  log_warning("Both TimeSeries have values for t={}. The value of the first TimeSeries is used",
408  ts1.get_time(t1_iterator));
409  }
410  ++t1_iterator;
411  ++t2_iterator;
412  }
413  else { // " > "
414  merged_ts.add_time_point(ts2.get_time(t2_iterator), ts2.get_value(t2_iterator));
415  ++t2_iterator;
416  }
417  }
418  // append remaining entries. at most one of the following for loops will be executed
419  for (; t1_iterator < t1_size; ++t1_iterator) {
420  merged_ts.add_time_point(ts1.get_time(t1_iterator), ts1.get_value(t1_iterator));
421  }
422  for (; t2_iterator < t2_size; ++t2_iterator) {
423  merged_ts.add_time_point(ts2.get_time(t2_iterator), ts2.get_value(t2_iterator));
424  }
425  return success(merged_ts);
426 }
427 
428 } // namespace mio
429 
430 #endif //MEMILIO_DATA_ANALYZE_RESULT_H
generic graph structure
Definition: graph.h:153
represents the mobility between two nodes.
Definition: metapopulation_mobility_instant.h:298
represents the simulation in one node of the graph.
Definition: metapopulation_mobility_instant.h:41
stores vectors of values at time points (or some other abstract variable) the value at each time poin...
Definition: time_series.h:58
void reserve(Eigen::Index n)
reserve capacity for n time points
Definition: time_series.h:332
Eigen::Ref< const Vector > get_value(Eigen::Index i) const
reference to value vector at time point i
Definition: time_series.h:298
Eigen::Index get_num_elements() const
number of elements of vector at each time point
Definition: time_series.h:205
bool is_strictly_monotonic() const
Check if the time is strictly monotonic increasing.
Definition: time_series.h:185
FP & get_last_time()
time of time point at index num_time_points - 1
Definition: time_series.h:286
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
Eigen::Ref< const Vector > get_last_value() const
reference to value vector at time point (num_timepoints - 1)
Definition: time_series.h:320
ad::internal::unary_intermediate< AD_TAPE_REAL, ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 >, ad::operations::ad_sqrt< AD_TAPE_REAL > > sqrt(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x1)
Definition: ad.hpp:1023
static double floor(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x)
Definition: ad.hpp:2451
static double ceil(const ad::internal::active_type< AD_TAPE_REAL, DATA_HANDLER_1 > &x)
Definition: ad.hpp:2449
InfectionState
InfectionState in ABM.
Definition: abm/infection_state.h:35
int size(Comm comm)
Return the size of the given communicator.
Definition: miompi.cpp:75
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
IOResult< TimeSeries< FP > > merge_time_series(const TimeSeries< FP > &ts1, const TimeSeries< FP > &ts2, bool add_values=false)
This function merges two TimeSeries by copying their time points and values to a new TimeSeries in th...
Definition: memilio/data/analyze_result.h:377
void log_warning(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:112
FP result_distance_2norm(const std::vector< mio::TimeSeries< FP >> &result1, const std::vector< mio::TimeSeries< FP >> &result2)
Compute the distance between two SECIR simulation results.
Definition: memilio/data/analyze_result.h:346
std::decay_t< decltype(interpolate_simulation_result(std::declval< T >()))> InterpolateResultT
helper template, type returned by overload interpolate_simulation_result(T t)
Definition: memilio/data/analyze_result.h:69
auto success()
Create an object that is implicitly convertible to a succesful IOResult<void>.
Definition: io.h:359
void log_error(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:100
std::vector< TimeSeries< FP > > ensemble_percentile(const std::vector< std::vector< TimeSeries< FP >>> &ensemble_result, FP p)
computes the p percentile of the result for each compartment, node, and time point.
Definition: memilio/data/analyze_result.h:317
TimeSeries< FP > interpolate_simulation_result(const TimeSeries< FP > &simulation_result, const FP abs_tol=FP{100.} *Limits< FP >::zero_tolerance())
Interpolate a given time series with evenly spaced, integer time points that represent whole days.
Definition: memilio/data/analyze_result.h:187
std::vector< TimeSeries< FP > > ensemble_mean(const std::vector< std::vector< TimeSeries< FP >>> &ensemble_results)
computes mean of each compartment, node, and time point over all runs input must be uniform as return...
Definition: memilio/data/analyze_result.h:290
std::vector< InterpolateResultT< T > > interpolate_ensemble_results(const std::vector< T > &ensemble_results)
Interpolates results of all runs with evenly spaced, integer time points that represent whole days.
Definition: memilio/data/analyze_result.h:78
std::vector< std::vector< TimeSeries< FP > > > sum_nodes(const std::vector< std::vector< TimeSeries< FP >>> &ensemble_result)
Definition: memilio/data/analyze_result.h:268
boost::outcome_v2::unchecked< T, IOStatus > IOResult
Value-or-error type for operations that return a value but can fail.
Definition: io.h:353
static constexpr FP zero_tolerance()=delete