Test normalized unbordered adapter and report its storage scope

This commit is contained in:
2026-09-19 13:45:57 -04:00
parent 2398118b34
commit 522a2cfc02
2 changed files with 95 additions and 1 deletions

View File

@@ -354,6 +354,8 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
metric("setup_jacobian_seconds", stats.setupOperatorSeconds); metric("setup_jacobian_seconds", stats.setupOperatorSeconds);
metric("setup_inverse_seconds", stats.setupInverseSeconds); metric("setup_inverse_seconds", stats.setupInverseSeconds);
metric("persistent_vector_bytes", stats.persistentVectorBytes); metric("persistent_vector_bytes", stats.persistentVectorBytes);
metric("unbordered_adapter_vector_bytes", sizeof(mfem::real_t) *
4 * static_cast<std::size_t>(state.normalizedOperator->Height()));
metric("applications", stats.applications); metric("applications", stats.applications);
metric("internal_jacobian_applications", stats.operatorApplications); metric("internal_jacobian_applications", stats.operatorApplications);
metric("internal_inverse_applications", stats.inverseApplications); metric("internal_inverse_applications", stats.inverseApplications);
@@ -643,6 +645,7 @@ void BenchmarkPreconditioners(State &state, const std::filesystem::path &output,
<< "The Schur partition uses every native surface parameter and generated scalar block, including a central border if present.\n" << "The Schur partition uses every native surface parameter and generated scalar block, including a central border if present.\n"
<< "Its field inverse restricts the unbordered structure inverse, not an exact field elimination.\n" << "Its field inverse restricts the unbordered structure inverse, not an exact field elimination.\n"
<< "Existing scalar-border construction is retained in setup cost but its inverse is not applied by the new wrapper.\n" << "Existing scalar-border construction is retained in setup cost but its inverse is not applied by the new wrapper.\n"
<< "The unbordered adapter is frozen at construction; rebuild it and the Schur wrapper for a new Jacobian.\n"
<< "setup_plus_solve_seconds includes construction, normalization binding, Krylov setup, and Krylov solve.\n" << "setup_plus_solve_seconds includes construction, normalization binding, Krylov setup, and Krylov solve.\n"
<< "setup_solve_and_true_check_seconds adds the separately measured original-J true-residual gate.\n" << "setup_solve_and_true_check_seconds adds the separately measured original-J true-residual gate.\n"
<< "measured_candidate_seconds also includes diagnostic refresh, coarse exactness checks, and inverse/Jv comparison.\n" << "measured_candidate_seconds also includes diagnostic refresh, coarse exactness checks, and inverse/Jv comparison.\n"

View File

@@ -45,7 +45,11 @@ namespace surface_border_schur_test {
class Operation final : public mfem::Operator { class Operation final : public mfem::Operator {
public: public:
explicit Operation(mfem::DenseMatrix matrix) explicit Operation(mfem::DenseMatrix matrix)
: mfem::Operator(matrix.Height()), matrix(std::move(matrix)) {} : mfem::Operator(matrix.Height()), matrix(std::move(matrix)),
residualScales(Height()), stateScales(Width()) {
residualScales = 1.0;
stateScales = 1.0;
}
void Mult(const mfem::Vector &input, mfem::Vector &output) const override { void Mult(const mfem::Vector &input, mfem::Vector &output) const override {
++applications; ++applications;
matrix.Mult(input, output); matrix.Mult(input, output);
@@ -53,7 +57,16 @@ namespace surface_border_schur_test {
bool IsPrepared() const { return prepared; } bool IsPrepared() const { return prepared; }
const Operation &GetProblem() const { return *this; } const Operation &GetProblem() const { return *this; }
std::uint64_t GetPreparationGeneration() const { return generation; } std::uint64_t GetPreparationGeneration() const { return generation; }
void DenormalizeResidual(const mfem::Vector &input, mfem::Vector &output) const {
for (int i = 0; i < input.Size(); ++i)
output(i) = input(i) * residualScales(i);
}
void NormalizeState(const mfem::Vector &input, mfem::Vector &output) const {
for (int i = 0; i < input.Size(); ++i)
output(i) = input(i) / stateScales(i);
}
mfem::DenseMatrix matrix; mfem::DenseMatrix matrix;
mfem::Vector residualScales, stateScales;
bool prepared{true}; bool prepared{true};
std::uint64_t generation{1}; std::uint64_t generation{1};
mutable std::uint64_t applications{0}; mutable std::uint64_t applications{0};
@@ -124,6 +137,7 @@ namespace surface_border_schur_test {
namespace blocks = utils::blocks; namespace blocks = utils::blocks;
struct StructureDescriptor { struct StructureDescriptor {
using BackendType = preconditioning::backend::Diagonal;
using CorrectionBlocks = blocks::type_list<blocks::density::mass::value, using CorrectionBlocks = blocks::type_list<blocks::density::mass::value,
blocks::surface_deformation::parameters::value, blocks::enthalpy::specific::value>; blocks::surface_deformation::parameters::value, blocks::enthalpy::specific::value>;
using ResidualBlocks = blocks::type_list<blocks::enthalpy::specific::residual, using ResidualBlocks = blocks::type_list<blocks::enthalpy::specific::residual,
@@ -147,6 +161,16 @@ namespace surface_border_schur_test {
std::array<Range, 5> rows{{{6, 0, 1}, {2, 1, 3}, {0, 4, 2}, {5, 6, 1}, {7, 7, 1}}}; std::array<Range, 5> rows{{{6, 0, 1}, {2, 1, 3}, {0, 4, 2}, {5, 6, 1}, {7, 7, 1}}};
const auto &GetCorrectionRanges() const { return columns; } const auto &GetCorrectionRanges() const { return columns; }
const auto &GetResidualRanges() const { return rows; } const auto &GetResidualRanges() const { return rows; }
void PackResidual(const mfem::Vector &input, mfem::Vector &output) const {
for (const auto &range : rows)
for (int i = 0; i < range.size; ++i)
output(range.preconditionerOffset + i) = input(range.equilibriumOffset + i);
}
void UnpackCorrection(const mfem::Vector &input, mfem::Vector &output) const {
for (const auto &range : columns)
for (int i = 0; i < range.size; ++i)
output(range.equilibriumOffset + i) = input(range.preconditionerOffset + i);
}
}; };
struct CoordinateBinding { struct CoordinateBinding {
RootDescriptor root; RootDescriptor root;
@@ -154,6 +178,73 @@ namespace surface_border_schur_test {
const auto &GetBlock() const { return root; } const auto &GetBlock() const { return root; }
const auto &GetCoordinateMap() const { return coordinates; } const auto &GetCoordinateMap() const { return coordinates; }
}; };
class PackedStructure final : public mfem::Solver {
public:
PackedStructure() : mfem::Solver(6), matrix(6) {
matrix = 0.0;
for (int i = 0; i < 6; ++i)
matrix(i, i) = i + 2.0;
matrix(0, 5) = 0.4;
matrix(4, 1) = -0.6;
}
void SetOperator(const mfem::Operator &) override {}
void Mult(const mfem::Vector &input, mfem::Vector &output) const override {
++applications;
matrix.Mult(input, output);
}
mfem::DenseMatrix matrix;
mutable unsigned applications{0};
};
struct GroupedStructure {
PackedStructure structure;
const auto &GetStructurePreconditioner() const { return structure; }
};
class PhysicalStructureBinding final : public mfem::Solver, public CoordinateBinding {
public:
static constexpr auto applicationContract = preconditioning::ApplicationContract::stationary_linear;
explicit PhysicalStructureBinding(const Operation &operation)
: mfem::Solver(operation.Width()), operation(operation), generation(operation.generation) {}
void SetOperator(const mfem::Operator &) override {}
void Mult(const mfem::Vector &, mfem::Vector &) const override {
throw std::logic_error("The adapter must not apply the scalar-bordered inverse.");
}
const auto &GetProblem() const { return operation; }
const auto &GetGroupedPreconditioner() const { return grouped; }
bool IsCurrent() const { return generation == operation.generation; }
const Operation &operation;
std::uint64_t generation;
GroupedStructure grouped;
};
}
TEST_CASE("Surface Border Schur Structure Adapter Preserves Nonunit Normalization And Typed Packing",
"[solver][surface_border_schur][serial]") {
using namespace surface_border_schur_test;
mfem::DenseMatrix matrix(8);
matrix = 0.0;
Operation operation(std::move(matrix));
operation.residualScales = Vector({2, 3, 5, 7, 11, 13, 17, 19});
operation.stateScales = Vector({23, 29, 31, 37, 41, 43, 47, 53});
PhysicalStructureBinding physical(operation);
probe::NormalizedUnborderedStructureInverse adapter(operation, physical);
STATIC_CHECK(probe::SurfaceBorderSchurInverse<decltype(adapter)>);
auto rhs = Vector({1, 2, 3, 4, 5, 6, 7, 8});
const auto expected = Vector({42.0 / 23, 112.0 / 29, 275.0 / 31, 3.0 / 37,
0, 240.4 / 43, 45.0 / 47, 0});
mfem::Vector observed(8);
adapter.Mult(rhs, observed);
CHECK(Difference(observed, expected) < 1.e-13);
CHECK(physical.grouped.structure.applications == 1);
adapter.Mult(rhs, rhs);
CHECK(Difference(rhs, expected) < 1.e-13);
physical.grouped.structure.iterative_mode = true;
CHECK_FALSE(adapter.IsCurrent());
physical.grouped.structure.iterative_mode = false;
++operation.generation;
physical.generation = operation.generation;
CHECK_FALSE(adapter.IsCurrent());
CHECK_THROWS_AS(adapter.Mult(rhs, observed), std::logic_error);
} }
TEST_CASE("Surface Border Schur Coordinates Include All Native Modes And Generated Central Border", TEST_CASE("Surface Border Schur Coordinates Include All Native Modes And Generated Central Border",