perf(jacobian-action): major updates to jacobian action application by removing redudant quadrature work. ~5x increase in speed

This commit is contained in:
2026-09-02 17:01:50 -04:00
parent 85500fef3b
commit 25510008dd
74 changed files with 8967 additions and 814 deletions

View File

@@ -84,15 +84,24 @@ export namespace mean_field::operators {
mfem::Vector &actionTrue
) const;
void ApplyDisplacementActionFull(
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> enthalpyDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *enthalpyDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DenseMatrix densityBasis;
mfem::DenseMatrix enthalpyBasis;
mfem::DenseMatrix inverseElementJacobians;
mfem::Vector weightedResidual;
mfem::Vector quadratureWeights;
@@ -122,6 +131,14 @@ export namespace mean_field::operators {
mutable mfem::Vector m_fullDisplacementAction;
mutable mfem::Vector m_fullResidual;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localDisplacementAction;
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};
bool m_isPrepared{false};
};

View File

@@ -0,0 +1,264 @@
module;
#include <algorithm>
#include <cmath>
#include <compare>
#include <cstdint>
#include <optional>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_central_density;
export import :field.mfem;
export import :model.compiled_fixed_central_density;
export namespace mean_field::operators {
struct CentralDensityDependencyStamp final {
std::uint64_t identity{0};
std::uint64_t revision{0};
constexpr auto operator<=>(const CentralDensityDependencyStamp &) const = default;
};
struct CentralDensityDependencies final {
CentralDensityDependencyStamp enthalpy;
constexpr auto operator<=>(const CentralDensityDependencies &) const = default;
};
struct PreparedCentralDensityReport final {
bool refreshedCentralEnthalpy{false};
bool refreshedBorder{false};
bool assembledResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return refreshedCentralEnthalpy || refreshedBorder || assembledResidual;
}
constexpr auto operator<=>(const PreparedCentralDensityReport &) const = default;
};
struct CentralDensityConstraintReport final {
double targetDensity;
double achievedDensity;
double targetEnthalpy;
double achievedEnthalpy;
double enthalpyResidual;
double scaledResidual;
};
struct CentralDensityJacobianInput final {
const mfem::Vector &enthalpyVariation;
double borderVariation;
};
struct CentralDensityJacobianOutput final {
mfem::Vector &enthalpyAction;
mfem::Vector &phaseAction;
};
struct CentralDensityJacobianTransposeInput final {
const mfem::Vector &enthalpyResidualDual;
double phaseResidualDual;
};
struct CentralDensityJacobianTransposeOutput final {
mfem::Vector &enthalpyDual;
mfem::Vector &borderDual;
};
/*
* Bordered central-density phase condition
*
* R_c(h) = h(0) - h(rho_c,target),
* R_h <- R_h + lambda_c e_c.
*
* The point functional e_c selects the unique scalar H1 vertex at the
* computational origin. Its coordinate transpose supplies the border
* column, so this contribution is algebraically symmetric before any
* independent scaling is applied by a solver.
*/
class PreparedCentralDensityConstraint final {
public:
PreparedCentralDensityConstraint(
field::FieldPointDofMap centerDof,
const MPI_Comm communicator
)
: m_centerDof(std::move(centerDof)),
m_communicator(communicator) {
}
PreparedCentralDensityReport Prepare(
const models::CompiledFixedCentralDensity &constraint,
const mfem::Vector &enthalpy,
const double border,
const CentralDensityDependencies &dependencies
) {
MFEM_VERIFY(
enthalpy.Size() == m_centerDof.field_size(),
"The central-density phase received an enthalpy vector with the wrong size."
);
MFEM_VERIFY(std::isfinite(border), "The central-density phase received a non-finite border value.");
const bool wasPrepared = m_isPrepared;
PreparedCentralDensityReport report;
if (!wasPrepared || dependencies.enthalpy != m_preparedDependencies.enthalpy) {
double localCentralEnthalpy = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
const double value = enthalpy(reducedDof);
MFEM_VERIFY(std::isfinite(value), "The central enthalpy is non-finite.");
localCentralEnthalpy += value;
}
m_centralEnthalpy = GlobalSum(localCentralEnthalpy);
report.refreshedCentralEnthalpy = true;
}
if (!wasPrepared || border != m_border) {
m_border = border;
report.refreshedBorder = true;
}
const bool targetChanged =
!m_constraint.has_value() || constraint.targetDensity() != m_constraint->targetDensity();
if (targetChanged) {
m_constraint = constraint;
}
if (report.refreshedCentralEnthalpy || report.refreshedBorder || targetChanged) {
m_cachedPhaseResidual = m_centralEnthalpy - m_constraint->targetEnthalpy().value();
report.assembledResidual = true;
}
m_preparedDependencies = dependencies;
m_isPrepared = true;
++m_preparationCount;
return report;
}
void AddResidual(
mfem::Vector &enthalpyResidual,
mfem::Vector &phaseResidual
) const {
VerifyPrepared();
VerifyOutputSizes(enthalpyResidual, phaseResidual);
for (const int reducedDof : m_centerDof.reduced_dofs()) {
enthalpyResidual(reducedDof) += m_border;
}
phaseResidual(0) = m_cachedPhaseResidual;
}
void ApplyJacobian(
const CentralDensityJacobianInput &input,
CentralDensityJacobianOutput output
) const {
VerifyPrepared();
MFEM_VERIFY(
input.enthalpyVariation.Size() == m_centerDof.field_size(),
"The central-density Jacobian received an enthalpy direction with the wrong size."
);
VerifyOutputSizes(output.enthalpyAction, output.phaseAction);
double localPhaseAction = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
output.enthalpyAction(reducedDof) += input.borderVariation;
localPhaseAction += input.enthalpyVariation(reducedDof);
}
output.phaseAction(0) = GlobalSum(localPhaseAction);
++m_jacobianApplicationCount;
}
void ApplyJacobianTranspose(
const CentralDensityJacobianTransposeInput &input,
CentralDensityJacobianTransposeOutput output
) const {
VerifyPrepared();
MFEM_VERIFY(
input.enthalpyResidualDual.Size() == m_centerDof.field_size(),
"The central-density transpose received an enthalpy residual dual with the wrong size."
);
MFEM_VERIFY(
output.enthalpyDual.Size() == m_centerDof.field_size() && output.borderDual.Size() == 1,
"The central-density transpose received output vectors with the wrong size."
);
double localBorderDual = 0.0;
for (const int reducedDof : m_centerDof.reduced_dofs()) {
output.enthalpyDual(reducedDof) += input.phaseResidualDual;
localBorderDual += input.enthalpyResidualDual(reducedDof);
}
output.borderDual(0) += GlobalSum(localBorderDual);
++m_transposeApplicationCount;
}
[[nodiscard]] CentralDensityConstraintReport GetConstraintReport() const {
VerifyPrepared();
const double targetEnthalpy = m_constraint->targetEnthalpy().value();
const double scale = std::max(std::abs(targetEnthalpy), 1.0e-300);
return {
.targetDensity = m_constraint->targetDensity().value(),
.achievedDensity =
m_constraint->densityFromEnthalpy(dimensions::SpecificEnthalpyValue{m_centralEnthalpy}).value(),
.targetEnthalpy = targetEnthalpy,
.achievedEnthalpy = m_centralEnthalpy,
.enthalpyResidual = m_cachedPhaseResidual,
.scaledResidual = m_cachedPhaseResidual / scale
};
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldPointDofMap &GetCenterDof() const noexcept {
return m_centerDof;
}
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept {
return m_preparationCount;
}
[[nodiscard]] std::uint64_t GetJacobianApplicationCount() const noexcept {
return m_jacobianApplicationCount;
}
[[nodiscard]] std::uint64_t GetTransposeApplicationCount() const noexcept {
return m_transposeApplicationCount;
}
private:
[[nodiscard]] double GlobalSum(const double localValue) const {
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_communicator);
return globalValue;
}
void VerifyOutputSizes(
const mfem::Vector &enthalpyOutput,
const mfem::Vector &phaseOutput
) const {
MFEM_VERIFY(
enthalpyOutput.Size() == m_centerDof.field_size() && phaseOutput.Size() == 1,
"The central-density phase received output vectors with the wrong size."
);
}
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The central-density phase must be prepared before application.");
}
field::FieldPointDofMap m_centerDof;
MPI_Comm m_communicator;
std::optional<models::CompiledFixedCentralDensity> m_constraint;
CentralDensityDependencies m_preparedDependencies;
double m_centralEnthalpy{0.0};
double m_border{0.0};
double m_cachedPhaseResidual{0.0};
std::uint64_t m_preparationCount{0};
mutable std::uint64_t m_jacobianApplicationCount{0};
mutable std::uint64_t m_transposeApplicationCount{0};
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,117 @@
module;
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_central_density_stellar_equilibrium;
export import :model.compiled_fixed_central_density;
export import :operators.prepared_central_density;
export import :operators.prepared_stellar_equilibrium;
export namespace mean_field::operators {
using CentralDensityStellarEquilibriumSpecificationModel = model::StellarModel<
models::
SpecificationSet<eos::Polytrope, models::FixedTotalMass, surface::Isobaric, models::FixedCentralDensity>>;
using CentralDensityStellarEquilibriumForm = utils::blocks::central_density_bordered_stellar_equilibrium_form;
using CentralDensityStellarEquilibriumJacobianForm =
utils::blocks::central_density_bordered_stellar_equilibrium_jacobian_form;
using CentralDensityStellarEquilibriumLayout = utils::blocks::form_layout<CentralDensityStellarEquilibriumForm>;
using CentralDensityStellarEquilibriumSystemManifest = EquilibriumSystemManifest<
CentralDensityStellarEquilibriumSpecificationModel,
CentralDensityStellarEquilibriumForm,
CentralDensityStellarEquilibriumJacobianForm>;
using CentralDensityStellarEquilibriumRootManifest = CentralDensityStellarEquilibriumSystemManifest;
struct PreparedCentralDensityStellarEquilibriumReport final {
PreparedStellarEquilibriumReport physical;
PreparedCentralDensityReport phase;
bool assembledResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return physical.DidAnyWork() || phase.DidAnyWork() || assembledResidual;
}
};
class PreparedCentralDensityStellarEquilibriumOperator final : public mfem::Operator {
public:
PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation,
models::CompiledFixedCentralDensity centralDensity
)
: PreparedCentralDensityStellarEquilibriumOperator(
f,
std::make_unique<PreparedStellarEquilibriumOperator>(
f,
domainMapper,
equationOfState,
std::move(fixedMassConstraint),
surfaceConstraint,
std::move(domainDeformation)
),
std::move(centralDensity),
MakeCenterDofMap(f)
) {
}
PreparedCentralDensityStellarEquilibriumOperator(const PreparedCentralDensityStellarEquilibriumOperator &) =
delete;
PreparedCentralDensityStellarEquilibriumOperator &
operator=(const PreparedCentralDensityStellarEquilibriumOperator &) = delete;
PreparedCentralDensityStellarEquilibriumOperator(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
PreparedCentralDensityStellarEquilibriumOperator &
operator=(PreparedCentralDensityStellarEquilibriumOperator &&) = delete;
PreparedCentralDensityStellarEquilibriumReport Prepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
);
void BuildResidual(mfem::Vector &residual) const;
void Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const override;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] const CentralDensityStellarEquilibriumLayout &GetLayout() const noexcept;
[[nodiscard]] const CentralDensityStellarEquilibriumRootManifest &GetRootManifest() const noexcept;
[[nodiscard]] const PreparedStellarEquilibriumOperator &GetPhysicalOperator() const noexcept;
[[nodiscard]] const PreparedCentralDensityConstraint &GetCentralDensityConstraint() const noexcept;
[[nodiscard]] RootConstraintReport GetFixedMassReport() const;
[[nodiscard]] CentralDensityConstraintReport GetCentralDensityReport() const;
private:
static field::FieldPointDofMap MakeCenterDofMap(const fem::FEM &f);
PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
models::CompiledFixedCentralDensity centralDensity,
field::FieldPointDofMap centerDof
);
void AssembleResidual();
void VerifyPrepared() const;
std::unique_ptr<PreparedStellarEquilibriumOperator> m_physicalOperator;
models::CompiledFixedCentralDensity m_centralDensity;
PreparedCentralDensityConstraint m_phaseConstraint;
CentralDensityStellarEquilibriumRootManifest m_rootManifest;
mfem::Vector m_cachedResidual;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -0,0 +1,37 @@
module;
#include <concepts>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:operators.prepared_constraint;
export import :model.compiled_fixed_mass;
export namespace mean_field::operators {
template <typename Candidate>
concept PreparedConstraint = requires(
std::remove_cvref_t<Candidate> &prepared,
const std::remove_cvref_t<Candidate> &constPrepared,
const typename std::remove_cvref_t<Candidate>::CompiledConstraintType &constraint,
const typename std::remove_cvref_t<Candidate>::Dependencies &dependencies,
const typename std::remove_cvref_t<Candidate>::JacobianInput &jacobianInput,
typename std::remove_cvref_t<Candidate>::JacobianTransposeOutput transposeOutput,
const mfem::Vector &residualDual,
mfem::Vector &result
) {
typename std::remove_cvref_t<Candidate>::SpecificationType;
typename std::remove_cvref_t<Candidate>::CompiledConstraintType;
typename std::remove_cvref_t<Candidate>::Dependencies;
typename std::remove_cvref_t<Candidate>::Report;
typename std::remove_cvref_t<Candidate>::JacobianInput;
typename std::remove_cvref_t<Candidate>::JacobianTransposeOutput;
requires models::CompiledConstraint<typename std::remove_cvref_t<Candidate>::CompiledConstraintType>;
{ prepared.Prepare(constraint, dependencies) } -> std::same_as<typename std::remove_cvref_t<Candidate>::Report>;
{ constPrepared.BuildResidual(result) } -> std::same_as<void>;
{ constPrepared.ApplyJacobian(jacobianInput, result) } -> std::same_as<void>;
{ constPrepared.ApplyJacobianTranspose(residualDual, transposeOutput) } -> std::same_as<void>;
{ constPrepared.IsPrepared() } noexcept -> std::same_as<bool>;
};
} // namespace mean_field::operators

