Files
MeanField_SERiF/tests/surface/relations.cpp
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

125 lines
7.5 KiB
C++

#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#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<quantities::SpecificEnthalpy, quantities::Entropy, quantities::Pressure>;
struct PressureLastEOS {
using Relations = eos::relations::RelationCatalog<PressureLast>;
/// 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<quantities::SpecificEnthalpy, quantities::Entropy> partial_derivative(PressureLast, quantities::WithRespectTo<quantities::Entropy>, quantities::EntropyValue, quantities::PressureValue) const {
return quantities::PartialDerivative<quantities::SpecificEnthalpy, quantities::Entropy>{3.0};
}
};
using CarrierInput = eos::relations::Relation<quantities::MassDensity, quantities::Pressure, quantities::MassDensity>;
struct CarrierInputEOS {
using Relations = eos::relations::RelationCatalog<CarrierInput>;
/// 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<quantities::MassDensity, quantities::MassDensity> partial_derivative(CarrierInput, quantities::WithRespectTo<quantities::MassDensity>, quantities::PressureValue, quantities::DensityValue) const {
return quantities::PartialDerivative<quantities::MassDensity, quantities::MassDensity>{0.5};
}
};
struct ForwardOnlyEOS {
using Relations = eos::relations::RelationCatalog<PressureFromDensityAndTemperature>;
/// 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<quantities::Pressure, quantities::MassDensity> partial_derivative(PressureFromDensityAndTemperature, quantities::WithRespectTo<quantities::MassDensity>, quantities::DensityValue, const quantities::TemperatureValue temperature) const {
return quantities::PartialDerivative<quantities::Pressure, quantities::MassDensity>{temperature.value()};
}
/// The temperature coupling is required even though density supplies the replaced row.
[[nodiscard]] quantities::PartialDerivative<quantities::Pressure, quantities::Temperature> partial_derivative(PressureFromDensityAndTemperature, quantities::WithRespectTo<quantities::Temperature>, const quantities::DensityValue density, quantities::TemperatureValue) const {
return quantities::PartialDerivative<quantities::Pressure, quantities::Temperature>{density.value()};
}
};
}
TEST_CASE("Surface EOS arguments follow relation order even when pressure is last", "[surface][unit]") {
using State = dependencies::Bindings<dependencies::Binding<quantities::Entropy, Entropy>, dependencies::Binding<quantities::SpecificEnthalpy, Enthalpy>>;
using Formulation = physics::PressureSurfaceFormulation<PressureLastEOS, State>;
physics::SurfacePoint<Formulation> point{};
point.fields.get<Enthalpy>() = 1.7;
point.fields.get<Entropy>() = 0.2;
const auto result = physics::evaluate_pressure_surface<Formulation>(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<Entropy>() == -3.0);
}
TEST_CASE("A carrier appearing among EOS inputs retains both derivative contributions", "[surface][unit]") {
using State = dependencies::Bindings<dependencies::Binding<quantities::MassDensity, Density>>;
using Formulation = physics::PressureSurfaceFormulation<CarrierInputEOS, State>;
physics::SurfacePoint<Formulation> point{};
point.fields.get<Density>() = 0.8;
const auto result = physics::evaluate_pressure_surface<Formulation>(CarrierInputEOS{}, physics::IsobaricSurface{quantities::PressureValue{0.4}}, point);
REQUIRE(result);
CHECK(result->residual == 0.0);
CHECK(result->field_derivatives.get<Density>() == 0.5);
}
TEST_CASE("A forward-only EOS can impose the same isobaric condition without inversion", "[surface][unit]") {
using Formulation = physics::DirectPressureSurfaceFormulation<ForwardOnlyEOS, GasState, quantities::MassDensity, PressureFromDensityAndTemperature>;
const auto result = physics::evaluate_pressure_surface<Formulation>(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<Density>() == 2.0);
CHECK(result->field_derivatives.get<Temperature>() == 0.4);
}
TEST_CASE("Pressure-only surface relations retain their analytic residual and derivative", "[surface][unit]") {
using Formulation = physics::PressureSurfaceFormulation<OffsetEOS, BarotropicState>;
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<Formulation> point{};
point.fields.get<Enthalpy>() = enthalpy;
const auto actual = physics::evaluate_pressure_surface<Formulation>(model, condition, point);
REQUIRE(actual);
CHECK(actual->residual == enthalpy - (model.offset + 2.0 * pressure));
CHECK(actual->field_derivatives.get<Enthalpy>() == 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::SurfaceFailure>(physics::make_pressure_surface_graph<GeneralFormulation>(), 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<physics::keys::EOSResponse>()[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<physics::keys::EOSResponse>()[0].value.value() == 0.0);
CHECK(cache.get<physics::keys::Linearization>()[0].field_derivatives.get<Entropy>() == -6.0);
}