#include #include #include #include #include #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 support(const ::mfem::FiniteElementSpace& space) { ::mfem::Array 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::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 selected_rows; }; ::mfem::Array rows{0}; const Boundary boundary{std::make_shared(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::max(); values[0] *= std::numeric_limits::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); }