perf(allocations): reduced overall allocations by 95%, increaseed jacobian applicatin by 2x

This commit uses global pre allocated work space to dramatically reduce memory usage and allocation time
This commit is contained in:
2026-09-10 06:50:56 -04:00
parent b3c04d507a
commit 75cc638739
66 changed files with 207183 additions and 99552 deletions

View File

@@ -0,0 +1,82 @@
module;
#include <cstdint>
#include <limits>
#include <span>
#include <mfem.hpp>
export module mean_field:deformation.safe_newton_step;
export import :mapping.domain_mapper;
export namespace mean_field::deformation {
/*
* One element-local quadrature rule at which a downstream operator will
* evaluate the mapped geometry. A caller may provide several entries for
* one element when several operators use different, non-nested rules.
* The integration-rule object must outlive the call.
*/
struct NewtonStepGeometryRule final {
int element{-1};
const mfem::IntegrationRule *integrationRule{nullptr};
};
struct LargestSafeNewtonStepSizeOptions final {
double maximumStepSize{1.0};
double determinantFloor{0.0};
double fractionToBoundarySafety{0.9};
void Validate() const;
};
/*
* The estimated boundary is the first alpha in [0, maximumStepSize] at
* which any sampled mapping determinant reaches determinantFloor. When
* no such point exists, boundaryStepSize equals maximumStepSize and
* limitedByGeometry is false. stepSize is the boundary multiplied by the
* safety fraction only when geometry is limiting.
*
* Element and rule indices are local to limitingRank. limitingRule is an
* index into that rank's input span.
*/
struct LargestSafeNewtonStepSizeEstimate final {
double stepSize{0.0};
double boundaryStepSize{0.0};
double minimumDeterminantAtAcceptedState{std::numeric_limits<double>::quiet_NaN()};
double minimumDeterminantAtMaximumStepSize{std::numeric_limits<double>::quiet_NaN()};
double limitingPointDeterminantAtStepSize{std::numeric_limits<double>::quiet_NaN()};
std::uint64_t sampledQuadraturePointCount{0};
bool limitedByGeometry{false};
int limitingRank{-1};
int limitingElement{-1};
int limitingRule{-1};
int limitingQuadraturePoint{-1};
};
/*
* Estimate the largest safe alpha for
*
* displacement(alpha) = acceptedVolumeDisplacement
* + alpha * volumeNewtonDirection.
*
* Both vectors use the displacement space's true-DOF layout. The
* compactification coordinate is held fixed. The result is collective on
* displacementSpace.GetComm() and is identical on every rank.
*
* The calculation is exact for the current domain mapper: its mapping
* Jacobian is affine along a displacement direction, so each sampled
* determinant is a polynomial of degree at most the spatial dimension.
* Compactified elements require an exterior map that explicitly advertises
* the same affine contract.
*/
[[nodiscard]] LargestSafeNewtonStepSizeEstimate estimate_largest_safe_newton_step_size(
const mapping::DomainMapper &domainMapper,
const mfem::ParFiniteElementSpace &displacementSpace,
const mfem::ParGridFunction &compactificationCoordinate,
const mfem::Vector &acceptedVolumeDisplacement,
const mfem::Vector &volumeNewtonDirection,
std::span<const NewtonStepGeometryRule> geometryRules,
const LargestSafeNewtonStepSizeOptions &options = {}
);
} // namespace mean_field::deformation

View File

