LUMIERA.clone/src/lib/random.hpp
Ichthyostega 806db414dd Copyright: clarify and simplify the file headers
* Lumiera source code always was copyrighted by individual contributors
 * there is no entity "Lumiera.org" which holds any copyrights
 * Lumiera source code is provided under the GPL Version 2+

== Explanations ==
Lumiera as a whole is distributed under Copyleft, GNU General Public License Version 2 or above.
For this to become legally effective, the ''File COPYING in the root directory is sufficient.''

The licensing header in each file is not strictly necessary, yet considered good practice;
attaching a licence notice increases the likeliness that this information is retained
in case someone extracts individual code files. However, it is not by the presence of some
text, that legally binding licensing terms become effective; rather the fact matters that a
given piece of code was provably copyrighted and published under a license. Even reformatting
the code, renaming some variables or deleting parts of the code will not alter this legal
situation, but rather creates a derivative work, which is likewise covered by the GPL!

The most relevant information in the file header is the notice regarding the
time of the first individual copyright claim. By virtue of this initial copyright,
the first author is entitled to choose the terms of licensing. All further
modifications are permitted and covered by the License. The specific wording
or format of the copyright header is not legally relevant, as long as the
intention to publish under the GPL remains clear. The extended wording was
based on a recommendation by the FSF. It can be shortened, because the full terms
of the license are provided alongside the distribution, in the file COPYING.
2024-11-17 23:42:55 +01:00

