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.
120 lines
7.2 KiB
C++
120 lines
7.2 KiB
C++
#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}));
|
|
}
|