s2ga/tests/test.cpp
2025-03-09 23:31:09 +03:00

355 lines
8.8 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <numbers>
#include <random>
#include <vector>
using namespace Catch;
constexpr double TOLERANCE = 1e-4;
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); // α=0.05
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);
}
};
}
inline double sphere(double x, double y)
{
return std::pow(x, 2.0) + std::pow(y, 2.0);
}
TEST_CASE("sphere(0, 0) = 0", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
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", "[test]")
{
REQUIRE(schaffer_n4(0.0, 1.25313) == Approx(0.292579).margin(TOLERANCE));
}
TEST_CASE("Should pass", "[test]")
{
REQUIRE_NOTHROW(foo());
}