/* ***************************************************************************** * kblib is a general utility library for C++14 and C++17, intended to provide * performant high-level abstractions and more expressive ways to do simple * things. * * Copyright (c) 2021 killerbee * * This program 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 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * ****************************************************************************/ /** * @file * @brief Provides utilities to correctly and expressively use C++11's random * number generation library, without requiring a PhD. * * @author killerbee * @date 2019-2021 * @copyright GNU General Public Licence v3.0 */ #ifndef RANDOM_H #define RANDOM_H #include "algorithm.h" #include "iterators.h" #include "memory.h" #include "simple.h" #include "stats.h" #include "tdecl.h" #include #include #include #if KBLIB_DEBUG_SEED_SEQ # include #endif namespace KBLIB_NS { /** * @brief Given a categorical distribution cats, selects one category * * @deprecated std::discrete_distribution provides the same functionality, with * a worse name. Because it exists, there is no reason to use this function. * * @param cats A sequence of category weights * @param r A -compatible RandomGenerator * @todo Refactor to remove the ugly unreachable stuff. */ template KBLIB_NODISCARD [[deprecated("Use std::discrete_distribution instead")]] constexpr auto chooseCategorical(Array&& cats, RandomGenerator& r) -> decltype(cats.size()) { std::uniform_real_distribution uniform( 0.0, kblib::accumulate(cats.begin(), cats.end(), 0.0)); freqtype choose = uniform(r); for (decltype(cats.size()) stop = 0; stop != cats.size(); ++stop) { choose -= cats[stop]; if (choose <= 0) { return stop; } } #ifdef __has_builtin # if __has_builtin(__builtin_unreachable) __builtin_unreachable(); # else return cats.size() - 1; # endif #else return cats.size() - 1; #endif } class KBLIB_NODISCARD trivial_seed_seq { public: using result_type = std::uint32_t; trivial_seed_seq() = default; template trivial_seed_seq(InputIt begin, InputIt end) : data(begin, end) {} template trivial_seed_seq(std::initializer_list il) : trivial_seed_seq(il.begin(), il.end()) {} template trivial_seed_seq(Source gen, std::size_t count) : data(count) { kblib::generate_n(data.begin(), count, std::ref(gen)); } // Work around std::linear_congruential_engine without overpulling from the // random source template trivial_seed_seq(Source gen, std::size_t count, std::size_t discard) : data(count + discard, 0x8b8b8b8bu) { kblib::generate_n(data.begin() + kblib::to_signed(discard), count, std::ref(gen)); } template auto generate(RandomAccessIt begin, RandomAccessIt end) const -> void { auto o_size = end - begin; auto d_size = to_signed(data.size()); #if KBLIB_DEBUG_SEED_SEQ if (o_size < d_size) { std::clog << "trivial_seed_seq: overseeded for output size of " << o_size << ", have data size " << d_size << '\n'; } #endif if (data.empty()) { std::fill(begin, end, 0x8b8b8b8bu); // copied from std::seed_seq } else { #if KBLIB_DEBUG_SEED_SEQ if (o_size > d_size) { std::clog << "trivial_seed_seq: unexpectedly wrapping, output size " << o_size << " greater than data size " << d_size << '\n'; } #endif auto dpos = begin; do { auto blk_size = saturating_cast(std::min(d_size, o_size)); dpos = std::copy_n(data.begin(), blk_size, dpos); } while ((o_size -= d_size) > 0); } return; } KBLIB_NODISCARD auto size() const noexcept -> std::size_t { return data.size(); } template auto param(OutputIt dest) const -> void { std::copy(data.begin(), data.end(), dest); return; } private: std::vector data; }; template struct state_size; template struct state_size> : std::integral_constant {}; template struct state_size> : std::integral_constant { // std::linear_congruential_engine discards 3 seed words due to hardcoded // hack for std::seed_seq, so we work around that with this trait KBLIB_CONSTANT_M std::size_t seed_discard = 3; }; template struct state_size> : std::integral_constant {}; template struct state_size> : state_size {}; template struct state_size> : state_size {}; template struct state_size> : state_size {}; template constexpr std::size_t state_size_v = state_size::value; static_assert(state_size_v == std::mt19937::state_size, ""); static_assert(state_size_v == std::mt19937_64::state_size * 2, ""); template constexpr std::size_t seed_discard_v = 0; template constexpr std::size_t seed_discard_v< T, void_t::seed_discard)>> = state_size:: seed_discard; static_assert(seed_discard_v == 3, "linear_congruential_engines discard 3 words of seed data"); template KBLIB_NODISCARD auto seeded(Source&& s) -> Gen { auto seed = trivial_seed_seq(std::ref(s), state_size_v, seed_discard_v); return Gen{seed}; } template KBLIB_NODISCARD auto seeded() -> Gen { return seeded(std::random_device{}); } template class KBLIB_NODISCARD transform_engine : URBG { private: using E = URBG; static_assert(std::is_default_constructible::value, ""); KBLIB_NODISCARD constexpr auto engine() -> E& { return static_cast(*this); } KBLIB_NODISCARD constexpr auto engine() const -> const E& { return static_cast(*this); } public: using result_type = typename Transform::result_type; constexpr transform_engine() = default; constexpr transform_engine(const transform_engine&) noexcept( std::is_nothrow_copy_constructible::value) = default; constexpr transform_engine(transform_engine&&) noexcept( std::is_nothrow_move_constructible::value) = default; constexpr transform_engine(result_type s) : E(s) {} template ::value>> constexpr transform_engine(SSeq& s) : E(s) {} KBLIB_NODISCARD auto operator=(const transform_engine&) -> transform_engine& = delete; KBLIB_NODISCARD auto operator=(transform_engine&&) -> transform_engine& = delete; ~transform_engine() = default; KBLIB_NODISCARD constexpr auto operator()() noexcept -> result_type { return Transform{}(engine()()); } // using E::seed; using E::discard; KBLIB_NODISCARD static constexpr auto min() noexcept -> result_type { return Transform::min(URBG::min(), URBG::max()); } KBLIB_NODISCARD static constexpr auto max() noexcept -> result_type { return Transform::max(URBG::min(), URBG::max()); } KBLIB_NODISCARD friend constexpr auto operator==( const transform_engine& lhs, const transform_engine& rhs) noexcept -> bool { return lhs.engine() == rhs.engine(); } KBLIB_NODISCARD friend constexpr auto operator!=( const transform_engine& lhs, const transform_engine& rhs) noexcept -> bool { return ! (lhs == rhs); } friend constexpr auto operator<<(std::ostream& os, const transform_engine& e) -> std::ostream& { return os << e.engine(); } friend constexpr auto operator>>(std::istream& is, transform_engine& e) -> std::istream& { return is >> e.engine(); } }; template struct state_size> : state_size {}; template struct shift_mask { using result_type = UIntType; template KBLIB_NODISCARD static constexpr auto g(UIntInput in) noexcept -> UIntType { return static_cast(in >> shift) & mask; } KBLIB_NODISCARD constexpr auto operator()(UIntType in) const noexcept -> UIntType { return g(in); } KBLIB_NODISCARD static constexpr auto min(UIntType min, KBLIB_UNUSED UIntType max) noexcept -> UIntType { return g(min); } KBLIB_NODISCARD static constexpr auto max(KBLIB_UNUSED UIntType min, UIntType max) noexcept -> UIntType { return g(max); } }; template KBLIB_NODISCARD constexpr auto ipow2(UIntType b) noexcept -> UIntType { if (b == std::numeric_limits::digits) { return 0u; } else { return UIntType{1} << b; } } inline namespace lcgs { // shortcut alias for common case of m = 2^b template using lcg_p2 = std::linear_congruential_engine; inline namespace common_lcgs { using rand48 = transform_engine, shift_mask>; using java_rand = rand48; using glibc_rand0 = transform_engine, shift_mask>; using ansic_rand = transform_engine, shift_mask>; using knuth_lcg = std::linear_congruential_engine< uint64_t, 6364136223846793005U, 1442695040888963407U, 0U>; /* Knuth's preferred 64-bit LCG */ } // namespace common_lcgs inline namespace best_lcgs { // mcg = multiplicative congruential generator // note that for an mcg to work effectively, the seed must be coprime to m // in this case, that means seeds must be odd // lcgs do not have this restriction. using lcg32 = lcg_p2; using mcg32 = lcg_p2; using lcg48 = lcg_p2; using mcg48 = lcg_p2; using lcg64 = lcg_p2; using mcg64 = lcg_p2; } // namespace best_lcgs } // namespace lcgs } // namespace KBLIB_NS #endif // RANDOM_H