#include "../s2ga.hpp" #include #include #include #include #include #include #include #include #include using namespace Catch; constexpr double TOLERANCE = 1e-4; TEST_CASE("lehmer64 rng") { s2ga::lehmer64 rng(0); REQUIRE(rng() != 0); const int N = 100; { std::vector 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 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") { 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(num_bins); const double critical_value = 16.92; // χ²(0.05, 9) std::vector 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); // α=0.05 std::vector 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); } }; } inline double sphere(double x, double y) { return std::pow(x, 2.0) + std::pow(y, 2.0); } TEST_CASE("sphere(0, 0) = 0") { REQUIRE(sphere(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double ackley(double x, double y) { return -20.0 * std::exp(-0.2 * std::sqrt(0.5 * sphere(x, y))) - std::exp(0.5 * (std::cos(2.0 * std::numbers::pi * x) + std::cos(2.0 * std::numbers::pi * y))) + std::numbers::e + 20.0; } TEST_CASE("ackley(0, 0) = 0") { REQUIRE(ackley(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double rastrigin(double x, double y) { const double A = 10.0; return 2.0 * A + (std::pow(x, 2.0) - A * std::cos(2.0 * std::numbers::pi * x)) + (std::pow(y, 2.0) - A * std::cos(2.0 * std::numbers::pi * y)); } TEST_CASE("rastrigin(0, 0) = 0") { REQUIRE(rastrigin(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double rosenbrock(double x, double y) { return 100.0 * std::pow(y - std::pow(x, 2.0), 2.0) + std::pow(1.0 - x, 2.0); } TEST_CASE("rosenbrock(1, 1) = 0") { REQUIRE(rosenbrock(1.0, 1.0) == Approx(0.0).margin(TOLERANCE)); } inline double bill(double x, double y) { return std::pow(1.5 - x + x * y, 2.0) + std::pow(2.25 - x + x * std::pow(y, 2.0), 2.0) + std::pow(2.625 - x + x * std::pow(y, 3.0), 2.0); } TEST_CASE("bill(3, 0.5) = 0") { REQUIRE(bill(3.0, 0.5) == Approx(0.0).margin(TOLERANCE)); } inline double goldstein_price(double x, double y) { return (1.0 + std::pow(x + y + 1.0, 2.0) * (19.0 - 14.0 * x + 3.0 * std::pow(x, 2.0) - 14.0 * y + 6.0 * x * y + 3.0 * std::pow(y, 2.0))) * (30.0 + std::pow(2.0 * x - 3.0 * y, 2.0) * (18.0 - 32.0 * x + 12.0 * std::pow(x, 2.0) + 48.0 * y - 36.0 * x * y + 27.0 * std::pow(y, 2.0))); } TEST_CASE("goldstein_price(0, -1) = 3") { REQUIRE(goldstein_price(0.0, -1.0) == Approx(3.0).margin(TOLERANCE)); } inline double booth(double x, double y) { return std::pow(x + 2.0 * y - 7.0, 2.0) + std::pow(2.0 * x + y - 5.0, 2.0); } TEST_CASE("booth(1, 3) = 0") { REQUIRE(booth(1.0, 3.0) == Approx(0.0).margin(TOLERANCE)); } inline double bukin_n6(double x, double y) { return 100.0 * std::sqrt(std::abs(y - 0.01 * std::pow(x, 2.0))) + 0.01 * std::abs(x + 10.0); } TEST_CASE("bukin_n6(-10, 1) = 0") { REQUIRE(bukin_n6(-10.0, 1.0) == Approx(0.0).margin(TOLERANCE)); } inline double matyas(double x, double y) { return 0.26 * sphere(x, y) - 0.48 * x * y; } TEST_CASE("matyas(0, 0) = 0") { REQUIRE(matyas(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double sin2(double x) { return std::pow(std::sin(x), 2.0); } inline double levi_n13(double x, double y) { return sin2(3.0 * std::numbers::pi * x) + std::pow(x - 1.0, 2.0) * (1.0 + sin2(3.0 * std::numbers::pi * y)) + std::pow(y - 1.0, 2.0) * (1.0 + sin2(2.0 * std::numbers::pi * y)); } TEST_CASE("levi_n13(1, 1) = 0") { REQUIRE(levi_n13(1.0, 1.0) == Approx(0.0).margin(TOLERANCE)); } inline double three_hump_camel(double x, double y) { return 2.0 * std::pow(x, 2.0) - 1.05 * std::pow(x, 4.0) + std::pow(x, 6.0) / 6.0 + x * y + std::pow(y, 2.0); } TEST_CASE("three_hump_camel(0, 0) = 0") { REQUIRE(three_hump_camel(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double eggholder(double x, double y) { return -(y + 47.0) * std::sin(std::sqrt(std::abs(x / 2.0 + (y + 47.0)))) - x * std::sin(std::sqrt(std::abs(x - (y + 47.0)))); } TEST_CASE("eggholder(512, 404.2319) = -959.6407") { REQUIRE(eggholder(512.0, 404.2319) == Approx(-959.6407).margin(TOLERANCE)); } inline double mccormick(double x, double y) { return std::sin(x + y) + std::pow(x - y, 2.0) - 1.5 * x + 2.5 * y + 1.0; } TEST_CASE("mccormick(-0.54719, -1.54719) = -1.9133") { REQUIRE(mccormick(-0.54719, -1.54719) == Approx(-1.9133).margin(TOLERANCE)); } inline double schaffer_n2(double x, double y) { return 0.5 + (sin2(std::pow(x, 2.0) - std::pow(y, 2.0)) - 0.5) / std::pow(1.0 + 0.001 * sphere(x, y), 2.0); } TEST_CASE("schaffer_n2(0, 0) = 0") { REQUIRE(schaffer_n2(0.0, 0.0) == Approx(0.0).margin(TOLERANCE)); } inline double cos2(double x) { return std::pow(std::cos(x), 2.0); } inline double schaffer_n4(double x, double y) { return 0.5 + (cos2(std::sin(std::abs(std::pow(x, 2.0) - std::pow(y, 2.0)))) - 0.5) / std::pow(1.0 + 0.001 * sphere(x, y), 2.0); } TEST_CASE("schaffer_n4(0, 1.25313) = 0.292579") { REQUIRE(schaffer_n4(0.0, 1.25313) == Approx(0.292579).margin(TOLERANCE)); } TEST_CASE("Should pass") { REQUIRE_NOTHROW(foo()); }