Snapshot optimized solver and audit diagnostics before numerical experiments

This commit is contained in:
2026-09-16 14:34:06 -04:00
parent 75cc638739
commit 80cc3d83d7
64 changed files with 11969 additions and 267 deletions

View File

@@ -23,6 +23,7 @@ export import :deformation.radial_extensions;
export import :deformation.surface_prescription;
export import :deformation.vacuum_extension;
export import :fem;
import :fem.reference_tables;
export import :field.mfem;
export import :utils.domain;
@@ -141,8 +142,7 @@ export namespace mean_field::deformation {
m_interiorSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_vacuumSurfacePullbackWorkspace(surfaceDisplacementSize()),
m_surfacePullbackWorkspace(surfaceDisplacementSize()),
m_parameterPullbackWorkspace(parameterCount()),
m_volumeGridFunctionWorkspace(std::make_unique<mfem::ParGridFunction>(&volumeDisplacementSpace)) {
m_parameterPullbackWorkspace(parameterCount()) {
validateCompatibility(surfaceScalarSpace, volumeDisplacementSpace, logicalReferenceMesh);
compileOwnership();
@@ -322,55 +322,8 @@ export namespace mean_field::deformation {
requireCurrentDiscretization();
requireVolumeSize(volumeDisplacement);
m_volumeGridFunctionWorkspace->SetFromTrueDofs(volumeDisplacement);
mfem::Mesh *mesh = m_volumeDisplacementSpace->GetMesh();
double localMinimumDeterminant = std::numeric_limits<double>::infinity();
int localGeometryIsFinite = 1;
for (int element = 0; element < mesh->GetNE(); ++element) {
mfem::ElementTransformation *transformation = mesh->GetElementTransformation(element);
const mfem::FiniteElement *finiteElement = m_volumeDisplacementSpace->GetFE(element);
// Positivity is a pointwise geometry requirement, not an
// integration-accuracy requirement. A rule only slightly
// above the displacement order can miss a narrow negative
// region of the determinant even when a downstream physics
// rule samples it. The determinant of a d-dimensional
// degree-p deformation gradient can vary at substantially
// higher order, so inspect at a conservative d*p scale.
const int geometryInspectionOrder =
std::max(finiteElement->GetOrder() + 2, 2 * spatialDimension() * finiteElement->GetOrder());
const mfem::IntegrationRule &rule =
mfem::IntRules.Get(transformation->GetGeometryType(), geometryInspectionOrder);
for (int point = 0; point < rule.GetNPoints(); ++point) {
transformation->SetIntPoint(&rule.IntPoint(point));
mfem::DenseMatrix deformationGradient;
m_volumeGridFunctionWorkspace->GetVectorGradient(*transformation, deformationGradient);
for (int component = 0; component < spatialDimension(); ++component) {
deformationGradient(component, component) += 1.0;
}
const double determinant = deformationGradient.Det();
if (!std::isfinite(determinant)) {
localGeometryIsFinite = 0;
} else {
localMinimumDeterminant = std::min(localMinimumDeterminant, determinant);
}
}
}
double globalMinimumDeterminant = 0.0;
int globalGeometryIsFinite = 0;
MPI_Allreduce(
&localMinimumDeterminant, &globalMinimumDeterminant, 1, MPI_DOUBLE, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
MPI_Allreduce(
&localGeometryIsFinite, &globalGeometryIsFinite, 1, MPI_INT, MPI_MIN,
m_volumeDisplacementSpace->GetComm()
);
if (globalGeometryIsFinite == 0) {
globalMinimumDeterminant = std::numeric_limits<double>::quiet_NaN();
}
const double globalMinimumDeterminant =
m_geometryInspector.Inspect(*m_volumeDisplacementSpace, volumeDisplacement);
++m_actionStatistics.geometryInspections;
return {.minimumJacobianDeterminant = globalMinimumDeterminant};
}
@@ -579,7 +532,7 @@ export namespace mean_field::deformation {
mutable mfem::Vector m_vacuumSurfacePullbackWorkspace;
mutable mfem::Vector m_surfacePullbackWorkspace;
mutable mfem::Vector m_parameterPullbackWorkspace;
mutable std::unique_ptr<mfem::ParGridFunction> m_volumeGridFunctionWorkspace;
mutable fem::PreparedGeometryInspector m_geometryInspector;
};
template <

View File

@@ -1,5 +1,7 @@
module;
#include <array>
#include <cstdint>
#include <memory>
#include <vector>
@@ -19,7 +21,37 @@ export namespace mean_field::fem {
[[nodiscard]] int GetDofCount() const;
[[nodiscard]] int GetDimension() const;
// Available only for a verified nodal H1/L2 hexahedron and Cartesian quadrature
// ordering (x fastest). Results have columns [value, dx, dy, dz] for
// each input component. Workspace is caller-owned; tables stay immutable.
[[nodiscard]] bool HasTensorEvaluation3D() const noexcept;
void EvaluateTensor3D(
const mfem::DenseMatrix &elementDofs,
mfem::DenseMatrix &valuesAndGradients,
mfem::Vector &workspace
) const;
// Transpose of the reference gradient. Input columns are [dx,dy,dz]
// per output component. Callers supply all physical factors and weights.
void ApplyGradientTransposeTensor3D(const mfem::DenseMatrix &quadratureDuals,
mfem::DenseMatrix &elementDuals,
mfem::Vector &workspace) const;
// Value-only scalar contractions avoid forming unused gradients.
void EvaluateValuesTensor3D(const mfem::Vector &elementDofs, mfem::Vector &values,
mfem::Vector &workspace) const;
void ApplyTransposeTensor3D(const mfem::Vector &quadratureDuals, mfem::Vector &elementDuals,
mfem::Vector &workspace) const;
// Apply the transpose of the value basis to quadrature duals. No weights
// are introduced here; callers supply their already weighted duals.
void ApplyTransposeTensor3D(
const mfem::DenseMatrix &quadratureDuals, mfem::DenseMatrix &elementDuals,
mfem::Vector &workspace
) const;
private:
void PrepareTensorEvaluation3D(const mfem::FiniteElement &element, const mfem::IntegrationRule &rule);
friend class ReferenceTableCache;
ScalarReferenceTable(
const mfem::FiniteElement &element,
@@ -29,6 +61,11 @@ export namespace mean_field::fem {
mfem::DenseMatrix m_values;
std::vector<mfem::DenseMatrix> m_gradients;
int m_dimension;
int m_tensorDofsPerAxis{0};
int m_tensorPointsPerAxis{0};
mfem::Array<int> m_tensorDofMap;
std::array<mfem::DenseMatrix, 3> m_tensorValues;
std::array<mfem::DenseMatrix, 3> m_tensorGradients;
};
class VectorReferenceTable {
@@ -37,8 +74,16 @@ export namespace mean_field::fem {
[[nodiscard]] int GetPointCount() const;
[[nodiscard]] int GetDofCount() const;
[[nodiscard]] int GetDimension() const;
[[nodiscard]] bool HasTensorEvaluation3D() const noexcept;
void EvaluateTensor3D(
const mfem::Vector &elementDofs, mfem::DenseMatrix &values, mfem::Vector &workspace
) const;
void ApplyTransposeTensor3D(
const mfem::DenseMatrix &quadratureDuals, mfem::Vector &elementDuals, mfem::Vector &workspace
) const;
private:
void PrepareTensorEvaluation3D(const mfem::FiniteElement &element, const mfem::IntegrationRule &rule);
friend class ReferenceTableCache;
VectorReferenceTable(
const mfem::FiniteElement &element,
@@ -48,6 +93,9 @@ export namespace mean_field::fem {
std::vector<mfem::DenseMatrix> m_values;
int m_dof_count;
int m_dimension;
int m_tensorClosedDofs{0}, m_tensorPointsPerAxis{0};
mfem::Array<int> m_tensorDofMap;
std::array<std::array<mfem::DenseMatrix, 3>, 3> m_tensorValues;
};
// Owned by one discretization, never process-global. Finite elements must
@@ -75,4 +123,29 @@ export namespace mean_field::fem {
struct Storage;
std::unique_ptr<Storage> m_storage;
};
// The production mapped-geometry inspection, independently benchmarkable.
// Keeps the original conservative rule and checks every volume element.
// Mesh/FES/node sequences invalidate the fixed inverse-Jacobian cache;
// raw node edits require Mesh::NodesUpdated() or ClearCache(). Displacement
// is always re-evaluated. Like prepared operators, scratch is not thread-safe.
class PreparedGeometryInspector {
public:
PreparedGeometryInspector();
~PreparedGeometryInspector();
PreparedGeometryInspector(const PreparedGeometryInspector &) = delete;
PreparedGeometryInspector &operator=(const PreparedGeometryInspector &) = delete;
PreparedGeometryInspector(PreparedGeometryInspector &&) noexcept;
PreparedGeometryInspector &operator=(PreparedGeometryInspector &&) noexcept;
// Collective minimum of det(I + grad(u)); NaN if any sampled value is
// non-finite. reference=true retains GridFunction::GetVectorGradient.
[[nodiscard]] double Inspect(mfem::ParFiniteElementSpace &space,
const mfem::Vector &displacement, bool reference = false) const;
void ClearCache() const;
[[nodiscard]] int GetTensorElementCount() const noexcept;
[[nodiscard]] std::uint64_t GetGeometryBuildCount() const noexcept;
private:
struct Storage;
std::unique_ptr<Storage> m_storage;
};
} // namespace mean_field::fem

View File

@@ -34,6 +34,16 @@ export namespace mean_field::mapping::compactification {
[[nodiscard]] double GetReferenceInfinityRadius() const noexcept;
[[nodiscard]] double GetCoordinateTolerance() const noexcept;
// Factors in delta J = scale * grad(delta u) + delta u (grad scale)^T.
// They depend only on the fixed compactification coordinate, so prepared
// operators can compute them once instead of evaluating the full map.
[[nodiscard]] MappingStatus EvaluateJacobianVariationFactors(
double coordinate,
const mfem::Vector &coordinateGradient,
double &scale,
mfem::Vector &scaleGradient
) const;
private:
struct RadialFactors {
double coordinate;

View File

@@ -1,6 +1,8 @@
module;
#include "mean_field.h"
#include <cstddef>
#include <cstdint>
export module mean_field:mapping.domain_mapper;
export import :mapping.types;
@@ -108,8 +110,35 @@ export namespace mean_field::mapping {
DomainMapper(const DomainMapper &) = delete;
DomainMapper &operator=(const DomainMapper &) = delete;
DomainMapper(DomainMapper &&) = default;
DomainMapper &operator=(DomainMapper &&) = default;
~DomainMapper();
DomainMapper(DomainMapper &&) noexcept;
DomainMapper &operator=(DomainMapper &&) noexcept;
struct FixedMeshGeometry {
mfem::Vector position;
mfem::DenseMatrix jacobian, inverseJacobian;
double weight{0.0};
};
struct GeometryCacheStatistics {
std::uint64_t hits{0}, misses{0};
std::size_t points{0};
};
// Only computational-mesh geometry is cached, never displacement or
// compactification state. MFEM mesh/node sequence changes invalidate it.
// After raw coordinate edits call Mesh::NodesUpdated(), as required by
// MFEM's own geometric-factor cache, or ClearGeometryCache(). A mesh
// must outlive its cached entries; clear before destroying/replacing it.
// Like MFEM element transformations, this cache is not thread-safe.
void SetGeometryCacheEnabled(bool enabled) const;
[[nodiscard]] bool IsGeometryCacheEnabled() const noexcept;
void ClearGeometryCache() const;
[[nodiscard]] GeometryCacheStatistics GetGeometryCacheStatistics() const;
// Sets the transformation's integration point. nullptr means use MFEM
// directly (disabled cache, non-mesh transformation, or unsupported data).
// Returned data remains valid until cache invalidation or mapper destruction.
[[nodiscard]] const FixedMeshGeometry *GetFixedMeshGeometry(
mfem::ElementTransformation &transformation, const mfem::IntegrationPoint &point
) const;
[[nodiscard]] MappingStatus EvaluatePoint(
const ElementMappingData &element_data,
@@ -119,12 +148,19 @@ export namespace mean_field::mapping {
MappingPointContext &context
) const;
// A sample from the same element DOFs and integration point. Its
// gradient is with respect to finite-element coordinates, not the mesh.
struct ReferenceDisplacementSample {
const mfem::Vector &value;
const mfem::DenseMatrix &gradient;
};
[[nodiscard]] MappingStatus EvaluateVolume(
const ElementMappingData &element_data,
mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point,
Workspace &workspace,
VolumeMappingContext &context
VolumeMappingContext &context,
const ReferenceDisplacementSample *displacementSample = nullptr
) const;
[[nodiscard]] MappingStatus EvaluateFace(
@@ -172,6 +208,12 @@ export namespace mean_field::mapping {
[[nodiscard]] const compactification::ExteriorDomainMap &GetExteriorMap() const noexcept;
private:
[[nodiscard]] MappingStatus EvaluatePointImpl(
const ElementMappingData &element_data, mfem::ElementTransformation &transformation,
const mfem::IntegrationPoint &integration_point, Workspace &workspace,
MappingPointContext &context, const FixedMeshGeometry *meshGeometry,
const ReferenceDisplacementSample *displacementSample = nullptr
) const;
void ValidateElementData(const ElementMappingData &element_data) const;
void EvaluateField(
@@ -214,6 +256,8 @@ export namespace mean_field::mapping {
FaceElementSide side
);
struct GeometryCache;
mutable std::unique_ptr<GeometryCache> m_geometryCache;
utils::DomainMapperOptions m_options;
std::unique_ptr<const compactification::ExteriorDomainMap> m_exterior_map;
};

View File

@@ -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

View File

@@ -156,6 +156,16 @@ export namespace mean_field::operators {
void BuildResidual(mfem::Vector &residual) const;
// Rows differentiate I_axis; changing Omega alone reuses both rows.
[[nodiscard]] bool PrepareJacobianRows() const;
[[nodiscard]] std::uint64_t GetJacobianRowPreparationCount() const noexcept;
void ApplyCompleteJacobianActionReference(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
double angularVelocityVariation,
mfem::Vector &action
) const;
void ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
@@ -223,6 +233,8 @@ export namespace mean_field::operators {
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;
[[nodiscard]] double EvaluateDisplacementMomentActionLocal(const mfem::Vector &displacementVariation) const;
[[nodiscard]] double EvaluateDensityMomentActionLocalReference(const mfem::Vector &densityVariation) const;
[[nodiscard]] double EvaluateDisplacementMomentActionLocalReference(const mfem::Vector &displacementVariation) const;
[[nodiscard]] double CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept;
[[nodiscard]] double CylindricalRadiusSquaredVariation(
const mfem::Vector &physicalPosition,
@@ -238,6 +250,11 @@ export namespace mean_field::operators {
std::vector<ElementPAData> m_elements;
AngularMomentumDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual;
mutable mfem::Vector m_densityJacobianLocal;
mutable mfem::Vector m_displacementJacobianLocal;
mutable bool m_jacobianRowsReady{false};
mutable bool m_jacobianRowsSupported{false};
mutable std::uint64_t m_jacobianRowPreparationCount{0};
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_densityVariationLocal;

View File

@@ -82,6 +82,20 @@ export namespace mean_field::operators {
mfem::Vector &action
) const override;
// Supported-coordinate coupling actions; omit identically absent blocks.
void ApplyEnthalpyJacobianAction(const mfem::Vector &enthalpyVariation, mfem::Vector &action,
bool useTensorPath = true) const;
void ApplyDisplacementJacobianAction(const mfem::Vector &displacementVariation, mfem::Vector &action,
bool useTensorPath = true) const;
// Retained complete action for isolation/reference comparisons.
void MultReference(const mfem::Vector &densityVariation, const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation, mfem::Vector &action) const;
// Previous optimized pointwise actions, sharing exactly the same state.
void MultCachedReference(const mfem::Vector &densityVariation, const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation, mfem::Vector &action) const;
[[nodiscard]] int GetTensorElementCount() const noexcept;
void BuildResidual(mfem::Vector &residual) const;
// Exact diagonal of the prepared density-to-closure block, expressed
@@ -118,15 +132,19 @@ export namespace mean_field::operators {
void VerifyPrepared() const;
void MultImpl(const mfem::Vector &densityVariation, const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation, mfem::Vector &action, bool useFastPath, bool useTensorPath) const;
void ApplyThermodynamicActionFull(
const mfem::Vector &densityVariationTrue,
const mfem::Vector *densityVariationTrue,
const mfem::Vector &enthalpyVariationTrue,
mfem::Vector &actionTrue
mfem::Vector &actionTrue, bool useTensorPath = true
) const;
void ApplyDisplacementActionFull(
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
mfem::Vector &actionTrue,
bool useFastPath = true, bool useTensorPath = true
) const;
struct ElementPAData {
@@ -180,6 +198,8 @@ export namespace mean_field::operators {
mutable mfem::Vector m_quadratureDisplacementAction;
mutable mfem::Vector m_elementDisplacementAction;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
mutable mfem::DenseMatrix m_tensorDisplacement;
mutable mfem::Vector m_tensorWorkspace;
std::uint64_t m_preparationCount{0};
bool m_isPrepared{false};

View File

@@ -66,6 +66,9 @@ export namespace mean_field::operators {
kernels::GravityDisplacementForceRejection>
TryPrepare();
// Retained two-pass preparation for isolation benchmarks and validation.
PreparedGravityDisplacementForceReport PrepareReference();
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -90,6 +93,31 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyCompleteJacobianActionReference(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
const mfem::Vector &gravityGradientVariation,
mfem::Vector &action
) const;
void ApplyCompleteJacobianActionCachedReference(
const mfem::Vector &densityVariation, const mfem::Vector &displacementVariation,
const mfem::Vector &gravityGradientVariation, mfem::Vector &action
) const;
[[nodiscard]] int GetTensorElementCount() const noexcept {
int count = 0;
for (const auto &data : m_elements)
if (data.fastApplication && data.displacementReferenceTable->HasTensorEvaluation3D() &&
data.gravityReferenceTable && data.gravityReferenceTable->HasTensorEvaluation3D()) ++count;
return count;
}
[[nodiscard]] int GetFastElementCount() const noexcept {
int count = 0;
for (const auto &data : m_elements) count += data.fastApplication;
return count;
}
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetResidualPreparationCount() const noexcept;
@@ -124,16 +152,20 @@ export namespace mean_field::operators {
[[nodiscard]] std::expected<
void,
kernels::GravityDisplacementForceRejection>
TryPrepareElementData();
TryPrepareElementData(bool assembleResidual);
[[nodiscard]] std::expected<PreparedGravityDisplacementForceReport, kernels::GravityDisplacementForceRejection>
TryPrepareImpl(bool useFastPreparation);
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &gravityGradientVariationTrue,
mfem::Vector &actionTrue
mfem::Vector &actionTrue,
bool useFastPath = true, bool useTensorPath = true
) const;
struct ElementPAData {
int elementId{-1};
bool fastApplication{false};
mfem::Array<int> densityDofs;
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
@@ -168,6 +200,8 @@ export namespace mean_field::operators {
mutable mfem::Vector m_gravityGradientVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_quadratureDensityVariation;
mutable mfem::DenseMatrix m_quadratureForce;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementGravityGradientVariation;
mutable mfem::Vector m_elementDisplacementVariation;
@@ -199,6 +233,9 @@ export namespace mean_field::operators {
mutable PreparedGravityDisplacementForceCompleteStatistics m_completeJacobianStatistics;
mutable mfem::DenseMatrix m_tensorDisplacement, m_tensorGravity, m_tensorElementDual;
mutable mfem::Vector m_tensorWorkspace;
bool m_preparedWithFastResidual{true};
bool m_isPrepared{false};
};

View File

@@ -38,6 +38,8 @@ export namespace mean_field::operators {
void Prepare(const mfem::Vector &displacement);
void PreparePrimal(const mfem::Vector &displacement);
// Previous weight-recomputation path for isolated preparation comparisons.
void PrepareReference(const mfem::Vector &displacement);
[[nodiscard]] GravitySourcePreparationResult TryPrepare(const mfem::Vector &displacement);
[[nodiscard]] GravitySourcePreparationResult TryPreparePrimal(const mfem::Vector &displacement);
void Mult(
@@ -64,6 +66,11 @@ export namespace mean_field::operators {
}
}
void MultDisplacementVariationTrueReference(
const mfem::Vector &densityTrue, const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue) const;
[[nodiscard]] int GetTensorVariationElementCount() const noexcept;
void MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action
@@ -109,7 +116,7 @@ export namespace mean_field::operators {
[[nodiscard]] GravitySourcePreparationResult TryPrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
PreparationMode mode, bool useCachedMeshWeight = true
);
const fem::FEM &m_fem;
@@ -137,6 +144,11 @@ export namespace mean_field::operators {
mutable mfem::Vector m_element_displacement_variation;
mutable mfem::Vector m_quadrature_variation_action;
mutable mfem::Vector m_element_variation_action;
void MultDisplacementVariationTrueImpl(const mfem::Vector &densityTrue,
const mfem::Vector &displacementVariationTrue, mfem::Vector &actionVariationTrue,
bool useTensorPath) const;
mutable mfem::DenseMatrix m_tensorSamples;
mutable mfem::Vector m_tensorWorkspace;
mutable mfem::DenseMatrix m_reference_displacement_jacobian;
mfem::Vector m_displacement_true;

View File

@@ -44,11 +44,29 @@ export namespace mean_field::operators {
const mfem::Vector &gravity_gradient,
mfem::Vector &action
) const override;
// Unmodified MFEM partial-assembly application on the same prepared data.
void MultReference(const mfem::Vector &gravityGradient, mfem::Vector &action) const;
[[nodiscard]] int GetFastMassElementCount() const noexcept { return m_fastMassElements; }
void MultDisplacementVariationTrue(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
// Retained prepared implementation for isolated A/B validation and
// profiling. Uses the same prepared state, quadrature and DOF maps.
void MultDisplacementVariationTrueReference(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
// Previous optimized pointwise path, excluding the new tensor contractions.
void MultDisplacementVariationTrueCachedReference(
const mfem::Vector &gravityGradientTrue, const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
[[nodiscard]] int GetFastVariationElementCount() const noexcept;
[[nodiscard]] int GetTensorVariationElementCount() const noexcept;
void AssembleDiagonal(mfem::Vector &diagonal) const override;
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
@@ -79,6 +97,11 @@ export namespace mean_field::operators {
mfem::Vector compactification;
const mfem::IntegrationRule *integrationRule{nullptr};
std::shared_ptr<const fem::VectorReferenceTable> gravityReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> displacementReferenceTable;
// One contiguous column per quadrature point: scale*J_mesh^-1
// (row-major, nine entries), followed by grad(scale). 3D VALUE
// displacement with an interior or Kelvin map; empty otherwise.
mfem::DenseMatrix tangentFactors;
// Fixed computational-mesh Piola factor, separate from J_map.
mfem::DenseMatrix meshPiolaJacobians;
mfem::Vector referenceWeights;
@@ -86,6 +109,13 @@ export namespace mean_field::operators {
};
[[nodiscard]] mapping::MappingStatus PrepareVariationData();
void MultDisplacementVariationTrueImpl(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue,
bool useFastPath, bool useTensorPath
) const;
void AddFastElementVariation(const ElementVariationData &data, bool useTensorPath) const;
[[nodiscard]] HDivMassPreparationResult TryPrepareImpl(
const mfem::Vector &displacement,
PreparationMode mode
@@ -129,9 +159,12 @@ export namespace mean_field::operators {
mutable mfem::DenseMatrix m_meshPiolaJacobian;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_massTensorVariation;
mutable mfem::DenseMatrix m_tensorDisplacement, m_tensorGravity, m_tensorDual;
mutable mfem::Vector m_tensorWorkspace;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
bool m_has_variation_data{false};
bool m_single_rank{true};
int m_fastMassElements{0};
};
} // namespace mean_field::operators

View File

@@ -142,6 +142,14 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
HydrostaticEquilibriumPreparationResult TryPrepareReference(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation);
PreparedHydrostaticEquilibriumReport PrepareReference(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation);
void BuildResidual(mfem::Vector &residual) const;
void ApplyEnthalpyJacobianAction(
@@ -179,6 +187,18 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyDisplacementJacobianActionReference(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
// Previous cached pointwise implementation, retained for benchmarking
// the tensor change separately from the older general mapping path.
void ApplyDisplacementJacobianActionCachedReference(
const mfem::Vector &displacementVariation, mfem::Vector &action
) const;
[[nodiscard]] int GetFastDisplacementElementCount() const noexcept;
[[nodiscard]] int GetTensorDisplacementElementCount() const noexcept;
void ApplyCompleteJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &gravityPotentialVariation,
@@ -244,6 +264,10 @@ export namespace mean_field::operators {
// Immutable reference values and gradients are shared by FE/rule.
std::shared_ptr<const fem::ScalarReferenceTable> enthalpyReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> gravityPotentialReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> displacementReferenceTable;
// Columns are quadrature points; each holds a column-major inverse
// deformed element Jacobian. Empty for the general mapping path.
mfem::DenseMatrix displacementInverseJacobians;
[[nodiscard]] const mfem::DenseMatrix &GetEnthalpyBasis() const {
return enthalpyReferenceTable->GetValues();
@@ -274,14 +298,24 @@ export namespace mean_field::operators {
mfem::Vector bernoulliConstantJacobian;
};
HydrostaticEquilibriumPreparationResult TryPrepareImpl(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation, bool useTensorPreparation);
void PrepareStaticPlan();
[[nodiscard]] std::optional<mapping::MappingStatus> PrepareGeometry();
[[nodiscard]] bool PrepareAlgebraicJacobianBlocks();
[[nodiscard]] std::optional<mapping::MappingStatus> PrepareGeometry(bool useTensorPreparation);
[[nodiscard]] bool PrepareAlgebraicJacobianBlocks(bool useTensorPreparation);
[[nodiscard]] bool PrepareRotation();
[[nodiscard]] bool PrepareBaseState();
[[nodiscard]] bool PrepareBaseState(bool useTensorPreparation);
void FinalizeDisplacementJacobianPreparation();
[[nodiscard]] bool AssembleCachedResidual();
void VerifyPrepared() const;
void ApplyDisplacementJacobianActionImpl(
const mfem::Vector &displacementVariation, mfem::Vector &action, bool useFastPath, bool useTensorPath
) const;
mutable mfem::DenseMatrix m_tensorDisplacementValuesAndGradients;
mutable mfem::Vector m_tensorDisplacementWorkspace;
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
@@ -307,6 +341,7 @@ export namespace mean_field::operators {
mutable PreparedHydrostaticCompleteJacobianStatistics m_completeJacobianStatistics;
bool m_preparedWithTensorPreparation{true};
bool m_isPrepared{false};
};

View File

@@ -147,6 +147,16 @@ export namespace mean_field::operators {
void BuildResidual(mfem::Vector &residual) const;
// Lazy, local-dual row assembly. Invalidated only by density/geometry.
// Returns false when this discretization uses the general fallback.
[[nodiscard]] bool PrepareJacobianRows() const;
[[nodiscard]] std::uint64_t GetJacobianRowPreparationCount() const noexcept;
void ApplyCompleteJacobianActionReference(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
void ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
@@ -230,6 +240,9 @@ export namespace mean_field::operators {
[[nodiscard]] double EvaluateDisplacementActionLocal(const mfem::Vector &displacementVariation) const;
[[nodiscard]] double EvaluateDensityActionLocalReference(const mfem::Vector &densityVariation) const;
[[nodiscard]] double EvaluateDisplacementActionLocalReference(const mfem::Vector &displacementVariation) const;
void AssembleDensityTransposeAction(
double residualDual,
mfem::Vector &densityDual
@@ -250,8 +263,16 @@ export namespace mean_field::operators {
MassNormalizationDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual;
mutable mfem::Vector m_densityJacobianLocal;
mutable mfem::Vector m_displacementJacobianLocal;
mutable bool m_jacobianRowsReady{false};
mutable bool m_jacobianRowsSupported{false};
mutable std::uint64_t m_jacobianRowPreparationCount{0};
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
double m_currentMass{0.0};
double m_targetMass{0.0};

View File

@@ -116,6 +116,20 @@ export namespace mean_field::operators {
const context::pressure_force::PressureForceDependencies &dependencies
);
// Original quadrature-by-quadrature matrix assembly for validation.
PreparedPressureForceReport PrepareReference(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies
);
// Previous blocked matrix preparation and pointwise field evaluation.
PreparedPressureForceReport PrepareCachedReference(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies);
void ApplyDisplacementJacobianActionCachedReference(
const mfem::Vector &displacementVariation, mfem::Vector &action) const;
[[nodiscard]] int GetTensorElementCount() const noexcept;
void BuildResidual(mfem::Vector &residual) const;
void ApplyEnthalpyJacobianAction(
@@ -128,6 +142,10 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyDisplacementJacobianActionReference(
const mfem::Vector &displacementVariation, mfem::Vector &action
) const;
void ApplyCompleteJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &displacementVariation,
@@ -178,6 +196,8 @@ export namespace mean_field::operators {
}
private:
void ApplyDisplacementJacobianActionImpl(const mfem::Vector &displacementVariation,
mfem::Vector &action, bool useFastPath, bool useTensorPath = true) const;
struct ConstructionData;
[[nodiscard]]
@@ -237,8 +257,14 @@ export namespace mean_field::operators {
};
void PrepareStaticPlan();
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareGeometry();
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareMaterialState();
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareGeometry(bool useTensorPreparation);
[[nodiscard]] std::optional<PressureForcePreparationRejection> PrepareMaterialState(bool useFastPreparation, bool useTensorPreparation);
[[nodiscard]] std::expected<PreparedPressureForceReport, PressureForcePreparationRejection>
TryPrepareImpl(
const context::pressure_force::PressureForceStateView &state,
const context::pressure_force::PressureForceDependencies &dependencies,
bool useFastPreparation, bool useTensorPreparation = true
);
void FinalizeDisplacementJacobianPreparation();
@@ -291,6 +317,10 @@ export namespace mean_field::operators {
mutable PreparedPressureForceCompleteJacobianStatistics m_completeJacobianStatistics;
mutable mfem::DenseMatrix m_tensorSamples, m_tensorDuals, m_tensorElement;
mutable mfem::Vector m_tensorWorkspace;
bool m_preparedWithTensorPreparation{true};
bool m_preparedWithFastMaterialAssembly{true};
bool m_isPrepared{false};
};

View File

@@ -4,6 +4,7 @@ module;
#include <cstdint>
#include <expected>
#include <optional>
#include <memory>
#include <vector>
#include <mfem.hpp>
@@ -11,6 +12,7 @@ module;
export module mean_field:operators.prepared_rotational_displacement_force;
export import :fem;
import :fem.reference_tables;
export import :mapping.domain_mapper;
export import :operators.context.rotational_displacement_force;
export import :operators.kernels.rotational_displacement_force;
@@ -72,6 +74,13 @@ export namespace mean_field::operators {
const physics::RigidRotation &rotation
);
// Retained two-pass preparation for isolation benchmarks and validation.
PreparedRotationalDisplacementForceReport PrepareReference(
const context::rotational_displacement_force::RotationalDisplacementForceStateView &state,
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -90,6 +99,23 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyCompleteJacobianActionReference(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
void ApplyCompleteJacobianActionCachedReference(
const mfem::Vector &densityVariation, const mfem::Vector &displacementVariation, mfem::Vector &action
) const;
[[nodiscard]] int GetTensorElementCount() const noexcept;
[[nodiscard]] int GetFastElementCount() const noexcept {
int count = 0;
for (const auto &data : m_elements) count += data.fastApplication;
return count;
}
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] const context::rotational_displacement_force::RotationalDisplacementForcePreparationStatistics &
@@ -124,20 +150,31 @@ export namespace mean_field::operators {
[[nodiscard]] std::expected<
void,
kernels::RotationalDisplacementForceRejection>
TryPrepareElementData();
TryPrepareElementData(bool assembleResidual);
[[nodiscard]] std::expected<PreparedRotationalDisplacementForceReport, kernels::RotationalDisplacementForceRejection>
TryPrepareImpl(
const context::rotational_displacement_force::RotationalDisplacementForceStateView &state,
const context::rotational_displacement_force::RotationalDisplacementForceDependencies &dependencies,
const physics::RigidRotation &rotation,
bool useFastPreparation
);
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
mfem::Vector &actionTrue,
bool useFastPath = true, bool useTensorPath = true
) const;
struct ElementPAData {
int elementId{-1};
bool fastApplication{false};
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
std::shared_ptr<const fem::ScalarReferenceTable> densityReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> displacementReferenceTable;
mfem::DenseMatrix inverseElementJacobians;
mfem::DenseMatrix centrifugalAccelerations;
mfem::Vector baseDensityValues;
@@ -150,6 +187,7 @@ export namespace mean_field::operators {
context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext m_context;
std::optional<physics::RigidRotation> m_rotation;
mfem::DenseMatrix m_centrifugalJacobian;
mfem::Vector m_cachedResidual;
std::vector<ElementPAData> m_elements;
mutable mfem::Vector m_densityVariationTrue;
@@ -158,6 +196,11 @@ export namespace mean_field::operators {
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_quadratureDensityVariation;
mutable mfem::DenseMatrix m_quadratureForce;
mutable mfem::DenseMatrix m_quadraturePositionVariation;
mutable mfem::DenseMatrix m_tensorDisplacement, m_tensorElement;
mutable mfem::Vector m_tensorWorkspace;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementAction;
@@ -181,6 +224,7 @@ export namespace mean_field::operators {
mutable PreparedRotationalDisplacementForceCompleteStatistics m_completeJacobianStatistics;
bool m_preparedWithFastResidual{true};
bool m_isPrepared{false};
};

View File

@@ -411,9 +411,27 @@ export namespace mean_field::preconditioning {
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityDivergenceSurrogate(const fem::FEM &f);
// Fixed computational-mesh divergence, owned by one prepared block.
// FES/mesh/node sequences and policy identity invalidate it. Raw node edits
// require Mesh::NodesUpdated(). Clear explicitly if the policy changes in place.
class PreparedGravityDivergenceCache {
public:
[[nodiscard]] const mfem::HypreParMatrix &Get(const fem::FEM &f);
void Clear() noexcept { m_matrix.reset(); }
[[nodiscard]] std::uint64_t GetBuildCount() const noexcept { return m_builds; }
private:
std::unique_ptr<mfem::HypreParMatrix> m_matrix;
const mfem::Mesh *m_mesh{nullptr};
const mfem::ParFiniteElementSpace *m_flux{nullptr}, *m_potential{nullptr};
const quadrature::RuleFactory *m_policy{nullptr};
long m_meshSequence{-1}, m_nodesSequence{-1}, m_fluxSequence{-1}, m_potentialSequence{-1};
std::uint64_t m_builds{0};
};
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> assembleGravityPotentialSchurSurrogate(
const fem::FEM &f,
const mfem::Vector &trueMassDiagonal
const mfem::Vector &trueMassDiagonal,
PreparedGravityDivergenceCache *divergenceCache = nullptr
);
struct GravityFieldBlockPreparationReport final {
@@ -548,6 +566,7 @@ export namespace mean_field::preconditioning {
RefreshMassInverse(geometryContext);
report.rebuiltMassInverse = true;
if (report.discretizationChanged) m_fixedDivergence.Clear();
auto potentialSchur = AssemblePotentialSchur(f, geometryContext);
m_potentialSchurInverse.Refresh(*potentialSchur);
m_potentialSchurSurrogate = std::move(potentialSchur);
@@ -586,6 +605,10 @@ export namespace mean_field::preconditioning {
return m_factorization;
}
[[nodiscard]] std::uint64_t GetDivergenceAssemblyCount() const noexcept {
return m_fixedDivergence.GetBuildCount();
}
[[nodiscard]] const PreparedGravityFieldBlockStatistics &GetStatistics() const noexcept {
return m_statistics;
}
@@ -632,13 +655,13 @@ export namespace mean_field::preconditioning {
}
}
[[nodiscard]] static std::unique_ptr<mfem::HypreParMatrix> AssemblePotentialSchur(
[[nodiscard]] std::unique_ptr<mfem::HypreParMatrix> AssemblePotentialSchur(
const fem::FEM &f,
const operators::context::gravity_field::GravityFieldGeometryContext &geometryContext
) {
mfem::Vector trueMassDiagonal;
geometryContext.GetMassOperator().AssembleTrueDiagonal(trueMassDiagonal);
return assembleGravityPotentialSchurSurrogate(f, trueMassDiagonal);
return assembleGravityPotentialSchurSurrogate(f, trueMassDiagonal, &m_fixedDivergence);
}
Block m_block;
@@ -647,6 +670,7 @@ export namespace mean_field::preconditioning {
field::FieldDofMap m_potentialMap;
ReducedGravityDivergenceOperator m_divergence;
PreparedMassInverse m_massInverse;
PreparedGravityDivergenceCache m_fixedDivergence;
std::unique_ptr<mfem::HypreParMatrix> m_potentialSchurSurrogate;
backend::PreparedHypreBoomerAMG<Mode> m_potentialSchurInverse;
ReducedFieldSolverAdapter m_reducedPotentialSchurInverse;

View File

@@ -784,9 +784,7 @@ export namespace mean_field::preconditioning {
) const {
VerifyBlock(enthalpy, 2, "enthalpy direction");
VerifyBlock(densityAction, 0, "density action");
m_operation->GetBarotropicClosureOperator().Mult(
m_zeroDensity, enthalpy, ZeroVolumeDisplacement(), densityAction
);
m_operation->GetBarotropicClosureOperator().ApplyEnthalpyJacobianAction(enthalpy, densityAction);
}
void ApplySurfaceToMaterial(
@@ -798,8 +796,8 @@ export namespace mean_field::preconditioning {
VerifyBlock(densityAction, 0, "density action");
VerifyBlock(enthalpyAction, 2, "enthalpy action");
GenerateDisplacement(surface);
m_operation->GetBarotropicClosureOperator().Mult(
m_zeroDensity, m_zeroEnthalpy, m_volumeDisplacement, densityAction
m_operation->GetBarotropicClosureOperator().ApplyDisplacementJacobianAction(
m_volumeDisplacement, densityAction
);
m_operation->GetHydrostaticOperator().ApplyDisplacementJacobianAction(m_volumeDisplacement, enthalpyAction);
m_operation->GetSurfaceConstraintOperator().ApplyJacobianRows(m_zeroEnthalpy, enthalpyAction);

View File

@@ -32,6 +32,9 @@ export namespace mean_field::solver::nonlinear {
double sufficientDecrease{1.0e-4};
double minimumStepLength{1.0e-8};
int maximumTrials{20};
// Skipping the predictive scan retains trial mapping validation and
// ordinary backtracking. Useful for small, warm-start Newton steps.
bool geometryPreflight{true};
void Validate() const {
if (!std::isfinite(initialStepLength) || initialStepLength <= 0.0) {

View File

@@ -143,11 +143,12 @@ namespace mean_field::solver::detail {
};
std::array<double, 9> minimumRealValues{};
std::array<double, 9> maximumRealValues{};
const std::array<int, 3> localIntegerValues{
options.maximumIterations, options.linearSolve.maximumIterations, options.backtracking.maximumTrials
const std::array<int, 4> localIntegerValues{
options.maximumIterations, options.linearSolve.maximumIterations, options.backtracking.maximumTrials,
static_cast<int>(options.backtracking.geometryPreflight)
};
std::array<int, 3> minimumIntegerValues{};
std::array<int, 3> maximumIntegerValues{};
std::array<int, 4> minimumIntegerValues{};
std::array<int, 4> maximumIntegerValues{};
if (MPI_Allreduce(
localRealValues.data(), minimumRealValues.data(), static_cast<int>(localRealValues.size()), MPI_DOUBLE,
@@ -341,7 +342,6 @@ export namespace mean_field::solver {
ValidateInitialState();
InitializeWorkspaces();
PrepareInitialOperator();
InitializeGeometryPreflightRules();
physicalInverse = std::unique_ptr<PhysicalInverseType>{new PhysicalInverseType(
PreparePhysicalInverse(std::move(preconditionerPrescription), RequireProblem(storage))
@@ -485,6 +485,10 @@ export namespace mean_field::solver {
const double maximumStepSize,
const double fractionToBoundarySafety
) {
if (!geometryPreflightRulesInitialized) {
InitializeGeometryPreflightRules();
geometryPreflightRulesInitialized = true;
}
normalizedOperator->DenormalizeState(normalizedCorrection, physicalCorrection);
const auto &physicalOperator = Problem().GetPhysicalOperator();
Problem().BuildVolumeDisplacementDirection(physicalCorrection, volumeDisplacementDirection);
@@ -513,6 +517,7 @@ export namespace mean_field::solver {
mfem::Vector volumeDisplacementDirection;
mfem::Vector candidatePhysicalState;
std::vector<deformation::NewtonStepGeometryRule> geometryPreflightRules;
bool geometryPreflightRulesInitialized{false};
double acceptedMinimumJacobianDeterminant{std::numeric_limits<double>::quiet_NaN()};
std::unique_ptr<NormalizedOperatorType> normalizedOperator;
std::unique_ptr<PhysicalInverseType> physicalInverse;
@@ -1271,31 +1276,33 @@ export namespace mean_field::solver {
}
const nonlinear::MetricEvaluation previousMetric = acceptedMetric;
const Clock::time_point geometryPreflightStart = Clock::now();
timings.geometryPreflight = state.EstimateLargestSafeStepSize(
nextLineSearchStepLength, options.backtracking.fractionToBoundarySafety
);
timings.geometryPreflightSeconds =
std::chrono::duration<double>(Clock::now() - geometryPreflightStart).count();
diagnostics.totalGeometryPreflightSeconds += timings.geometryPreflightSeconds;
diagnostics.lastGeometryPreflight = timings.geometryPreflight;
if (timings.geometryPreflight->limitedByGeometry) {
++diagnostics.geometryLimitedIterations;
}
if (timings.geometryPreflight->stepSize < options.backtracking.minimumStepLength) {
diagnostics.finalResidualNorm = acceptedMetric.residualNorm;
NotifyAfter(
iteration, nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
diagnostics.initialResidualNorm, previousMetric, acceptedMetric, linearReport, timings, state
if (options.backtracking.geometryPreflight) {
const Clock::time_point geometryPreflightStart = Clock::now();
timings.geometryPreflight = state.EstimateLargestSafeStepSize(
nextLineSearchStepLength, options.backtracking.fractionToBoundarySafety
);
return Failure(
state, std::move(diagnostics), StellarEquilibriumFailureReason::globalization_failure,
"The geometry preflight found no orientation-preserving Newton step at or above the "
"configured minimum step length."
);
}
timings.geometryPreflightSeconds =
std::chrono::duration<double>(Clock::now() - geometryPreflightStart).count();
diagnostics.totalGeometryPreflightSeconds += timings.geometryPreflightSeconds;
diagnostics.lastGeometryPreflight = timings.geometryPreflight;
if (timings.geometryPreflight->limitedByGeometry) {
++diagnostics.geometryLimitedIterations;
}
if (timings.geometryPreflight->stepSize < options.backtracking.minimumStepLength) {
diagnostics.finalResidualNorm = acceptedMetric.residualNorm;
NotifyAfter(
iteration, nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
diagnostics.initialResidualNorm, previousMetric, acceptedMetric, linearReport, timings, state
);
return Failure(
state, std::move(diagnostics), StellarEquilibriumFailureReason::globalization_failure,
"The geometry preflight found no orientation-preserving Newton step at or above the "
"configured minimum step length."
);
}
}
bool accepted = false;
double acceptedStepLength = 0.0;
int lineSearchTrials = 0;
@@ -1303,7 +1310,8 @@ export namespace mean_field::solver {
StellarEquilibriumFailureReason rejectionReason =
StellarEquilibriumFailureReason::globalization_failure;
std::string rejectionMessage = "The backtracking line search found no acceptable Newton step.";
double stepLength = timings.geometryPreflight->stepSize;
double stepLength = timings.geometryPreflight ?
timings.geometryPreflight->stepSize : nextLineSearchStepLength;
const Clock::time_point lineSearchStart = Clock::now();
try {