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:
2026-11-09 13:53:17 -05:00
parent 74e55a6859
commit cb20faeb3d
202 changed files with 35505 additions and 953 deletions

View File

@@ -0,0 +1,169 @@
#include <catch2/catch_test_macros.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include "serif/tests/surface/contexts.hpp"
namespace {
using namespace serif::tests::surface;
using Status = dependencies::PreparationStatus;
using Response = physics::keys::EOSResponse;
using Linearization = physics::keys::Linearization;
using OffsetFormulation = physics::PressureSurfaceFormulation<OffsetEOS, BarotropicState>;
}
TEST_CASE("Surface graph construction performs no physics and pressure-only response is broadcast", "[surface][unit]") {
OffsetEOS model;
const auto context = offset_context(model, 0.4, {0.7, 1.1, 1.4});
auto graph = physics::make_pressure_surface_graph<OffsetEOS, BarotropicState>();
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(std::move(graph), context);
CHECK(model.evaluations == 0);
CHECK_FALSE(cache.ready());
CHECK_THROWS_AS(cache.get<Linearization>(), std::logic_error);
const auto prepared = cache.try_prepare(context);
REQUIRE(prepared);
CHECK(prepared->computed_count() == 2);
CHECK(model.evaluations == 1);
REQUIRE(cache.get<Response>().size() == 1);
const auto& rows = cache.get<Linearization>();
REQUIRE(rows.size() == 3);
for (std::size_t index = 0; index < rows.size(); ++index) {
const double expected = context.get<Enthalpy>().values[index] - (0.3 + 2.0 * 0.4);
CHECK(rows[index].residual == expected);
CHECK(rows[index].field_derivatives.get<Enthalpy>() == 1.0);
}
}
TEST_CASE("Surface source changes refresh only the calculations that depend on them", "[surface][unit]") {
OffsetEOS model;
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<OffsetFormulation>(), offset_context(model, 0.0, {0.3, 0.7}));
REQUIRE(cache.try_prepare(offset_context(model, 0.0, {0.3, 0.7})));
CHECK(cache.get<Linearization>()[0].residual == 0.0);
auto report = cache.try_prepare(offset_context(model, 0.0, {0.3, 0.7}));
REQUIRE(report);
CHECK(report->computed_count() == 0);
CHECK(model.evaluations == 1);
report = cache.try_prepare(offset_context(model, 0.0, {0.4, 0.9}));
REQUIRE(report);
CHECK(report->status<Response>() == Status::reused);
CHECK(report->status<Linearization>() == Status::computed);
CHECK(model.evaluations == 1);
report = cache.try_prepare(offset_context(model, 0.2, {0.4, 0.9}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(model.evaluations == 2);
model.offset = 0.6;
report = cache.try_prepare(offset_context(model, 0.2, {0.4, 0.9}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(cache.get<Linearization>()[0].residual == 0.4 - (0.6 + 2.0 * 0.2));
OffsetEOS replacement{.offset = model.offset};
report = cache.try_prepare(offset_context(replacement, 0.2, {0.4, 0.9}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(replacement.evaluations == 1);
report = cache.try_prepare(offset_context(replacement, 0.2, {0.4, 0.9}), {}, dependencies::PreparationMode::rebuild);
REQUIRE(report);
CHECK(report->computed_count() == 2);
}
TEST_CASE("Surface composition response caches values and all partial derivatives together", "[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})));
CHECK(model.evaluations == 2);
CHECK(model.partials == 4);
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Entropy>() == -3.0);
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Composition>() == -5.0);
auto report = cache.try_prepare(general_context(model, 0.4, {1.8, 2.0}, {0.2, 0.3}, {0.1, 0.2}));
REQUIRE(report);
CHECK(report->status<Response>() == Status::reused);
CHECK(model.partials == 4);
report = cache.try_prepare(general_context(model, 0.4, {1.8, 2.0}, {0.4, 0.3}, {0.1, 0.2}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(model.evaluations == 4);
report = cache.try_prepare(general_context(model, 0.4, {1.8, 2.0}, {0.4, 0.3}, {0.3, 0.2}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(model.evaluations == 6);
model.scale = 2.0;
REQUIRE(cache.try_prepare(general_context(model, 0.4, {1.8, 2.0}, {0.4, 0.3}, {0.3, 0.2})));
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Entropy>() == -6.0);
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Composition>() == -10.0);
}
TEST_CASE("Density surface graph refreshes nonlinear temperature derivatives", "[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, 0.6}, {1.0, 2.0}));
REQUIRE(cache.try_prepare(gas_context(model, 0.8, {0.4, 0.6}, {1.0, 2.0})));
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Temperature>() == 0.8);
CHECK(cache.get<Linearization>()[1].field_derivatives.get<Temperature>() == 0.2);
const auto report = cache.try_prepare(gas_context(model, 0.8, {0.4, 0.6}, {2.0, 4.0}));
REQUIRE(report);
CHECK(report->computed_count() == 2);
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Temperature>() == 0.2);
CHECK(cache.get<Linearization>()[1].field_derivatives.get<Temperature>() == 0.05);
}
TEST_CASE("Direct pressure surface reuses EOS work when prescribed pressure changes", "[surface][unit]") {
GasEOS model;
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<DirectGasFormulation>(), gas_context(model, 0.8, {0.4, 0.6}, {1.0, 2.0}));
REQUIRE(cache.try_prepare(gas_context(model, 0.8, {0.4, 0.6}, {1.0, 2.0})));
const auto report = cache.try_prepare(gas_context(model, 0.9, {0.4, 0.6}, {1.0, 2.0}));
REQUIRE(report);
CHECK(report->status<Response>() == Status::reused);
CHECK(report->status<Linearization>() == Status::computed);
CHECK(model.evaluations == 2);
CHECK(cache.get<Linearization>()[0].residual == 0.4 * 1.0 - 0.9);
CHECK(cache.get<Linearization>()[1].field_derivatives.get<Density>() == 2.0);
CHECK(cache.get<Linearization>()[1].field_derivatives.get<Temperature>() == 0.6);
}
TEST_CASE("Surface cache requested results and moves retain the ordinary dependency lifecycle", "[surface][unit]") {
OffsetEOS model;
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<OffsetFormulation>(), offset_context(model, 0.0, {0.5}));
const auto report = cache.try_prepare(offset_context(model, 0.0, {std::numeric_limits<double>::quiet_NaN()}), dependencies::RequestedResults<Response>{});
REQUIRE(report);
CHECK(report->computed_count() == 1);
CHECK_FALSE(cache.contains<Linearization>());
CHECK_THROWS_AS(cache.get<Linearization>(), std::logic_error);
REQUIRE(cache.try_prepare(offset_context(model, 0.0, {0.5})));
auto moved = std::move(cache);
CHECK_FALSE(cache.ready());
REQUIRE(moved.ready());
CHECK(moved.get<Linearization>()[0].residual == 0.5 - 0.3);
}
TEST_CASE("Surface batch with aliased quantities gathers each term once and sums every derivative", "[surface][unit]") {
using SharedState = dependencies::Bindings<
dependencies::Binding<quantities::SpecificEnthalpy, Enthalpy>,
dependencies::Binding<quantities::Entropy, Enthalpy>,
dependencies::Binding<ElectronFraction, Enthalpy>
>;
GeneralEOS model;
const auto context = dependencies::make_dependency_context(
dependencies::borrow<physics::keys::EquationOfState>(model, dependencies::track_by([](const GeneralEOS& eos) {
return eos.scale;
})),
physics::pressure_condition(physics::IsobaricSurface{quantities::PressureValue{0.4}}),
physics::boundary_values<Enthalpy>({0.2, 0.3})
);
auto cache = dependencies::make_computation_cache<physics::SurfaceFailure>(physics::make_pressure_surface_graph<GeneralEOS, SharedState>(), context);
REQUIRE(cache.try_prepare(context));
REQUIRE(cache.get<Linearization>().size() == 2);
CHECK(cache.get<Linearization>()[0].field_derivatives.get<Enthalpy>() == -7.0);
CHECK(cache.get<Linearization>()[1].field_derivatives.get<Enthalpy>() == -7.0);
}