Program Listing for File RngThread.hpp

↰ Return to documentation for file (src/utils/RngThread.hpp)

#ifndef RNGUTILS_HPP
#define RNGUTILS_HPP

#include "ThreadVec.hpp"
#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <memory>
#ifdef _OPENMP
#   include <omp.h>
#endif
#include <pcg_extras.hpp>
#include <pcg_random.hpp>
#include <random>
#include <sstream>
#include <stdexcept>
#include <string>

namespace utils {

    using RngType = pcg64;
    inline constexpr size_t rngStateWidth = 128;
    using RngState = std::array<char, rngStateWidth>;

    class RngThread {

    public:

        RngThread()
        {
            for (auto& r : rngEngines_)
            {
                pcg_extras::seed_seq_from<std::random_device> seedSource;
                r = std::make_unique<RngType>(seedSource);
            }
        }

        RngThread(const RngType::state_type seed)
        {
            for (int i = 0; i < rngEngines_.size(); ++i)
            {
                rngEngines_[i] = std::make_unique<RngType>(seed + i); // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access)
            }
        }

        ~RngThread() = default;

        // Disable copy and move constructors and assignment operators
        RngThread(const RngThread&) = delete;
        RngThread(RngThread&&) = delete;
        auto operator=(const RngThread&) -> RngThread& = delete;
        auto operator=(RngThread&&) -> RngThread& = delete;

        void seed(const RngType::state_type seed)
        {
            for (int i = 0; i < rngEngines_.size(); ++i)
            {
                rngEngines_[i]->seed(seed+i); // NOLINT(cppcoreguidelines-pro-bounds-avoid-unchecked-container-access)
            }
        }

        auto operator()() const -> RngType& { return *rngEngines_(); }

        [[nodiscard]] auto getState() const -> RngState
        {
            std::ostringstream stateStream;
            stateStream << *rngEngines_();
            const std::string state = stateStream.str();
            if (state.size() >= rngStateWidth)
            {
                throw std::runtime_error(
                    "RngThread: serialized state does not fit in the "
                    "fixed-width RngState buffer");
            }
            RngState buf{};
            std::ranges::copy(state, buf.begin());
            return buf;
        }

        void setState(const RngState& state)
        {
            std::istringstream stateStream{std::string(state.data())};
            stateStream >> *rngEngines_();
        }

    private:

        ThreadVec<std::unique_ptr<pcg64> > rngEngines_;
    };

    inline auto rng() -> RngThread&
    {
        try
        {
            static RngThread instance;
            return instance;
        }
        catch (const std::exception& error)
        {
            std::cerr << "FATAL: random number generator initialization "
                "failed: " << error.what() << "\n";
            std::exit(1); // NOLINT(concurrency-mt-unsafe) -- the program is terminating regardless; thread-safety of the shutdown path doesn't matter
        }
    }

} // namespace utils

#endif // RNGUTILS_HPP