#include #include #include #include #include #include #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 scales(double mass, double radius, double gravity = 1.0) { return normalization::prepare_stellar_reference_scales(dimensions::QuantityValue{mass}, dimensions::QuantityValue{radius}, dimensions::QuantityValue{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::quiet_NaN(), std::numeric_limits::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)); }