#include #include #include #include #include #include "serif/solver/normalization/diagonal.hpp" #include "serif/solver/normalization/point_rows.hpp" namespace { namespace normalization = serif::solver::normalization; } // Ported from MeanField tests/normalization/physical_riesz.cpp, without assuming matching pressure and enthalpy scales. TEST_CASE("Replaced residual rows use point metrics and independent physical units", "[normalization][unit][math]") { const std::array metric{4.0, 0.0, 16.0, 0.0}; const std::array point_rows{1, 3}; const auto enthalpy = normalization::make_point_replaced_residual_factors(metric, 5.0, point_rows, 5.0); REQUIRE(enthalpy); CHECK((*enthalpy)[0] == Catch::Approx(1.0 / 10.0)); CHECK((*enthalpy)[1] == Catch::Approx(1.0 / 5.0)); CHECK((*enthalpy)[2] == Catch::Approx(1.0 / 20.0)); CHECK((*enthalpy)[3] == Catch::Approx(1.0 / 5.0)); const auto pressure = normalization::make_point_replaced_residual_factors(metric, 5.0, point_rows, 7.0); REQUIRE(pressure); CHECK((*pressure)[0] == (*enthalpy)[0]); CHECK((*pressure)[2] == (*enthalpy)[2]); CHECK((*pressure)[1] == Catch::Approx(1.0 / 7.0)); CHECK((*pressure)[3] == Catch::Approx(1.0 / 7.0)); } TEST_CASE("Point normalization rejects duplicate and out-of-range physical row selections", "[normalization][unit][validation]") { const std::array metric{4.0, 9.0}; const auto duplicate = normalization::make_point_replaced_residual_factors(metric, 1.0, std::array{1, 1}, 1.0); REQUIRE_FALSE(duplicate); CHECK_THAT(duplicate.error(), Catch::Matchers::ContainsSubstring("INVALID_ARGUMENT")); for (const int row : {-1, 2}) { const auto outside = normalization::make_point_replaced_residual_factors(metric, 1.0, std::array{row}, 1.0); REQUIRE_FALSE(outside); CHECK_THAT(outside.error(), Catch::Matchers::ContainsSubstring("OUT_OF_RANGE_ERROR")); } CHECK_FALSE(normalization::make_point_replaced_residual_factors({}, 1.0, std::array{0}, 1.0)); } TEST_CASE("Only replaced residual rows may have unusable volume metrics", "[normalization][unit][validation]") { for (const double invalid : {0.0, -1.0, std::numeric_limits::quiet_NaN(), std::numeric_limits::infinity()}) { const std::array metric{4.0, invalid}; const auto replaced = normalization::make_point_replaced_residual_factors(metric, 2.0, std::array{1}, 5.0); REQUIRE(replaced); CHECK((*replaced)[0] == Catch::Approx(0.25)); CHECK((*replaced)[1] == Catch::Approx(0.2)); CHECK_FALSE(normalization::make_point_replaced_residual_factors(metric, 2.0, {}, 5.0)); CHECK_FALSE(normalization::make_point_replaced_residual_factors(metric, invalid, std::array{1}, 5.0)); CHECK_FALSE(normalization::make_point_replaced_residual_factors(metric, 2.0, std::array{1}, invalid)); } } TEST_CASE("Point row composition validates factors only in the representations actually used", "[normalization][unit][math]") { const auto extreme_bulk = normalization::make_point_replaced_residual_factors(std::array{1.0e300, 0.0}, 1.0e-310, std::array{1}, 2.0); REQUIRE(extreme_bulk); CHECK((*extreme_bulk)[0] == Catch::Approx(1.0e160).epsilon(1.0e-12)); CHECK((*extreme_bulk)[1] == 0.5); const auto no_points = normalization::make_point_replaced_residual_factors(std::array{4.0}, 2.0, {}, 1.0e-310); REQUIRE(no_points); CHECK(no_points->front() == 0.25); CHECK_FALSE(normalization::make_point_replaced_residual_factors(std::array{4.0}, 2.0, std::array{0}, 1.0e-310)); const auto no_bulk = normalization::make_point_replaced_residual_factors(std::array{0.0}, 1.0e-310, std::array{0}, 2.0); REQUIRE(no_bulk); CHECK(no_bulk->front() == 0.5); const auto empty = normalization::make_point_replaced_residual_factors({}, 1.0e-310, {}, 1.0e-310); REQUIRE(empty); CHECK(empty->empty()); const auto empty_factors = normalization::make_riesz_factors(serif::discretization::blocks::RepresentedValueKind::residual, {}, 1.0e-310); REQUIRE(empty_factors); CHECK(empty_factors->empty()); }