View File

@@ -2,6 +2,7 @@ module;
#include <compare>
#include <cstdint>
#include <vector>
#include <mfem.hpp>
@@ -105,6 +106,29 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
const mfem::Vector &gravityGradientVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *gravityGradientDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix mappingJacobians;
mfem::DenseMatrix inverseMeshJacobians;
mfem::DenseMatrix baseGravityReferenceValues;
mfem::Vector baseDensityValues;
mfem::Vector referenceWeights;
};
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
@@ -112,11 +136,34 @@ export namespace mean_field::operators {
context::gravity_field::GravityFieldRevisions m_preparedRevisions;
mfem::Vector m_cachedResidual;
std::vector<ElementPAData> m_elements;
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_gravityGradientVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_actionTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_gravityGradientVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementGravityGradientVariation;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementAction;
mutable mfem::Vector m_densityShape;
mutable mfem::Vector m_displacementShape;
mutable mfem::Vector m_baseGravityReferenceValue;
mutable mfem::Vector m_gravityVariationReferenceValue;
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;
std::uint64_t m_residualPreparationCount{0};
mutable std::uint64_t m_residualApplicationCount{0};

View File

@@ -22,6 +22,11 @@ export namespace mean_field::operators {
const mfem::Vector &density,
mfem::Vector &action
) const override;
void MultDisplacementVariationTrue(
const mfem::Vector &densityTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
@@ -41,13 +46,18 @@ export namespace mean_field::operators {
mfem::Array<int> density_dofs;
mfem::Array<int> potential_dofs;
mfem::Array<int> displacement_dofs;
mfem::DofTransformation *density_dof_transformation{nullptr};
mfem::DofTransformation *potential_dof_transformation{nullptr};
mfem::DofTransformation *displacement_dof_transformation{nullptr};
const mfem::IntegrationRule *integration_rule{nullptr};
// Rows are quadrature points; columns are element DOFs.
mfem::DenseMatrix density_basis;
mfem::DenseMatrix potential_basis;
mfem::DenseMatrix inverse_element_jacobians;
// Contains quadrature weight, mesh Jacobian, mapped Jacobian,
// and 4*pi*G.
@@ -67,6 +77,15 @@ export namespace mean_field::operators {
mutable mfem::Vector m_density_true;
mutable mfem::Vector m_potential_true;
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_density_local;
mutable mfem::Vector m_displacement_variation_local;
mutable mfem::Vector m_local_variation_action;
mutable mfem::Vector m_element_density;
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;
std::uint64_t m_preparation_count{0};

View File

@@ -2,6 +2,7 @@ module;
#include <cstdint>
#include <memory>
#include <mfem.hpp>
#include <vector>
export module mean_field:operators.prepared_hdiv_mass;
export import :fem;
@@ -21,6 +22,11 @@ export namespace mean_field::operators {
const mfem::Vector &gravity_gradient,
mfem::Vector &action
) const override;
void MultDisplacementVariationTrue(
const mfem::Vector &gravityGradientTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionVariationTrue
) const;
void AssembleDiagonal(mfem::Vector &diagonal) const override;
void AssembleTrueDiagonal(mfem::Vector &diagonal) const;
@@ -31,6 +37,21 @@ export namespace mean_field::operators {
[[nodiscard]] const field::FieldDofMap &GetDisplacementMap() const noexcept;
private:
struct ElementVariationData {
int elementId{-1};
mfem::Array<int> gravityGradientDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::DofTransformation *gravityGradientDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::Vector baseDisplacement;
mfem::Vector compactification;
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix frozenMappingData;
};
void PrepareVariationData();
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domain_mapper;
@@ -48,6 +69,21 @@ export namespace mean_field::operators {
mutable mfem::Vector m_action_true;
mutable mfem::Vector m_domain_action_true;
mfem::Vector m_displacement_true;
std::vector<ElementVariationData> m_variationElements;
mutable mapping::DomainMapper::Workspace m_variationWorkspace;
mutable mapping::VolumeMappingContext m_baseMappingContext;
mutable mapping::VolumeMappingVariation m_mappingVariation;
mutable mfem::Vector m_gravityGradientLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localVariationAction;
mutable mfem::Vector m_elementGravityGradient;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementVariationAction;
mutable mfem::Vector m_gravityGradientValue;
mutable mfem::Vector m_massTensorVariationAction;
mutable mfem::DenseMatrix m_gravityGradientShape;
mutable mfem::DenseMatrix m_massTensorVariation;
std::uint64_t m_preparation_count{0};
bool m_is_prepared{false};
};

View File

@@ -9,7 +9,9 @@ export module mean_field:operators.prepared_mass_normalization;
export import :fem;
export import :mapping.domain_mapper;
export import :model.compiled_fixed_mass;
export import :operators.context.gravity_field;
export import :operators.prepared_constraint;
export import :utils.blocks;
export namespace mean_field::operators {
@@ -33,6 +35,16 @@ export namespace mean_field::operators {
double targetMass{0.0};
};
struct FixedMassJacobianInput final {
const mfem::Vector &densityVariation;
const mfem::Vector &displacementVariation;
};
struct FixedMassJacobianTransposeOutput final {
mfem::Vector &densityDual;
mfem::Vector &displacementDual;
};
struct PreparedMassNormalizationReport final {
bool rebuiltStaticPlan{false};
bool refreshedGeometry{false};
@@ -43,12 +55,15 @@ export namespace mean_field::operators {
[[nodiscard]] bool DidAnyWork() const noexcept {
return rebuiltStaticPlan || refreshedGeometry || refreshedDensity || updatedTargetMass || assembledResidual;
}
constexpr auto operator<=>(const PreparedMassNormalizationReport &) const = default;
};
struct PreparedMassNormalizationActionStatistics final {
std::uint64_t densityApplications{0};
std::uint64_t displacementApplications{0};
std::uint64_t completeApplications{0};
std::uint64_t transposeApplications{0};
constexpr auto operator<=>(const PreparedMassNormalizationActionStatistics &) const = default;
};
@@ -66,6 +81,13 @@ export namespace mean_field::operators {
*/
class PreparedMassNormalizationOperator final {
public:
using SpecificationType = models::FixedTotalMass;
using CompiledConstraintType = models::CompiledFixedMass;
using Dependencies = MassNormalizationDependencies;
using Report = PreparedMassNormalizationReport;
using JacobianInput = FixedMassJacobianInput;
using JacobianTransposeOutput = FixedMassJacobianTransposeOutput;
PreparedMassNormalizationOperator(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
@@ -82,6 +104,11 @@ export namespace mean_field::operators {
const MassNormalizationDependencies &dependencies
);
PreparedMassNormalizationReport Prepare(
const models::CompiledFixedMass &constraint,
const MassNormalizationDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
@@ -100,6 +127,22 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
void ApplyJacobian(
const FixedMassJacobianInput &input,
mfem::Vector &action
) const;
void ApplyCompleteJacobianTransposeAction(
double residualDual,
mfem::Vector &densityDual,
mfem::Vector &displacementDual
) const;
void ApplyJacobianTranspose(
const mfem::Vector &residualDual,
FixedMassJacobianTransposeOutput output
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] double GetCurrentMass() const;
[[nodiscard]] double GetTargetMass() const;
@@ -145,6 +188,16 @@ export namespace mean_field::operators {
[[nodiscard]] double EvaluateDisplacementActionLocal(const mfem::Vector &displacementVariation) const;
void AssembleDensityTransposeAction(
double residualDual,
mfem::Vector &densityDual
) const;
void AssembleDisplacementTransposeAction(
double residualDual,
mfem::Vector &displacementDual
) const;
[[nodiscard]] double GlobalSum(double localValue) const;
const fem::FEM &m_fem;
@@ -167,6 +220,10 @@ export namespace mean_field::operators {
bool m_isPrepared{false};
};
using PreparedFixedMass = PreparedMassNormalizationOperator;
static_assert(PreparedConstraint<PreparedFixedMass>);
using MassNormalizationLayout = utils::blocks::form_layout<utils::blocks::barotropic_equilibrium_form>;
class PreparedMassNormalizationJacobianOperator final : public mfem::Operator {
@@ -181,6 +238,11 @@ export namespace mean_field::operators {
mfem::Vector &action
) const override;
void MultTranspose(
const mfem::Vector &residualDual,
mfem::Vector &stateDual
) const override;
[[nodiscard]] const MassNormalizationLayout &GetLayout() const noexcept;
private:

View File

@@ -3,6 +3,7 @@ module;
#include <compare>
#include <cstdint>
#include <optional>
#include <vector>
#include <mfem.hpp>
@@ -103,6 +104,25 @@ export namespace mean_field::operators {
private:
void VerifyPrepared() const;
void PrepareElementData();
void ApplyPreparedCompleteJacobianActionTrue(
const mfem::Vector &densityVariationTrue,
const mfem::Vector &displacementVariationTrue,
mfem::Vector &actionTrue
) const;
struct ElementPAData {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
const mfem::IntegrationRule *integrationRule{nullptr};
mfem::DenseMatrix inverseElementJacobians;
mfem::DenseMatrix centrifugalAccelerations;
mfem::Vector baseDensityValues;
mfem::Vector quadratureWeights;
};
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
@@ -111,9 +131,24 @@ export namespace mean_field::operators {
std::optional<physics::RigidRotation> m_rotation;
mfem::Vector m_cachedResidual;
std::vector<ElementPAData> m_elements;
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_actionTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_localAction;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementDisplacementVariation;
mutable mfem::Vector m_elementAction;
mutable mfem::Vector m_densityShape;
mutable mfem::Vector m_displacementShape;
mutable mfem::Vector m_physicalPositionVariation;
mutable mfem::Vector m_centrifugalAcceleration;
mutable mfem::Vector m_centrifugalAccelerationVariation;
mutable mfem::Vector m_weightedForce;
mutable mfem::DenseMatrix m_referenceDisplacementDShape;
mutable mfem::DenseMatrix m_referenceDisplacementJacobian;
context::rotational_displacement_force::RotationalDisplacementForceDependencies m_preparedDependencies;

View File

@@ -15,6 +15,7 @@ export import :fem;
export import :field.mfem;
export import :mapping.domain_mapper;
export import :model.stellar;
export import :model.typed_stellar;
export import :operators.context.gravity_field;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
@@ -23,6 +24,7 @@ export import :operators.prepared_displacement_residual;
export import :operators.prepared_hydrostatic_equilibrium;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_surface_constraint;
export import :operators.root_manifest;
export import :physics.rigid_rotation;
export import :utils.blocks;
@@ -82,6 +84,16 @@ export namespace mean_field::operators {
using StellarEquilibriumLayout =
utils::blocks::form_layout<utils::blocks::surface_deformed_stellar_equilibrium_form>;
using StellarEquilibriumSpecificationModel =
model::StellarModel<models::SpecificationSet<eos::Polytrope, models::FixedTotalMass, surface::Isobaric>>;
using StellarEquilibriumSystemManifest = EquilibriumSystemManifest<
StellarEquilibriumSpecificationModel,
utils::blocks::surface_deformed_stellar_equilibrium_form,
utils::blocks::surface_deformed_stellar_equilibrium_jacobian_form>;
using StellarEquilibriumRootManifest = StellarEquilibriumSystemManifest;
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public:
template <models::StellarModelType Model>
@@ -101,12 +113,27 @@ export namespace mean_field::operators {
f,
domainMapper,
stellarModel.equationOfState(),
stellarModel.targetMass(),
models::compileConstraint(models::FixedTotalMass{dimensions::MassValue{stellarModel.targetMass()}}),
PressureSurfaceConstraintView{stellarModel.compiledSurfaceConstraint()},
deformation::PreparedDomainDeformationRuntime{stellarModel.compileDomainDeformation(f)}
) {
}
/*
* Authoritative construction path for a compiled equilibrium system.
* The caller owns the EOS and compiled surface constraint for this
* operator's lifetime; the remaining compiled contributions are
* transferred into the operator.
*/
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
);
PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator(PreparedStellarEquilibriumOperator &&) = delete;
@@ -128,6 +155,12 @@ export namespace mean_field::operators {
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] double GetTargetMass() const noexcept;
[[nodiscard]] const StellarEquilibriumLayout &GetLayout() const noexcept;
[[nodiscard]] const StellarEquilibriumRootManifest &GetRootManifest() const noexcept;
[[nodiscard]] RootStateView<utils::blocks::surface_deformed_stellar_equilibrium_form>
GetRootStateView(const mfem::Vector &state) const;
[[nodiscard]] ResidualView<utils::blocks::surface_deformed_stellar_equilibrium_form>
GetResidualView(mfem::Vector &residual) const;
[[nodiscard]] RootConstraintReport GetFixedMassReport() const;
[[nodiscard]] const StellarEquilibriumDependencies &GetDependencies() const;
[[nodiscard]] const PreparedStellarEquilibriumStatistics &GetStatistics() const noexcept;
@@ -160,16 +193,7 @@ export namespace mean_field::operators {
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint,
deformation::PreparedDomainDeformationRuntime domainDeformation
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
models::CompiledFixedMass fixedMassConstraint,
PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
);
@@ -177,7 +201,7 @@ export namespace mean_field::operators {
void AssembleResidual();
void VerifyPrepared() const;
StellarEquilibriumLayout m_layout;
StellarEquilibriumRootManifest m_rootManifest;
mfem::Array<int> m_gravityStateOffsets;
context::gravity_field::GravityFieldLinearizationContext m_gravityContext;
@@ -198,7 +222,7 @@ export namespace mean_field::operators {
mfem::Vector m_generatedVolumeDisplacement;
mfem::Vector m_fullMechanicalResidual;
mfem::Vector m_cachedResidual;
double m_targetMass{0.0};
models::CompiledFixedMass m_fixedMassConstraint;
mutable PreparedStellarEquilibriumStatistics m_statistics;
bool m_isPrepared{false};

View File

@@ -14,10 +14,10 @@ export import :field.mfem;
export import :surface.compiled;
namespace mean_field::operators::detail {
template <eos::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
eos::QuantityValue<Quantity> quantityValue;
template <dimensions::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
dimensions::QuantityValue<Quantity> quantityValue;
[[nodiscard]] eos::QuantityValue<Quantity> value(Quantity) const noexcept {
[[nodiscard]] dimensions::QuantityValue<Quantity> value(Quantity) const noexcept {
return quantityValue;
}
};
@@ -37,7 +37,7 @@ export namespace mean_field::operators {
typename std::remove_cvref_t<Candidate>::CarrierQuantity;
typename std::remove_cvref_t<Candidate>::CarrierField;
typename std::remove_cvref_t<Candidate>::SurfaceDependencies;
} && std::same_as<typename std::remove_cvref_t<Candidate>::PhysicalQuantity, eos::quantity::Pressure> &&
} && std::same_as<typename std::remove_cvref_t<Candidate>::PhysicalQuantity, dimensions::quantity::Pressure> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField,
typename std::remove_cvref_t<Candidate>::CarrierField> &&
@@ -112,7 +112,7 @@ export namespace mean_field::operators {
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
dimensions::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) =
static_cast<const Constraint *>(constraint)->residual(state);
@@ -132,10 +132,10 @@ export namespace mean_field::operators {
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const int reducedDof = surfaceRows.reduced_dofs()[surfaceIndex];
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
dimensions::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
const detail::SingleQuantitySurfaceState<CarrierQuantity> variation{
eos::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
dimensions::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
};
rowAction(reducedDof) = static_cast<const Constraint *>(constraint)->jacobianAction(state, variation);
}

View File

@@ -0,0 +1,601 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <concepts>
#include <cstddef>
#include <optional>
#include <span>
#include <stdexcept>
#include <string_view>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:operators.root_manifest;
export import :model.compiled_fixed_mass;
export import :model.compiled_fixed_central_density;
export import :model.specifications;
export import :utils.blocks;
export namespace mean_field::operators {
enum class RootBlockKind { value, residual };
enum class RootBlockProvenance { physical_operator, model_specification };
enum class RootRowInjection { physical_equation, append_global, replace_carrier_rows };
enum class RootColumnPolicy { physical_state, existing_physical_multiplier, solver_border, no_column };
enum class RootScalePolicy { unscaled, target_relative };
struct RootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockKind kind;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
int canonicalIndex;
int offset;
int size;
double scale;
};
struct RootRowReplacementDescriptor final {
std::string_view stableId;
std::string_view sourceSpecification;
models::SpecificationRole role;
int carrierResidualBlock;
int replacedRowCount;
};
struct RootConstraintDescriptor final {
std::string_view stableId;
models::SpecificationRole role;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
int valueBlock;
int residualBlock;
int rowArity;
int columnArity;
double target;
std::optional<double> carrierTarget;
std::string_view targetUnits;
std::string_view residualUnits;
double residualScale;
};
struct CentralDensityManifestInput final {
double targetDensity;
double targetEnthalpy;
int centerDofCount;
};
struct RootConstraintReport final {
RootConstraintDescriptor descriptor;
double achieved;
double dimensionalResidual;
double scaledResidual;
};
namespace detail {
struct StaticRootBlockDescriptor final {
std::string_view stableId;
std::string_view symbol;
RootBlockProvenance provenance;
std::string_view source;
RootRowInjection rowInjection;
RootColumnPolicy columnPolicy;
RootScalePolicy scalePolicy;
};
template <typename Block> struct RootBlockTraits;
#define MEAN_FIELD_PHYSICAL_VALUE_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::physical_state, \
RootScalePolicy::unscaled \
}; \
}
#define MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(BlockType, StableId, Symbol) \
template <> struct RootBlockTraits<BlockType> { \
static constexpr StaticRootBlockDescriptor descriptor{ \
StableId, \
Symbol, \
RootBlockProvenance::physical_operator, \
"stellar_equilibrium", \
RootRowInjection::physical_equation, \
RootColumnPolicy::no_column, \
RootScalePolicy::unscaled \
}; \
}
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::density::mass::value,
"density",
"rho"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::displacement::geometry::value,
"volume_displacement",
"d"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::surface_deformation::parameters::value,
"surface_deformation",
"q"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::gradient::value,
"gravity_gradient",
"g"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::gravity::poisson::value,
"gravity_potential",
"Phi"
);
MEAN_FIELD_PHYSICAL_VALUE_BLOCK(
utils::blocks::enthalpy::specific::value,
"specific_enthalpy",
"h"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::gradient::residual,
"gravity_gradient_relation",
"R_g"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::gravity::poisson::residual,
"poisson_balance",
"R_Phi"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::density::mass::residual,
"barotropic_closure",
"R_rho"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::displacement::geometry::residual,
"mechanical_balance",
"R_d"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::surface_deformation::shape_equilibrium::residual,
"surface_shape_balance",
"R_q"
);
MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK(
utils::blocks::enthalpy::specific::residual,
"hydrostatic_balance",
"R_h"
);
#undef MEAN_FIELD_PHYSICAL_VALUE_BLOCK
#undef MEAN_FIELD_PHYSICAL_RESIDUAL_BLOCK
template <> struct RootBlockTraits<utils::blocks::fixed_total_mass::mass_normalization::value> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_total_mass.multiplier",
"C",
RootBlockProvenance::model_specification,
"FixedTotalMass",
RootRowInjection::physical_equation,
RootColumnPolicy::existing_physical_multiplier,
RootScalePolicy::unscaled
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_total_mass::mass_normalization::residual> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_total_mass.residual",
"R_M",
RootBlockProvenance::model_specification,
"FixedTotalMass",
RootRowInjection::append_global,
RootColumnPolicy::no_column,
RootScalePolicy::target_relative
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_central_density::central_value::value> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_central_density.border",
"lambda_rho_c",
RootBlockProvenance::model_specification,
"FixedCentralDensity",
RootRowInjection::physical_equation,
RootColumnPolicy::solver_border,
RootScalePolicy::unscaled
};
};
template <> struct RootBlockTraits<utils::blocks::fixed_central_density::central_value::residual> {
static constexpr StaticRootBlockDescriptor descriptor{
"fixed_central_density.residual", "R_rho_c",
RootBlockProvenance::model_specification, "FixedCentralDensity",
RootRowInjection::append_global, RootColumnPolicy::no_column,
RootScalePolicy::target_relative
};
};
template <typename Block>
[[nodiscard]] constexpr double blockScale(
const double fixedMassScale,
const double centralDensityScale
) noexcept {
if constexpr (std::same_as<Block, utils::blocks::fixed_total_mass::mass_normalization::residual>) {
return fixedMassScale;
} else if constexpr (std::same_as<Block, utils::blocks::fixed_central_density::central_value::residual>) {
return centralDensityScale;
} else {
return 1.0;
}
}
template <
RootBlockKind Kind,
typename... Blocks>
[[nodiscard]] std::array<
RootBlockDescriptor,
sizeof...(Blocks)>
makeBlockDescriptors(
const mfem::Array<int> &offsets,
const double fixedMassScale,
const double centralDensityScale,
utils::blocks::type_list<Blocks...>
) {
std::array<RootBlockDescriptor, sizeof...(Blocks)> descriptors{};
int index = 0;
((descriptors[index] =
{.stableId = RootBlockTraits<Blocks>::descriptor.stableId,
.symbol = RootBlockTraits<Blocks>::descriptor.symbol,
.kind = Kind,
.provenance = RootBlockTraits<Blocks>::descriptor.provenance,
.source = RootBlockTraits<Blocks>::descriptor.source,
.rowInjection = RootBlockTraits<Blocks>::descriptor.rowInjection,
.columnPolicy = RootBlockTraits<Blocks>::descriptor.columnPolicy,
.scalePolicy = RootBlockTraits<Blocks>::descriptor.scalePolicy,
.canonicalIndex = index,
.offset = offsets[index],
.size = offsets[index + 1] - offsets[index],
.scale = blockScale<Blocks>(fixedMassScale, centralDensityScale)},
++index),
...);
return descriptors;
}
template <models::SpecifiedModelType Model>
inline constexpr bool hasCentralDensity = Model::template containsSpecification<models::FixedCentralDensity>;
template <models::SpecifiedModelType Model>
inline constexpr std::size_t rootConstraintCount = 2 + (hasCentralDensity<Model> ? 1 : 0);
template <
models::SpecifiedModelType Model,
typename Form>
[[nodiscard]] std::array<
RootConstraintDescriptor,
rootConstraintCount<Model>>
makeConstraintDescriptors(
const double targetMass,
const double targetSurfacePressure,
const double fixedMassScale,
const std::optional<CentralDensityManifestInput> centralDensity
) {
std::array<RootConstraintDescriptor, rootConstraintCount<Model>> descriptors{};
descriptors[0] = {
.stableId = "FixedTotalMass",
.role = models::SpecificationRole::invariant,
.rowInjection = RootRowInjection::append_global,
.columnPolicy = RootColumnPolicy::existing_physical_multiplier,
.valueBlock = models::FixedMassLayoutRequest::valueBlock<Form>().index,
.residualBlock = models::FixedMassLayoutRequest::residualBlock<Form>().index,
.rowArity = 1,
.columnArity = 1,
.target = targetMass,
.carrierTarget = targetMass,
.targetUnits = "mass",
.residualUnits = "mass",
.residualScale = fixedMassScale
};
descriptors[1] = {
.stableId = "IsobaricSurface",
.role = models::SpecificationRole::boundary_condition,
.rowInjection = RootRowInjection::replace_carrier_rows,
.columnPolicy = RootColumnPolicy::no_column,
.valueBlock = -1,
.residualBlock =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term).index,
.rowArity = 0,
.columnArity = 0,
.target = targetSurfacePressure,
.carrierTarget = std::nullopt,
.targetUnits = "pressure",
.residualUnits = "specific_enthalpy",
.residualScale = 1.0
};
if constexpr (hasCentralDensity<Model>) {
if (!centralDensity.has_value()) {
throw std::invalid_argument(
"A model containing FixedCentralDensity requires central-density manifest metadata."
);
}
descriptors[2] = {
.stableId = "FixedCentralDensity",
.role = models::SpecificationRole::phase_condition,
.rowInjection = RootRowInjection::append_global,
.columnPolicy = RootColumnPolicy::solver_border,
.valueBlock = models::CentralDensityLayoutRequest::valueBlock<Form>().index,
.residualBlock = models::CentralDensityLayoutRequest::residualBlock<Form>().index,
.rowArity = 1,
.columnArity = 1,
.target = centralDensity->targetDensity,
.carrierTarget = centralDensity->targetEnthalpy,
.targetUnits = "density",
.residualUnits = "specific_enthalpy",
.residualScale = std::max(std::abs(centralDensity->targetEnthalpy), 1.0e-300)
};
} else if (centralDensity.has_value()) {
throw std::invalid_argument(
"Central-density manifest metadata was provided to a model without FixedCentralDensity."
);
}
return descriptors;
}
} // namespace detail
template <typename Form> class RootStateView final {
public:
RootStateView(
const mfem::Vector &state,
const utils::blocks::form_layout<Form> &layout
)
: m_state(state),
m_layout(layout) {
if (state.Size() != layout.value_offsets().Last()) {
throw std::invalid_argument("RootStateView received a vector with the wrong size.");
}
}
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto valueBlock = utils::blocks::get_value_block<Form>(term);
return mfem::Vector(
const_cast<mfem::real_t *>(m_state.GetData()) + m_layout.offset(valueBlock), m_layout.size(valueBlock)
);
}
[[nodiscard]] const mfem::Vector &vector() const noexcept {
return m_state;
}
private:
const mfem::Vector &m_state;
const utils::blocks::form_layout<Form> &m_layout;
};
template <typename Form> class ResidualView final {
public:
ResidualView(
mfem::Vector &residual,
const utils::blocks::form_layout<Form> &layout
)
: m_residual(residual),
m_layout(layout) {
if (residual.Size() != layout.residual_offsets().Last()) {
throw std::invalid_argument("ResidualView received a vector with the wrong size.");
}
}
template <typename Term> [[nodiscard]] mfem::Vector block(const Term &term) const {
constexpr auto residualBlock = utils::blocks::get_residual_block<Form>(term);
return mfem::Vector(m_residual.GetData() + m_layout.offset(residualBlock), m_layout.size(residualBlock));
}
template <typename Term>
void assign(
const Term &term,
const mfem::Vector &source
) const {
mfem::Vector destination = block(term);
if (destination.Size() != source.Size()) {
throw std::invalid_argument("ResidualView block assignment has the wrong size.");
}
destination = source;
}
[[nodiscard]] mfem::Vector &vector() const noexcept {
return m_residual;
}
private:
mfem::Vector &m_residual;
const utils::blocks::form_layout<Form> &m_layout;
};
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
class CompiledRootManifest final {
public:
using ModelType = Model;
using FormType = Form;
using JacobianType = JacobianForm;
using Layout = utils::blocks::form_layout<Form>;
using StateView = RootStateView<Form>;
using DirectionView = RootStateView<Form>;
using RootResidualView = ResidualView<Form>;
static constexpr models::ModelCompilationClass compilationClass = Model::compilationClass;
static constexpr bool symbolicallySquare = Model::symbolicallySquare;
CompiledRootManifest(
const std::array<
int,
Form::value_block_count> &valueSizes,
const std::array<
int,
Form::residual_block_count> &residualSizes,
const double targetMass,
const double targetSurfacePressure,
const int replacedSurfaceRowCount,
const std::optional<CentralDensityManifestInput> centralDensity = std::nullopt
)
: m_layout(
valueSizes,
residualSizes
),
m_fixedMassScale(
std::max(
std::abs(targetMass),
1.0e-300
)
),
m_centralDensityScale(
centralDensity.has_value() ? std::max(
std::abs(centralDensity->targetEnthalpy),
1.0e-300
)
: 1.0
),
m_valueBlocks(
detail::makeBlockDescriptors<RootBlockKind::value>(
m_layout.value_offsets(),
m_fixedMassScale,
m_centralDensityScale,
typename Form::value_blocks{}
)
),
m_residualBlocks(
detail::makeBlockDescriptors<RootBlockKind::residual>(
m_layout.residual_offsets(),
m_fixedMassScale,
m_centralDensityScale,
typename Form::residual_blocks{}
)
),
m_replacements{RootRowReplacementDescriptor{
.stableId = "isobaric_surface.replacement",
.sourceSpecification = "IsobaricSurface",
.role = models::SpecificationRole::boundary_condition,
.carrierResidualBlock =
utils::blocks::get_residual_block<Form>(utils::blocks::enthalpy_field.specific_term).index,
.replacedRowCount = replacedSurfaceRowCount
}},
m_constraints(
detail::makeConstraintDescriptors<
Model,
Form>(
targetMass,
targetSurfacePressure,
m_fixedMassScale,
centralDensity
)
) {
if (replacedSurfaceRowCount < 0) {
throw std::invalid_argument(
"An equilibrium-system manifest cannot contain a negative replacement-row count."
);
}
if (centralDensity.has_value() && centralDensity->centerDofCount < 0) {
throw std::invalid_argument(
"An equilibrium-system manifest cannot contain a negative central-DOF count."
);
}
if constexpr (compilationClass == models::EquilibriumSystemCompilation::complete_equilibrium_system) {
if (m_layout.value_offsets().Last() != m_layout.residual_offsets().Last()) {
throw std::invalid_argument(
"A complete equilibrium system must have equal state and equation dimensions."
);
}
}
}
[[nodiscard]] const Layout &layout() const noexcept {
return m_layout;
}
[[nodiscard]] StateView stateView(const mfem::Vector &state) const {
return {state, m_layout};
}
[[nodiscard]] DirectionView directionView(const mfem::Vector &direction) const {
return {direction, m_layout};
}
[[nodiscard]] RootResidualView residualView(mfem::Vector &residual) const {
return {residual, m_layout};
}
[[nodiscard]] std::span<const RootBlockDescriptor> valueBlocks() const noexcept {
return m_valueBlocks;
}
[[nodiscard]] std::span<const RootBlockDescriptor> residualBlocks() const noexcept {
return m_residualBlocks;
}
[[nodiscard]] std::span<const RootRowReplacementDescriptor> rowReplacements() const noexcept {
return m_replacements;
}
[[nodiscard]] std::span<const RootConstraintDescriptor> constraints() const noexcept {
return m_constraints;
}
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
specificationDescriptors() noexcept {
return Model::runtimeSpecificationDescriptors();
}
[[nodiscard]] RootConstraintReport fixedMassReport(const double achievedMass) const {
const RootConstraintDescriptor &descriptor = m_constraints[0];
const double residual = achievedMass - descriptor.target;
return {
.descriptor = descriptor,
.achieved = achievedMass,
.dimensionalResidual = residual,
.scaledResidual = residual / descriptor.residualScale
};
}
private:
Layout m_layout;
double m_fixedMassScale;
double m_centralDensityScale;
std::array<RootBlockDescriptor, Form::value_block_count> m_valueBlocks;
std::array<RootBlockDescriptor, Form::residual_block_count> m_residualBlocks;
std::array<RootRowReplacementDescriptor, 1> m_replacements;
std::array<RootConstraintDescriptor, detail::rootConstraintCount<Model>> m_constraints;
};
// Physics-facing names for the public equilibrium-system boundary. The
// root-oriented names remain available while existing solver consumers
// migrate, but new APIs should expose these aliases.
using EquilibriumBlockKind = RootBlockKind;
using EquilibriumBlockProvenance = RootBlockProvenance;
using EquilibriumEquationInjection = RootRowInjection;
using EquilibriumGeneratedVariablePolicy = RootColumnPolicy;
using EquilibriumScalePolicy = RootScalePolicy;
using EquilibriumBlockDescriptor = RootBlockDescriptor;
using EquilibriumEquationReplacementDescriptor = RootRowReplacementDescriptor;
using EquilibriumSpecificationDescriptor = RootConstraintDescriptor;
using EquilibriumSpecificationReport = RootConstraintReport;
template <typename Form> using EquilibriumStateView = RootStateView<Form>;
template <typename Form> using EquilibriumResidualView = ResidualView<Form>;
template <models::SpecifiedModelType Model, typename Form, typename JacobianForm>
requires utils::blocks::valid_jacobian_form<Form, JacobianForm>
using EquilibriumSystemManifest = CompiledRootManifest<Model, Form, JacobianForm>;
} // namespace mean_field::operators

View File

@@ -0,0 +1,210 @@
module;
#include <concepts>
#include <cstddef>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.stellar_equilibrium_problem;
export import :deformation.domain_deformation;
export import :equilibrium.stellar_discretization;
export import :model.typed_stellar;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :surface.compiler;
export namespace mean_field::equilibrium {
template <typename Candidate>
concept StellarEquilibriumModel = model::StellarModelType<Candidate> && requires {
requires std::remove_cvref_t<Candidate>::template containsSpecification<eos::Polytrope>;
requires std::remove_cvref_t<Candidate>::template containsSpecification<surface::Isobaric>;
requires std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedTotalMass>;
requires std::remove_cvref_t<Candidate>::specificationCount ==
3 + static_cast<std::size_t>(
std::remove_cvref_t<Candidate>::template containsSpecification<models::FixedCentralDensity>
);
};
template <StellarEquilibriumModel Model> class StellarEquilibriumProblem final {
public:
using ModelType = std::remove_cvref_t<Model>;
static constexpr bool hasFixedCentralDensity =
ModelType::template containsSpecification<models::FixedCentralDensity>;
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
using PreparedOperatorType = std::conditional_t<
hasFixedCentralDensity,
operators::PreparedCentralDensityStellarEquilibriumOperator,
operators::PreparedStellarEquilibriumOperator>;
using CompiledSurfaceConstraintType =
surface::CompiledPressureSurfaceConstraintT<surface::BarotropicSurfaceFormulation, eos::Polytrope>;
StellarEquilibriumProblem(
ModelType stellarModel,
const StellarDiscretization discretization
)
requires(!hasFixedCentralDensity)
: m_stellarModel(std::move(stellarModel)),
m_discretization(discretization),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
m_preparedOperator(
m_discretization.finiteElementModel(),
m_discretization.domainMapper(),
m_stellarModel.template specification<eos::Polytrope>(),
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
) {
VerifyProblem();
}
StellarEquilibriumProblem(
ModelType stellarModel,
const StellarDiscretization discretization
)
requires hasFixedCentralDensity
: m_stellarModel(std::move(stellarModel)),
m_discretization(discretization),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
m_preparedOperator(
m_discretization.finiteElementModel(),
m_discretization.domainMapper(),
m_stellarModel.template specification<eos::Polytrope>(),
models::compileConstraint(m_stellarModel.template specification<models::FixedTotalMass>()),
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel()),
models::compileConstraint(
m_stellarModel.template specification<models::FixedCentralDensity>(),
m_stellarModel.template specification<eos::Polytrope>()
)
) {
VerifyProblem();
}
StellarEquilibriumProblem(const StellarEquilibriumProblem &) = delete;
StellarEquilibriumProblem &operator=(const StellarEquilibriumProblem &) = delete;
StellarEquilibriumProblem(StellarEquilibriumProblem &&) = delete;
StellarEquilibriumProblem &operator=(StellarEquilibriumProblem &&) = delete;
[[nodiscard]] const ModelType &GetStellarModel() const noexcept {
return m_stellarModel;
}
[[nodiscard]] const StellarDiscretization &GetDiscretization() const noexcept {
return m_discretization;
}
[[nodiscard]] const CompiledSurfaceConstraintType &GetCompiledSurfaceConstraint() const noexcept {
return m_compiledSurfaceConstraint;
}
[[nodiscard]] PreparedOperatorType &GetPreparedOperator() noexcept {
return m_preparedOperator;
}
[[nodiscard]] const PreparedOperatorType &GetPreparedOperator() const noexcept {
return m_preparedOperator;
}
[[nodiscard]] const auto &GetManifest() const noexcept {
return m_preparedOperator.GetRootManifest();
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
} else {
return m_preparedOperator.GetSurfaceConstraintOperator().GetSurfaceRows();
}
}
[[nodiscard]] int StateSize() const noexcept {
return m_preparedOperator.Width();
}
[[nodiscard]] int EquationSize() const noexcept {
return m_preparedOperator.Height();
}
[[nodiscard]] const mfem::Operator &GetLinearizationOperator() const noexcept {
return m_preparedOperator;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
return m_preparedOperator.Prepare(state, dependencies, rotation);
}
void BuildResidual(mfem::Vector &residual) const {
m_preparedOperator.BuildResidual(residual);
}
void ApplyLinearization(
const mfem::Vector &direction,
mfem::Vector &action
) const {
m_preparedOperator.Mult(direction, action);
}
private:
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<surface::BarotropicSurfaceFormulation>(
stellarModel.template specification<surface::Isobaric>(),
stellarModel.template specification<eos::Polytrope>()
);
}
[[nodiscard]] static deformation::PreparedDomainDeformationRuntime
CompileDefaultDomainDeformation(fem::FEM &finiteElementModel) {
MFEM_VERIFY(
finiteElementModel.mesh != nullptr,
"Default stellar domain-deformation compilation requires a physical mesh."
);
mfem::Vector referenceCenter(finiteElementModel.mesh->SpaceDimension());
referenceCenter = 0.0;
return deformation::PreparedDomainDeformationRuntime{deformation::compileDomainDeformation(
deformation::NodalRadialSurface{std::move(referenceCenter)},
deformation::PowerLawRadialInteriorExtension{}, deformation::FixedInfinityRadialVacuumExtension{},
finiteElementModel
)};
}
void VerifyProblem() const {
MFEM_VERIFY(symbolicallySquare, "A stellar equilibrium problem must be symbolically square.");
MFEM_VERIFY(
StateSize() == EquationSize(),
"The discretized stellar equilibrium problem has unequal state and equation dimensions."
);
MFEM_VERIFY(m_discretization.isCurrent(), "The stellar equilibrium problem has a stale discretization.");
}
ModelType m_stellarModel;
StellarDiscretization m_discretization;
CompiledSurfaceConstraintType m_compiledSurfaceConstraint;
PreparedOperatorType m_preparedOperator;
};
template <StellarEquilibriumModel Model>
[[nodiscard]] auto discretize(
Model &&stellarModel,
const StellarDiscretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
return StellarEquilibriumProblem<ModelType>{std::forward<Model>(stellarModel), discretization};
}
template <StellarEquilibriumModel Model>
[[nodiscard]] auto discretize(
Model &&stellarModel,
fem::FEM &finiteElementModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
}
} // namespace mean_field::equilibrium

View File

@@ -0,0 +1,26 @@
module;
#include <type_traits>
#include <utility>
export module mean_field:operators.stellar_equilibrium_system;
export import :operators.stellar_equilibrium_problem;
export namespace mean_field::equilibrium {
// Transitional source-compatible names. New code should use
// StellarEquilibriumProblem and equilibrium::discretize.
template <typename Candidate>
concept CurrentlySupportedStellarModel = StellarEquilibriumModel<Candidate>;
template <StellarEquilibriumModel Model> using StellarEquilibriumSystem = StellarEquilibriumProblem<Model>;
template <StellarEquilibriumModel Model>
[[nodiscard]] auto makeStellarEquilibriumSystem(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
Model &&stellarModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel, domainMapper});
}
} // namespace mean_field::equilibrium