test(tests): added test suite
The test suite is extensive. Note that LLMs were used to aid in the development of the test suite. All LLM generated code was manually reviewed.
This commit is contained in:
119
tests/normalization/configuration.cpp
Normal file
119
tests/normalization/configuration.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <numbers>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
|
||||
#include "serif/solver/normalization/configuration.hpp"
|
||||
|
||||
namespace {
|
||||
namespace normalization = serif::solver::normalization;
|
||||
namespace dimensions = serif::dimensions;
|
||||
using Gravity = dimensions::QuantityValue<dimensions::GravitationalConstant>;
|
||||
using Catch::Matchers::WithinRel;
|
||||
|
||||
/** @brief Keep dimensional reference inputs visible in each numerical test. */
|
||||
[[nodiscard]] auto reference_scheme(const double n, const double mass, const double density) {
|
||||
return normalization::LaneEmdenApproximation{
|
||||
.n = n,
|
||||
.mass = dimensions::QuantityValue<dimensions::Mass>{mass},
|
||||
.central_density = dimensions::QuantityValue<dimensions::MassDensity>{density}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Normalization configuration preserves explicit choices without numerical preparation", "[unit][normalization][configuration]") {
|
||||
const auto defaults = normalization::make_normalization();
|
||||
CHECK(defaults.scheme.n == 3.0);
|
||||
CHECK(defaults.gravitational_constant.value() == 1.0);
|
||||
|
||||
// An invalid numerical input can still be described; preparation supplies the recoverable failure.
|
||||
const auto options = normalization::make_normalization(normalization::NormalizationOptions{
|
||||
.scheme = reference_scheme(-1.0, 7.0, 2.0),
|
||||
.coordinates = normalization::Dimensionless{},
|
||||
.gravitational_constant = Gravity{0.25}
|
||||
});
|
||||
CHECK(options.scheme.n == -1.0);
|
||||
CHECK(options.scheme.mass.value() == 7.0);
|
||||
CHECK(options.scheme.central_density.value() == 2.0);
|
||||
CHECK(options.gravitational_constant.value() == 0.25);
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(options.scheme, options.gravitational_constant));
|
||||
}
|
||||
|
||||
TEST_CASE("Lane Emden normalization reference radii match analytic polytropes", "[unit][normalization][configuration]") {
|
||||
constexpr double mass = 7.0;
|
||||
constexpr double central_density = 2.0;
|
||||
constexpr double gravity = 0.25;
|
||||
for (const double n : {0.0, 1.0}) {
|
||||
const auto reference = normalization::prepare_reference_scales(reference_scheme(n, mass, central_density), Gravity{gravity});
|
||||
REQUIRE(reference);
|
||||
double radius_cubed = 3.0 * mass / (4.0 * std::numbers::pi * central_density);
|
||||
if (n == 1.0) {
|
||||
radius_cubed = std::numbers::pi * mass / (4.0 * central_density);
|
||||
}
|
||||
const double radius = std::cbrt(radius_cubed);
|
||||
CHECK_THAT(reference->radius.value(), WithinRel(radius, 2.0e-8));
|
||||
CHECK_THAT(reference->mass.value(), WithinRel(mass, 2.0e-14));
|
||||
CHECK(reference->gravitational_constant.value() == gravity);
|
||||
CHECK_THAT(reference->density.value(), WithinRel(mass / radius_cubed, 6.0e-8));
|
||||
CHECK_THAT(reference->specific_energy.value(), WithinRel(gravity * mass / radius, 2.0e-8));
|
||||
CHECK_THAT(reference->pressure.value(), WithinRel(gravity * mass * mass / std::pow(radius, 4), 8.0e-8));
|
||||
CHECK_THAT(reference->angular_velocity.value(), WithinRel(std::sqrt(gravity * mass / radius_cubed), 4.0e-8));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Lane Emden reference preparation respects consistent physical-unit conversions", "[unit][normalization][configuration]") {
|
||||
constexpr double mass_conversion = 1000.0;
|
||||
constexpr double length_conversion = 100.0;
|
||||
constexpr double time_conversion = 10000.0;
|
||||
constexpr double original_mass = 2.0;
|
||||
constexpr double original_density = 3.0;
|
||||
constexpr double original_gravity = 0.8;
|
||||
const auto original = normalization::prepare_reference_scales(reference_scheme(3.0, original_mass, original_density), Gravity{original_gravity});
|
||||
const auto converted = normalization::prepare_reference_scales(reference_scheme(
|
||||
3.0,
|
||||
original_mass * mass_conversion,
|
||||
original_density * mass_conversion / std::pow(length_conversion, 3)
|
||||
), Gravity{original_gravity * std::pow(length_conversion, 3) / (mass_conversion * time_conversion * time_conversion)});
|
||||
REQUIRE(original);
|
||||
REQUIRE(converted);
|
||||
CHECK_THAT(converted->radius.value(), WithinRel(original->radius.value() * length_conversion, 2.0e-13));
|
||||
CHECK_THAT(converted->density.value(), WithinRel(original->density.value() * mass_conversion / std::pow(length_conversion, 3), 2.0e-13));
|
||||
CHECK_THAT(converted->acceleration.value(), WithinRel(original->acceleration.value() * length_conversion / std::pow(time_conversion, 2), 2.0e-13));
|
||||
CHECK_THAT(converted->specific_energy.value(), WithinRel(original->specific_energy.value() * std::pow(length_conversion / time_conversion, 2), 2.0e-13));
|
||||
CHECK_THAT(converted->pressure.value(), WithinRel(original->pressure.value() * mass_conversion / (length_conversion * std::pow(time_conversion, 2)), 2.0e-13));
|
||||
CHECK_THAT(converted->angular_velocity.value(), WithinRel(original->angular_velocity.value() / time_conversion, 2.0e-13));
|
||||
CHECK_THAT(converted->angular_momentum.value(), WithinRel(original->angular_momentum.value() * mass_conversion * length_conversion * length_conversion / time_conversion, 2.0e-13));
|
||||
CHECK_THAT(converted->force.value(), WithinRel(original->force.value() * mass_conversion * length_conversion / std::pow(time_conversion, 2), 2.0e-13));
|
||||
}
|
||||
|
||||
TEST_CASE("Normalization reference failures stay explicit for unusable inputs and missing surfaces", "[unit][normalization][configuration]") {
|
||||
for (const double invalid : {0.0, -1.0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity()}) {
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(reference_scheme(1.0, invalid, 1.0), Gravity{1.0}));
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(reference_scheme(1.0, 1.0, invalid), Gravity{1.0}));
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(reference_scheme(1.0, 1.0, 1.0), Gravity{invalid}));
|
||||
}
|
||||
const auto infinite_polytrope = normalization::prepare_reference_scales(reference_scheme(5.0, 1.0, 1.0), Gravity{1.0});
|
||||
REQUIRE_FALSE(infinite_polytrope);
|
||||
CHECK_FALSE(infinite_polytrope.error().empty());
|
||||
auto unresolved = reference_scheme(3.0, 1.0, 1.0);
|
||||
unresolved.integration.maximum_radius = 1.0;
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(unresolved, Gravity{1.0}));
|
||||
}
|
||||
|
||||
TEST_CASE("Explicit normalization scales share validation and require a consistent G", "[unit][normalization][configuration]") {
|
||||
const auto reference = normalization::prepare_stellar_reference_scales(dimensions::QuantityValue<dimensions::Mass>{2.0}, dimensions::QuantityValue<dimensions::Length>{3.0}, Gravity{0.5});
|
||||
REQUIRE(reference);
|
||||
const auto options = normalization::make_normalization(normalization::NormalizationOptions{
|
||||
.scheme = *reference,
|
||||
.gravitational_constant = Gravity{0.5}
|
||||
});
|
||||
const auto prepared = normalization::prepare_reference_scales(options.scheme, options.gravitational_constant);
|
||||
REQUIRE(prepared);
|
||||
CHECK(*prepared == *reference);
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(options.scheme, Gravity{1.0}));
|
||||
auto invalid = *reference;
|
||||
invalid.radius = dimensions::QuantityValue<dimensions::Length>{0.0};
|
||||
CHECK_FALSE(normalization::prepare_reference_scales(invalid, Gravity{0.5}));
|
||||
}
|
||||
Reference in New Issue
Block a user