|
- /// \file
- // Range v3 library
- //
- // Copyright Casey Carter 2016
- //
- // Use, modification and distribution is subject to the
- // Boost Software License, Version 1.0. (See accompanying
- // file LICENSE_1_0.txt or copy at
- // http://www.boost.org/LICENSE_1_0.txt)
- //
- // Project home: https://github.com/ericniebler/range-v3
- //
-
- /*
- * Random-Number Utilities (randutil)
- * Addresses common issues with C++11 random number generation.
- * Makes good seeding easier, and makes using RNGs easy while retaining
- * all the power.
- *
- * The MIT License (MIT)
- *
- * Copyright (c) 2015 Melissa E. O'Neill
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
- #ifndef RANGES_V3_UTILITY_RANDOM_HPP
- #define RANGES_V3_UTILITY_RANDOM_HPP
-
- #include <array>
- #include <cstddef>
- #include <cstdint>
- #include <initializer_list>
- #include <new>
- #include <random>
-
- #include <meta/meta.hpp>
-
- #include <concepts/concepts.hpp>
-
- #include <range/v3/range_fwd.hpp>
-
- #include <range/v3/algorithm/copy.hpp>
- #include <range/v3/algorithm/generate.hpp>
- #include <range/v3/functional/invoke.hpp>
- #include <range/v3/functional/reference_wrapper.hpp>
- #include <range/v3/iterator/concepts.hpp>
-
- #if !RANGES_CXX_THREAD_LOCAL
- #include <mutex>
- #endif
-
- RANGES_DIAGNOSTIC_PUSH
- RANGES_DIAGNOSTIC_IGNORE_CXX17_COMPAT
-
- namespace ranges
- {
- /// \addtogroup group-numerics
- /// @{
- // clang-format off
- CPP_def
- (
- template(typename Gen)
- concept uniform_random_bit_generator,
- requires(int)
- (
- Gen::min(),
- Gen::max(),
- concepts::requires_<same_as<invoke_result_t<Gen&>, decltype(Gen::min())>>,
- concepts::requires_<same_as<invoke_result_t<Gen&>, decltype(Gen::max())>>
- ) &&
- invocable<Gen &> &&
- unsigned_integral<invoke_result_t<Gen&>>
- );
- // clang-format on
- /// @}
-
- /// \cond
- namespace detail
- {
- namespace randutils
- {
- inline std::array<std::uint32_t, 8> get_entropy()
- {
- std::array<std::uint32_t, 8> seeds;
-
- // Hopefully high-quality entropy from random_device.
- #if defined(__GLIBCXX__) && defined(RANGES_WORKAROUND_VALGRIND_RDRAND)
- std::random_device rd{"/dev/urandom"};
- #else
- std::random_device rd;
- #endif
- std::uniform_int_distribution<std::uint32_t> dist{};
- ranges::generate(seeds, [&] { return dist(rd); });
-
- return seeds;
- }
-
- template<typename I>
- constexpr auto fast_exp(I x, I power, I result = I{1}) -> CPP_ret(I)( //
- requires unsigned_integral<I>)
- {
- return power == I{0}
- ? result
- : randutils::fast_exp(
- x * x, power >> 1, result * (power & I{1} ? x : 1));
- }
-
- //////////////////////////////////////////////////////////////////////////////
- //
- // seed_seq_fe
- //
- //////////////////////////////////////////////////////////////////////////////
-
- /*
- * seed_seq_fe implements a fixed-entropy seed sequence; it conforms to all
- * the requirements of a Seed Sequence concept.
- *
- * seed_seq_fe<N> implements a seed sequence which seeds based on a store of
- * N * 32 bits of entropy. Typically, it would be initialized with N or more
- * integers.
- *
- * seed_seq_fe128 and seed_seq_fe256 are provided as convenience typedefs for
- * 128- and 256-bit entropy stores respectively. These variants outperform
- * std::seed_seq, while being better mixing the bits it is provided as
- * entropy. In almost all common use cases, they serve as better drop-in
- * replacements for seed_seq.
- *
- * Technical details
- *
- * Assuming it constructed with M seed integers as input, it exhibits the
- * following properties
- *
- * * Diffusion/Avalanche: A single-bit change in any of the M inputs has a
- * 50% chance of flipping every bit in the bitstream produced by generate.
- * Initializing the N-word entropy store with M words requires O(N * M)
- * time precisely because of the avalanche requirements. Once constructed,
- * calls to generate are linear in the number of words generated.
- *
- * * Bias freedom/Bijection: If M == N, the state of the entropy store is a
- * bijection from the M inputs (i.e., no states occur twice, none are
- * omitted). If M > N the number of times each state can occur is the same
- * (each state occurs 2**(32*(M-N)) times, where ** is the power function).
- * If M < N, some states cannot occur (bias) but no state occurs more
- * than once (it's impossible to avoid bias if M < N; ideally N should not
- * be chosen so that it is more than M).
- *
- * Likewise, the generate function has similar properties (with the entropy
- * store as the input data). If more outputs are requested than there is
- * entropy, some outputs cannot occur. For example, the Mersenne Twister
- * will request 624 outputs, to initialize its 19937-bit state, which is
- * much larger than a 128-bit or 256-bit entropy pool. But in practice,
- * limiting the Mersenne Twister to 2**128 possible initializations gives
- * us enough initializations to give a unique initialization to trillions
- * of computers for billions of years. If you really have 624 words of
- * *real* high-quality entropy you want to use, you probably don't need
- * an entropy mixer like this class at all. But if you *really* want to,
- * nothing is stopping you from creating a randutils::seed_seq_fe<624>.
- *
- * * As a consequence of the above properties, if all parts of the provided
- * seed data are kept constant except one, and the remaining part is varied
- * through K different states, K different output sequences will be
- * produced.
- *
- * * Also, because the amount of entropy stored is fixed, this class never
- * performs dynamic allocation and is free of the possibility of generating
- * an exception.
- *
- * Ideas used to implement this code include hashing, a simple PCG generator
- * based on an MCG base with an XorShift output function and permutation
- * functions on tuples.
- *
- * More detail at
- * http://www.pcg-random.org/posts/developing-a-seed_seq-alternative.html
- */
-
- template<std::size_t count, typename IntRep = std::uint32_t>
- struct seed_seq_fe
- {
- public:
- CPP_assert(unsigned_integral<IntRep>);
- typedef IntRep result_type;
-
- private:
- static constexpr std::size_t mix_rounds = 1 + (count <= 2);
-
- static constexpr std::uint32_t INIT_A = 0x43b0d7e5;
- static constexpr std::uint32_t MULT_A = 0x931e8875;
-
- static constexpr std::uint32_t INIT_B = 0x8b51f9dd;
- static constexpr std::uint32_t MULT_B = 0x58f38ded;
-
- static constexpr std::uint32_t MIX_MULT_L = 0xca01f9dd;
- static constexpr std::uint32_t MIX_MULT_R = 0x4973f715;
- static constexpr std::uint32_t XSHIFT = sizeof(IntRep) * 8 / 2;
-
- std::array<IntRep, count> mixer_;
-
- template<typename I, typename S>
- auto mix_entropy(I first, S last) -> CPP_ret(void)( //
- requires input_iterator<I> && sentinel_for<S, I> &&
- convertible_to<iter_reference_t<I>, IntRep>)
- {
- auto hash_const = INIT_A;
- auto hash = [&](IntRep value) RANGES_INTENDED_MODULAR_ARITHMETIC {
- value ^= hash_const;
- hash_const *= MULT_A;
- value *= hash_const;
- value ^= value >> XSHIFT;
- return value;
- };
- auto mix = [](IntRep x, IntRep y) RANGES_INTENDED_MODULAR_ARITHMETIC {
- IntRep result = MIX_MULT_L * x - MIX_MULT_R * y;
- result ^= result >> XSHIFT;
- return result;
- };
-
- for(auto & elem : mixer_)
- {
- if(first != last)
- {
- elem = hash(static_cast<IntRep>(*first));
- ++first;
- }
- else
- elem = hash(IntRep{0});
- }
- for(auto & src : mixer_)
- for(auto & dest : mixer_)
- if(&src != &dest)
- dest = mix(dest, hash(src));
- for(; first != last; ++first)
- for(auto & dest : mixer_)
- dest = mix(dest, hash(static_cast<IntRep>(*first)));
- }
-
- public:
- seed_seq_fe(const seed_seq_fe &) = delete;
- void operator=(const seed_seq_fe &) = delete;
-
- template<typename T>
- CPP_ctor(seed_seq_fe)(std::initializer_list<T> init)( //
- requires convertible_to<T const &, IntRep>)
- {
- seed(init.begin(), init.end());
- }
-
- template<typename I, typename S>
- CPP_ctor(seed_seq_fe)(I first, S last)( //
- requires input_iterator<I> && sentinel_for<S, I> &&
- convertible_to<iter_reference_t<I>, IntRep>)
- {
- seed(first, last);
- }
-
- // generating functions
- template<typename I, typename S>
- RANGES_INTENDED_MODULAR_ARITHMETIC auto generate(I first,
- S const last) const
- -> CPP_ret(void)( //
- requires random_access_iterator<I> && sentinel_for<S, I>)
- {
- auto src_begin = mixer_.begin();
- auto src_end = mixer_.end();
- auto src = src_begin;
- auto hash_const = INIT_B;
- for(; first != last; ++first)
- {
- auto dataval = *src;
- if(++src == src_end)
- src = src_begin;
- dataval ^= hash_const;
- hash_const *= MULT_B;
- dataval *= hash_const;
- dataval ^= dataval >> XSHIFT;
- *first = dataval;
- }
- }
-
- constexpr std::size_t size() const
- {
- return count;
- }
-
- template<typename O>
- RANGES_INTENDED_MODULAR_ARITHMETIC auto param(O dest) const
- -> CPP_ret(void)( //
- requires weakly_incrementable<O> &&
- indirectly_copyable<decltype(mixer_.begin()), O>)
- {
- constexpr IntRep INV_A = randutils::fast_exp(MULT_A, IntRep(-1));
- constexpr IntRep MIX_INV_L =
- randutils::fast_exp(MIX_MULT_L, IntRep(-1));
-
- auto mixer_copy = mixer_;
- for(std::size_t round = 0; round < mix_rounds; ++round)
- {
- // Advance to the final value. We'll backtrack from that.
- auto hash_const =
- INIT_A * randutils::fast_exp(MULT_A, IntRep(count * count));
-
- for(auto src = mixer_copy.rbegin(); src != mixer_copy.rend();
- ++src)
- for(auto rdest = mixer_copy.rbegin();
- rdest != mixer_copy.rend();
- ++rdest)
- if(src != rdest)
- {
- IntRep revhashed = *src;
- auto mult_const = hash_const;
- hash_const *= INV_A;
- revhashed ^= hash_const;
- revhashed *= mult_const;
- revhashed ^= revhashed >> XSHIFT;
- IntRep unmixed = *rdest;
- unmixed ^= unmixed >> XSHIFT;
- unmixed += MIX_MULT_R * revhashed;
- unmixed *= MIX_INV_L;
- *rdest = unmixed;
- }
-
- for(auto i = mixer_copy.rbegin(); i != mixer_copy.rend(); ++i)
- {
- IntRep unhashed = *i;
- unhashed ^= unhashed >> XSHIFT;
- unhashed *= randutils::fast_exp(hash_const, IntRep(-1));
- hash_const *= INV_A;
- unhashed ^= hash_const;
- *i = unhashed;
- }
- }
- ranges::copy(mixer_copy, dest);
- }
-
- template<typename I, typename S>
- auto seed(I first, S last) -> CPP_ret(void)( //
- requires input_iterator<I> && sentinel_for<S, I> &&
- convertible_to<iter_reference_t<I>, IntRep>)
- {
- mix_entropy(first, last);
- // For very small sizes, we do some additional mixing. For normal
- // sizes, this loop never performs any iterations.
- for(std::size_t i = 1; i < mix_rounds; ++i)
- stir();
- }
-
- seed_seq_fe & stir()
- {
- mix_entropy(mixer_.begin(), mixer_.end());
- return *this;
- }
- };
-
- using seed_seq_fe128 = seed_seq_fe<4, std::uint32_t>;
- using seed_seq_fe256 = seed_seq_fe<8, std::uint32_t>;
-
- //////////////////////////////////////////////////////////////////////////////
- //
- // auto_seeded
- //
- //////////////////////////////////////////////////////////////////////////////
-
- /*
- * randutils::auto_seeded
- *
- * Extends a seed sequence class with a nondeterministic default
- * constructor. Uses a variety of local sources of entropy to portably
- * initialize any seed sequence to a good default state.
- *
- * In normal use, it's accessed via one of the following type aliases, which
- * use seed_seq_fe128 and seed_seq_fe256 above.
- *
- * randutils::auto_seed_128
- * randutils::auto_seed_256
- *
- * It's discussed in detail at
- * http://www.pcg-random.org/posts/simple-portable-cpp-seed-entropy.html
- * and its motivation (why you can't just use std::random_device) here
- * http://www.pcg-random.org/posts/cpps-random_device.html
- */
-
- template<typename SeedSeq>
- struct auto_seeded : public SeedSeq
- {
- auto_seeded()
- : auto_seeded(randutils::get_entropy())
- {}
- template<std::size_t N>
- auto_seeded(std::array<std::uint32_t, N> const & seeds)
- : SeedSeq(seeds.begin(), seeds.end())
- {}
- using SeedSeq::SeedSeq;
-
- const SeedSeq & base() const
- {
- return *this;
- }
- SeedSeq & base()
- {
- return *this;
- }
- };
-
- using auto_seed_128 = auto_seeded<seed_seq_fe128>;
- using auto_seed_256 = auto_seeded<seed_seq_fe256>;
- } // namespace randutils
-
- using default_URNG = meta::if_c<(sizeof(void *) >= sizeof(long long)),
- std::mt19937_64, std::mt19937>;
-
- #if !RANGES_CXX_THREAD_LOCAL
- template<typename URNG>
- class sync_URNG : private URNG
- {
- mutable std::mutex mtx_;
-
- public:
- using URNG::URNG;
- sync_URNG() = default;
- using typename URNG::result_type;
- result_type operator()()
- {
- std::lock_guard<std::mutex> guard{mtx_};
- return static_cast<URNG &>(*this)();
- }
- using URNG::max;
- using URNG::min;
- };
- using default_random_engine = sync_URNG<default_URNG>;
- #else
- using default_random_engine = default_URNG;
- #endif
-
- template<typename T = void>
- default_random_engine & get_random_engine()
- {
- using Seeder = meta::if_c<(sizeof(default_URNG) > 16),
- randutils::auto_seed_256,
- randutils::auto_seed_128>;
-
- #if RANGES_CXX_THREAD_LOCAL >= RANGES_CXX_THREAD_LOCAL_11
- static thread_local default_random_engine engine{Seeder{}.base()};
-
- #elif RANGES_CXX_THREAD_LOCAL
- static __thread bool initialized = false;
- static __thread meta::_t<std::aligned_storage<sizeof(default_random_engine),
- alignof(default_random_engine)>>
- storage;
-
- if(!initialized)
- {
- ::new(static_cast<void *>(&storage))
- default_random_engine{Seeder{}.base()};
- initialized = true;
- }
- auto & engine = reinterpret_cast<default_random_engine &>(storage);
- #else
- static default_random_engine engine{Seeder{}.base()};
- #endif // RANGES_CXX_THREAD_LOCAL
-
- return engine;
- }
- } // namespace detail
- /// \endcond
- } // namespace ranges
-
- RANGES_DIAGNOSTIC_POP
-
- #endif
|