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,99 @@
#include <limits>
#include <memory>
#include <vector>
#include <catch2/catch_approx.hpp>
#include <catch2/catch_test_macros.hpp>
#include "mfem/fem/fe_coll.hpp"
#include "serif/backend/mfem/discretization/dof_map.hpp"
#include "serif/backend/mfem/solver/linear/solve.hpp"
#include "serif/backend/mfem/solver/normalization/operators.hpp"
#include "serif/backend/mfem/operators/actions.hpp"
#include "serif/backend/mfem/geometry/surface/boundary_rows.hpp"
namespace {
/** @brief Select every serial DOF through the same map used by production term bindings. */
class FullBinding : public serif::discretization::backend::mfem::DofMap {
/** @brief Use all elements of the test domain. */
[[nodiscard]] static ::mfem::Array<int> support(const ::mfem::FiniteElementSpace& space) {
::mfem::Array<int> supported(space.GetNE());
supported = 1;
return supported;
}
public:
/** @brief Borrow the immutable finite-element space. */
explicit FullBinding(::mfem::FiniteElementSpace& space) : DofMap(space, support(space)) {}
};
struct Fixture {
::mfem::Mesh mesh{::mfem::Mesh::MakeCartesian3D(2, 2, 1, ::mfem::Element::HEXAHEDRON)};
::mfem::H1_FECollection collection{3, 3};
::mfem::FiniteElementSpace space{&mesh, &collection};
FullBinding binding{space};
};
/** @brief Exercise the real physical DOF input check before the outer action adapter can inspect its output. */
class CheckedPhysicalIdentity final : public ::mfem::Operator {
public:
/** @brief Borrow the same supported finite-element binding used by field actions. */
explicit CheckedPhysicalIdentity(const FullBinding& binding) : ::mfem::Operator(binding.size()), m_binding(binding) {}
/** @brief Validate dimensional coefficients at the physical action boundary. */
void Mult(const ::mfem::Vector& input, ::mfem::Vector& output) const override {
m_binding.validate_values(input);
output = input;
}
private:
const FullBinding& m_binding;
};
/** @brief Keep this failure-classification test independent of inverse conditioning. */
class IdentityInverse final : public ::mfem::Solver {
public:
/** @brief Match the physical coefficient count. */
explicit IdentityInverse(int size) : ::mfem::Solver(size) {}
/** @brief The identity inverse does not store a borrowed operator. */
void SetOperator(const ::mfem::Operator&) override {}
/** @brief Apply the unchanged vector. */
void Mult(const ::mfem::Vector& input, ::mfem::Vector& output) const override { output = input; }
};
}
TEST_CASE("Checked Krylov classifies physical DOF and pressure-boundary overflow without hiding shape errors", "[solver][linear][overflow]") {
namespace solver = serif::solver;
Fixture fixture;
IdentityInverse inverse(fixture.binding.size());
::mfem::Vector rhs(fixture.binding.size()), correction(rhs.Size());
rhs = 0.0;
rhs[0] = 1.0;
SECTION("Overflowing a finite recycled guess inside physical normalization resets it to zero") {
const CheckedPhysicalIdentity physical(fixture.binding);
const double tiny = std::numeric_limits<double>::min();
const auto coordinates = solver::normalization::make_diagonal_normalization(std::vector(rhs.Size(), tiny), std::vector(rhs.Size(), tiny));
REQUIRE(coordinates);
solver::normalization::backend::mfem::ScaledJacobianOperator normalized(physical, *coordinates);
correction = 1.0e100;
const auto report = solver::linear::backend::mfem::solve({}, normalized, inverse,
solver::preconditioner::ApplicationContract::fixed_linear, rhs, correction);
REQUIRE(report.converged());
CHECK(report.initial_residual_norm == 1.0);
CHECK(correction[0] == Catch::Approx(1.0));
}
SECTION("A finite boundary derivative producing infinity becomes a numerical failure report") {
struct Boundary { std::shared_ptr<const serif::discretization::backend::mfem::DofSelection> selected_rows; };
::mfem::Array<int> rows{0};
const Boundary boundary{std::make_shared<const serif::discretization::backend::mfem::DofSelection>(fixture.binding, rows)};
const auto action = [boundary](const auto& input, auto& output) {
output = input;
::mfem::Vector values(1);
values[0] = input[0] * std::numeric_limits<double>::max();
values[0] *= std::numeric_limits<double>::max();
serif::geometry::surface::backend::mfem::detail::replace_boundary_rows(boundary, values, output);
};
const auto operation = serif::operators::backend::mfem::linearized_action(rhs.Size(), rhs.Size(), action, action);
correction = 0.0;
const auto report = solver::linear::backend::mfem::solve({}, *operation.action(), inverse,
solver::preconditioner::ApplicationContract::fixed_linear, rhs, correction);
CHECK(report.status == solver::linear::Status::non_finite);
}
::mfem::Vector wrong_size(1);
wrong_size = 0.0;
CHECK_THROWS_AS(fixture.binding.validate_values(wrong_size), std::invalid_argument);
}