random_number_generator.h Source File

CPP API: random_number_generator.h Source File
random_number_generator.h
Go to the documentation of this file.
1 /*
2 * Copyright (C) 2020-2026 MEmilio
3 *
4 * Authors: Daniel Abele
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 MIO_RANDOM_NUMBER_GENERATOR_H
22 #define MIO_RANDOM_NUMBER_GENERATOR_H
23 
26 #include "memilio/utils/logging.h"
27 #include "memilio/utils/miompi.h"
28 #include "memilio/utils/span.h"
30 
31 MSVC_WARNING_DISABLE_PUSH(4127) //conditional expression is constant
33 GCC_CLANG_DIAGNOSTIC(ignored "-Wexpansion-to-defined") //Random123 handles the portability of this warning internally
34 #include "Random123/array.h"
35 #include "Random123/threefry.h"
38 
39 #include <cassert>
40 #include <cstdint>
41 #include <functional>
42 #include <limits>
43 #include <numeric>
44 #include <random>
45 #include <type_traits>
46 
47 namespace mio
48 {
49 
115 template <class Derived>
116 class RandomNumberGeneratorBase
117 {
118 public:
119  using result_type = uint64_t;
120 
125  static constexpr result_type min()
126  {
128  }
129 
134  static constexpr result_type max()
135  {
137  }
138 
144  result_type operator()();
145 };
146 
147 namespace details
148 {
152 inline uint64_t to_uint64(r123array2x32 tf_array)
153 {
154  uint64_t i;
155  std::memcpy(&i, tf_array.data(), sizeof(uint64_t));
156  return i;
157 }
158 
162 inline r123array2x32 to_r123_array(uint64_t i)
163 {
164  threefry2x32_ctr_t c;
165  std::memcpy(c.data(), &i, sizeof(uint64_t));
166  return c;
167 }
168 } // namespace details
169 
174 template <class T>
175 struct MEMILIO_ENABLE_EBO Key : TypeSafe<T, Key<T>>, OperatorComparison<Key<T>> {
176  static_assert(std::is_unsigned<T>::value, "Underlying Integer type must be unsigned.");
177  using TypeSafe<T, Key<T>>::TypeSafe;
178 };
179 static_assert(sizeof(Key<uint32_t>) == sizeof(uint32_t), "Empty Base Optimization isn't working.");
180 
185 template <class T>
186 struct MEMILIO_ENABLE_EBO Counter : TypeSafe<T, Counter<T>>,
187  OperatorComparison<Counter<T>>,
188  OperatorAdditionSubtraction<Counter<T>> {
189  static_assert(std::is_unsigned<T>::value, "Underlying Integer type must be unsigned.");
190  using TypeSafe<T, Counter<T>>::TypeSafe;
191 };
192 static_assert(sizeof(Counter<uint32_t>) == sizeof(uint32_t), "Empty Base Optimization isn't working.");
193 
194 template <class Derived>
195 auto RandomNumberGeneratorBase<Derived>::operator()() -> result_type
196 {
197  //generate a random sample using the Random123 library.
198  //Use another threefryNxR algorithm if larger or more random samples are needed than 64 bit.
199  auto self = static_cast<Derived*>(this);
200  auto c = static_cast<uint64_t>(self->get_counter().get());
201  auto k = static_cast<uint64_t>(self->get_key().get());
202  auto r = details::to_uint64(threefry2x32(details::to_r123_array(k), details::to_r123_array(c)));
203  self->increment_counter();
204  return r;
205 }
206 
213 template <class SeedSeq>
214 Key<uint64_t> seed_rng_key(SeedSeq& seed_seq)
215 {
216  auto tf_key = threefry2x32_key_t::seed(seed_seq);
217  return Key<uint64_t>(details::to_uint64(tf_key));
218 }
219 
236 template <class UIntC, class UIntN, class CounterS>
237 Counter<UIntC> rng_totalsequence_counter(UIntN subsequence_idx, CounterS counter)
238 {
239  //use UIntC for variables because it's the biggest integer type in this function
240  static const UIntC BITS_PER_BYTE = 8;
241  static const UIntC C_BITS = sizeof(UIntC) * BITS_PER_BYTE;
242  static const UIntC S_BITS = sizeof(CounterS) * BITS_PER_BYTE;
243  static const UIntC N_BITS = C_BITS - S_BITS;
244 
245  static_assert(S_BITS < C_BITS, "Subsequence counter must be smaller than total sequence counter.");
246  static_assert(N_BITS <= C_BITS, "Subsequence index must not be bigger than total sequence counter.");
247  static_assert(N_BITS <= sizeof(UIntN) * BITS_PER_BYTE, "Subsequence index must be at least N bits");
248 
249  assert(UIntC(subsequence_idx) <= (UIntC(1) << N_BITS) &&
250  "Subsequence index is too large."); //(1 << N) is the same as (2^N)
251 
252  //N high bits: subsequence idx
253  //S low bits: subsequence counter
254  //=> C = N + S bits: total sequence counter
255  //example:
256  //subsequence index uint32_t(181) = 0x000000B5
257  //subsequence counter uint32_t(41309) = 0x0000A15D
258  //total sequence counter = 0x000000B50000A15D
259  const auto i = static_cast<UIntC>(subsequence_idx);
260  const auto s = static_cast<UIntC>(counter.get());
261  const auto c = (i << S_BITS) + s; //shift subsequence index to the high bits, add subsequence counter into low bits
262  return Counter<UIntC>{c};
263 }
264 
275 template <class UIntS, class CounterC>
276 Counter<UIntS> rng_subsequence_counter(CounterC counter)
277 {
278  using UIntC = typename CounterC::ValueType;
279  static const UIntC C_BYTES = sizeof(UIntC);
280  static const UIntC S_BYTES = sizeof(UIntS);
281 
282  static_assert(S_BYTES < C_BYTES, "Subsequence counter must be smaller than total sequence counter.");
283 
284  //the subsequence counter is in the lower bits of total sequence counter
285  //see rng_totalsequence_counter above
286  //so we just need to truncate the counter to get the subsequence counter
287  return Counter<UIntS>(static_cast<UIntS>(counter.get()));
288 }
289 
295 class RandomNumberGenerator : public RandomNumberGeneratorBase<RandomNumberGenerator>
296 {
297 public:
298  RandomNumberGenerator()
299  : m_counter(0)
300  {
301  seed(generate_seeds());
302  }
303 
304  Key<uint64_t> get_key() const
305  {
306  return m_key;
307  }
308  Counter<uint64_t> get_counter() const
309  {
310  return m_counter;
311  }
312  void set_counter(Counter<uint64_t> counter)
313  {
314  m_counter = counter;
315  }
316  void increment_counter()
317  {
318  ++m_counter;
319  }
320  static std::vector<uint32_t> generate_seeds()
321  {
322  std::random_device rd;
323  return {rd(), rd(), rd(), rd(), rd(), rd()};
324  }
325 
326  void seed(const std::vector<uint32_t>& seeds)
327  {
328  m_seeds = seeds;
329  std::seed_seq seed_seq(m_seeds.begin(), m_seeds.end());
330  m_key = seed_rng_key(seed_seq);
331  }
332 
333  const std::vector<uint32_t> get_seeds() const
334  {
335  return m_seeds;
336  }
337 
341  void synchronize()
342  {
343 #ifdef MEMILIO_ENABLE_MPI
344  int rank;
345  MPI_Comm_rank(mpi::get_world(), &rank);
346  int num_seeds;
347  if (rank == 0) {
348  num_seeds = int(m_seeds.size());
349  }
350  MPI_Bcast(&num_seeds, 1, MPI_INT, 0, mpi::get_world());
351  if (rank != 0) {
352  m_seeds.assign(num_seeds, 0);
353  }
354  MPI_Bcast(m_seeds.data(), num_seeds, MPI_UNSIGNED, 0, mpi::get_world());
355  if (rank != 0) {
356  seed(m_seeds);
357  }
358 #endif
359  }
360 
362  auto default_serialize()
363  {
364  return Members("RandomNumberGenerator").add("key", m_key).add("counter", m_counter).add("seeds", m_seeds);
365  }
366 
367 private:
368  Key<uint64_t> m_key;
369  Counter<uint64_t> m_counter;
370  std::vector<uint32_t> m_seeds;
371 };
372 
378 RandomNumberGenerator& thread_local_rng();
379 
383 inline void log_rng_seeds(const RandomNumberGenerator& rng, LogLevel level)
384 {
385  const auto& seeds = rng.get_seeds();
386  std::stringstream ss;
387  bool first = true;
388  for (auto& s : seeds) {
389  if (!first) {
390  ss << ", ";
391  }
392  first = false;
393  ss << s;
394  }
395  log(level, "Using RNG with seeds: {0}.", ss.str());
396 }
397 
401 inline void log_thread_local_rng_seeds(LogLevel level)
402 {
403  log_rng_seeds(thread_local_rng(), level);
404 }
405 
414 template <class DistT>
415 class DistributionAdapter
416 {
417 public:
421  using ResultType = typename DistT::result_type;
422 
427  struct ParamType {
428  using DistType = DistributionAdapter<DistT>;
429 
430  template <typename... Ps>
431  requires std::is_constructible_v<typename DistT::param_type, Ps...>
432  ParamType(Ps&&... ps)
433  : params(std::forward<Ps>(ps)...)
434  {
435  }
436 
441  static DistributionAdapter& get_distribution_instance()
442  {
443  return DistType::get_instance();
444  }
445 
446  typename DistT::param_type params;
447  };
451  using GeneratorFunction = std::function<ResultType(const typename DistT::param_type& p)>;
452 
453 private:
459  DistributionAdapter() = default;
460  DistributionAdapter(const DistributionAdapter&) = default;
461  DistributionAdapter& operator=(const DistributionAdapter&) = default;
462  DistributionAdapter(DistributionAdapter&&) = default;
463  DistributionAdapter& operator=(DistributionAdapter&&) = default;
464 
465 public:
473  template <class RNG, class... T>
474  ResultType operator()(RNG& rng, T&&... params)
475  {
476  if (m_generator) {
477  //unlikely outside of tests
478  return m_generator(typename DistT::param_type{std::forward<T>(params)...});
479  }
480  else {
481  return DistT(std::forward<T>(params)...)(rng);
482  }
483  }
484 
488  GeneratorFunction get_generator() const
489  {
490  return m_generator;
491  }
492 
496  void set_generator(GeneratorFunction g)
497  {
498  m_generator = g;
499  }
500 
507  static DistributionAdapter& get_instance()
508  {
509  static DistributionAdapter instance;
510  return instance;
511  }
512 
513 private:
514  GeneratorFunction m_generator;
515 };
516 
524 template <class Int>
525 class DiscreteDistributionInPlace
526 {
527 public:
531  using result_type = Int;
532 
536  class param_type
537  {
538  public:
539  using distribution = DiscreteDistributionInPlace;
540 
541  param_type() = default;
542 
543  param_type(Span<ScalarType> weights)
544  : m_weights(weights)
545  {
546  }
547 
548  Span<ScalarType> weights() const
549  {
550  return m_weights;
551  }
552 
553  private:
554  Span<ScalarType> m_weights;
555  };
556 
561  DiscreteDistributionInPlace() = default;
562 
566  DiscreteDistributionInPlace(Span<ScalarType> weights)
567  : m_params(weights)
568  {
569  }
570 
574  DiscreteDistributionInPlace(param_type params)
575  : m_params(params)
576  {
577  }
578 
583  void reset()
584  {
585  }
586 
590  param_type param()
591  {
592  return m_params;
593  }
594 
598  void param(param_type p)
599  {
600  m_params = p;
601  }
602 
606  Span<ScalarType> weights()
607  {
608  return m_params.weights();
609  }
610 
615  template <class RNG>
616  result_type operator()(RNG& rng)
617  {
618  return (*this)(rng, m_params);
619  }
620 
626  template <class RNG>
627  result_type operator()(RNG& rng, param_type p)
628  {
629  auto weights = p.weights();
630  if (weights.size() <= 1) {
631  return 0;
632  }
633  auto sum = std::accumulate(weights.begin(), weights.end(), 0.0);
634  auto u = std::uniform_real_distribution<ScalarType>()(
635  rng, std::uniform_real_distribution<ScalarType>::param_type{0.0, sum});
636  auto intermediate_sum = 0.0;
637  for (size_t i = 0; i < weights.size(); ++i) {
638  intermediate_sum += weights.get_ptr()[i];
639  if (u < intermediate_sum) {
640  return i;
641  }
642  }
643  assert(false && "this should never happen.");
644  return result_type(-1);
645  }
646 
647 private:
648  param_type m_params;
649 };
650 
655 template <class Int>
656 using DiscreteDistribution = DistributionAdapter<DiscreteDistributionInPlace<Int>>;
657 
662 template <class Real>
663 using ExponentialDistribution = DistributionAdapter<std::exponential_distribution<Real>>;
664 
669 template <class Real>
670 using NormalDistribution = DistributionAdapter<std::normal_distribution<Real>>;
671 
676 template <class Int>
677 using UniformIntDistribution = DistributionAdapter<std::uniform_int_distribution<Int>>;
678 
683 template <class Real>
684 using UniformDistribution = DistributionAdapter<std::uniform_real_distribution<Real>>;
685 
686 template <class IOContext, class UniformDistributionParams,
687  class Real = typename UniformDistributionParams::DistType::ResultType>
688  requires std::is_same_v<UniformDistributionParams, typename UniformDistribution<Real>::ParamType>
689 void serialize_internal(IOContext& io, const UniformDistributionParams& p)
690 {
691  auto obj = io.create_object("UniformDistributionParams");
692  obj.add_element("a", p.params.a());
693  obj.add_element("b", p.params.b());
694 }
695 
696 template <class IOContext, class UniformDistributionParams,
697  class Real = typename UniformDistributionParams::DistType::ResultType>
698  requires std::is_same_v<UniformDistributionParams, typename UniformDistribution<Real>::ParamType>
699 IOResult<UniformDistributionParams> deserialize_internal(IOContext& io, Tag<UniformDistributionParams>)
700 {
701  auto obj = io.expect_object("UniformDistributionParams");
702  auto a = obj.expect_element("a", Tag<Real>{});
703  auto b = obj.expect_element("b", Tag<Real>{});
704  return apply(
705  io,
706  [](auto&& a_, auto&& b_) {
707  return UniformDistributionParams{a_, b_};
708  },
709  a, b);
710 }
711 
716 template <class Int>
717 using PoissonDistribution = DistributionAdapter<std::poisson_distribution<Int>>;
718 
723 template <class Real>
724 using LogNormalDistribution = DistributionAdapter<std::lognormal_distribution<Real>>;
725 
730 template <class Real>
731 using GammaDistribution = DistributionAdapter<std::gamma_distribution<Real>>;
732 
737 template <class Real>
738 using NormalDistribution = DistributionAdapter<std::normal_distribution<Real>>;
739 
740 } // namespace mio
741 
742 #endif
#define MEMILIO_ENABLE_EBO
Definition: compiler_diagnostics.h:75
#define MSVC_WARNING_POP()
Definition: compiler_diagnostics.h:44
#define GCC_CLANG_DIAGNOSTIC(...)
Definition: compiler_diagnostics.h:62
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
trait_value< T >::RETURN_TYPE & value(T &x)
Definition: ad.hpp:3308
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
A collection of classes to simplify handling of matrix shapes in meta programming.
Definition: models/abm/analyze_result.h:30
auto i
Definition: io.h:809
details::ApplyResultT< F, T... > apply(IOContext &io, F f, const IOResult< T > &... rs)
Evaluate a function with zero or more unpacked IOResults as arguments.
Definition: io.h:481
requires(!std::is_trivial_v< T >) void BinarySerializerObject
Definition: binary_serializer.h:333
LogLevel
Definition: logging.h:40
void log(LogLevel level, spdlog::string_view_t fmt, const Args &... args)
Definition: logging.h:128
auto max(const Eigen::MatrixBase< A > &a, B &&b)
coefficient wise maximum of two matrices.
Definition: eigen_util.h:171
RandomNumberGenerator & thread_local_rng()
Definition: random_number_generator.cpp:25
IOResult< T > deserialize_internal(IOContext &io, Tag< T > tag)
Deserialization implementation for the default serialization feature.
Definition: default_serialize.h:236
void serialize_internal(IOContext &io, const T &a)
Serialization implementation for the default serialization feature.
Definition: default_serialize.h:213
MSVC_WARNING_DISABLE_PUSH(4127) GCC_CLANG_DIAGNOSTIC(ignored "-Wexpansion-to-defined") namespace mio
Definition: random_number_generator.h:31