305 lines
9 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
RANDOM.hpp - support for random number generation with controlled seed
Copyright (C)
2024, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
*/
/** @file random.hpp
** Generating (pseudo) random numbers with controlled seed.
** As an extension on top of the [C++ random number framework], several instances
** of random number sequence generators can be easily created with a controlled seed.
** For simplified usage, two default instances are exposed as global variable
** - lib::defaultGen uses fixed seeding (planned: make this configurable)
** - lib::entropyGen always uses true randomness as seed value.
** [C++ random number framework]: https://en.cppreference.com/w/cpp/numeric/random
** @see Random_test
*/
#ifndef LIB_RANDOM_H
#define LIB_RANDOM_H
#include "lib/integral.hpp"
#include "lib/hash-value.h"
#include "lib/nocopy.hpp"
#include <random>
namespace lib {
namespace {
inline uint constexpr
_iBOUND()
{
return 1u+uint(std::numeric_limits<int>::max());
}
}
/** Establishes a seed point for any instance or performance. */
class SeedNucleus
{
public:
virtual ~SeedNucleus(); ///< this is an interface
virtual uint64_t getSeed() =0;
};
/**
* Access point to a selection of random number sources.
* For each kind of performance or usage, a common execution scheme
* is established to initiate generated number sequences, allowing
* for seemingly random yet reproducible behaviour — or for actually
* contingent behaviour when necessary. Client code should rely on
* [Dependency-Injection](\ref lib::Depend) or the static accessors.
*/
template<class GEN>
class RandomSequencer
{
GEN generator_;
public:
/** Build new generator, drawing seed from a virtual seed source */
RandomSequencer (SeedNucleus&);
/** Build new generator, drawing a seed from given parent generator */
template<class G>
RandomSequencer (RandomSequencer<G>&);
// default copy operations (can copy and assign a state)
int i(uint bound =_iBOUND()); ///< drop-in replacement for `rand() % bound`
int i32(); ///< random number from _full integer range_ (incl. negative values)
uint64_t u64(); ///< random 64bit number from full range.
double uni(); ///< random double drawn from interval `[0.0 ... 1.0[`
double range (double start, double bound); ///< random double from designated interval (upper bound excluded)
double normal(double mean=0.0, double stdev=1.0); ///< normal distribution (gaussian)
HashVal hash(); ///< _non-zero_ hash value from full 64bit range
/** generic adapter: draw next number to use the given distribution */
template<class DIST>
auto distribute(DIST);
/** inject controlled randomisation */
void reseed (SeedNucleus&);
/** wrapper to use this generator for seeding other generators */
class Seed;
};
template<class GEN>
class RandomSequencer<GEN>::Seed
: public SeedNucleus
{
RandomSequencer& srcGen_;
public:
Seed(RandomSequencer& parent)
: srcGen_{parent}
{ }
uint64_t
getSeed() override
{
return srcGen_.u64();
}
};
/**
* PRNG engine to use by default: 64bit Mersenne twister.
*/
using Random = RandomSequencer<std::mt19937_64>;
/** a global default RandomSequencer for mundane purposes */
extern Random defaultGen;
/** a global RandomSequencer seeded with real entropy */
extern Random entropyGen;
/* ===== convenience accessors ===== */
/** @return a random integer ∈ [0 ... bound[ */
inline int
rani (uint bound =_iBOUND())
{
return defaultGen.i(bound);
}
/** @return a random double ∈ [start ... bound[ */
inline double
ranRange (double start, double bound)
{
return defaultGen.range (start, bound);
}
inline double
ranNormal(double mean =0.0, double stdev =1.0)
{
return defaultGen.normal (mean, stdev);
}
/** @return a random *non-zero* HashVal */
inline lib::HashVal
ranHash()
{
return defaultGen.hash();
}
/** inject true randomness into the #defaultGen */
void randomiseRandomness();
/** draw seed another Generator from the default RandomSequencer */
SeedNucleus& seedFromDefaultGen();
/* ===== Implementation details ===== */
template<class GEN>
inline
RandomSequencer<GEN>::RandomSequencer (SeedNucleus& nucleus)
: generator_{nucleus.getSeed()}
{ }
template<class GEN>
template<class G>
inline
RandomSequencer<GEN>::RandomSequencer (RandomSequencer<G>& parent)
: generator_{RandomSequencer<GEN>::Seed{parent}.getSeed()}
{ }
template<class GEN>
inline void
RandomSequencer<GEN>::reseed (SeedNucleus& nucleus)
{
generator_.seed (nucleus.getSeed());
}
template<class GEN>
template<class DIST>
inline auto
RandomSequencer<GEN>::distribute (DIST distribution)
{
return distribution (generator_);
}
/** @param bound upper bound (exclusive!) */
template<class GEN>
inline int
RandomSequencer<GEN>::i (uint bound)
{
if (!bound) bound=1;
--bound;
uint upper{std::numeric_limits<int>::max()};
upper = bound < upper? bound : upper;
return distribute (std::uniform_int_distribution<int> (0, upper));
}
template<class GEN>
inline int
RandomSequencer<GEN>::i32()
{
return distribute (std::uniform_int_distribution<int> {std::numeric_limits<int>::min()
,std::numeric_limits<int>::max()});
}
template<class GEN>
inline uint64_t
RandomSequencer<GEN>::u64()
{
return distribute (std::uniform_int_distribution<uint64_t> {std::numeric_limits<uint64_t>::min()
,std::numeric_limits<uint64_t>::max()});
}
template<class GEN>
inline double
RandomSequencer<GEN>::uni()
{
return range (0.0, 1.0);
}
template<class GEN>
inline double
RandomSequencer<GEN>::range (double start, double bound)
{
return distribute (std::uniform_real_distribution<double>{start,bound});
}
template<class GEN>
inline double
RandomSequencer<GEN>::normal (double mean, double stdev)
{
return distribute (std::normal_distribution<double>{mean,stdev});
}
template<class GEN>
inline HashVal
RandomSequencer<GEN>::hash()
{
return distribute (std::uniform_int_distribution<lib::HashVal>{lib::HashVal(1)});
}
/**
* Adapter to protect against data corruption caused by concurrent access.
* Random number generators in general are _not thread safe;_ when used from
* several threads concurrently, it is not a question _if_, but only a question
* _when_ the internal state will become corrupted, leading to degraded and biased
* distributions of produced numbers. For some usage scenarios however, ignoring
* this fact and still using a single generator from several threads may be acceptable,
* if the quality of the distribution actually does not matter and only some diffusion
* of numbers is required (e.g. adding a random sleep interval). But there is a catch:
* whenever the value range of generated numbers is less than the total range of the used
* data representation, then corruption of the internal state may lead to producing numbers
* outside the defined range. This adapter can be used to safeguard against this scenario.
* @remark typically using a 64bit generator on a 64bit platform is inherently safe, yet
* using a 32bit generator may rely on 64bit values internally, and then this problem
* may be triggered. See RandomConcurrent_test with the 32bit Mersenne twister as demo.
*/
template<class GEN>
class CappedGen
: public GEN
{
public:
using GEN::GEN;
typename GEN::result_type
operator()()
{
if constexpr (GEN::max() < std::numeric_limits<typename GEN::result_type>::max())
return GEN::operator()() % (GEN::max()+1);
else
return GEN::operator()();
}
};
template<class GEN>
auto
buildCappedSubSequence (RandomSequencer<GEN>& src)
{
typename RandomSequencer<GEN>::Seed seedChain(src);
RandomSequencer<CappedGen<GEN>> subSeq{seedChain};
return subSeq;
}
} // namespace lib
#endif /*LIB_RANDOM_H*/