Snapshot optimized solver and audit diagnostics before numerical experiments
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
module;
|
||||
|
||||
#include <concepts>
|
||||
#include <mpi.h>
|
||||
#include <cstdint>
|
||||
#include <expected>
|
||||
#include <memory>
|
||||
@@ -22,9 +23,45 @@ export import :utils.domain;
|
||||
namespace mean_field::normalization::detail {
|
||||
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
|
||||
|
||||
[[nodiscard]] inline bool CanAssembleExactPartialDiagonal(mfem::ParFiniteElementSpace &space, bool hdiv) {
|
||||
// For hanging nodes, diag(P^T A P) contains cross terms. MFEM's PA
|
||||
// diagonal is approximate there, so retain assembled exact diagonals.
|
||||
int eligible = space.GetMesh()->Conforming() && !space.IsVariableOrder() && space.GetVDim() == 1;
|
||||
for (int e = 0; eligible && e < space.GetNE(); ++e) {
|
||||
const auto *fe = space.GetFE(e);
|
||||
eligible = fe->GetGeomType() == mfem::Geometry::CUBE &&
|
||||
(hdiv ? dynamic_cast<const mfem::RT_HexahedronElement *>(fe) != nullptr :
|
||||
dynamic_cast<const mfem::NodalTensorFiniteElement *>(fe) != nullptr);
|
||||
}
|
||||
int allEligible = 0;
|
||||
if (MPI_Allreduce(&eligible, &allEligible, 1, MPI_INT, MPI_MIN, space.GetComm()) != MPI_SUCCESS)
|
||||
throw std::runtime_error("Could not choose a collective normalization assembly path.");
|
||||
return allEligible != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleMassDiagonal(
|
||||
mfem::ParBilinearForm &mass, mfem::ParFiniteElementSpace &space, bool partial
|
||||
) {
|
||||
if (partial) {
|
||||
mass.SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
mass.Assemble();
|
||||
mfem::Vector diagonal(space.GetTrueVSize());
|
||||
mass.AssembleDiagonal(diagonal);
|
||||
return diagonal;
|
||||
}
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (!matrix) throw std::runtime_error("Reference Riesz mass assembly failed.");
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleScalarMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space,
|
||||
mfem::Array<int> *domainMarker = nullptr
|
||||
mfem::Array<int> *domainMarker = nullptr,
|
||||
bool reference = false
|
||||
) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
if (domainMarker == nullptr) {
|
||||
@@ -32,34 +69,80 @@ namespace mean_field::normalization::detail {
|
||||
} else {
|
||||
mass.AddDomainIntegrator(new mfem::MassIntegrator(), *domainMarker);
|
||||
}
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference scalar Riesz mass assembly failed.");
|
||||
}
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
return AssembleMassDiagonal(mass, space, !reference && CanAssembleExactPartialDiagonal(space, false));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space) {
|
||||
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space, bool reference = false) {
|
||||
mfem::ParBilinearForm mass(&space);
|
||||
mass.AddDomainIntegrator(new mfem::VectorFEMassIntegrator());
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference H(div) Riesz mass assembly failed.");
|
||||
return AssembleMassDiagonal(mass, space, !reference && CanAssembleExactPartialDiagonal(space, true));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool CanAssembleDirectBoundaryDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, int attribute
|
||||
) {
|
||||
int eligible = !space.Nonconforming() && !space.IsVariableOrder() && space.GetVDim() == 1 &&
|
||||
dynamic_cast<const mfem::H1_FECollection *>(space.FEColl()) != nullptr;
|
||||
mfem::Array<int> dofs;
|
||||
for (int b = 0; eligible && b < space.GetNBE(); ++b) {
|
||||
if (space.GetBdrAttribute(b) != attribute) continue;
|
||||
// A general DOF transformation would require transforming the
|
||||
// full element matrix before extracting its diagonal.
|
||||
eligible = space.GetBdrElementVDofs(b, dofs) == nullptr;
|
||||
}
|
||||
mfem::Vector diagonal;
|
||||
matrix->GetDiag(diagonal);
|
||||
return diagonal;
|
||||
int allEligible = 0;
|
||||
if (MPI_Allreduce(&eligible, &allEligible, 1, MPI_INT, MPI_MIN, space.GetComm()) != MPI_SUCCESS)
|
||||
throw std::runtime_error("Could not choose a collective surface normalization assembly path.");
|
||||
return allEligible != 0;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleDirectBoundaryDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, int attribute
|
||||
) {
|
||||
mfem::Vector localDiagonal(space.GetVSize());
|
||||
localDiagonal = 0.0;
|
||||
mfem::Array<int> dofs;
|
||||
mfem::Vector shape, elementDiagonal;
|
||||
// GetNBE includes tagged internal interfaces. MFEM's boundary PA face
|
||||
// restriction uses FaceType::Boundary and omits the star/vacuum interface.
|
||||
for (int b = 0; b < space.GetNBE(); ++b) {
|
||||
if (space.GetBdrAttribute(b) != attribute) continue;
|
||||
const auto &element = *space.GetBE(b);
|
||||
auto &transformation = *space.GetBdrElementTransformation(b);
|
||||
space.GetBdrElementVDofs(b, dofs);
|
||||
shape.SetSize(element.GetDof());
|
||||
elementDiagonal.SetSize(element.GetDof());
|
||||
elementDiagonal = 0.0;
|
||||
// Identical rule and physical shapes to MassIntegrator's default
|
||||
// element assembly; only the diagonal of w * shape * shape^T is formed.
|
||||
const auto &rule = mfem::MassIntegrator::GetRule(element, element, transformation);
|
||||
for (int q = 0; q < rule.GetNPoints(); ++q) {
|
||||
const auto &point = rule.IntPoint(q);
|
||||
transformation.SetIntPoint(&point);
|
||||
element.CalcPhysShape(transformation, shape);
|
||||
const double weight = transformation.Weight() * point.weight;
|
||||
for (int i = 0; i < element.GetDof(); ++i)
|
||||
elementDiagonal(i) += weight * shape(i) * shape(i);
|
||||
}
|
||||
for (int i = 0; i < dofs.Size(); ++i) {
|
||||
const int dof = dofs[i] < 0 ? -1 - dofs[i] : dofs[i];
|
||||
localDiagonal(dof) += elementDiagonal(i); // orientation signs are squared
|
||||
}
|
||||
}
|
||||
mfem::Vector trueDiagonal(space.GetTrueVSize());
|
||||
// Conforming H1 prolongation only copies scalar DOFs, so summing the
|
||||
// local diagonal gives diag(P^T A P) exactly, including shared DOFs.
|
||||
if (const auto *prolongation = space.GetProlongationMatrix())
|
||||
prolongation->MultTranspose(localDiagonal, trueDiagonal);
|
||||
else
|
||||
trueDiagonal = localDiagonal;
|
||||
return trueDiagonal;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline mfem::Vector AssembleSurfaceMassDiagonal(
|
||||
const fem::FEM &finiteElements,
|
||||
const field::ScalarBoundaryDofMap &surfaceMap
|
||||
const field::ScalarBoundaryDofMap &surfaceMap,
|
||||
bool reference = false
|
||||
) {
|
||||
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
|
||||
marker = 0;
|
||||
@@ -69,16 +152,12 @@ namespace mean_field::normalization::detail {
|
||||
}
|
||||
marker[attribute - 1] = 1;
|
||||
|
||||
if (!reference && CanAssembleDirectBoundaryDiagonal(*finiteElements.surfaceDeformationFes, attribute))
|
||||
return surfaceMap.gather(AssembleDirectBoundaryDiagonal(*finiteElements.surfaceDeformationFes, attribute));
|
||||
|
||||
mfem::ParBilinearForm mass(finiteElements.surfaceDeformationFes.get());
|
||||
mass.AddBoundaryIntegrator(new mfem::MassIntegrator(), marker);
|
||||
mass.Assemble();
|
||||
mass.Finalize();
|
||||
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
|
||||
if (matrix == nullptr) {
|
||||
throw std::runtime_error("Reference surface Riesz mass assembly failed.");
|
||||
}
|
||||
mfem::Vector ambientDiagonal;
|
||||
matrix->GetDiag(ambientDiagonal);
|
||||
auto ambientDiagonal = AssembleMassDiagonal(mass, *finiteElements.surfaceDeformationFes, false);
|
||||
return surfaceMap.gather(ambientDiagonal);
|
||||
}
|
||||
|
||||
@@ -100,6 +179,19 @@ namespace mean_field::normalization::detail {
|
||||
} // namespace mean_field::normalization::detail
|
||||
|
||||
export namespace mean_field::normalization {
|
||||
// Isolated verification/profiling entry points. reference=true retains the
|
||||
// full assembled true-DOF diagonal; production uses exact PA for supported
|
||||
// volume spaces and direct marked-boundary quadrature for supported surfaces.
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceScalarMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, mfem::Array<int> *domainMarker = nullptr, bool reference = false
|
||||
) { return detail::AssembleScalarMassDiagonal(space, domainMarker, reference); }
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceHDivMassDiagonal(
|
||||
mfem::ParFiniteElementSpace &space, bool reference = false
|
||||
) { return detail::AssembleHDivMassDiagonal(space, reference); }
|
||||
[[nodiscard]] inline mfem::Vector AssembleReferenceSurfaceMassDiagonal(
|
||||
const fem::FEM &finiteElements, const field::ScalarBoundaryDofMap &surfaceMap, bool reference = false
|
||||
) { return detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap, reference); }
|
||||
|
||||
/*
|
||||
* Runtime preparation paired with the compile-time normalization plan.
|
||||
* The operator compiler is the authority for which blocks a specification
|
||||
|
||||
Reference in New Issue
Block a user