parameter_studies.h Source File

CPP API: parameter_studies.h Source File
parameter_studies.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Daniel Abele, 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 MIO_COMPARTMENTS_PARAMETER_STUDIES_H
21 #define MIO_COMPARTMENTS_PARAMETER_STUDIES_H
22 
24 #include "memilio/io/io.h"
25 #include "memilio/utils/logging.h"
26 #include "memilio/utils/miompi.h"
29 
30 #include <cassert>
31 #include <cmath>
32 #include <cstdint>
33 #include <type_traits>
34 #include <utility>
35 #include <vector>
36 
37 namespace mio
38 {
39 
47 template <class ParameterType, typename TimeType, typename StepType = TimeType>
49 {
50 public:
51  using Parameters = ParameterType;
52  using Time = TimeType;
53  using Step = StepType;
54 
55 private:
57  template <class CreateSimulationFunction>
58  requires std::is_invocable_v<CreateSimulationFunction, Parameters, Time, Step, size_t>
59  using SimulationT = std::decay_t<std::invoke_result_t<CreateSimulationFunction, Parameters, Time, Step, size_t>>;
61  template <class CreateSimulationFunction, class ProcessSimulationResultFunction>
62  requires std::is_invocable_v<ProcessSimulationResultFunction, SimulationT<CreateSimulationFunction>, size_t>
63  using ProcessedResultT = std::decay_t<
64  std::invoke_result_t<ProcessSimulationResultFunction, SimulationT<CreateSimulationFunction>, size_t>>;
66  template <class CreateSimulationFunction, class ProcessSimulationResultFunction>
68  std::conditional_t<std::is_void_v<ProcessedResultT<CreateSimulationFunction, ProcessSimulationResultFunction>>,
69  void,
70  std::vector<ProcessedResultT<CreateSimulationFunction, ProcessSimulationResultFunction>>>;
71 
72 public:
73  // TODO: replacement for "set_params_distributions_normal"? Maybe a special ctor for UncertainParameterSet?
74 
84  ParameterStudy(const Parameters& parameters, Time t0, Time tmax, Step dt, size_t num_runs)
85  : m_parameters(parameters)
86  , m_num_runs(num_runs)
87  , m_t0{t0}
88  , m_tmax{tmax}
89  , m_dt(dt)
90  {
91  }
92 
112  template <class CreateSimulationFunction, class ProcessSimulationResultFunction>
113  EnsembleResultT<CreateSimulationFunction, ProcessSimulationResultFunction>
114  run_serial(CreateSimulationFunction&& create_simulation,
115  ProcessSimulationResultFunction&& process_simulation_result)
116  {
117  return run_impl(0, m_num_runs, std::forward<CreateSimulationFunction>(create_simulation),
118  std::forward<ProcessSimulationResultFunction>(process_simulation_result));
119  }
120 
121  template <class CreateSimulationFunction>
122  std::vector<SimulationT<CreateSimulationFunction>> run_serial(CreateSimulationFunction&& create_simulation)
123  {
124  return run_serial(
125  std::forward<CreateSimulationFunction>(create_simulation),
127  return std::move(sim);
128  });
129  }
156  template <class CreateSimulationFunction, class ProcessSimulationResultFunction>
157  EnsembleResultT<CreateSimulationFunction, ProcessSimulationResultFunction>
158  run(CreateSimulationFunction&& create_simulation, ProcessSimulationResultFunction&& process_simulation_result)
159  {
161  int num_procs = 1, rank = 0;
162 
163 #ifdef MEMILIO_ENABLE_MPI
164  MPI_Comm_size(mpi::get_world(), &num_procs);
165  MPI_Comm_rank(mpi::get_world(), &rank);
166 #endif
167 
168  //The ParameterDistributions used for sampling parameters use thread_local_rng()
169  //So we set our own RNG to be used.
170  //Assume that sampling uses the thread_local_rng() and isn't multithreaded
171  m_rng.synchronize();
172 
173  std::vector<size_t> run_distribution = distribute_runs(m_num_runs, num_procs);
174  size_t start_run_idx = std::accumulate(run_distribution.begin(), run_distribution.begin() + rank, size_t(0));
175  size_t end_run_idx = start_run_idx + run_distribution[rank];
176 
177  if constexpr (std::is_void_v<ResultT>) {
178  // if the processor returns nothing, there is nothing to synchronize
179  run_impl(start_run_idx, end_run_idx, std::forward<CreateSimulationFunction>(create_simulation),
180  std::forward<ProcessSimulationResultFunction>(process_simulation_result));
181  return;
182  }
183  else {
184  auto ensemble_result =
185  run_impl(start_run_idx, end_run_idx, std::forward<CreateSimulationFunction>(create_simulation),
186  std::forward<ProcessSimulationResultFunction>(process_simulation_result));
187 
188 #ifdef MEMILIO_ENABLE_MPI
189  //gather results
190  if (rank == 0) {
191  for (int src_rank = 1; src_rank < num_procs; ++src_rank) {
192  int bytes_size;
193  MPI_Recv(&bytes_size, 1, MPI_INT, src_rank, 0, mpi::get_world(), MPI_STATUS_IGNORE);
194  ByteStream bytes(bytes_size);
195  MPI_Recv(bytes.data(), bytes.data_size(), MPI_BYTE, src_rank, 0, mpi::get_world(),
196  MPI_STATUS_IGNORE);
197 
198  IOResult<ResultT> src_ensemble_results = deserialize_binary(bytes, Tag<ResultT>{});
199  if (!src_ensemble_results) {
200  log_error("Error receiving ensemble results from rank {}.", src_rank);
201  }
202  std::copy(src_ensemble_results.value().begin(), src_ensemble_results.value().end(),
203  std::back_inserter(ensemble_result));
204  }
205  }
206  else {
207  ByteStream bytes = serialize_binary(ensemble_result);
208  int bytes_size = int(bytes.data_size());
209  MPI_Send(&bytes_size, 1, MPI_INT, 0, 0, mpi::get_world());
210  MPI_Send(bytes.data(), bytes.data_size(), MPI_BYTE, 0, 0, mpi::get_world());
211  ensemble_result.clear(); //only return root process
212  }
213 #endif
214 
215  return ensemble_result;
216  }
217  }
218 
220  size_t get_num_runs() const
221  {
222  return m_num_runs;
223  }
224 
226  Time get_tmax() const
227  {
228  return m_tmax;
229  }
230 
232  Time get_t0() const
233  {
234  return m_t0;
235  }
236 
238  Time get_dt() const
239  {
240  return m_dt;
241  }
242 
247  const Parameters& get_parameters() const
248  {
249  return m_parameters;
250  }
252  {
253  return m_parameters;
254  }
258  RandomNumberGenerator& get_rng()
259  {
260  return m_rng;
261  }
262 
263 private:
265  template <class T>
266  inline auto make_ensemble_result()
267  {
268  if constexpr (std::is_void_v<T>) {
269  return char(0); // placeholder, as we cannot instanciate void
270  }
271  else {
272  T result;
273  result.reserve(m_num_runs);
274  return result;
275  }
276  }
277 
288  template <class CreateSimulationFunction, class ProcessSimulationResultFunction>
289  EnsembleResultT<CreateSimulationFunction, ProcessSimulationResultFunction>
290  run_impl(size_t start_run_idx, size_t end_run_idx, CreateSimulationFunction&& create_simulation,
291  ProcessSimulationResultFunction&& process_simulation_result)
292  {
294  assert(start_run_idx <= end_run_idx);
295  // this bool (and all code that it is used in) enables using void functions
296  constexpr bool should_gather_results = !std::is_void_v<ResultT>;
297 
298  // Note that this overwrites seed and counter of thread_local_rng, but it does not replace it.
300 
301  // the result is not used or returned if the processing function returns nothing (i.e. void)
302  [[maybe_unused]] auto ensemble_result = make_ensemble_result<ResultT>();
303 
304  for (size_t run_idx = start_run_idx; run_idx < end_run_idx; run_idx++) {
305  log(LogLevel::info, "ParameterStudies: run {}", run_idx);
306 
307  //prepare rng for this run by setting the counter to the right offset
308  //Add the old counter so that this call of run() produces different results
309  //from the previous call
310  Counter<uint64_t> run_rng_counter =
311  m_rng.get_counter() +
312  rng_totalsequence_counter<uint64_t>(static_cast<uint32_t>(run_idx), Counter<uint32_t>(0));
313  thread_local_rng().set_counter(run_rng_counter);
314 
315  //sample
317  create_simulation(std::as_const(m_parameters), std::as_const(m_t0), std::as_const(m_dt), run_idx);
318 
319  // the create_counter is only used for debug logging, hence it is unused in Release builds
320  [[maybe_unused]] const uint64_t create_counter = (thread_local_rng().get_counter() - run_rng_counter).get();
321  log_debug("ParameterStudy: Generated {} random numbers creating simulation #{}.", create_counter, run_idx);
322 
323  //perform run
324  sim.advance(m_tmax);
325 
326  log_debug("ParameterStudy: Generated {} random numbers running simulation #{}.",
327  run_rng_counter.get() - create_counter, run_idx);
328 
329  //handle result
330  if constexpr (should_gather_results) {
331  ensemble_result.emplace_back(process_simulation_result(std::move(sim), run_idx));
332  }
333  else {
334  process_simulation_result(std::move(sim), run_idx);
335  }
336  }
337 
338  //Set the counter of our RNG so that future calls of run() produce different parameters.
339  m_rng.set_counter(m_rng.get_counter() + rng_totalsequence_counter<uint64_t>(m_num_runs, Counter<uint32_t>(0)));
340 
341  if constexpr (should_gather_results) {
342  return ensemble_result;
343  }
344  // else: there is nothing to return, because process_simulation_result returns void
345  }
346 
354  static std::vector<size_t> distribute_runs(size_t num_runs, int num_procs)
355  {
356  assert(num_procs > 0);
357  //evenly distribute runs
358  //lower processes do one more run if runs are not evenly distributable
359  size_t num_runs_local = num_runs / num_procs; //integer division!
360  size_t remainder = num_runs % num_procs;
361 
362  std::vector<size_t> run_distribution(num_procs);
363  std::fill(run_distribution.begin(), run_distribution.begin() + remainder, num_runs_local + 1);
364  std::fill(run_distribution.begin() + remainder, run_distribution.end(), num_runs_local);
365 
366  return run_distribution;
367  }
368 
369  ParameterType m_parameters;
370  size_t m_num_runs;
373  RandomNumberGenerator m_rng;
374 };
375 
383 template <typename FP, class Sim>
384 auto make_sampled_graph_simulation(const Graph<typename Sim::Model, MobilityParameters<FP>>& sampled_graph, FP t0,
385  FP dt_node_sim, FP dt_graph_sim)
386 {
388 
389  SimGraph sim_graph;
390 
391  for (auto&& node : sampled_graph.nodes()) {
392  sim_graph.add_node(node.id, node.property, t0, dt_node_sim);
393  }
394  for (auto&& edge : sampled_graph.edges()) {
395  sim_graph.add_edge(edge.start_node_idx, edge.end_node_idx, edge.property);
396  }
397 
398  return make_mobility_sim<FP, Sim>(t0, dt_graph_sim, std::move(sim_graph));
399 }
400 
401 } // namespace mio
402 
403 #endif // MIO_COMPARTMENTS_PARAMETER_STUDIES_H
In-memory stream of bytes.
Definition: binary_serializer.h:39
const unsigned char * data() const
Get the pointer to the buffer of the stream.
Definition: binary_serializer.h:111
size_t data_size() const
Get the size of the buffer of the stream.
Definition: binary_serializer.h:124
generic graph structure
Definition: graph.h:153
represents the mobility between two nodes.
Definition: metapopulation_mobility_instant.h:298
parameters that influence mobility.
Definition: metapopulation_mobility_instant.h:123
Class used to perform multiple simulation runs with randomly sampled parameters.
Definition: parameter_studies.h:49
ParameterType Parameters
Definition: parameter_studies.h:51
size_t get_num_runs() const
Return the number of total runs that the study will make.
Definition: parameter_studies.h:220
Time m_tmax
Start and end time for the simulations.
Definition: parameter_studies.h:371
auto make_ensemble_result()
Return the ensemble result vector, or "void" in form of a char.
Definition: parameter_studies.h:266
Time m_t0
Definition: parameter_studies.h:371
StepType Step
Definition: parameter_studies.h:53
ParameterType m_parameters
Stores parameters used to create a simulation for each run.
Definition: parameter_studies.h:369
const Parameters & get_parameters() const
Get the input parameters that each simulation in the study is created from.
Definition: parameter_studies.h:247
RandomNumberGenerator m_rng
The random number generator used by the study.
Definition: parameter_studies.h:373
TimeType Time
Definition: parameter_studies.h:52
size_t m_num_runs
Total number of runs (i.e. simulations) to do when calling "run".
Definition: parameter_studies.h:370
Step m_dt
Initial step size of the simulation. Some integrators may adapt their step size during simulation.
Definition: parameter_studies.h:372
ParameterStudy(const Parameters &parameters, Time t0, Time tmax, Step dt, size_t num_runs)
Create a parameter study with some parameters.
Definition: parameter_studies.h:84
Time get_tmax() const
Return the final time point for simulations.
Definition: parameter_studies.h:226
std::vector< SimulationT< CreateSimulationFunction > > run_serial(CreateSimulationFunction &&create_simulation)
Run all simulations in serial.
Definition: parameter_studies.h:122
RandomNumberGenerator & get_rng()
Access the study's random number generator.
Definition: parameter_studies.h:258
std::decay_t< std::invoke_result_t< ProcessSimulationResultFunction, SimulationT< CreateSimulationFunction >, size_t > > ProcessedResultT
The return type of process_simulation_result. Ensures that the function is invocable.
Definition: parameter_studies.h:64
Parameters & get_parameters()
Get the input parameters that each simulation in the study is created from.
Definition: parameter_studies.h:251
EnsembleResultT< CreateSimulationFunction, ProcessSimulationResultFunction > run(CreateSimulationFunction &&create_simulation, ProcessSimulationResultFunction &&process_simulation_result)
Run all simulations distributed over multiple MPI ranks.
Definition: parameter_studies.h:158
Time get_dt() const
Return the initial step sized used by simulations.
Definition: parameter_studies.h:238
EnsembleResultT< CreateSimulationFunction, ProcessSimulationResultFunction > run_serial(CreateSimulationFunction &&create_simulation, ProcessSimulationResultFunction &&process_simulation_result)
Run all simulations in serial.
Definition: parameter_studies.h:114
Time get_t0() const
Return the initial time point for simulations.
Definition: parameter_studies.h:232
EnsembleResultT< CreateSimulationFunction, ProcessSimulationResultFunction > run_impl(size_t start_run_idx, size_t end_run_idx, CreateSimulationFunction &&create_simulation, ProcessSimulationResultFunction &&process_simulation_result)
Main loop creating and running simulations.
Definition: parameter_studies.h:290
std::conditional_t< std::is_void_v< ProcessedResultT< CreateSimulationFunction, ProcessSimulationResultFunction > >, void, std::vector< ProcessedResultT< CreateSimulationFunction, ProcessSimulationResultFunction > >> EnsembleResultT
Type returned by run functions. Is void if ProcessedResultT is, otherwise a vector of ProcessedResult...
Definition: parameter_studies.h:70
static std::vector< size_t > distribute_runs(size_t num_runs, int num_procs)
Distribute a number of runs over a number of processes.
Definition: parameter_studies.h:354
std::decay_t< std::invoke_result_t< CreateSimulationFunction, Parameters, Time, Step, size_t > > SimulationT
The return type of create_simulation. Ensures that the function is invocable.
Definition: parameter_studies.h:59
Comm get_world()
Get the global MPI communicator.
Definition: miompi.cpp:32
int rank(Comm comm)
Return the rank of the calling process on the given communicator.
Definition: miompi.cpp:63
std::chrono::steady_clock::time_point TimeType
Definition: definitions.h:40
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
boost::outcome_v2::in_place_type_t< T > Tag
Type that is used for overload resolution.
Definition: io.h:407
IOResult< T > deserialize_binary(ByteStream &stream, Tag< T >, int flags=0)
Deserialize an object from binary format.
Definition: binary_serializer.h:468
requires(!std::is_trivial_v< T >) void BinarySerializerObject
Definition: binary_serializer.h:333
void log(LogLevel level, spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:128
void log_error(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:100
ByteStream serialize_binary(const T &t, int flags=0)
Serialize an object into binary format.
Definition: binary_serializer.h:452
void log_debug(spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:118
auto make_sampled_graph_simulation(const Graph< typename Sim::Model, MobilityParameters< FP >> &sampled_graph, FP t0, FP dt_node_sim, FP dt_graph_sim)
Create a GraphSimulation from a parameter graph.
Definition: parameter_studies.h:384
RandomNumberGenerator & thread_local_rng()
Definition: random_number_generator.cpp:25
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