File indexing completed on 2026-10-29 08:25:02
0001
0002
0003
0004
0005
0006 #include <algorithms/random.h>
0007
0008 #include <catch2/catch_approx.hpp>
0009 #include <catch2/catch_test_macros.hpp>
0010
0011 #include <concepts>
0012 #include <cstddef>
0013 #include <cstdint>
0014 #include <limits>
0015 #include <memory>
0016 #include <random>
0017 #include <string>
0018 #include <string_view>
0019 #include <vector>
0020
0021 using namespace algorithms;
0022 using Catch::Approx;
0023
0024 namespace {
0025
0026 using value_type = detail::CachedBitGenerator::result_type;
0027
0028
0029 struct CountingEngine {
0030 std::shared_ptr<value_type> next = std::make_shared<value_type>(0);
0031 std::shared_ptr<std::vector<std::size_t>> calls =
0032 std::make_shared<std::vector<std::size_t>>();
0033
0034 std::vector<value_type> operator()(std::size_t n) const {
0035 calls->push_back(n);
0036 std::vector<value_type> ret(n);
0037 for (auto& v : ret) {
0038 v = (*next)++;
0039 }
0040 return ret;
0041 }
0042 };
0043
0044
0045 RandomEngineCB mtEngine(std::size_t seed) {
0046 auto gen = std::make_shared<std::mt19937_64>(seed);
0047 return [gen](std::size_t n) {
0048 std::vector<value_type> ret(n);
0049 for (auto& v : ret) {
0050 v = (*gen)();
0051 }
0052 return ret;
0053 };
0054 }
0055
0056
0057 RandomEngineCB constantEngine(value_type value) {
0058 return [value](std::size_t n) { return std::vector<value_type>(n, value); };
0059 }
0060
0061 struct Message {
0062 LogLevel level;
0063 std::string msg;
0064 bool operator==(const Message&) const = default;
0065 };
0066
0067 std::vector<Message>& messages() {
0068 static std::vector<Message> m;
0069 return m;
0070 }
0071
0072 void capture() {
0073 messages().clear();
0074 LogSvc::instance().init([](LogLevel l, std::string_view, std::string_view msg) {
0075 messages().push_back({l, std::string{msg}});
0076 });
0077 }
0078
0079 constexpr int kDraws = 10000;
0080
0081 }
0082
0083 TEST_CASE("CachedBitGenerator is a uniform random bit generator", "[random]") {
0084 STATIC_REQUIRE(std::uniform_random_bit_generator<detail::CachedBitGenerator>);
0085 STATIC_REQUIRE(detail::CachedBitGenerator::min() == 0);
0086 STATIC_REQUIRE(detail::CachedBitGenerator::max() == std::numeric_limits<value_type>::max());
0087 }
0088
0089 TEST_CASE("CachedBitGenerator refreshes its cache when exhausted", "[random]") {
0090 const CountingEngine engine;
0091 detail::CachedBitGenerator gen{engine, 3};
0092
0093
0094 CHECK(engine.calls->empty());
0095
0096 std::vector<value_type> values;
0097 for (int i = 0; i < 7; ++i) {
0098 values.push_back(gen());
0099 }
0100 CHECK(values == std::vector<value_type>{0, 1, 2, 3, 4, 5, 6});
0101 CHECK(*engine.calls == std::vector<std::size_t>{3, 3, 3});
0102 }
0103
0104 TEST_CASE("Generator draws uniform integers", "[random]") {
0105 const Generator gen{mtEngine(1), 16};
0106
0107 for (int i = 0; i < kDraws; ++i) {
0108 const auto def = gen.uniform_int();
0109 STATIC_REQUIRE(std::is_same_v<decltype(def), const int>);
0110 REQUIRE((def == 0 || def == 1));
0111
0112 const auto custom = gen.uniform_int<int64_t>(-5, 5);
0113 STATIC_REQUIRE(std::is_same_v<decltype(custom), const int64_t>);
0114 REQUIRE(custom >= -5);
0115 REQUIRE(custom <= 5);
0116 }
0117 }
0118
0119 TEST_CASE("Generator draws uniform floating point numbers", "[random]") {
0120 const Generator gen{mtEngine(2), 16};
0121
0122 double sum = 0;
0123 for (int i = 0; i < kDraws; ++i) {
0124 const auto def = gen.uniform_double();
0125 STATIC_REQUIRE(std::is_same_v<decltype(def), const double>);
0126 REQUIRE(def >= 0.);
0127 REQUIRE(def < 1.);
0128 sum += def;
0129
0130 const auto custom = gen.uniform_double<float>(2.f, 3.f);
0131 STATIC_REQUIRE(std::is_same_v<decltype(custom), const float>);
0132 REQUIRE(custom >= 2.f);
0133 REQUIRE(custom <= 3.f);
0134 }
0135 CHECK(sum / kDraws == Approx(0.5).margin(0.02));
0136 }
0137
0138 TEST_CASE("Generator draws Poisson distributed numbers", "[random]") {
0139 const Generator gen{mtEngine(3), 16};
0140
0141 double sum_def = 0;
0142 double sum_custom = 0;
0143 for (int i = 0; i < kDraws; ++i) {
0144 const auto def = gen.poisson();
0145 STATIC_REQUIRE(std::is_same_v<decltype(def), const int>);
0146 REQUIRE(def >= 0);
0147 sum_def += def;
0148
0149 const auto custom = gen.poisson<int64_t>(2.5);
0150 STATIC_REQUIRE(std::is_same_v<decltype(custom), const int64_t>);
0151 REQUIRE(custom >= 0);
0152 sum_custom += custom;
0153 }
0154 CHECK(sum_def / kDraws == Approx(1.).margin(0.05));
0155 CHECK(sum_custom / kDraws == Approx(2.5).margin(0.1));
0156 }
0157
0158 TEST_CASE("Generator draws exponentially distributed numbers", "[random]") {
0159 const Generator gen{mtEngine(4), 16};
0160
0161 double sum_def = 0;
0162 double sum_custom = 0;
0163 for (int i = 0; i < kDraws; ++i) {
0164 const auto def = gen.exponential();
0165 REQUIRE(def >= 0.);
0166 sum_def += def;
0167
0168 const auto custom = gen.exponential<float>(4.f);
0169 STATIC_REQUIRE(std::is_same_v<decltype(custom), const float>);
0170 REQUIRE(custom >= 0.f);
0171 sum_custom += custom;
0172 }
0173 CHECK(sum_def / kDraws == Approx(1.).margin(0.05));
0174 CHECK(sum_custom / kDraws == Approx(0.25).margin(0.0125));
0175 }
0176
0177 TEST_CASE("Generator draws Gaussian distributed numbers", "[random]") {
0178 const Generator gen{mtEngine(5), 16};
0179
0180 double sum_def = 0;
0181 double sum2_def = 0;
0182 double sum_custom = 0;
0183 for (int i = 0; i < kDraws; ++i) {
0184 const auto def = gen.gaussian();
0185 sum_def += def;
0186 sum2_def += def * def;
0187
0188 const auto custom = gen.gaussian<float>(10.f, 2.f);
0189 STATIC_REQUIRE(std::is_same_v<decltype(custom), const float>);
0190 sum_custom += custom;
0191 }
0192 CHECK(sum_def / kDraws == Approx(0.).margin(0.05));
0193 CHECK(sum2_def / kDraws == Approx(1.).margin(0.05));
0194 CHECK(sum_custom / kDraws == Approx(10.).margin(0.1));
0195 }
0196
0197 TEST_CASE("Generators with the same engine sequence draw the same numbers", "[random]") {
0198 const Generator gen1{mtEngine(6), 16};
0199 const Generator gen2{mtEngine(6), 16};
0200 for (int i = 0; i < 100; ++i) {
0201 REQUIRE(gen1.gaussian() == gen2.gaussian());
0202 }
0203 }
0204
0205 TEST_CASE("RandomSvc creates engines that return the requested number of values", "[random]") {
0206 auto engine = RandomSvc::instance().createEngine(7);
0207 CHECK(engine(0).empty());
0208 CHECK(engine(5).size() == 5);
0209 }
0210
0211 TEST_CASE("RandomSvc engines with the same seed draw the same numbers", "[random]") {
0212 auto& svc = RandomSvc::instance();
0213 auto engine1 = svc.createEngine(8);
0214 auto engine2 = svc.createEngine(8);
0215 CHECK(engine1(5) == engine2(5));
0216 }
0217
0218 TEST_CASE("RandomSvc engines draw new numbers on every call", "[random]") {
0219 auto engine = RandomSvc::instance().createEngine(9);
0220 CHECK(engine(5) != engine(5));
0221 }
0222
0223 TEST_CASE("RandomSvc engines with different seeds draw different numbers", "[random]") {
0224 auto& svc = RandomSvc::instance();
0225 auto engine1 = svc.createEngine(10);
0226 auto engine2 = svc.createEngine(11);
0227 CHECK(engine1(5) != engine2(5));
0228 }
0229
0230 TEST_CASE("RandomSvc engines are independent", "[random]") {
0231 auto& svc = RandomSvc::instance();
0232 auto engine1 = svc.createEngine(12);
0233 auto engine2 = svc.createEngine(12);
0234 auto engine3 = svc.createEngine(13);
0235
0236
0237 const auto first = engine1(5);
0238 engine3(5);
0239 const auto second = engine1(5);
0240 CHECK(first != second);
0241 CHECK(engine2(5) == first);
0242 CHECK(engine2(5) == second);
0243 }
0244
0245 TEST_CASE("Generators do not repeat when their cache is refreshed", "[random]") {
0246 const Generator gen{RandomSvc::instance().createEngine(14), 4};
0247 std::vector<double> values;
0248 for (int i = 0; i < 8; ++i) {
0249 values.push_back(gen.uniform_double());
0250 }
0251 CHECK(std::vector<double>(values.begin(), values.begin() + 4) !=
0252 std::vector<double>(values.begin() + 4, values.end()));
0253 }
0254
0255
0256 TEST_CASE("RandomSvc provides generators", "[random]") {
0257 capture();
0258 auto& svc = RandomSvc::instance();
0259 CHECK(svc.name() == "RandomSvc");
0260
0261
0262 const double before = svc.generator().uniform_double();
0263 CHECK(before >= 0.);
0264 CHECK(before < 1.);
0265
0266
0267 svc.init();
0268 CHECK(messages().empty());
0269
0270
0271 svc.init(constantEngine(0));
0272 CHECK(svc.generator().uniform_double() == 0.);
0273 CHECK(messages() ==
0274 std::vector<Message>{{LogLevel::kInfo, "Loading external generator function."}});
0275
0276
0277 const CountingEngine counting;
0278 svc.setProperty("cacheSize", std::size_t{4});
0279 svc.init(counting);
0280 const auto gen = svc.generator();
0281 for (int i = 0; i < 5; ++i) {
0282 gen.uniform_int<uint64_t>(0, std::numeric_limits<uint64_t>::max());
0283 }
0284 CHECK(*counting.calls == std::vector<std::size_t>{4, 4});
0285
0286
0287 messages().clear();
0288 svc.setProperty("seed", std::size_t{42});
0289 svc.init();
0290 const double seeded = svc.generator().uniform_double();
0291 CHECK(seeded >= 0.);
0292 CHECK(seeded < 1.);
0293 svc.init(constantEngine(0));
0294 CHECK(svc.generator().uniform_double() == 0.);
0295 CHECK(messages() ==
0296 std::vector<Message>{
0297 {LogLevel::kInfo, "Custom random seed requested: 42"},
0298 {LogLevel::kInfo, "Loading external generator function."},
0299 {LogLevel::kWarning,
0300 "Custom random seed request ignored when using external generator function"}});
0301 }