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.
100 lines
5.2 KiB
C++
100 lines
5.2 KiB
C++
#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);
|
|
}
|