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

170 lines
8.5 KiB
C++

#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);
}