@@ -14,6 +14,7 @@ export import :utils.misc;
export import :utils.user;
export import :quadrature.mfem;
export import :field.mfem;
export import :fem.reference_tables;
export namespace mean_field::fem {
using GravityField = field::Field<field::Gravity>;
@@ -145,6 +146,13 @@ export namespace mean_field::fem {
[[nodiscard]] bool has_mapping() const {
return domainMapperStateless != nullptr && displacement != nullptr && compactificationCoordinate != nullptr;
}
[[nodiscard]] const ReferenceTableCache &GetReferenceTables() const {
return *m_reference_tables;
}
private:
std::unique_ptr<ReferenceTableCache> m_reference_tables{std::make_unique<ReferenceTableCache>()};
};
FEM setup_fem(

View File

@@ -0,0 +1,78 @@
module;
#include <memory>
#include <vector>
#include <mfem.hpp>
export module mean_field:fem.reference_tables;
export namespace mean_field::fem {
// These tables contain only reference-element data: no mesh coordinates,
// orientation, element DOF transforms, or state-dependent mapping factors.
class ScalarReferenceTable {
public:
[[nodiscard]] const mfem::DenseMatrix &GetValues() const;
// Available for GRAD elements; otherwise throws std::out_of_range.
[[nodiscard]] const mfem::DenseMatrix &GetGradients(int point) const;
[[nodiscard]] int GetPointCount() const;
[[nodiscard]] int GetDofCount() const;
[[nodiscard]] int GetDimension() const;
private:
friend class ReferenceTableCache;
ScalarReferenceTable(
const mfem::FiniteElement &element,
const mfem::IntegrationRule &rule
);
mfem::DenseMatrix m_values;
std::vector<mfem::DenseMatrix> m_gradients;
int m_dimension;
};
class VectorReferenceTable {
public:
[[nodiscard]] const mfem::DenseMatrix &GetValues(int point) const;
[[nodiscard]] int GetPointCount() const;
[[nodiscard]] int GetDofCount() const;
[[nodiscard]] int GetDimension() const;
private:
friend class ReferenceTableCache;
VectorReferenceTable(
const mfem::FiniteElement &element,
const mfem::IntegrationRule &rule
);
std::vector<mfem::DenseMatrix> m_values;
int m_dof_count;
int m_dimension;
};
// Owned by one discretization, never process-global. Finite elements must
// remain alive and immutable while this cache is used; rebuild the cache if
// their collections are replaced. Rules are keyed by their actual points
// and weights, so temporary, copied, or modified rules are safe to use.
// Returned immutable handles also keep tables alive after cache destruction.
class ReferenceTableCache {
public:
ReferenceTableCache();
~ReferenceTableCache();
ReferenceTableCache(const ReferenceTableCache &) = delete;
ReferenceTableCache &operator=(const ReferenceTableCache &) = delete;
[[nodiscard]] std::shared_ptr<const ScalarReferenceTable> GetScalarTable(
const mfem::FiniteElement &element,
const mfem::IntegrationRule &rule
) const;
[[nodiscard]] std::shared_ptr<const VectorReferenceTable> GetVectorTable(
const mfem::FiniteElement &element,
const mfem::IntegrationRule &rule
) const;
private:
struct Storage;
std::unique_ptr<Storage> m_storage;
};
} // namespace mean_field::fem

View File

@@ -31,6 +31,16 @@ export namespace mean_field::mapping::compactification {
public:
virtual ~ExteriorDomainMap() = default;
/*
* Return true when, with the reference and compactification data held
* fixed, both outputs of Evaluate are affine functions of the
* displaced position and displacement Jacobian. This is the contract
* required by the exact determinant-polynomial geometry preflight.
*/
[[nodiscard]] virtual bool IsAffineInDisplacement() const noexcept {
return false;
}
[[nodiscard]] virtual MappingStatus Evaluate(
const ExteriorMapInput &input,
ExteriorMapResult &result

View File

@@ -12,6 +12,10 @@ export namespace mean_field::mapping::compactification {
public:
explicit KelvinCompactification(options::KelvinCompactificationOptions options);
[[nodiscard]] bool IsAffineInDisplacement() const noexcept override {
return true;
}
[[nodiscard]] MappingStatus Evaluate(
const ExteriorMapInput &input,
ExteriorMapResult &result
@@ -45,4 +49,4 @@ export namespace mean_field::mapping::compactification {
options::KelvinCompactificationOptions m_options;
};
} // namespace mean_field::mapping::compactification
} // namespace mean_field::mapping::compactification

View File

@@ -80,6 +80,7 @@ export namespace mean_field::mapping {
mfem::Vector m_shape;
mfem::DenseMatrix m_reference_dshape;
mfem::DenseMatrix m_mesh_dshape;
mfem::DenseMatrix m_reference_field_jacobian;
mfem::Vector m_field_value;
mfem::DenseMatrix m_field_jacobian;

View File

@@ -0,0 +1,42 @@
module;
#include <mfem.hpp>
#include <vector>
export module mean_field:mapping.prepared_cache;
import :mapping.types;
export namespace mean_field::mapping {
// Flat, owning storage for prepared volume contexts. Load into reusable
// workspaces: no MFEM buffers or pointer aliases are owned per quadrature
// point. The layout retains every public context field without recomputing
// inverses or changing the mapper's numerical contract.
class VolumeMappingCache {
public:
void SetSize(
int point_count,
int dimension
);
void Store(
int point,
const VolumeMappingContext &context
);
void Load(
int point,
VolumeMappingContext &context
) const;
void LoadInverseJacobian(
int point,
mfem::DenseMatrix &inverse
) const;
[[nodiscard]] int GetPointCount() const;
[[nodiscard]] int GetDimension() const;
private:
[[nodiscard]] const double *GetPointData(int point) const;
std::vector<double> m_data;
int m_point_count{0};
int m_dimension{0};
int m_point_stride{0};
};
} // namespace mean_field::mapping

View File

@@ -14,6 +14,7 @@ export import :mapping.compactification;
export import :mapping.kelvin;
export import :mapping.transformations;
export import :mapping.types;
export import :mapping.prepared_cache;
export import :mapping.compactification.options;
export import :integrators.advection;
export import :integrators.centrifugal;
@@ -86,6 +87,7 @@ export import :deformation.interior_extension;
export import :deformation.vacuum_extension;
export import :deformation.radial_extensions;
export import :deformation.domain_deformation;
export import :deformation.safe_newton_step;
export import :model.stellar;
export import :operators.root_manifest;
export import :operators.prepared_constraint;

View File

@@ -4,6 +4,7 @@ module;
#include <cstdint>
#include <expected>
#include <limits>
#include <memory>
#include <optional>
#include <stdexcept>
#include <vector>
@@ -14,6 +15,7 @@ export module mean_field:operators.prepared_angular_momentum;
export import :fem;
export import :mapping.domain_mapper;
export import :mapping.prepared_cache;
export import :model.compiled_fixed_angular_momentum;
export import :operators.context.gravity_field;
@@ -188,15 +190,13 @@ export namespace mean_field::operators {
[[nodiscard]] const PreparedAngularMomentumActionStatistics &GetActionStatistics() const noexcept;
[[nodiscard]] const models::CompiledFixedAngularMomentum &GetCompiledConstraint() const noexcept;
private:
struct QuadraturePointData final {
mfem::IntegrationPoint integrationPoint;
mfem::Vector densityShape;
mapping::VolumeMappingContext mappingContext;
double density{0.0};
double cylindricalRadiusSquared{0.0};
};
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
struct ElementPAData final {
int elementId{-1};
mfem::Array<int> densityDofs;
@@ -205,9 +205,14 @@ export namespace mean_field::operators {
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DofTransformation *compactificationDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::Vector baseDisplacement;
mfem::Vector compactification;
std::vector<QuadraturePointData> quadraturePoints;
std::shared_ptr<const fem::ScalarReferenceTable> densityBasis;
mapping::VolumeMappingCache mappingContexts;
mfem::Vector density;
mfem::Vector quadratureWeights;
mfem::Vector cylindricalRadiusSquared;
};
void BuildStaticPlan();

View File

@@ -2,6 +2,7 @@ module;
#include <cstdint>
#include <expected>
#include <memory>
#include <mfem.hpp>
#include <vector>
@@ -97,6 +98,12 @@ export namespace mean_field::operators {
[[nodiscard]] const context::barotropic::BarotropicClosurePreparationStatistics &
GetContextPreparationStatistics() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
struct ConstructionData;
@@ -132,8 +139,11 @@ export namespace mean_field::operators {
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DenseMatrix densityBasis;
mfem::DenseMatrix enthalpyBasis;
const mfem::IntegrationRule *integrationRule{nullptr};
std::shared_ptr<const fem::ScalarReferenceTable> densityBasis;
std::shared_ptr<const fem::ScalarReferenceTable> enthalpyBasis;
std::shared_ptr<const fem::ScalarReferenceTable> displacementBasis;
mfem::DenseMatrix inverseElementJacobians;
mfem::Vector weightedResidual;
@@ -169,7 +179,6 @@ export namespace mean_field::operators {
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_quadratureDisplacementAction;
mutable mfem::Vector m_elementDisplacementAction;
mutable mfem::DenseMatrix m_referenceDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
std::uint64_t m_preparationCount{0};

View File

@@ -3,6 +3,7 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <memory>
#include <vector>
#include <mfem.hpp>
@@ -14,6 +15,7 @@ export import :mapping.domain_mapper;
export import :operators.context.gravity_field;
export import :operators.kernels.gravity_displacement_force;
export import :utils.blocks;
import :fem.reference_tables;
export namespace mean_field::operators {
struct PreparedGravityDisplacementForceReport final {
@@ -111,6 +113,12 @@ export namespace mean_field::operators {
[[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext &
GetGravityContext() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
void VerifyPrepared() const;
[[nodiscard]] std::expected<
@@ -133,6 +141,10 @@ export namespace mean_field::operators {
mfem::DofTransformation *gravityGradientDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
std::shared_ptr<const fem::ScalarReferenceTable> densityReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> displacementReferenceTable;
std::shared_ptr<const fem::VectorReferenceTable> gravityReferenceTable;
mfem::DenseMatrix meshPiolaJacobians;
mfem::DenseMatrix mappingJacobians;
mfem::DenseMatrix inverseMeshJacobians;
mfem::DenseMatrix baseGravityReferenceValues;
@@ -164,16 +176,17 @@ export namespace mean_field::operators {
mutable mfem::Vector m_displacementShape;
mutable mfem::Vector m_baseGravityReferenceValue;
mutable mfem::Vector m_gravityVariationReferenceValue;
mutable mfem::Vector m_gravityVariationReferenceCellValue;
mutable mfem::Vector m_mappedBaseGravity;
mutable mfem::Vector m_mappedGravityVariation;
mutable mfem::Vector m_mappedGeometryVariation;
mutable mfem::Vector m_forceValue;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_referenceDisplacementDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
mutable mfem::DenseMatrix m_displacementJacobianVariation;
mutable mfem::DenseMatrix m_mappingJacobian;
mutable mfem::DenseMatrix m_inverseMeshJacobian;
mutable mfem::DenseMatrix m_meshPiolaJacobian;
std::uint64_t m_residualPreparationCount{0};
mutable std::uint64_t m_residualApplicationCount{0};

View File

@@ -58,6 +58,12 @@ export namespace mean_field::operators {
[[nodiscard]] const field::FieldDofMap &GetPotentialMap() const noexcept;
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.element_id, *data.integration_rule);
}
}
void MultTranspose(
const mfem::Vector &potential,
mfem::Vector &action
@@ -80,6 +86,10 @@ export namespace mean_field::operators {
const mfem::IntegrationRule *integration_rule{nullptr};
// Rows are quadrature points; columns are element DOFs.
std::shared_ptr<const fem::ScalarReferenceTable> density_reference;
std::shared_ptr<const fem::ScalarReferenceTable> potential_reference;
std::shared_ptr<const fem::ScalarReferenceTable> displacement_reference;
// Non-VALUE map types retain their element-dependent physical basis.
mfem::DenseMatrix density_basis;
mfem::DenseMatrix potential_basis;
mfem::DenseMatrix inverse_element_jacobians;
@@ -87,6 +97,14 @@ export namespace mean_field::operators {
// Contains quadrature weight, mesh Jacobian, mapped Jacobian,
// and 4*pi*G.
mfem::Vector quadrature_data;
[[nodiscard]] const mfem::DenseMatrix &GetDensityBasis() const {
return density_reference ? density_reference->GetValues() : density_basis;
}
[[nodiscard]] const mfem::DenseMatrix &GetPotentialBasis() const {
return potential_reference ? potential_reference->GetValues() : potential_basis;
}
};
[[nodiscard]] GravitySourcePreparationResult TryPrepareImpl(
@@ -119,7 +137,6 @@ 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;
mutable mfem::DenseMatrix m_reference_displacement_dshape;
mutable mfem::DenseMatrix m_reference_displacement_jacobian;
mfem::Vector m_displacement_true;

View File

@@ -10,6 +10,7 @@ export module mean_field:operators.prepared_hdiv_mass;
export import :fem;
export import :field.mfem;
export import :mapping.domain_mapper;
import :fem.reference_tables;
export namespace mean_field::operators {
enum class HDivMassPreparationRejectionReason : std::uint8_t { invalid_mapping, non_finite_arithmetic };
@@ -58,6 +59,12 @@ export namespace mean_field::operators {
[[nodiscard]] const field::FieldDofMap &GetFluxMap() const noexcept;
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementVariationData &data : m_variationElements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
enum class PreparationMode : std::uint8_t { primal, linearization };
@@ -71,6 +78,10 @@ export namespace mean_field::operators {
mfem::Vector baseDisplacement;
mfem::Vector compactification;
const mfem::IntegrationRule *integrationRule{nullptr};
std::shared_ptr<const fem::VectorReferenceTable> gravityReferenceTable;
// Fixed computational-mesh Piola factor, separate from J_map.
mfem::DenseMatrix meshPiolaJacobians;
mfem::Vector referenceWeights;
mfem::DenseMatrix frozenMappingData;
};
@@ -112,7 +123,10 @@ export namespace mean_field::operators {
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementVariationAction;
mutable mfem::Vector m_gravityGradientValue;
mutable mfem::Vector m_gravityReferenceCellValue;
mutable mfem::Vector m_referenceCellDual;
mutable mfem::Vector m_massTensorVariationAction;
mutable mfem::DenseMatrix m_meshPiolaJacobian;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_massTensorVariation;
std::uint64_t m_preparation_count{0};

View File

@@ -4,6 +4,7 @@ module;
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <stdexcept>
#include <vector>
@@ -13,7 +14,9 @@ module;
export module mean_field:operators.prepared_hydrostatic_equilibrium;
export import :fem;
export import :fem.reference_tables;
export import :mapping.domain_mapper;
export import :mapping.prepared_cache;
export import :operators.context.hydrostatic_equilibrium;
export import :physics.rigid_rotation;
@@ -216,6 +219,12 @@ export namespace mean_field::operators {
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
struct ElementPAData {
int elementId{-1};
@@ -232,15 +241,23 @@ export namespace mean_field::operators {
const mfem::IntegrationRule *integrationRule{nullptr};
// Rows are quadrature points and columns are element DOFs.
mfem::DenseMatrix enthalpyBasis;
mfem::DenseMatrix gravityPotentialBasis;
// Immutable reference values and gradients are shared by FE/rule.
std::shared_ptr<const fem::ScalarReferenceTable> enthalpyReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> gravityPotentialReferenceTable;
[[nodiscard]] const mfem::DenseMatrix &GetEnthalpyBasis() const {
return enthalpyReferenceTable->GetValues();
}
[[nodiscard]] const mfem::DenseMatrix &GetGravityPotentialBasis() const {
return gravityPotentialReferenceTable->GetValues();
}
// Rows are quadrature points and columns are physical components.
mfem::DenseMatrix physicalPositions;
mfem::Vector quadratureWeights;
std::vector<mapping::VolumeMappingContext> baseMappingContexts;
mapping::VolumeMappingCache baseMappingContexts;
std::optional<mapping::ElementDisplacementData> baseDisplacementData;

View File

@@ -3,6 +3,7 @@ module;
#include <compare>
#include <cstdint>
#include <expected>
#include <memory>
#include <mfem.hpp>
#include <optional>
#include <vector>
@@ -11,6 +12,7 @@ export module mean_field:operators.prepared_mass_normalization;
export import :fem;
export import :mapping.domain_mapper;
export import :mapping.prepared_cache;
export import :model.compiled_fixed_mass;
export import :operators.context.gravity_field;
export import :operators.prepared_constraint;
@@ -187,14 +189,13 @@ export namespace mean_field::operators {
[[nodiscard]] const context::gravity_field::GravityFieldLinearizationContext &
GetGravityContext() const noexcept;
private:
struct QuadraturePointData final {
mfem::IntegrationPoint integrationPoint;
mfem::Vector densityShape;
mapping::VolumeMappingContext mappingContext;
double density{0.0};
};
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
struct ElementPAData final {
int elementId{-1};
@@ -206,10 +207,15 @@ export namespace mean_field::operators {
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DofTransformation *compactificationDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::Vector baseDisplacement;
mfem::Vector compactification;
std::vector<QuadraturePointData> quadraturePoints;
std::shared_ptr<const fem::ScalarReferenceTable> densityBasis;
mapping::VolumeMappingCache mappingContexts;
mfem::Vector density;
mfem::Vector quadratureWeights;
};
void BuildStaticPlan();

View File

@@ -4,6 +4,7 @@ module;
#include <cstddef>
#include <cstdint>
#include <expected>
#include <memory>
#include <optional>
#include <vector>
@@ -13,8 +14,10 @@ export module mean_field:operators.prepared_pressure_force;
export import :eos.polytrope;
export import :fem;
export import :fem.reference_tables;
export import :field.mfem;
export import :mapping.domain_mapper;
export import :mapping.prepared_cache;
export import :operators.context.pressure_force;
export import :utils.blocks;
@@ -168,6 +171,12 @@ export namespace mean_field::operators {
[[nodiscard]]
const fem::FEM &GetFEM() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
struct ConstructionData;
@@ -196,10 +205,13 @@ export namespace mean_field::operators {
const mfem::IntegrationRule *integrationRule{nullptr};
/*
* Rows are quadrature points and columns are enthalpy DOFs.
*/
mfem::DenseMatrix enthalpyBasis;
// Immutable reference values and gradients are shared by FE/rule.
std::shared_ptr<const fem::ScalarReferenceTable> enthalpyReferenceTable;
std::shared_ptr<const fem::ScalarReferenceTable> displacementReferenceTable;
[[nodiscard]] const mfem::DenseMatrix &GetEnthalpyBasis() const {
return enthalpyReferenceTable->GetValues();
}
/*
* Each entry is:
@@ -208,11 +220,9 @@ export namespace mean_field::operators {
* x
* physical dimension.
*/
std::vector<mfem::DenseMatrix> referenceTestGradients;
std::vector<mfem::DenseMatrix> physicalTestGradients;
std::vector<mapping::VolumeMappingContext> baseMappingContexts;
mapping::VolumeMappingCache baseMappingContexts;
std::optional<mapping::ElementDisplacementData> baseDisplacementData;

View File

@@ -113,6 +113,12 @@ export namespace mean_field::operators {
[[nodiscard]] const context::rotational_displacement_force::RotationalDisplacementForceLinearizationContext &
GetContext() const noexcept;
template <typename Visitor> void VisitMappedGeometryRules(Visitor &&visitor) const {
for (const ElementPAData &data : m_elements) {
visitor(data.elementId, *data.integrationRule);
}
}
private:
void VerifyPrepared() const;
[[nodiscard]] std::expected<

View File

@@ -269,6 +269,10 @@ export namespace mean_field::operators {
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
[[nodiscard]] const mfem::Vector &GetGeneratedVolumeDisplacement() const;
void BuildVolumeDisplacementDirection(
const mfem::Vector &surfaceDeformationDirection,
mfem::Vector &volumeDisplacementDirection
) const;
[[nodiscard]] const mfem::Vector &GetFullMechanicalResidual() const;
[[nodiscard]] const StellarEquilibriumDependencyStamp &GetGeneratedDisplacementDependency() const;

View File

@@ -2785,6 +2785,22 @@ export namespace mean_field::operators {
return *m_physical;
}
void BuildVolumeDisplacementDirection(
const mfem::Vector &stateDirection,
mfem::Vector &volumeDisplacementDirection
) const {
if (stateDirection.Size() != Width()) {
throw std::invalid_argument(
"The prepared stellar-equilibrium root received a state direction with the wrong size."
);
}
const auto rootDirection = m_manifest.directionView(stateDirection);
m_physical->BuildVolumeDisplacementDirection(
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term),
volumeDisplacementDirection
);
}
template <models::ModelSpecification Specification>
requires ModelType::template
containsSpecification<Specification> [[nodiscard]] const auto &GetPreparedContribution() const noexcept {

View File

@@ -298,6 +298,13 @@ export namespace mean_field::equilibrium {
m_preparedOperator.Mult(direction, action);
}
void BuildVolumeDisplacementDirection(
const mfem::Vector &stateDirection,
mfem::Vector &volumeDisplacementDirection
) const {
m_preparedOperator.BuildVolumeDisplacementDirection(stateDirection, volumeDisplacementDirection);
}
private:
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<

View File

@@ -381,7 +381,7 @@ export namespace mean_field::solver {
export namespace mean_field::solver::linear {
struct FGMRESOptions final {
int restartLength{50};
int printLevel{-1};
int printLevel{1};
void Validate() const {
if (restartLength <= 0) {

View File

@@ -17,6 +17,7 @@ module;
export module mean_field:solver.newton;
export import :deformation.safe_newton_step;
export import :solver.linear_backend;
export namespace mean_field::solver::nonlinear {
@@ -264,11 +265,13 @@ export namespace mean_field::solver::nonlinear {
double relativeResidualNorm{0.0};
double merit{0.0};
double iterationSeconds{0.0};
double geometryPreflightSeconds{0.0};
double lineSearchSeconds{0.0};
double trialPreparationSeconds{0.0};
double metricEvaluationSeconds{0.0};
double preconditionerRefreshSeconds{0.0};
double rollbackSeconds{0.0};
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> geometryPreflight{};
std::optional<LinearSolveReport> linearSolve{};
MPI_Comm communicator{MPI_COMM_NULL};
std::span<const mfem::real_t> physicalState{};

View File

@@ -1,5 +1,6 @@
module;
#include <algorithm>
#include <array>
#include <chrono>
#include <cmath>
@@ -8,6 +9,7 @@ module;
#include <cstdint>
#include <exception>
#include <expected>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
@@ -17,6 +19,7 @@ module;
#include <string_view>
#include <type_traits>
#include <utility>
#include <vector>
#include <mfem.hpp>
#include <mpi.h>
@@ -37,6 +40,11 @@ export namespace mean_field::solver {
template <typename Context, typename NewtonConfiguration, typename Observer> class StellarEquilibriumSolver;
} // namespace mean_field::solver
export namespace mean_field::solver::detail {
// Internal, synchronous experiment access; not a stable solver API.
struct StellarEquilibriumContextDiagnostics;
}
namespace mean_field::solver::detail {
template <typename Model, typename Discretization>
using StellarContextProblem =
@@ -256,6 +264,7 @@ export namespace mean_field::solver {
private:
template <typename, typename, typename> friend class StellarEquilibriumSolver;
friend struct detail::StellarEquilibriumContextAssembly;
friend struct detail::StellarEquilibriumContextDiagnostics;
using NormalizedOperatorType = detail::StellarContextNormalizedOperator<ProblemType>;
using PhysicalInverseType = detail::StellarContextPhysicalInverse<PreconditionerPrescriptionType, ProblemType>;
@@ -323,11 +332,16 @@ export namespace mean_field::solver {
trialNormalizedResidual(RequireProblem(storage).EquationSize()),
linearRightHandSide(RequireProblem(storage).EquationSize()),
normalizedCorrection(RequireProblem(storage).StateSize()),
physicalCorrection(RequireProblem(storage).StateSize()),
volumeDisplacementDirection(
RequireProblem(storage).GetPhysicalOperator().GetDomainDeformation().volumeDisplacementSize()
),
candidatePhysicalState(RequireProblem(storage).StateSize()),
normalizedOperator(std::make_unique<NormalizedOperatorType>(RequireProblem(storage))) {
ValidateInitialState();
InitializeWorkspaces();
PrepareInitialOperator();
InitializeGeometryPreflightRules();
physicalInverse = std::unique_ptr<PhysicalInverseType>{new PhysicalInverseType(
PreparePhysicalInverse(std::move(preconditionerPrescription), RequireProblem(storage))
@@ -375,6 +389,9 @@ export namespace mean_field::solver {
trialNormalizedResidual.Size() == problem->EquationSize() &&
linearRightHandSide.Size() == problem->EquationSize() &&
normalizedCorrection.Size() == problem->StateSize() &&
physicalCorrection.Size() == problem->StateSize() &&
volumeDisplacementDirection.Size() ==
problem->GetPhysicalOperator().GetDomainDeformation().volumeDisplacementSize() &&
candidatePhysicalState.Size() == problem->StateSize() &&
storage->physicalState->Size() == problem->StateSize();
}
@@ -464,6 +481,26 @@ export namespace mean_field::solver {
}
}
[[nodiscard]] deformation::LargestSafeNewtonStepSizeEstimate EstimateLargestSafeStepSize(
const double maximumStepSize,
const double fractionToBoundarySafety
) {
normalizedOperator->DenormalizeState(normalizedCorrection, physicalCorrection);
const auto &physicalOperator = Problem().GetPhysicalOperator();
Problem().BuildVolumeDisplacementDirection(physicalCorrection, volumeDisplacementDirection);
const fem::FEM &finiteElements =
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(Problem());
return deformation::estimate_largest_safe_newton_step_size(
Problem().GetDiscretization().domainMapper(), *finiteElements.displacementFes,
*finiteElements.compactificationCoordinate, physicalOperator.GetGeneratedVolumeDisplacement(),
volumeDisplacementDirection, geometryPreflightRules,
{.maximumStepSize = maximumStepSize,
.determinantFloor = 0.0,
.fractionToBoundarySafety = fractionToBoundarySafety}
);
}
std::shared_ptr<Storage> storage;
DependencyLedger dependencyLedger;
mfem::Vector acceptedNormalizedState;
@@ -472,7 +509,10 @@ export namespace mean_field::solver {
mfem::Vector trialNormalizedResidual;
mfem::Vector linearRightHandSide;
mfem::Vector normalizedCorrection;
mfem::Vector physicalCorrection;
mfem::Vector volumeDisplacementDirection;
mfem::Vector candidatePhysicalState;
std::vector<deformation::NewtonStepGeometryRule> geometryPreflightRules;
double acceptedMinimumJacobianDeterminant{std::numeric_limits<double>::quiet_NaN()};
std::unique_ptr<NormalizedOperatorType> normalizedOperator;
std::unique_ptr<PhysicalInverseType> physicalInverse;
@@ -565,12 +605,14 @@ export namespace mean_field::solver {
void InitializeWorkspaces() {
normalizedOperator->NormalizeState(AcceptedPhysicalState(), acceptedNormalizedState);
trialNormalizedState = acceptedNormalizedState;
acceptedNormalizedResidual = 0.0;
trialNormalizedResidual = 0.0;
linearRightHandSide = 0.0;
normalizedCorrection = 0.0;
candidatePhysicalState = AcceptedPhysicalState();
trialNormalizedState = acceptedNormalizedState;
acceptedNormalizedResidual = 0.0;
trialNormalizedResidual = 0.0;
linearRightHandSide = 0.0;
normalizedCorrection = 0.0;
physicalCorrection = 0.0;
volumeDisplacementDirection = 0.0;
candidatePhysicalState = AcceptedPhysicalState();
}
void PrepareInitialOperator() {
@@ -582,6 +624,97 @@ export namespace mean_field::solver {
trialNormalizedResidual = acceptedNormalizedResidual;
}
void AppendGeometryPreflightRule(
const int element,
const mfem::IntegrationRule &integrationRule
) {
geometryPreflightRules.push_back({.element = element, .integrationRule = &integrationRule});
}
void InitializeGeometryPreflightRules() {
const ProblemType &problem = Problem();
const fem::FEM &finiteElements =
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(problem);
if (finiteElements.mesh == nullptr || finiteElements.displacementFes == nullptr ||
finiteElements.compactificationCoordinate == nullptr) {
throw std::logic_error(
"The Newton geometry preflight requires complete displacement geometry data."
);
}
if (!problem.GetPhysicalOperator().GetDomainDeformation().descriptor().linearOnReferenceGeometry) {
throw std::invalid_argument(
"The Newton geometry preflight requires a domain deformation that is linear on the "
"reference geometry."
);
}
geometryPreflightRules.clear();
geometryPreflightRules.reserve(
static_cast<std::size_t>(finiteElements.mesh->GetNE()) * static_cast<std::size_t>(10)
);
const int dimension = problem.GetDiscretization().domainMapper().GetDimension();
for (int element = 0; element < finiteElements.mesh->GetNE(); ++element) {
const mfem::FiniteElement *finiteElement = finiteElements.displacementFes->GetFE(element);
mfem::ElementTransformation *transformation =
finiteElements.mesh->GetElementTransformation(element);
if (finiteElement == nullptr || transformation == nullptr) {
throw std::logic_error(
"The Newton geometry preflight encountered incomplete element geometry data."
);
}
const int geometryInspectionOrder =
std::max(finiteElement->GetOrder() + 2, 2 * dimension * finiteElement->GetOrder());
AppendGeometryPreflightRule(
element, mfem::IntRules.Get(transformation->GetGeometryType(), geometryInspectionOrder)
);
}
const auto appendPreparedRules = [this](const auto &preparedOperator) {
preparedOperator.VisitMappedGeometryRules(
[this](const int element, const mfem::IntegrationRule &integrationRule) {
AppendGeometryPreflightRule(element, integrationRule);
}
);
};
const auto &physicalOperator = problem.GetPhysicalOperator();
const auto &gravityGeometry = physicalOperator.GetGravityContext().GetGeometryContext();
appendPreparedRules(gravityGeometry.GetMassOperator());
appendPreparedRules(gravityGeometry.GetSourceOperator());
appendPreparedRules(physicalOperator.GetBarotropicClosureOperator());
appendPreparedRules(physicalOperator.GetHydrostaticOperator());
const auto &displacementOperator = physicalOperator.GetDisplacementOperator();
appendPreparedRules(displacementOperator.GetPressureOperator());
appendPreparedRules(displacementOperator.GetGravityOperator());
appendPreparedRules(displacementOperator.GetRotationalOperator());
appendPreparedRules(physicalOperator.GetMassNormalizationOperator());
if constexpr (ProblemType::hasFixedAngularMomentum) {
appendPreparedRules(problem.GetPreparedOperator().GetAngularMomentumConstraint());
}
const auto ruleLess = [](const deformation::NewtonStepGeometryRule &left,
const deformation::NewtonStepGeometryRule &right) {
if (left.element != right.element) {
return left.element < right.element;
}
return std::less<const mfem::IntegrationRule *>{}(left.integrationRule, right.integrationRule);
};
std::sort(geometryPreflightRules.begin(), geometryPreflightRules.end(), ruleLess);
geometryPreflightRules.erase(
std::unique(
geometryPreflightRules.begin(), geometryPreflightRules.end(),
[](const deformation::NewtonStepGeometryRule &left,
const deformation::NewtonStepGeometryRule &right) {
return left.element == right.element && left.integrationRule == right.integrationRule;
}
),
geometryPreflightRules.end()
);
}
[[nodiscard]] auto PrepareOperator(const mfem::Vector &normalizedState) {
if constexpr (ProblemType::generatedRotationProviderCount == 0) {
return normalizedOperator->Prepare(
@@ -695,6 +828,42 @@ export namespace mean_field::solver {
concept StellarEquilibriumContextType = IsStellarEquilibriumContext<std::remove_cvref_t<Candidate>>::value;
} // namespace mean_field::solver
export namespace mean_field::solver::detail {
struct StellarEquilibriumContextDiagnostics final {
// The callback must not retain references to runtime storage. It may
// prepare trial states, but accepted vectors must remain unchanged.
// Restore the production preparation and correction on every exit.
template <typename Context, typename Callback>
static void WithState(Context &context, Callback &&callback) {
if (context.hasActiveSolver() || !context.isReady()) {
throw std::logic_error("Diagnostics require a ready context with no active solver.");
}
auto &state = *context.m_state;
mfem::Vector savedCorrection(state.normalizedCorrection);
context.AcquireSolver();
try {
state.BeginEvaluation();
std::invoke(std::forward<Callback>(callback), state,
equilibrium::detail::StellarEquilibriumProblemFactory::FiniteElementModel(state.Problem()));
state.RestoreAccepted();
state.normalizedCorrection = savedCorrection;
context.ReleaseSolver();
} catch (...) {
const auto original = std::current_exception();
try {
state.RestoreAccepted();
state.normalizedCorrection = savedCorrection;
} catch (...) {
context.ReleaseSolver();
throw;
}
context.ReleaseSolver();
std::rethrow_exception(original);
}
}
};
}
namespace mean_field::solver::detail {
[[nodiscard]] inline physics::RigidRotation ZeroRigidRotation() {
mfem::Vector angularVelocity(3);
@@ -973,12 +1142,14 @@ export namespace mean_field::solver {
struct IterationTimings final {
Clock::time_point start{};
double geometryPreflightSeconds{0.0};
double lineSearchSeconds{0.0};
double trialPreparationSeconds{0.0};
double metricEvaluationSeconds{0.0};
double preconditionerRefreshSeconds{0.0};
double rollbackSeconds{0.0};
double observerSeconds{0.0};
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> geometryPreflight;
};
public:
@@ -1100,14 +1271,39 @@ export namespace mean_field::solver {
}
const nonlinear::MetricEvaluation previousMetric = acceptedMetric;
bool accepted = false;
double acceptedStepLength = 0.0;
int lineSearchTrials = 0;
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
);
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;
nonlinear::MetricEvaluation trialMetric{};
StellarEquilibriumFailureReason rejectionReason =
StellarEquilibriumFailureReason::globalization_failure;
std::string rejectionMessage = "The backtracking line search found no acceptable Newton step.";
double stepLength = nextLineSearchStepLength;
double stepLength = timings.geometryPreflight->stepSize;
const Clock::time_point lineSearchStart = Clock::now();
try {
@@ -1553,11 +1749,13 @@ export namespace mean_field::solver {
.relativeResidualNorm = RelativeResidual(metric.residualNorm, initialResidualNorm),
.merit = metric.merit,
.iterationSeconds = DurationExcludingObserver(timings.start, timings.observerSeconds),
.geometryPreflightSeconds = timings.geometryPreflightSeconds,
.lineSearchSeconds = timings.lineSearchSeconds,
.trialPreparationSeconds = timings.trialPreparationSeconds,
.metricEvaluationSeconds = timings.metricEvaluationSeconds,
.preconditionerRefreshSeconds = timings.preconditionerRefreshSeconds,
.rollbackSeconds = timings.rollbackSeconds,
.geometryPreflight = timings.geometryPreflight,
.linearSolve = linearReport,
.communicator = state.Problem().GetCommunicator(),
.physicalState = detail::ReadOnlySpan(state.AcceptedPhysicalState()),

View File

@@ -6,6 +6,7 @@ module;
export module mean_field:solver.stellar_equilibrium_types;
export import :deformation.safe_newton_step;
export import :solver.linear_backend;
export namespace mean_field::solver {
@@ -46,15 +47,18 @@ export namespace mean_field::solver {
int inadmissibleLineSearchTrials{0};
int nonFiniteLineSearchTrials{0};
int insufficientDecreaseTrials{0};
int geometryLimitedIterations{0};
double initialResidualNorm{0.0};
double finalResidualNorm{0.0};
double lastAcceptedStepLength{0.0};
double totalLinearSolveSeconds{0.0};
double totalGeometryPreflightSeconds{0.0};
double totalLineSearchSeconds{0.0};
double totalTrialPreparationSeconds{0.0};
double totalMetricEvaluationSeconds{0.0};
double totalPreconditionerRefreshSeconds{0.0};
double totalRollbackSeconds{0.0};
std::optional<deformation::LargestSafeNewtonStepSizeEstimate> lastGeometryPreflight;
std::optional<LinearSolveReport> lastLinearSolve;
};
} // namespace mean_field::solver