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.
125 lines
7.5 KiB
C++
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);
|
|
}
|