#include #include #include #include #include "serif/eos/models/polytropic.hpp" #include "serif/tests/surface/models.hpp" namespace { using namespace serif; using namespace serif::tests::surface; } TEST_CASE("Isobaric surface prescribes pressure without selecting a carrier", "[surface][unit]") { // MeanField: Constant Pressure Surface Prescribes Only A Pressure Quantity. const physics::IsobaricSurface condition{quantities::PressureValue{0.03125}}; CHECK(condition.target_pressure() == quantities::PressureValue{0.03125}); CHECK(condition.descriptor().target_pressure == 0.03125); CHECK(condition.continuation_identity() == std::array{0.03125}); CHECK(physics::IsobaricSurface{physics::IsobaricSurface::Parameters{.PSurf = quantities::PressureValue{0.4}}}.target_pressure() == quantities::PressureValue{0.4}); for (const double invalid : {-0.1, std::numeric_limits::infinity(), -std::numeric_limits::infinity(), std::numeric_limits::quiet_NaN()}) { CHECK_THROWS_AS(physics::IsobaricSurface{quantities::PressureValue{invalid}}, std::invalid_argument); } } TEST_CASE("Polytropic surface resolves pressure through its declared enthalpy relation", "[surface][unit]") { // MeanField: Polytropic EOS Resolves Constant Surface Pressure Through Its Enthalpy Relation. using Formulation = physics::PressureSurfaceFormulation; const eos::models::Polytrope equation_of_state{3.0, 0.25}; const physics::IsobaricSurface condition{quantities::PressureValue{0.03125}}; const double required = eos::evaluate(equation_of_state, condition.target_pressure()).value(); physics::SurfacePoint point{}; point.fields.get() = required; const auto result = physics::evaluate_pressure_surface(equation_of_state, condition, point); REQUIRE(result); CHECK(result->residual == 0.0); CHECK(result->field_derivatives.get() * -0.19 == -0.19); CHECK(result->position_derivative == std::array{}); } TEST_CASE("General pressure surface uses the local composition", "[surface][unit]") { // MeanField: General EOS Resolves Constant Surface Pressure With Local Composition. const GeneralEOS equation_of_state; const physics::IsobaricSurface condition{quantities::PressureValue{0.4}}; const auto first = general_point(2.0 * 0.4 + 3.0 * 0.2 + 5.0 * 0.1, 0.2, 0.1); const auto second = general_point(2.0 * 0.4 + 3.0 * 0.3 + 5.0 * 0.1, 0.3, 0.1); REQUIRE(first.fields.get() != second.fields.get()); const auto first_result = physics::evaluate_pressure_surface(equation_of_state, condition, first); const auto second_result = physics::evaluate_pressure_surface(equation_of_state, condition, second); REQUIRE(first_result); REQUIRE(second_result); CHECK(first_result->residual == 0.0); CHECK(second_result->residual == 0.0); } TEST_CASE("General pressure surface Jacobian retains every MeanField state dependency", "[surface][unit]") { // MeanField: preserve its finite-difference step, direction, and 2e-9 absolute gate. const GeneralEOS equation_of_state; const physics::IsobaricSurface condition{quantities::PressureValue{0.4}}; constexpr double step = 1.0e-7; const auto point = general_point(1.7, 0.2, 0.1); const auto forward = general_point(1.7 + step * 0.7, 0.2 - step * 0.2, 0.1 + step * 0.05); const auto backward = general_point(1.7 - step * 0.7, 0.2 + step * 0.2, 0.1 - step * 0.05); const auto result = physics::evaluate_pressure_surface(equation_of_state, condition, point); const auto plus = physics::evaluate_pressure_surface(equation_of_state, condition, forward); const auto minus = physics::evaluate_pressure_surface(equation_of_state, condition, backward); REQUIRE(result); REQUIRE(plus); REQUIRE(minus); const double action = result->field_derivatives.get() * 0.7 + result->field_derivatives.get() * -0.2 + result->field_derivatives.get() * 0.05; CHECK(action == 0.7 - 3.0 * -0.2 - 5.0 * 0.05); CHECK_THAT((plus->residual - minus->residual) / (2.0 * step), Catch::Matchers::WithinAbs(action, 2.0e-9)); } TEST_CASE("Isobaric surface supports density without an enthalpy quantity or EOS relation", "[surface][unit]") { const GasEOS equation_of_state; for (const double pressure : {0.0, 0.4, 2.0}) { for (const double temperature : {0.5, 1.0, 3.0}) { CAPTURE(pressure, temperature); const physics::IsobaricSurface condition{quantities::PressureValue{pressure}}; const auto point = gas_point(pressure / temperature, temperature); const auto inverse = physics::evaluate_pressure_surface(equation_of_state, condition, point); const auto direct = physics::evaluate_pressure_surface(equation_of_state, condition, point); REQUIRE(inverse); REQUIRE(direct); CHECK(inverse->residual == 0.0); CHECK_THAT(direct->residual, Catch::Matchers::WithinAbs(0.0, 1.0e-15)); CHECK(inverse->field_derivatives.get() == 1.0); CHECK(inverse->field_derivatives.get() == pressure / (temperature * temperature)); CHECK(direct->field_derivatives.get() == temperature); CHECK(direct->field_derivatives.get() == pressure / temperature); } } } TEST_CASE("Nonlinear density and direct pressure residuals have independent finite-difference checks", "[surface][unit]") { const GasEOS equation_of_state; const physics::IsobaricSurface condition{quantities::PressureValue{0.8}}; constexpr double step = 1.0e-6; const auto check = [&]() { const auto result = physics::evaluate_pressure_surface(equation_of_state, condition, gas_point(0.6, 1.3)); const auto plus = physics::evaluate_pressure_surface(equation_of_state, condition, gas_point(0.6 + step * 0.31, 1.3 - step * 0.27)); const auto minus = physics::evaluate_pressure_surface(equation_of_state, condition, gas_point(0.6 - step * 0.31, 1.3 + step * 0.27)); REQUIRE(result); REQUIRE(plus); REQUIRE(minus); const double action = result->field_derivatives.template get() * 0.31 - result->field_derivatives.template get() * 0.27; CHECK_THAT((plus->residual - minus->residual) / (2.0 * step), Catch::Matchers::WithinAbs(action, 2.0e-9)); }; check.template operator()(); check.template operator()(); } TEST_CASE("Shared thermodynamic targets accumulate their Jacobian contributions", "[surface][unit]") { using SharedState = dependencies::Bindings< dependencies::Binding, dependencies::Binding, dependencies::Binding >; using Formulation = physics::PressureSurfaceFormulation; physics::SurfacePoint point{}; point.fields.get() = 0.2; const auto result = physics::evaluate_pressure_surface(GeneralEOS{}, physics::IsobaricSurface{quantities::PressureValue{0.4}}, point); REQUIRE(result); CHECK_THAT(result->residual, Catch::Matchers::WithinAbs(0.2 - (2.0 * 0.4 + 8.0 * 0.2), 1.0e-15)); CHECK(result->field_derivatives.get() == 1.0 - 3.0 - 5.0); }