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.
This commit is contained in:
138
tests/surface/rejection.cpp
Normal file
138
tests/surface/rejection.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
|
||||
#include "serif/tests/surface/contexts.hpp"
|
||||
|
||||
namespace {
|
||||
using namespace serif;
|
||||
using namespace serif::tests::surface;
|
||||
using Linearization = physics::keys::Linearization;
|
||||
|
||||
struct NonFinitePartialEOS : GeneralEOS {
|
||||
using GeneralEOS::partial_derivative;
|
||||
|
||||
/// An independently bad partial must reject the response even when the EOS value is finite.
|
||||
[[nodiscard]] quantities::PartialDerivative<quantities::SpecificEnthalpy, ElectronFraction> partial_derivative(EnthalpyFromPressureAndComposition, quantities::WithRespectTo<ElectronFraction>, quantities::PressureValue, quantities::EntropyValue, quantities::QuantityValue<ElectronFraction>) const {
|
||||
return quantities::PartialDerivative<quantities::SpecificEnthalpy, ElectronFraction>{std::numeric_limits<double>::infinity()};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
TEST_CASE("Surface point validation rejects every required non-finite quantity before the EOS", "[surface][unit]") {
|
||||
GeneralEOS model;
|
||||
const physics::IsobaricSurface condition{quantities::PressureValue{0.4}};
|
||||
for (const double invalid : {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity(), -std::numeric_limits<double>::infinity()}) {
|
||||
for (const auto& point : {general_point(invalid, 0.2, 0.1), general_point(1.7, invalid, 0.1), general_point(1.7, 0.2, invalid)}) {
|
||||
const auto result = physics::evaluate_pressure_surface<GeneralFormulation>(model, condition, point);
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().reason == physics::SurfaceFailureReason::non_finite_state);
|
||||
}
|
||||
}
|
||||
CHECK(model.evaluations == 0);
|
||||
}
|
||||
|
||||
TEST_CASE("Surface rejects non-finite EOS results and partials without clipping them", "[surface][unit]") {
|
||||
const physics::IsobaricSurface condition{quantities::PressureValue{0.4}};
|
||||
SECTION("EOS value") {
|
||||
const OffsetEOS model{.offset = std::numeric_limits<double>::infinity()};
|
||||
using Formulation = physics::PressureSurfaceFormulation<OffsetEOS, BarotropicState>;
|
||||
physics::SurfacePoint<Formulation> point{};
|
||||
point.fields.get<Enthalpy>() = 0.4;
|
||||
const auto result = physics::evaluate_pressure_surface<Formulation>(model, condition, point);
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().reason == physics::SurfaceFailureReason::non_finite_eos_response);
|
||||
}
|
||||
SECTION("EOS partial") {
|
||||
using Formulation = physics::PressureSurfaceFormulation<NonFinitePartialEOS, GeneralState>;
|
||||
const auto result = physics::evaluate_pressure_surface<Formulation>(NonFinitePartialEOS{}, condition, general_point(1.7, 0.2, 0.1));
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().reason == physics::SurfaceFailureReason::non_finite_eos_response);
|
||||
}
|
||||
SECTION("Residual subtraction overflows after a finite EOS result") {
|
||||
const OffsetEOS model{.offset = -std::numeric_limits<double>::max()};
|
||||
using Formulation = physics::PressureSurfaceFormulation<OffsetEOS, BarotropicState>;
|
||||
physics::SurfacePoint<Formulation> point{};
|
||||
point.fields.get<Enthalpy>() = std::numeric_limits<double>::max();
|
||||
const auto result = physics::evaluate_pressure_surface<Formulation>(model, condition, point);
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().reason == physics::SurfaceFailureReason::non_finite_linearization);
|
||||
}
|
||||
SECTION("Finite negative carrier values retain their declared residual") {
|
||||
using Formulation = physics::PressureSurfaceFormulation<OffsetEOS, BarotropicState>;
|
||||
physics::SurfacePoint<Formulation> point{};
|
||||
point.fields.get<Enthalpy>() = -0.2;
|
||||
const auto result = physics::evaluate_pressure_surface<Formulation>(OffsetEOS{}, condition, point);
|
||||
REQUIRE(result);
|
||||
CHECK(result->residual == -0.2 - (0.3 + 2.0 * 0.4));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("A rejected surface batch hides all previous results and permits a valid retry", "[surface][unit]") {
|
||||
GeneralEOS model;
|
||||
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<GeneralFormulation>(), general_context(model, 0.4, {1.7, 1.9}, {0.2, 0.3}, {0.1, 0.2}));
|
||||
REQUIRE(cache.try_prepare(general_context(model, 0.4, {1.7, 1.9}, {0.2, 0.3}, {0.1, 0.2})));
|
||||
|
||||
auto bad_enthalpy = std::vector<double>{1.7, 1.9};
|
||||
auto bad_entropy = std::vector<double>{0.2, 0.3};
|
||||
SECTION("Carrier rejection after a reusable EOS response") {
|
||||
bad_enthalpy[1] = std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
SECTION("EOS rejection after the first sample has been evaluated") {
|
||||
bad_entropy[1] = std::numeric_limits<double>::infinity();
|
||||
}
|
||||
const auto rejected = cache.try_prepare(general_context(model, 0.4, bad_enthalpy, bad_entropy, {0.1, 0.2}));
|
||||
REQUIRE_FALSE(rejected);
|
||||
CHECK(rejected.error().error.sample == 1);
|
||||
CHECK_FALSE(cache.ready());
|
||||
CHECK(cache.preparation_count() == 1);
|
||||
CHECK_THROWS_AS(cache.get<Linearization>(), std::logic_error);
|
||||
CHECK_THROWS_AS(cache.get<physics::keys::EOSResponse>(), std::logic_error);
|
||||
|
||||
REQUIRE(cache.try_prepare(general_context(model, 0.4, {1.8, 2.0}, {0.2, 0.3}, {0.1, 0.2})));
|
||||
CHECK(cache.preparation_count() == 2);
|
||||
REQUIRE(cache.get<Linearization>().size() == 2);
|
||||
CHECK(cache.get<Linearization>()[0].residual == 1.8 - (2.0 * 0.4 + 3.0 * 0.2 + 5.0 * 0.1));
|
||||
}
|
||||
|
||||
TEST_CASE("Surface graph rejects mismatched columns instead of broadcasting a state-dependent relation", "[surface][unit]") {
|
||||
GeneralEOS model;
|
||||
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<GeneralFormulation>(), general_context(model, 0.4, {1.7, 1.9}, {0.2, 0.3}, {0.1, 0.2}));
|
||||
|
||||
SECTION("EOS columns disagree") {
|
||||
const auto result = cache.try_prepare(general_context(model, 0.4, {1.7, 1.9}, {0.2, 0.3}, {0.1}));
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().error.reason == physics::SurfaceFailureReason::inconsistent_sample_count);
|
||||
CHECK(model.evaluations == 0);
|
||||
}
|
||||
SECTION("A singleton state-dependent response is not a constant") {
|
||||
const auto result = cache.try_prepare(general_context(model, 0.4, {1.7, 1.9}, {0.2}, {0.1}));
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().error.reason == physics::SurfaceFailureReason::inconsistent_sample_count);
|
||||
}
|
||||
SECTION("Empty EOS columns") {
|
||||
const auto result = cache.try_prepare(general_context(model, 0.4, {}, {}, {}));
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().error.reason == physics::SurfaceFailureReason::empty_surface);
|
||||
}
|
||||
CHECK_FALSE(cache.ready());
|
||||
REQUIRE(cache.try_prepare(general_context(model, 0.4, {1.7, 1.9}, {0.2, 0.3}, {0.1, 0.2})));
|
||||
}
|
||||
|
||||
TEST_CASE("A pressure-only surface rejects an empty carrier batch", "[surface][unit]") {
|
||||
OffsetEOS model;
|
||||
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<OffsetEOS, BarotropicState>(), offset_context(model, 0.0, {}));
|
||||
const auto result = cache.try_prepare(offset_context(model, 0.0, {}));
|
||||
REQUIRE_FALSE(result);
|
||||
CHECK(result.error().error.reason == physics::SurfaceFailureReason::empty_surface);
|
||||
CHECK_FALSE(cache.ready());
|
||||
}
|
||||
|
||||
TEST_CASE("An existing EOS domain exception leaves the surface cache unavailable until recovery", "[surface][unit]") {
|
||||
GasEOS model;
|
||||
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<GasFormulation>(), gas_context(model, 0.8, {0.4}, {1.0}));
|
||||
REQUIRE(cache.try_prepare(gas_context(model, 0.8, {0.4}, {1.0})));
|
||||
CHECK_THROWS_AS(cache.try_prepare(gas_context(model, 0.8, {0.4}, {0.0})), eos::EOSEvaluationError);
|
||||
CHECK_FALSE(cache.ready());
|
||||
CHECK_THROWS_AS(cache.get<Linearization>(), std::logic_error);
|
||||
REQUIRE(cache.try_prepare(gas_context(model, 0.8, {0.4}, {2.0})));
|
||||
CHECK(cache.get<Linearization>()[0].residual == 0.0);
|
||||
}
|
||||
Reference in New Issue
Block a user