#include #include #include "serif/geometry/surface/evaluation.hpp" #include "serif/tests/surface/contexts.hpp" namespace { using namespace serif; using namespace serif::tests::surface; using PressureLast = eos::relations::Relation; struct PressureLastEOS { using Relations = eos::relations::RelationCatalog; /// Distinct coefficients detect an accidental permutation of pressure and state arguments. [[nodiscard]] quantities::SpecificEnthalpyValue evaluate(PressureLast, const quantities::EntropyValue entropy, const quantities::PressureValue pressure) const { return quantities::SpecificEnthalpyValue{3.0 * entropy.value() + 2.0 * pressure.value()}; } /// Pressure is prescribed, so the only partial needed is the entropy derivative. [[nodiscard]] quantities::PartialDerivative partial_derivative(PressureLast, quantities::WithRespectTo, quantities::EntropyValue, quantities::PressureValue) const { return quantities::PartialDerivative{3.0}; } }; using CarrierInput = eos::relations::Relation; struct CarrierInputEOS { using Relations = eos::relations::RelationCatalog; /// A manufactured implicit-carrier relation checks the identity contribution against a nonzero EOS carrier partial. [[nodiscard]] quantities::DensityValue evaluate(CarrierInput, const quantities::PressureValue pressure, const quantities::DensityValue density) const { return quantities::DensityValue{pressure.value() + 0.5 * density.value()}; } /// The final residual's density partial must be 1 - 0.5. [[nodiscard]] quantities::PartialDerivative partial_derivative(CarrierInput, quantities::WithRespectTo, quantities::PressureValue, quantities::DensityValue) const { return quantities::PartialDerivative{0.5}; } }; struct ForwardOnlyEOS { using Relations = eos::relations::RelationCatalog; /// This model exposes only pressure evaluation; there is no callable density inverse. [[nodiscard]] quantities::PressureValue evaluate(PressureFromDensityAndTemperature, const quantities::DensityValue density, const quantities::TemperatureValue temperature) const { return quantities::PressureValue{density.value() * temperature.value()}; } /// The row's density derivative comes directly from the forward relation. [[nodiscard]] quantities::PartialDerivative partial_derivative(PressureFromDensityAndTemperature, quantities::WithRespectTo, quantities::DensityValue, const quantities::TemperatureValue temperature) const { return quantities::PartialDerivative{temperature.value()}; } /// The temperature coupling is required even though density supplies the replaced row. [[nodiscard]] quantities::PartialDerivative partial_derivative(PressureFromDensityAndTemperature, quantities::WithRespectTo, const quantities::DensityValue density, quantities::TemperatureValue) const { return quantities::PartialDerivative{density.value()}; } }; } TEST_CASE("Surface EOS arguments follow relation order even when pressure is last", "[surface][unit]") { using State = dependencies::Bindings, dependencies::Binding>; using Formulation = physics::PressureSurfaceFormulation; physics::SurfacePoint point{}; point.fields.get() = 1.7; point.fields.get() = 0.2; const auto result = physics::evaluate_pressure_surface(PressureLastEOS{}, physics::IsobaricSurface{quantities::PressureValue{0.4}}, point); REQUIRE(result); CHECK(result->residual == 1.7 - (3.0 * 0.2 + 2.0 * 0.4)); CHECK(result->field_derivatives.get() == -3.0); } TEST_CASE("A carrier appearing among EOS inputs retains both derivative contributions", "[surface][unit]") { using State = dependencies::Bindings>; using Formulation = physics::PressureSurfaceFormulation; physics::SurfacePoint point{}; point.fields.get() = 0.8; const auto result = physics::evaluate_pressure_surface(CarrierInputEOS{}, physics::IsobaricSurface{quantities::PressureValue{0.4}}, point); REQUIRE(result); CHECK(result->residual == 0.0); CHECK(result->field_derivatives.get() == 0.5); } TEST_CASE("A forward-only EOS can impose the same isobaric condition without inversion", "[surface][unit]") { using Formulation = physics::DirectPressureSurfaceFormulation; const auto result = physics::evaluate_pressure_surface(ForwardOnlyEOS{}, physics::IsobaricSurface{quantities::PressureValue{0.8}}, gas_point(0.4, 2.0)); REQUIRE(result); CHECK(result->residual == 0.0); CHECK(result->field_derivatives.get() == 2.0); CHECK(result->field_derivatives.get() == 0.4); } TEST_CASE("Pressure-only surface relations retain their analytic residual and derivative", "[surface][unit]") { using Formulation = physics::PressureSurfaceFormulation; const OffsetEOS model; for (const double pressure : {0.0, 0.03125, 0.4}) { const physics::IsobaricSurface condition{quantities::PressureValue{pressure}}; for (const double enthalpy : {-0.2, 0.0, 0.7}) { physics::SurfacePoint point{}; point.fields.get() = enthalpy; const auto actual = physics::evaluate_pressure_surface(model, condition, point); REQUIRE(actual); CHECK(actual->residual == enthalpy - (model.offset + 2.0 * pressure)); CHECK(actual->field_derivatives.get() == 1.0); CHECK(actual->position_derivative == discretization::forms::VectorValue{}); } } } TEST_CASE("Equal EOS values cannot conceal changed surface derivatives", "[surface][unit]") { GeneralEOS model; auto cache = dependencies::make_computation_cache(physics::make_pressure_surface_graph(), general_context(model, 0.0, {0.4}, {0.0}, {0.0})); REQUIRE(cache.try_prepare(general_context(model, 0.0, {0.4}, {0.0}, {0.0}))); CHECK(cache.get()[0].value.value() == 0.0); model.scale = 2.0; const auto report = cache.try_prepare(general_context(model, 0.0, {0.4}, {0.0}, {0.0})); REQUIRE(report); CHECK(report->computed_count() == 2); CHECK(cache.get()[0].value.value() == 0.0); CHECK(cache.get()[0].field_derivatives.get() == -6.0); }