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,89 @@
#include <limits>
#include <memory>
#include <optional>
#include <utility>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include "mfem/linalg/densemat.hpp"
#include "serif/backend/mfem/model/scalar_row.hpp"
#include "serif/backend/mfem/model/system.hpp"
TEST_CASE("A scalar residual row retains its exact functional without retaining or repeating the source action", "[model][scalar_row][unit]") {
namespace backend = serif::model::backend::mfem;
namespace execution = serif::operators::backend::mfem;
int forward_calls = 0, transpose_calls = 0;
std::optional<execution::LinearizedBlock> cached;
std::weak_ptr<int> lifetime;
{
auto owner = std::make_shared<int>(1);
lifetime = owner;
const auto source = execution::linearized_action(1, 3, [&, owner](const auto& input, auto& output) {
++forward_calls;
output[0] = 2.0 * input[0] - 3.0 * input[1] + 5.0 * input[2];
}, [&, owner](const auto& input, auto& output) {
++transpose_calls;
output[0] = 2.0 * input[0];
output[1] = -3.0 * input[0];
output[2] = 5.0 * input[0];
});
auto result = backend::cache_scalar_row(source);
REQUIRE(result);
cached.emplace(std::move(*result));
}
CHECK(lifetime.expired());
CHECK(forward_calls == 0);
CHECK(transpose_calls == 1);
const auto system = backend::make_physical_system(1, 3, {}, {{0, 0, *cached}});
mfem::Vector direction(3), action, transpose, dual(1);
direction[0] = 7.0;
direction[1] = 11.0;
direction[2] = 13.0;
dual[0] = 0.25;
system->jacobian().action()->Mult(direction, action);
CHECK(action[0] == Catch::Approx(46.0));
system->jacobian().action()->MultTranspose(dual, transpose);
CHECK(transpose[0] == Catch::Approx(0.5));
CHECK(transpose[1] == Catch::Approx(-0.75));
CHECK(transpose[2] == Catch::Approx(1.25));
const auto selected = system->block(0, 1, 1, 2);
mfem::Vector selected_direction(direction.GetData() + 1, 2);
selected.action()->Mult(selected_direction, action);
CHECK(action[0] == Catch::Approx(32.0));
const auto matrix = selected.assemble();
CHECK((*matrix)(0, 0) == Catch::Approx(-3.0));
CHECK((*matrix)(0, 1) == Catch::Approx(5.0));
CHECK(forward_calls == 0);
CHECK(transpose_calls == 1);
}
TEST_CASE("Scalar row caching rejects a spatial residual without invoking its transpose", "[model][scalar_row][unit]") {
namespace execution = serif::operators::backend::mfem;
int calls = 0;
const auto source = execution::linearized_action(2, 3, [](const auto&, auto&) {}, [&](const auto&, auto&) {
++calls;
});
CHECK_THROWS(serif::model::backend::mfem::cache_scalar_row(source));
CHECK(calls == 0);
}
TEST_CASE("Scalar row caching preserves numerical failure classification for nonfinite derivatives", "[model][scalar_row][unit]") {
namespace execution = serif::operators::backend::mfem;
for (const double value : {std::numeric_limits<double>::quiet_NaN(), std::numeric_limits<double>::infinity()}) {
auto matrix = std::make_shared<mfem::DenseMatrix>(1, 1);
(*matrix)(0, 0) = value;
// A raw matrix deliberately bypasses the checked callback adapter to exercise the cache guard.
const auto source = execution::linearized_block(std::shared_ptr<const mfem::DenseMatrix>(matrix));
const auto raw_result = serif::model::backend::mfem::cache_scalar_row(source);
REQUIRE_FALSE(raw_result);
CHECK(raw_result.error().reason == execution::PreparationRejectionReason::NON_FINITE);
const auto checked = execution::linearized_action(1, 1, [source](const auto& input, auto& output) {
source.action()->Mult(input, output);
}, [source](const auto& input, auto& output) {
source.action()->MultTranspose(input, output);
});
const auto checked_result = serif::model::backend::mfem::cache_scalar_row(checked);
REQUIRE_FALSE(checked_result);
CHECK(checked_result.error().reason == execution::PreparationRejectionReason::NON_FINITE);
}
}