Files
Emily Boudreaux cb20faeb3d 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.
2026-11-09 13:53:17 -05:00

102 lines
6.6 KiB
C++

#include <cmath>
#include <limits>
#include <expected>
#include <string>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include "serif/solver/normalization/scales.hpp"
namespace
{
namespace dimensions = serif::dimensions;
namespace normalization = serif::solver::normalization;
/** @brief Supply typed reference values while retaining concise physical test cases. */
std::expected<normalization::StellarReferenceScales, std::string> scales(double mass, double radius, double gravity = 1.0)
{
return normalization::prepare_stellar_reference_scales(dimensions::QuantityValue<dimensions::Mass>{mass}, dimensions::QuantityValue<dimensions::Length>{radius}, dimensions::QuantityValue<dimensions::GravitationalConstant>{gravity});
}
}
// Ported from MeanField tests/normalization/physical_riesz.cpp.
TEST_CASE("Characteristic stellar scales satisfy gravity virial and rotation identities", "[normalization][unit][physics]")
{
constexpr double mass = 7.0;
constexpr double radius = 3.0;
constexpr double gravity = 5.0;
const auto prepared = scales(mass, radius, gravity);
REQUIRE(prepared);
const normalization::StellarReferenceScales &reference = *prepared;
CHECK(reference.density.value() == Catch::Approx(mass / std::pow(radius, 3)));
CHECK(reference.acceleration.value() == Catch::Approx(gravity * mass / std::pow(radius, 2)));
CHECK(reference.specific_energy.value() == Catch::Approx(gravity * mass / radius));
CHECK(reference.pressure.value() == Catch::Approx(gravity * mass * mass / std::pow(radius, 4)));
CHECK(reference.angular_velocity.value() == Catch::Approx(std::sqrt(gravity * mass / std::pow(radius, 3))));
CHECK(reference.angular_momentum.value() == Catch::Approx(mass * std::sqrt(gravity * mass * radius)));
const double virial = reference.pressure.value() * std::pow(radius, 3);
CHECK(virial == Catch::Approx(mass * reference.specific_energy.value()).epsilon(2.0e-15));
CHECK(virial == Catch::Approx(reference.force.value() * radius).epsilon(2.0e-15));
CHECK(reference.angular_velocity.value() * reference.angular_velocity.value() * radius == Catch::Approx(reference.acceleration.value()).epsilon(2.0e-15));
CHECK(reference.angular_momentum.value() == Catch::Approx(mass * radius * radius * reference.angular_velocity.value()).epsilon(2.0e-15));
}
TEST_CASE("Characteristic scales obey stellar homology exponents", "[normalization][unit][physics]")
{
const auto reference = scales(2.5, 4.0, 3.0);
constexpr double mass_factor = 11.0;
constexpr double radius_factor = 0.2;
constexpr double gravity_factor = 7.0;
const auto transformed = scales(2.5 * mass_factor, 4.0 * radius_factor, 3.0 * gravity_factor);
REQUIRE(reference);
REQUIRE(transformed);
CHECK(transformed->density.value() / reference->density.value() == Catch::Approx(mass_factor / std::pow(radius_factor, 3)).epsilon(4.0e-15));
CHECK(transformed->acceleration.value() / reference->acceleration.value() == Catch::Approx(gravity_factor * mass_factor / std::pow(radius_factor, 2)).epsilon(4.0e-15));
CHECK(transformed->inverse_time_squared.value() / reference->inverse_time_squared.value() == Catch::Approx(gravity_factor * mass_factor / std::pow(radius_factor, 3)).epsilon(4.0e-15));
CHECK(transformed->specific_energy.value() / reference->specific_energy.value() == Catch::Approx(gravity_factor * mass_factor / radius_factor).epsilon(4.0e-15));
CHECK(transformed->pressure.value() / reference->pressure.value() == Catch::Approx(gravity_factor * mass_factor * mass_factor / std::pow(radius_factor, 4)).epsilon(4.0e-15));
CHECK(transformed->angular_velocity.value() / reference->angular_velocity.value() == Catch::Approx(std::sqrt(gravity_factor * mass_factor / std::pow(radius_factor, 3))).epsilon(4.0e-15));
CHECK(transformed->angular_momentum.value() / reference->angular_momentum.value() == Catch::Approx(mass_factor * std::sqrt(gravity_factor * mass_factor * radius_factor)).epsilon(4.0e-15));
CHECK(transformed->force.value() / reference->force.value() == Catch::Approx(gravity_factor * mass_factor * mass_factor / std::pow(radius_factor, 2)).epsilon(4.0e-15));
}
TEST_CASE("Typed scales distinguish residual quantities and share the gravitational energy scale", "[normalization][unit][physics]")
{
const auto reference = scales(9.0, 2.0, 4.0);
REQUIRE(reference);
CHECK(normalization::characteristic_scale(*reference, dimensions::Dimensionless{}).value() == 1.0);
CHECK(normalization::characteristic_scale(*reference, dimensions::Mass{}).value() == 9.0);
CHECK(normalization::characteristic_scale(*reference, dimensions::Length{}).value() == 2.0);
CHECK(normalization::characteristic_scale(*reference, dimensions::MassDensity{}) == reference->density);
CHECK(normalization::characteristic_scale(*reference, dimensions::Acceleration{}) == reference->acceleration);
CHECK(normalization::characteristic_scale(*reference, dimensions::InverseTimeSquared{}) == reference->inverse_time_squared);
CHECK(normalization::characteristic_scale(*reference, dimensions::Pressure{}) == reference->pressure);
CHECK(normalization::characteristic_scale(*reference, dimensions::AngularVelocity{}) == reference->angular_velocity);
CHECK(normalization::characteristic_scale(*reference, dimensions::AngularMomentum{}) == reference->angular_momentum);
CHECK(normalization::characteristic_scale(*reference, dimensions::Force{}) == reference->force);
CHECK(normalization::characteristic_scale(*reference, dimensions::SpecificEnergy{}) == reference->specific_energy);
CHECK(normalization::characteristic_scale(*reference, dimensions::SpecificEnthalpy{}).value() == reference->specific_energy.value());
CHECK(normalization::characteristic_scale(*reference, dimensions::GravitationalPotential{}).value() == reference->specific_energy.value());
CHECK(normalization::characteristic_scale(*reference, dimensions::SpecificEnthalpy{}).value() != reference->density.value());
CHECK(*reference == *scales(9.0, 2.0, 4.0));
CHECK_FALSE(*reference == *scales(9.0, 2.1, 4.0));
}
TEST_CASE("Characteristic scale preparation rejects invalid and unrepresentable references", "[normalization][unit][validation]")
{
for (const double invalid : {0.0, -1.0, std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity()})
{
CHECK_FALSE(scales(invalid, 1.0));
CHECK_FALSE(scales(1.0, invalid));
CHECK_FALSE(scales(1.0, 1.0, invalid));
}
CHECK_FALSE(scales(1.0e300, 1.0e-200, 1.0e100));
CHECK_FALSE(scales(1.0, 1.0e300));
CHECK_FALSE(scales(1.0e-300, 1.0));
REQUIRE(scales(1.0, 1.0));
}