s2ga/tests/test.cpp
2025-03-12 21:44:21 +03:00

263 lines
6.7 KiB
C++

#include "../s2ga.hpp"
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_all.hpp>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_range_equals.hpp>
#include <cmath>
#include <random>
#include <vector>
using namespace Catch;
using namespace s2ga;
TEST_CASE("(dummy)", "[test]")
{
enum State
{
HUNGRY,
HAS_FOOD,
HAS_HOUSE,
HAS_CLOTHES,
HAS_WOOD,
HAS_TOOLS,
HAS_FABRIC,
HAS_ANIMAL_SKIN,
IS_WET,
HAS_FIRE,
IS_RAINING
};
action_list<State> actions =
{
{
.name = "COLLECT FRUITS",
.cost = 2.0f,
.positive_effects = bm<State>(HAS_FOOD),
.negative_preconds = bm<State>(IS_RAINING),
},
{
.name = "COLLECT FRUITS",
.cost = 2.0f,
.positive_effects = bm<State>(HAS_FOOD),
.negative_preconds = bm<State>(IS_RAINING),
},
{
.name = "MAKE FISH TRAP",
.cost = 0.5f,
.positive_effects = bm<State>(HAS_FOOD),
.negative_effects = bm<State>(HAS_WOOD),
.positive_preconds = bm<State>(HAS_TOOLS),
.negative_preconds = bm<State>(IS_WET),
},
{
.name = "BUILD HOUSE",
.cost = 8.0f,
.positive_effects = bm<State>(HAS_HOUSE),
.negative_effects = bm<State>(HAS_WOOD, IS_WET),
.positive_preconds = bm<State>(HAS_TOOLS, HAS_WOOD),
.negative_preconds = bm<State>(HAS_HOUSE),
},
{
.name = "CRAFT TOOLS",
.cost = 5.0f,
.positive_effects = bm<State>(HAS_TOOLS),
.negative_effects = bm<State>(HAS_WOOD),
.positive_preconds = bm<State>(HAS_WOOD),
},
{
.name = "MAKE CLOTHES",
.cost = 4.0f,
.positive_effects = bm<State>(HAS_CLOTHES),
.negative_effects = bm<State>(HAS_FABRIC),
.positive_preconds = bm<State>(HAS_FABRIC),
.negative_preconds = bm<State>(IS_WET),
},
{
.name = "HUNT ANIMAL",
.cost = 6.0f,
.positive_effects = bm<State>(HAS_ANIMAL_SKIN, HAS_FOOD),
.negative_effects = bm<State>(HUNGRY),
.positive_preconds = bm<State>(HAS_TOOLS),
.negative_preconds = bm<State>(IS_RAINING),
},
{
.name = "MAKE FIRE",
.cost = 2.0f,
.positive_effects = bm<State>(HAS_FIRE),
.negative_effects = bm<State>(HAS_WOOD, IS_WET),
.positive_preconds = bm<State>(HAS_WOOD),
},
{
.name = "COLLECT WOOD",
.cost = 2.0f,
.positive_effects = bm<State>(HAS_WOOD),
.negative_preconds = bm<State>(IS_RAINING),
},
{
.name = "WAIT OUT RAIN",
.cost = 5.0f,
.negative_effects = bm<State>(IS_RAINING),
.positive_preconds = bm<State>(IS_RAINING),
}
};
for(auto& action: actions)
{
action.print();
}
}
TEST_CASE("lehmer64 rng", "[test]")
{
s2ga::lehmer64 rng(0);
REQUIRE(rng() != 0);
const int N = 100;
{
std::vector<int> ivalues;
rng.seed(4);
for(int i = 0; i < N; ++i)
{
ivalues.emplace_back(rng.random(0, 256));
}
rng.seed(4);
for(int i = 0; i < N; ++i)
{
REQUIRE(ivalues[i] == rng.random(0, 256));
}
}
{
std::vector<float> fvalues;
rng.seed(5);
for(int i = 0; i < N; ++i)
{
fvalues.emplace_back(rng.random(-256.0f, 256.0f));
}
rng.seed(5);
for(int i = 0; i < N; ++i)
{
REQUIRE(fvalues[i] == rng.random(-256.0f, 256.0f));
}
}
}
TEST_CASE("uniform distribution tests", "[test]")
{
s2ga::lehmer64 rng(42); // Fixed seed for reproducibility
SECTION("integer uniformity - chi-squared test")
{
const int min = 0;
const int max = 9;
const int num_samples = 1'000'000;
const int num_bins = max - min + 1;
const double expected = num_samples / static_cast<double>(num_bins);
const double critical_value = 16.92; // χ²(0.05, 9)
std::vector<int> counts(num_bins, 0);
for(int i = 0; i < num_samples; ++i)
{
int val = rng.random(min, max);
++counts[val - min];
}
double chi_sq = 0.0;
for(int count: counts)
{
double diff = count - expected;
chi_sq += (diff * diff) / expected;
}
CHECK(chi_sq < critical_value);
}
SECTION("floating point uniformity - Kolmogorov-Smirnov test")
{
const double min = 0.0;
const double max = 1.0;
const int num_samples = 100'000;
const double ks_critical = 1.36 / std::sqrt(num_samples);
std::vector<double> samples(num_samples);
std::generate(samples.begin(), samples.end(),
[&] { return rng.random(min, max); });
std::sort(samples.begin(), samples.end());
double d_plus = 0.0;
double d_minus = 0.0;
for(size_t i = 0; i < samples.size(); ++i)
{
double fn = (i + 1.0) / num_samples;
double f = samples[i];
d_plus = std::max(d_plus, fn - f);
d_minus = std::max(d_minus, f - (i / (num_samples - 1.0)));
}
double d_stat = std::max(d_plus, d_minus);
CHECK(d_stat < ks_critical);
}
SECTION("edge case coverage")
{
const int iterations = 10'000;
// Test minimum and maximum inclusion
bool hit_min = false;
bool hit_max = false;
for(int i = 0; i < iterations; ++i)
{
int val = rng.random(1, 10);
if(val == 1)
hit_min = true;
if(val == 10)
hit_max = true;
}
CHECK(hit_min);
CHECK(hit_max);
// Floating point bounds check
for(int i = 0; i < iterations; ++i)
{
double val = rng.random(0.0, 1.0);
CHECK(val >= 0.0);
CHECK(val < 1.0);
}
}
}
TEST_CASE("random benchmarking", "[benchmark]")
{
s2ga::lehmer64 rng(0);
const int N = 100'000'000;
std::mt19937 std_rng(4);
std::uniform_int_distribution dist(-512, 512);
BENCHMARK("mt19937 100'000'000")
{
for(int i = 0; i < N; ++i)
{
(void)dist(std_rng);
}
};
BENCHMARK("lehmer64 100'000'000")
{
for(int i = 0; i < N; ++i)
{
(void)dist(rng);
}
};
}