feat(surface): major work on implementing surface constraints in a presciption agnostic manner

This commit is contained in:
2026-08-30 16:41:14 -04:00
parent 36adfa1174
commit 0a7f18c5c7
95 changed files with 30144 additions and 25766 deletions

View File

@@ -0,0 +1,103 @@
module;
#include <cmath>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_centering_constraint;
export import :field.mfem;
export namespace mean_field::operators {
struct PreparedCenteringConstraintReport final {
bool cachedCenterDisplacement{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedCenterDisplacement;
}
};
/*
* Strong translational gauge: the material point at the computational
* origin has zero displacement. The three corresponding displacement
* residual rows replace redundant force-balance rows.
*/
class PreparedCenteringConstraint final {
public:
explicit PreparedCenteringConstraint(field::FieldPointDofMap centerRows)
: m_centerRows(std::move(centerRows)),
m_centerDisplacement(m_centerRows.size()) {
}
[[nodiscard]] PreparedCenteringConstraintReport Prepare(
const mfem::Vector &displacement,
const bool displacementChanged
) {
MFEM_VERIFY(
displacement.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement vector with the wrong size."
);
PreparedCenteringConstraintReport report;
if (!m_isPrepared || displacementChanged) {
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
const double value = displacement(m_centerRows.reduced_dofs()[centerIndex]);
MFEM_VERIFY(
std::isfinite(value), "The centering constraint received a non-finite center displacement."
);
m_centerDisplacement(centerIndex) = value;
}
report.cachedCenterDisplacement = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &displacementResidual) const {
VerifyPrepared();
MFEM_VERIFY(
displacementResidual.Size() == m_centerRows.field_size(),
"The centering constraint received a displacement residual with the wrong size."
);
for (int centerIndex = 0; centerIndex < m_centerRows.size(); ++centerIndex) {
displacementResidual(m_centerRows.reduced_dofs()[centerIndex]) = m_centerDisplacement(centerIndex);
}
}
void ApplyJacobianRows(
const mfem::Vector &displacementVariation,
mfem::Vector &displacementAction
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_centerRows.field_size() &&
displacementAction.Size() == m_centerRows.field_size(),
"The centering constraint received a Jacobian vector with the wrong size."
);
for (const int centerRow : m_centerRows.reduced_dofs()) {
displacementAction(centerRow) = displacementVariation(centerRow);
}
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldPointDofMap &GetCenterRows() const noexcept {
return m_centerRows;
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The centering constraint must be prepared before row application.");
}
field::FieldPointDofMap m_centerRows;
mfem::Vector m_centerDisplacement;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -1,7 +1,9 @@
module;
#include <compare>
#include <concepts>
#include <cstdint>
#include <type_traits>
#include <mfem.hpp>
@@ -16,9 +18,11 @@ export import :operators.context.gravity_field;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
export import :operators.prepared_barotropic_closure;
export import :operators.prepared_centering_constraint;
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 :physics.rigid_rotation;
export import :utils.blocks;
@@ -50,11 +54,14 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumReport hydrostatic;
PreparedDisplacementResidualReport displacement;
PreparedMassNormalizationReport massNormalization;
PreparedSurfaceConstraintReport surfaceConstraint;
PreparedCenteringConstraintReport centeringConstraint;
bool assembledResidual{false};
[[nodiscard]] bool DidAnyChildWork() const noexcept {
return gravity.DidAnyWork() || barotropicClosure.DidAnyWork() || hydrostatic.DidAnyWork() ||
displacement.DidAnyWork() || massNormalization.DidAnyWork();
displacement.DidAnyWork() || massNormalization.DidAnyWork() || surfaceConstraint.DidAnyWork() ||
centeringConstraint.DidAnyWork();
}
[[nodiscard]] bool DidAnyWork() const noexcept {
@@ -74,19 +81,27 @@ export namespace mean_field::operators {
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public:
template <models::StellarModelType Model>
requires std::same_as<
typename std::remove_cvref_t<Model>::EquationOfStateType,
eos::Polytrope> &&
SingleFieldPressureSurfaceConstraintFor<
typename std::remove_cvref_t<Model>::SurfaceConstraintType,
field::Enthalpy> &&
std::is_lvalue_reference_v<Model &&>
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
const models::StellarModel &stellarModel
);
Model &&stellarModel
)
: PreparedStellarEquilibriumOperator(
f,
domainMapper,
stellarModel.equationOfState(),
stellarModel.targetMass(),
PressureSurfaceConstraintView{stellarModel.compiledSurfaceConstraint()}
) {
}
PreparedStellarEquilibriumOperator(const PreparedStellarEquilibriumOperator &) = delete;
PreparedStellarEquilibriumOperator &operator=(const PreparedStellarEquilibriumOperator &) = delete;
@@ -122,6 +137,8 @@ export namespace mean_field::operators {
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
[[nodiscard]] const PreparedCenteringConstraint &GetCenteringConstraintOperator() const noexcept;
private:
struct ConstructionData;
@@ -133,6 +150,15 @@ export namespace mean_field::operators {
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint
);
PreparedStellarEquilibriumOperator(
fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const eos::Polytrope &equationOfState,
double targetMass,
PressureSurfaceConstraintView surfaceConstraint,
ConstructionData constructionData
);
@@ -150,6 +176,8 @@ export namespace mean_field::operators {
PreparedHydrostaticEquilibriumOperator m_hydrostaticOperator;
PreparedDisplacementResidualOperator m_displacementOperator;
PreparedMassNormalizationOperator m_massNormalizationOperator;
PreparedPressureSurfaceConstraint m_surfaceConstraintOperator;
PreparedCenteringConstraint m_centeringConstraintOperator;
StellarEquilibriumDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual;

View File

@@ -0,0 +1,235 @@
module;
#include <cmath>
#include <concepts>
#include <memory>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:operators.prepared_surface_constraint;
export import :field.mfem;
export import :surface.compiled;
namespace mean_field::operators::detail {
template <eos::ThermodynamicQuantityType Quantity> struct SingleQuantitySurfaceState final {
eos::QuantityValue<Quantity> quantityValue;
[[nodiscard]] eos::QuantityValue<Quantity> value(Quantity) const noexcept {
return quantityValue;
}
};
} // namespace mean_field::operators::detail
export namespace mean_field::operators {
/*
* Runtime enforcement currently supports a pointwise pressure constraint
* whose row field is also its sole state field. The concept is expressed
* entirely in compiled-constraint metadata: no thermodynamic carrier or
* concrete field is selected by this prepared layer.
*/
template <typename Candidate>
concept SingleFieldPressureSurfaceConstraint =
requires {
typename std::remove_cvref_t<Candidate>::PhysicalQuantity;
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>::SurfaceDependencies::RowField,
typename std::remove_cvref_t<Candidate>::CarrierField> &&
std::same_as<
typename std::remove_cvref_t<Candidate>::SurfaceDependencies::StateFieldTypes,
field::TypeList<typename std::remove_cvref_t<Candidate>::CarrierField>>;
template <typename Candidate, typename Field>
concept SingleFieldPressureSurfaceConstraintFor =
SingleFieldPressureSurfaceConstraint<Candidate> &&
std::same_as<typename std::remove_cvref_t<Candidate>::SurfaceDependencies::RowField, Field>;
/*
* Non-owning runtime bridge for a statically compiled pressure constraint.
* There is one function-pointer dispatch per complete row application;
* the concrete loop remains templated so EOS operations can be inlined.
*/
class PressureSurfaceConstraintView final {
public:
template <SingleFieldPressureSurfaceConstraint Constraint>
explicit PressureSurfaceConstraintView(const Constraint &constraint) noexcept
: m_constraint(std::addressof(constraint)),
m_applyResidualRows(&applyResidualRows<Constraint>),
m_applyJacobianRows(&applyJacobianRows<Constraint>),
m_descriptor(constraint.descriptor()) {
}
void ApplyResidualRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) const {
m_applyResidualRows(m_constraint, surfaceState, surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
m_applyJacobianRows(m_constraint, surfaceState, surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] surface::PressureSurfaceDescriptor descriptor() const noexcept {
return m_descriptor;
}
private:
using ApplyResidualRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
mfem::Vector &
);
using ApplyJacobianRowsFunction = void (*)(
const void *,
const mfem::Vector &,
const field::FieldBoundaryDofMap &,
const mfem::Vector &,
mfem::Vector &
);
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyResidualRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
mfem::Vector &rowResidual
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) {
const detail::SingleQuantitySurfaceState<CarrierQuantity> state{
eos::QuantityValue<CarrierQuantity>{surfaceState(surfaceIndex)}
};
rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) =
static_cast<const Constraint *>(constraint)->residual(state);
}
}
template <SingleFieldPressureSurfaceConstraint Constraint>
static void applyJacobianRows(
const void *constraint,
const mfem::Vector &surfaceState,
const field::FieldBoundaryDofMap &surfaceRows,
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) {
using CarrierQuantity = typename Constraint::CarrierQuantity;
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)}
};
const detail::SingleQuantitySurfaceState<CarrierQuantity> variation{
eos::QuantityValue<CarrierQuantity>{stateVariation(reducedDof)}
};
rowAction(reducedDof) = static_cast<const Constraint *>(constraint)->jacobianAction(state, variation);
}
}
const void *m_constraint;
ApplyResidualRowsFunction m_applyResidualRows;
ApplyJacobianRowsFunction m_applyJacobianRows;
surface::PressureSurfaceDescriptor m_descriptor;
};
struct PreparedSurfaceConstraintReport final {
bool cachedSurfaceState{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return cachedSurfaceState;
}
};
class PreparedPressureSurfaceConstraint final {
public:
PreparedPressureSurfaceConstraint(
field::FieldBoundaryDofMap surfaceRows,
const PressureSurfaceConstraintView constraint
)
: m_surfaceRows(std::move(surfaceRows)),
m_constraint(constraint),
m_surfaceState(m_surfaceRows.size()) {
}
[[nodiscard]] PreparedSurfaceConstraintReport Prepare(
const mfem::Vector &reducedState,
const bool stateChanged
) {
MFEM_VERIFY(
reducedState.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a state vector with the wrong size."
);
PreparedSurfaceConstraintReport report;
if (!m_isPrepared || stateChanged) {
for (int surfaceIndex = 0; surfaceIndex < m_surfaceRows.size(); ++surfaceIndex) {
const double value = reducedState(m_surfaceRows.reduced_dofs()[surfaceIndex]);
MFEM_VERIFY(std::isfinite(value), "The pressure surface constraint received non-finite state.");
m_surfaceState(surfaceIndex) = value;
}
report.cachedSurfaceState = true;
}
m_isPrepared = true;
return report;
}
void ApplyResidualRows(mfem::Vector &rowResidual) const {
VerifyPrepared();
MFEM_VERIFY(
rowResidual.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a residual vector with the wrong size."
);
m_constraint.ApplyResidualRows(m_surfaceState, m_surfaceRows, rowResidual);
}
void ApplyJacobianRows(
const mfem::Vector &stateVariation,
mfem::Vector &rowAction
) const {
VerifyPrepared();
MFEM_VERIFY(
stateVariation.Size() == m_surfaceRows.field_size() && rowAction.Size() == m_surfaceRows.field_size(),
"The pressure surface constraint received a Jacobian vector with the wrong size."
);
m_constraint.ApplyJacobianRows(m_surfaceState, m_surfaceRows, stateVariation, rowAction);
}
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared;
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetSurfaceRows() const noexcept {
return m_surfaceRows;
}
[[nodiscard]] surface::PressureSurfaceDescriptor GetPhysicalCondition() const noexcept {
return m_constraint.descriptor();
}
private:
void VerifyPrepared() const {
MFEM_VERIFY(m_isPrepared, "The pressure surface constraint must be prepared before row application.");
}
field::FieldBoundaryDofMap m_surfaceRows;
PressureSurfaceConstraintView m_constraint;
mfem::Vector m_surfaceState;
bool m_isPrepared{false};
};
} // namespace mean_field::operators