feat(libmeanfield): variadic refactor

also added normaliztion operator
This commit is contained in:
2026-09-06 10:15:00 -04:00
parent 71423d543f
commit 76818f2f82
63 changed files with 28794 additions and 1119 deletions

View File

@@ -0,0 +1,590 @@
module;
#include <algorithm>
#include <array>
#include <cmath>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_angular_momentum;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] bool is_vacuum_attribute(const int attribute) {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
void validate_finite_vector(const mfem::Vector &vector, const char *message) {
for (int index = 0; index < vector.Size(); ++index) {
MFEM_VERIFY(std::isfinite(vector(index)), message);
}
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
mfem::Vector &localVector
) {
MFEM_VERIFY(trueVector.Size() == finiteElementSpace.GetTrueVSize(), "True vector has the wrong size.");
localVector.SetSize(finiteElementSpace.GetVSize());
const mfem::Operator *prolongation = finiteElementSpace.GetProlongationMatrix();
if (prolongation != nullptr) {
prolongation->Mult(trueVector, localVector);
} else {
localVector = trueVector;
}
}
[[nodiscard]] const mfem::IntegrationRule &get_moment_of_inertia_rule(
const mean_field::fem::FEM &f,
const mfem::FiniteElement &densityElement,
const mfem::ElementTransformation &transformation
) {
using DensityField = mean_field::field::Field<mean_field::field::Density>;
MFEM_VERIFY(
densityElement.GetOrder() == mean_field::field::Density::Scalar::familyOrder,
"The angular-momentum element does not match the registered density field."
);
const mean_field::quadrature::Query query =
DensityField::make_query<mean_field::field::Density::Form::Quadrupole>(
mean_field::quadrature::QuadratureRole::discretization,
transformation.OrderW(),
std::array<int, 1>{2},
mean_field::utils::DOMAINS::STELLAR,
mean_field::quadrature::MappingKind::general
);
const auto resolution = f.quadratureFactory->get(query, transformation.GetGeometryType());
MFEM_VERIFY(
resolution.integration_rule != nullptr,
"The quadrature policy did not return an angular-momentum integration rule."
);
return *resolution.integration_rule;
}
void validate_shared_gravity_revisions(
const mean_field::operators::context::gravity_field::GravityFieldLinearizationContext &gravityContext,
const mean_field::operators::AngularMomentumDependencies &dependencies
) {
MFEM_VERIFY(
gravityContext.IsPrepared(),
"PreparedAngularMomentumOperator requires the shared gravity context to be prepared first."
);
const auto &revisions = gravityContext.GetRevisions();
MFEM_VERIFY(
revisions.discretization.value == dependencies.discretization.revision &&
revisions.density.value == dependencies.density.revision &&
revisions.displacement.value == dependencies.displacement.revision,
"PreparedAngularMomentumOperator received revisions that do not match the shared gravity context."
);
}
void validate_identity_transition(
const mean_field::operators::AngularMomentumDependencyStamp &prepared,
const mean_field::operators::AngularMomentumDependencyStamp &requested,
const char *message
) {
MFEM_VERIFY(prepared.identity == requested.identity || prepared.revision != requested.revision, message);
}
} // namespace
namespace mean_field::operators {
PreparedAngularMomentumOperator::PreparedAngularMomentumOperator(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext,
models::CompiledFixedAngularMomentum constraint
)
: m_fem(f),
m_domainMapper(domainMapper),
m_gravityContext(gravityContext),
m_constraint(std::move(constraint)) {
MFEM_VERIFY(m_fem.mesh != nullptr, "PreparedAngularMomentumOperator requires a mesh.");
MFEM_VERIFY(
m_fem.mesh->Dimension() == 3 && m_domainMapper.GetDimension() == 3,
"PreparedAngularMomentumOperator currently requires a three-dimensional mapped domain."
);
MFEM_VERIFY(
m_fem.densityFes != nullptr && m_fem.displacementFes != nullptr &&
m_fem.compactificationFes != nullptr && m_fem.compactificationCoordinate != nullptr &&
m_fem.quadratureFactory != nullptr,
"PreparedAngularMomentumOperator requires density, displacement, compactification, and quadrature data."
);
MFEM_VERIFY(
m_gravityContext.GetDensityMap().full_size() == m_fem.densityFes->GetTrueVSize() &&
m_gravityContext.GetDisplacementMap().full_size() == m_fem.displacementFes->GetTrueVSize(),
"PreparedAngularMomentumOperator received incompatible shared FieldDof maps."
);
m_densityVariationTrue.SetSize(m_gravityContext.GetDensityMap().full_size());
m_displacementVariationTrue.SetSize(m_gravityContext.GetDisplacementMap().full_size());
}
PreparedAngularMomentumReport PreparedAngularMomentumOperator::Prepare(
const double angularVelocity,
const AngularMomentumDependencies &dependencies
) {
MFEM_VERIFY(
std::isfinite(angularVelocity),
"PreparedAngularMomentumOperator requires a finite angular-velocity coordinate."
);
validate_shared_gravity_revisions(m_gravityContext, dependencies);
if (m_isPrepared) {
validate_identity_transition(
m_preparedDependencies.discretization,
dependencies.discretization,
"A new angular-momentum discretization identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.density,
dependencies.density,
"A new angular-momentum density identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.displacement,
dependencies.displacement,
"A new angular-momentum displacement identity must change its revision."
);
validate_identity_transition(
m_preparedDependencies.rotation,
dependencies.rotation,
"A new angular-momentum rotation identity must change its revision."
);
}
const bool rebuildStaticPlan =
!m_isPrepared || dependencies.discretization != m_preparedDependencies.discretization;
const bool refreshGeometry =
rebuildStaticPlan || dependencies.displacement != m_preparedDependencies.displacement;
const bool refreshDensity = rebuildStaticPlan || dependencies.density != m_preparedDependencies.density;
const bool updateAngularVelocity =
!m_isPrepared || dependencies.rotation != m_preparedDependencies.rotation ||
angularVelocity != m_angularVelocity;
m_isPrepared = false;
PreparedAngularMomentumReport report;
if (rebuildStaticPlan) {
BuildStaticPlan();
report.rebuiltStaticPlan = true;
}
if (refreshGeometry) {
RefreshGeometry(m_gravityContext.GetGeometryContext().GetDisplacementTrue());
report.refreshedGeometry = true;
}
if (refreshDensity) {
RefreshDensity(m_gravityContext.GetDensityTrue());
report.refreshedDensity = true;
}
if (updateAngularVelocity) {
m_angularVelocity = angularVelocity;
report.updatedAngularVelocity = true;
}
if (refreshGeometry || refreshDensity || updateAngularVelocity) {
AssembleResidual();
report.assembledResidual = true;
}
m_preparedDependencies = dependencies;
m_isPrepared = true;
return report;
}
void PreparedAngularMomentumOperator::BuildStaticPlan() {
m_elements.clear();
m_elements.reserve(m_fem.mesh->GetNE());
int localStellarElementCount = 0;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
MFEM_VERIFY(transformation != nullptr, "Angular-momentum preparation received a null transformation.");
if (is_vacuum_attribute(transformation->Attribute)) {
continue;
}
++localStellarElementCount;
m_elements.emplace_back();
ElementPAData &data = m_elements.back();
data.elementId = elementId;
data.densityDofTransformation = m_fem.densityFes->GetElementDofs(elementId, data.densityDofs);
data.displacementDofTransformation =
m_fem.displacementFes->GetElementVDofs(elementId, data.displacementDofs);
data.compactificationDofTransformation =
m_fem.compactificationFes->GetElementDofs(elementId, data.compactificationDofs);
const mfem::FiniteElement &densityElement = *m_fem.densityFes->GetFE(elementId);
const mfem::IntegrationRule &integrationRule =
get_moment_of_inertia_rule(m_fem, densityElement, *transformation);
data.quadraturePoints.resize(integrationRule.GetNPoints());
for (int quadraturePoint = 0; quadraturePoint < integrationRule.GetNPoints(); ++quadraturePoint) {
QuadraturePointData &point = data.quadraturePoints[quadraturePoint];
point.integrationPoint = integrationRule.IntPoint(quadraturePoint);
point.densityShape.SetSize(densityElement.GetDof());
densityElement.CalcShape(point.integrationPoint, point.densityShape);
}
}
int globalStellarElementCount = 0;
MPI_Allreduce(
&localStellarElementCount,
&globalStellarElementCount,
1,
MPI_INT,
MPI_SUM,
m_fem.mesh->GetComm()
);
MFEM_VERIFY(globalStellarElementCount > 0, "PreparedAngularMomentumOperator found no stellar elements.");
}
void PreparedAngularMomentumOperator::RefreshGeometry(const mfem::Vector &displacement) {
MFEM_VERIFY(
displacement.Size() == m_fem.displacementFes->GetTrueVSize(),
"Angular-momentum geometry has the wrong displacement size."
);
validate_finite_vector(displacement, "Angular-momentum geometry contains a non-finite displacement.");
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, displacement, displacementLocal);
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
for (ElementPAData &data : m_elements) {
displacementLocal.GetSubVector(data.displacementDofs, data.baseDisplacement);
m_fem.compactificationCoordinate->GetSubVector(data.compactificationDofs, data.compactification);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(data.baseDisplacement);
}
if (data.compactificationDofTransformation != nullptr) {
data.compactificationDofTransformation->InvTransformPrimal(data.compactification);
}
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
const mapping::ElementDisplacementData displacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementCompactificationData compactificationData(
compactificationElement,
data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = displacementData,
.compactification = compactificationData
};
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
for (QuadraturePointData &point : data.quadraturePoints) {
const mapping::MappingStatus status = m_domainMapper.EvaluateVolume(
mappingData,
*transformation,
point.integrationPoint,
workspace,
point.mappingContext
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid && !point.mappingContext.mapping.compactified,
"Mapped angular-momentum geometry is invalid. Element: " << data.elementId
);
point.cylindricalRadiusSquared =
CylindricalRadiusSquared(point.mappingContext.mapping.physical_position);
}
}
}
void PreparedAngularMomentumOperator::RefreshDensity(const mfem::Vector &density) {
MFEM_VERIFY(
density.Size() == m_fem.densityFes->GetTrueVSize(),
"Angular-momentum density has the wrong size."
);
validate_finite_vector(density, "Angular-momentum density contains a non-finite value.");
mfem::Vector densityLocal;
true_to_local(*m_fem.densityFes, density, densityLocal);
mfem::Vector elementDensity;
for (ElementPAData &data : m_elements) {
densityLocal.GetSubVector(data.densityDofs, elementDensity);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(elementDensity);
}
for (QuadraturePointData &point : data.quadraturePoints) {
point.density = elementDensity * point.densityShape;
MFEM_VERIFY(std::isfinite(point.density), "Angular-momentum quadrature density is non-finite.");
}
}
}
void PreparedAngularMomentumOperator::AssembleResidual() {
double localMomentOfInertia = 0.0;
for (const ElementPAData &data : m_elements) {
for (const QuadraturePointData &point : data.quadraturePoints) {
localMomentOfInertia += point.density * point.cylindricalRadiusSquared *
point.mappingContext.quadrature.weight;
}
}
m_momentOfInertia = GlobalSum(localMomentOfInertia);
MFEM_VERIFY(
std::isfinite(m_momentOfInertia) && m_momentOfInertia >= 0.0,
"PreparedAngularMomentumOperator assembled an invalid moment of inertia."
);
m_currentAngularMomentum = m_angularVelocity * m_momentOfInertia;
m_cachedResidual.SetSize(1);
m_cachedResidual(0) = m_currentAngularMomentum - m_constraint.targetAngularMomentum().value();
++m_preparationCount;
}
void PreparedAngularMomentumOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
++m_residualApplicationCount;
}
double PreparedAngularMomentumOperator::EvaluateDensityMomentActionLocal(
const mfem::Vector &densityVariation
) const {
MFEM_VERIFY(
densityVariation.Size() == m_fem.densityFes->GetTrueVSize(),
"Angular-momentum density action has the wrong true-vector size."
);
true_to_local(*m_fem.densityFes, densityVariation, m_densityVariationLocal);
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
m_densityVariationLocal.GetSubVector(data.densityDofs, m_elementDensityVariation);
if (data.densityDofTransformation != nullptr) {
data.densityDofTransformation->InvTransformPrimal(m_elementDensityVariation);
}
for (const QuadraturePointData &point : data.quadraturePoints) {
localAction += (m_elementDensityVariation * point.densityShape) *
point.cylindricalRadiusSquared * point.mappingContext.quadrature.weight;
}
}
return localAction;
}
double PreparedAngularMomentumOperator::EvaluateDisplacementMomentActionLocal(
const mfem::Vector &displacementVariation
) const {
MFEM_VERIFY(
displacementVariation.Size() == m_fem.displacementFes->GetTrueVSize(),
"Angular-momentum displacement action has the wrong true-vector size."
);
true_to_local(*m_fem.displacementFes, displacementVariation, m_displacementVariationLocal);
mapping::DomainMapper::Workspace workspace(m_fem.mesh->Dimension());
mapping::VolumeMappingVariation variation;
double localAction = 0.0;
for (const ElementPAData &data : m_elements) {
m_displacementVariationLocal.GetSubVector(data.displacementDofs, m_elementDisplacementVariation);
if (data.displacementDofTransformation != nullptr) {
data.displacementDofTransformation->InvTransformPrimal(m_elementDisplacementVariation);
}
const mfem::FiniteElement &displacementElement = *m_fem.displacementFes->GetFE(data.elementId);
const mfem::FiniteElement &compactificationElement = *m_fem.compactificationFes->GetFE(data.elementId);
const mapping::ElementDisplacementData baseDisplacementData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, data.baseDisplacement);
const mapping::ElementDisplacementData directionData =
mapping::ElementDisplacementDataFromElementVDofs(displacementElement, m_elementDisplacementVariation);
const mapping::ElementCompactificationData compactificationData(
compactificationElement,
data.compactification
);
const mapping::ElementMappingData mappingData{
.displacement = baseDisplacementData,
.compactification = compactificationData
};
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(data.elementId);
for (const QuadraturePointData &point : data.quadraturePoints) {
const mapping::MappingStatus status = m_domainMapper.EvaluateVolumeVariation(
mappingData,
directionData,
*transformation,
point.integrationPoint,
point.mappingContext,
workspace,
variation
);
MFEM_VERIFY(
status == mapping::MappingStatus::valid,
"Mapped angular-momentum variation is invalid. Element: " << data.elementId
);
const double radiusSquaredVariation = CylindricalRadiusSquaredVariation(
point.mappingContext.mapping.physical_position,
variation.mapping.physical_position_variation
);
localAction += point.density *
(radiusSquaredVariation * point.mappingContext.quadrature.weight +
point.cylindricalRadiusSquared * variation.weight_variation);
}
}
return localAction;
}
void PreparedAngularMomentumOperator::ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size(),
"Angular-momentum density action has the wrong reduced size."
);
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity * GlobalSum(EvaluateDensityMomentActionLocal(m_densityVariationTrue));
++m_actionStatistics.densityApplications;
}
void PreparedAngularMomentumOperator::ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Angular-momentum displacement action has the wrong reduced size."
);
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity *
GlobalSum(EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue));
++m_actionStatistics.displacementApplications;
}
void PreparedAngularMomentumOperator::ApplyAngularVelocityJacobianAction(
const double angularVelocityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
action.SetSize(1);
action(0) = m_momentOfInertia * angularVelocityVariation;
++m_actionStatistics.angularVelocityApplications;
}
void PreparedAngularMomentumOperator::ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
const double angularVelocityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
densityVariation.Size() == m_gravityContext.GetDensityMap().reduced_size() &&
displacementVariation.Size() == m_gravityContext.GetDisplacementMap().reduced_size(),
"Angular-momentum complete action has incompatible reduced coordinates."
);
validate_finite_vector(densityVariation, "Angular-momentum density direction is non-finite.");
validate_finite_vector(displacementVariation, "Angular-momentum displacement direction is non-finite.");
MFEM_VERIFY(std::isfinite(angularVelocityVariation), "Angular-velocity direction is non-finite.");
m_gravityContext.GetDensityMap().scatter(densityVariation, m_densityVariationTrue);
m_gravityContext.GetDisplacementMap().scatter(displacementVariation, m_displacementVariationTrue);
const double localMomentAction = EvaluateDensityMomentActionLocal(m_densityVariationTrue) +
EvaluateDisplacementMomentActionLocal(m_displacementVariationTrue);
action.SetSize(1);
action(0) = m_angularVelocity * GlobalSum(localMomentAction) +
m_momentOfInertia * angularVelocityVariation;
++m_actionStatistics.completeApplications;
}
double PreparedAngularMomentumOperator::CylindricalRadiusSquared(
const mfem::Vector &physicalPosition
) const noexcept {
const auto &axis = m_constraint.specification().axis();
const auto &center = m_constraint.specification().center();
double radiusSquared = 0.0;
double axialPosition = 0.0;
for (int component = 0; component < 3; ++component) {
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
radiusSquared += relative * relative;
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
}
return std::max(0.0, radiusSquared - axialPosition * axialPosition);
}
double PreparedAngularMomentumOperator::CylindricalRadiusSquaredVariation(
const mfem::Vector &physicalPosition,
const mfem::Vector &physicalPositionVariation
) const noexcept {
const auto &axis = m_constraint.specification().axis();
const auto &center = m_constraint.specification().center();
double relativeDotVariation = 0.0;
double axialPosition = 0.0;
double axialVariation = 0.0;
for (int component = 0; component < 3; ++component) {
const double relative = physicalPosition(component) - center[static_cast<std::size_t>(component)];
relativeDotVariation += relative * physicalPositionVariation(component);
axialPosition += axis[static_cast<std::size_t>(component)] * relative;
axialVariation += axis[static_cast<std::size_t>(component)] * physicalPositionVariation(component);
}
return 2.0 * (relativeDotVariation - axialPosition * axialVariation);
}
double PreparedAngularMomentumOperator::GlobalSum(const double localValue) const {
double globalValue = 0.0;
MPI_Allreduce(&localValue, &globalValue, 1, MPI_DOUBLE, MPI_SUM, m_fem.mesh->GetComm());
return globalValue;
}
bool PreparedAngularMomentumOperator::IsPrepared() const noexcept {
if (!m_isPrepared || !m_gravityContext.IsPrepared()) {
return false;
}
const auto &revisions = m_gravityContext.GetRevisions();
return revisions.discretization.value == m_preparedDependencies.discretization.revision &&
revisions.density.value == m_preparedDependencies.density.revision &&
revisions.displacement.value == m_preparedDependencies.displacement.revision;
}
double PreparedAngularMomentumOperator::GetMomentOfInertia() const {
VerifyPrepared();
return m_momentOfInertia;
}
double PreparedAngularMomentumOperator::GetAngularVelocity() const {
VerifyPrepared();
return m_angularVelocity;
}
double PreparedAngularMomentumOperator::GetCurrentAngularMomentum() const {
VerifyPrepared();
return m_currentAngularMomentum;
}
double PreparedAngularMomentumOperator::GetTargetAngularMomentum() const noexcept {
return m_constraint.targetAngularMomentum().value();
}
physics::RigidRotation PreparedAngularMomentumOperator::GetRotation() const {
VerifyPrepared();
return m_constraint.makeRotation(m_angularVelocity);
}
AngularMomentumConstraintReport PreparedAngularMomentumOperator::GetConstraintReport() const {
VerifyPrepared();
const double target = GetTargetAngularMomentum();
const double residual = m_currentAngularMomentum - target;
return {
.targetAngularMomentum = target,
.achievedAngularMomentum = m_currentAngularMomentum,
.momentOfInertia = m_momentOfInertia,
.angularVelocity = m_angularVelocity,
.dimensionalResidual = residual,
.scaledResidual = residual / std::max(std::abs(target), 1.0e-300)
};
}
std::uint64_t PreparedAngularMomentumOperator::GetPreparationCount() const noexcept {
return m_preparationCount;
}
std::uint64_t PreparedAngularMomentumOperator::GetResidualApplicationCount() const noexcept {
return m_residualApplicationCount;
}
const PreparedAngularMomentumActionStatistics &
PreparedAngularMomentumOperator::GetActionStatistics() const noexcept {
return m_actionStatistics;
}
const models::CompiledFixedAngularMomentum &
PreparedAngularMomentumOperator::GetCompiledConstraint() const noexcept {
return m_constraint;
}
void PreparedAngularMomentumOperator::VerifyPrepared() const {
MFEM_VERIFY(IsPrepared(), "The angular-momentum invariant must be prepared before application.");
}
} // namespace mean_field::operators

View File

@@ -1,223 +0,0 @@
module;
#include <array>
#include <cmath>
#include <cstdint>
#include <memory>
#include <utility>
#include <mfem.hpp>
module mean_field;
import :operators.prepared_central_density_stellar_equilibrium;
namespace {
using DomainSchema = mean_field::utils::domain::CoreEnvelopeVacuumDomainSchema;
using PhysicalForm = mean_field::utils::blocks::surface_deformed_stellar_equilibrium_form;
using BorderedForm = mean_field::operators::CentralDensityStellarEquilibriumForm;
[[nodiscard]] std::array<
int,
BorderedForm::value_block_count>
make_value_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
std::array<int, BorderedForm::value_block_count> sizes{};
for (int block = 0; block < PhysicalForm::value_block_count; ++block) {
sizes[block] = physicalLayout.value_offsets()[block + 1] - physicalLayout.value_offsets()[block];
}
sizes[PhysicalForm::value_block_count] = 1;
return sizes;
}
[[nodiscard]] std::array<
int,
BorderedForm::residual_block_count>
make_residual_sizes(const mean_field::operators::StellarEquilibriumLayout &physicalLayout) {
std::array<int, BorderedForm::residual_block_count> sizes{};
for (int block = 0; block < PhysicalForm::residual_block_count; ++block) {
sizes[block] = physicalLayout.residual_offsets()[block + 1] - physicalLayout.residual_offsets()[block];
}
sizes[PhysicalForm::residual_block_count] = 1;
return sizes;
}
[[nodiscard]] mean_field::operators::CentralDensityDependencies
make_phase_dependencies(const mean_field::operators::StellarEquilibriumDependencies &dependencies) {
return {.enthalpy = {.identity = dependencies.enthalpy.identity, .revision = dependencies.enthalpy.revision}};
}
void validate_finite_scalar(
const double value,
const char *message
) {
MFEM_VERIFY(std::isfinite(value), message);
}
} // namespace
namespace mean_field::operators {
field::FieldPointDofMap PreparedCentralDensityStellarEquilibriumOperator::MakeCenterDofMap(const fem::FEM &f) {
MFEM_VERIFY(
f.mesh != nullptr && f.enthalpyFes != nullptr,
"The central-density phase requires the mesh and enthalpy finite-element space."
);
const field::FieldDofMap enthalpyMap = field::make_field_dof_map<field::Enthalpy, DomainSchema>(*f.enthalpyFes);
mfem::Vector origin(f.mesh->SpaceDimension());
origin = 0.0;
return field::make_field_point_dof_map<field::Enthalpy>(*f.enthalpyFes, enthalpyMap, origin, 1.0e-12);
}
PreparedCentralDensityStellarEquilibriumOperator::PreparedCentralDensityStellarEquilibriumOperator(
fem::FEM &f,
std::unique_ptr<PreparedStellarEquilibriumOperator> physicalOperator,
models::CompiledFixedCentralDensity centralDensity,
field::FieldPointDofMap centerDof
)
: mfem::Operator(
physicalOperator->Height() + 1,
physicalOperator->Width() + 1
),
m_physicalOperator(std::move(physicalOperator)),
m_centralDensity(std::move(centralDensity)),
m_phaseConstraint(
std::move(centerDof),
f.mesh->GetComm()
),
m_rootManifest(
make_value_sizes(m_physicalOperator->GetLayout()),
make_residual_sizes(m_physicalOperator->GetLayout()),
m_physicalOperator->GetTargetMass(),
m_physicalOperator->GetSurfaceConstraintOperator().GetPhysicalCondition().targetPressure,
m_physicalOperator->GetSurfaceConstraintOperator().GetSurfaceRows().size(),
CentralDensityManifestInput{
.targetDensity = m_centralDensity.targetDensity().value(),
.targetEnthalpy = m_centralDensity.targetEnthalpy().value(),
.centerDofCount = 1
}
) {
MFEM_VERIFY(
Width() == m_rootManifest.layout().value_offsets().Last() &&
Height() == m_rootManifest.layout().residual_offsets().Last(),
"The central-density bordered root has inconsistent dimensions."
);
}
PreparedCentralDensityStellarEquilibriumReport PreparedCentralDensityStellarEquilibriumOperator::Prepare(
const mfem::Vector &state,
const StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
MFEM_VERIFY(state.Size() == Width(), "The central-density bordered root received a state with the wrong size.");
const auto stateView = m_rootManifest.stateView(state);
const mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector border = stateView.block(utils::blocks::fixed_central_density_phase.central_value_term);
validate_finite_scalar(border(0), "The central-density bordered root received a non-finite border value.");
mfem::Vector physicalState(const_cast<mfem::real_t *>(state.GetData()), m_physicalOperator->Width());
m_isPrepared = false;
PreparedCentralDensityStellarEquilibriumReport report;
report.physical = m_physicalOperator->Prepare(physicalState, dependencies, rotation);
report.phase =
m_phaseConstraint.Prepare(m_centralDensity, enthalpy, border(0), make_phase_dependencies(dependencies));
if (report.physical.assembledResidual || report.phase.DidAnyWork() || m_cachedResidual.Size() != Height()) {
AssembleResidual();
report.assembledResidual = true;
}
m_isPrepared = true;
return report;
}
void PreparedCentralDensityStellarEquilibriumOperator::AssembleResidual() {
mfem::Vector physicalResidual;
m_physicalOperator->BuildResidual(physicalResidual);
m_cachedResidual.SetSize(Height());
m_cachedResidual = 0.0;
mfem::Vector physicalDestination(m_cachedResidual.GetData(), physicalResidual.Size());
physicalDestination = physicalResidual;
const auto residualView = m_rootManifest.residualView(m_cachedResidual);
mfem::Vector enthalpyResidual = residualView.block(utils::blocks::enthalpy_field.specific_term);
mfem::Vector phaseResidual = residualView.block(utils::blocks::fixed_central_density_phase.central_value_term);
m_phaseConstraint.AddResidual(enthalpyResidual, phaseResidual);
}
void PreparedCentralDensityStellarEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
VerifyPrepared();
residual = m_cachedResidual;
}
void PreparedCentralDensityStellarEquilibriumOperator::Mult(
const mfem::Vector &direction,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
direction.Size() == Width(), "The central-density bordered root received a direction with the wrong size."
);
const auto directionView = m_rootManifest.directionView(direction);
const mfem::Vector enthalpyDirection = directionView.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector borderDirection =
directionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
validate_finite_scalar(
borderDirection(0), "The central-density bordered root received a non-finite border direction."
);
mfem::Vector physicalDirection(const_cast<mfem::real_t *>(direction.GetData()), m_physicalOperator->Width());
mfem::Vector physicalAction;
m_physicalOperator->Mult(physicalDirection, physicalAction);
action.SetSize(Height());
action = 0.0;
mfem::Vector physicalDestination(action.GetData(), physicalAction.Size());
physicalDestination = physicalAction;
const auto actionView = m_rootManifest.residualView(action);
mfem::Vector enthalpyAction = actionView.block(utils::blocks::enthalpy_field.specific_term);
mfem::Vector phaseAction = actionView.block(utils::blocks::fixed_central_density_phase.central_value_term);
m_phaseConstraint.ApplyJacobian(
{.enthalpyVariation = enthalpyDirection, .borderVariation = borderDirection(0)},
{.enthalpyAction = enthalpyAction, .phaseAction = phaseAction}
);
}
bool PreparedCentralDensityStellarEquilibriumOperator::IsPrepared() const noexcept {
return m_isPrepared && m_physicalOperator->IsPrepared() && m_phaseConstraint.IsPrepared();
}
const CentralDensityStellarEquilibriumLayout &
PreparedCentralDensityStellarEquilibriumOperator::GetLayout() const noexcept {
return m_rootManifest.layout();
}
const CentralDensityStellarEquilibriumRootManifest &
PreparedCentralDensityStellarEquilibriumOperator::GetRootManifest() const noexcept {
return m_rootManifest;
}
const PreparedStellarEquilibriumOperator &
PreparedCentralDensityStellarEquilibriumOperator::GetPhysicalOperator() const noexcept {
return *m_physicalOperator;
}
const PreparedCentralDensityConstraint &
PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityConstraint() const noexcept {
return m_phaseConstraint;
}
RootConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetFixedMassReport() const {
VerifyPrepared();
return m_physicalOperator->GetFixedMassReport();
}
CentralDensityConstraintReport PreparedCentralDensityStellarEquilibriumOperator::GetCentralDensityReport() const {
VerifyPrepared();
return m_phaseConstraint.GetConstraintReport();
}
void PreparedCentralDensityStellarEquilibriumOperator::VerifyPrepared() const {
MFEM_VERIFY(IsPrepared(), "The central-density bordered root must be prepared before application.");
}
} // namespace mean_field::operators

View File

@@ -903,6 +903,47 @@ namespace mean_field::operators {
++m_algebraicJacobianStatistics.bernoulliConstantApplications;
}
void PreparedHydrostaticEquilibriumOperator::ApplyRotationAmplitudeJacobianAction(
const double fractionalAngularVelocityVariation,
mfem::Vector &action
) const {
VerifyPrepared();
MFEM_VERIFY(
std::isfinite(fractionalAngularVelocityVariation),
"Prepared hydrostatic rotation-amplitude Jacobian received a non-finite variation."
);
mfem::Vector localAction(m_fem.enthalpyFes->GetVSize());
localAction = 0.0;
mfem::Vector weightedVariation;
mfem::Vector elementAction;
for (const ElementPAData &data : m_elements) {
const int quadraturePointCount = data.quadratureWeights.Size();
MFEM_VERIFY(
data.rotationPotential.Size() == quadraturePointCount,
"Prepared hydrostatic rotation-amplitude Jacobian has stale rotation data."
);
weightedVariation.SetSize(quadraturePointCount);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
weightedVariation(quadraturePoint) =
-2.0 * fractionalAngularVelocityVariation * data.quadratureWeights(quadraturePoint) *
data.rotationPotential(quadraturePoint);
}
elementAction.SetSize(data.enthalpyDofs.Size());
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);
if (data.enthalpyDofTransformation != nullptr) {
data.enthalpyDofTransformation->TransformDual(elementAction);
}
localAction.AddElementVector(data.enthalpyDofs, elementAction);
}
local_to_true(*m_fem.enthalpyFes, localAction, m_fullEnthalpyAction);
action.SetSize(m_context.GetEnthalpyMap().reduced_size());
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, action);
++m_algebraicJacobianStatistics.rotationAmplitudeApplications;
}
void PreparedHydrostaticEquilibriumOperator::ApplyAlgebraicJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &gravityPotentialVariation,

View File

@@ -356,8 +356,11 @@ namespace mean_field::operators {
m_rootManifest(
constructionData.valueSizes,
constructionData.residualSizes,
fixedMassConstraint.targetMass().value(),
surfaceConstraint.descriptor().targetPressure,
StellarEquilibriumSpecificationModel{
equationOfState,
surface::Isobaric{
dimensions::PressureValue{surfaceConstraint.descriptor().targetPressure}},
fixedMassConstraint.specification()},
constructionData.pressureSurfaceRows.size()
),
m_gravityStateOffsets(constructionData.gravityStateOffsets),
@@ -431,6 +434,7 @@ namespace mean_field::operators {
m_fullMechanicalAction.SetSize(m_domainDeformation.volumeDisplacementSize());
m_surfaceShapeAction.SetSize(m_domainDeformation.parameterCount());
m_pullbackDerivativeAction.SetSize(m_domainDeformation.parameterCount());
m_densityVolumeIntegralAction.SetSize(1);
m_gravityState = 0.0;
m_gravityDirection = 0.0;
@@ -441,6 +445,7 @@ namespace mean_field::operators {
m_fullMechanicalAction = 0.0;
m_surfaceShapeAction = 0.0;
m_pullbackDerivativeAction = 0.0;
m_densityVolumeIntegralAction = 0.0;
}
PreparedStellarEquilibriumReport PreparedStellarEquilibriumOperator::Prepare(
@@ -494,13 +499,13 @@ namespace mean_field::operators {
const auto rootState = m_rootManifest.stateView(state);
const mfem::Vector reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
const mfem::Vector surfaceDeformationParameters =
const auto reducedDensity = rootState.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationParameters =
rootState.block(utils::blocks::surface_deformation_field.parameters_term);
const mfem::Vector gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
const mfem::Vector gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
const mfem::Vector reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector bernoulli =
const auto gravityGradient = rootState.block(utils::blocks::gravity_field.gradient_term);
const auto gravityPotential = rootState.block(utils::blocks::gravity_field.poisson_term);
const auto reducedEnthalpy = rootState.block(utils::blocks::enthalpy_field.specific_term);
const auto bernoulli =
rootState.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
const bool generatedGeometryChanged =
@@ -633,13 +638,13 @@ namespace mean_field::operators {
const auto rootDirection = m_rootManifest.directionView(direction);
const mfem::Vector reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
const mfem::Vector surfaceDeformationDirection =
const auto reducedDensityDirection = rootDirection.block(utils::blocks::density_field.mass_term);
const auto surfaceDeformationDirection =
rootDirection.block(utils::blocks::surface_deformation_field.parameters_term);
const mfem::Vector gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
const mfem::Vector gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
const mfem::Vector reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
const mfem::Vector bernoulliDirection =
const auto gravityGradientDirection = rootDirection.block(utils::blocks::gravity_field.gradient_term);
const auto gravityPotentialDirection = rootDirection.block(utils::blocks::gravity_field.poisson_term);
const auto reducedEnthalpyDirection = rootDirection.block(utils::blocks::enthalpy_field.specific_term);
const auto bernoulliDirection =
rootDirection.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
m_domainDeformation.applyJacobian(
@@ -794,6 +799,41 @@ namespace mean_field::operators {
return m_massNormalizationOperator;
}
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralDensityAction(
const mfem::Vector &densityDirection
) const {
VerifyPrepared();
m_massNormalizationOperator.ApplyDensityJacobianAction(
densityDirection,
m_densityVolumeIntegralAction
);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,
"The density-volume integral must produce one global scalar."
);
return m_densityVolumeIntegralAction(0);
}
double PreparedStellarEquilibriumOperator::ApplyDensityVolumeIntegralSurfaceShapeAction(
const mfem::Vector &surfaceShapeDirection
) const {
VerifyPrepared();
m_domainDeformation.applyJacobian(
m_surfaceDeformationParameters,
surfaceShapeDirection,
m_volumeDisplacementDirection
);
m_massNormalizationOperator.ApplyDisplacementJacobianAction(
m_volumeDisplacementDirection,
m_densityVolumeIntegralAction
);
MFEM_VERIFY(
m_densityVolumeIntegralAction.Size() == 1,
"The density-volume shape derivative must produce one global scalar."
);
return m_densityVolumeIntegralAction(0);
}
const PreparedPressureSurfaceConstraint &
PreparedStellarEquilibriumOperator::GetSurfaceConstraintOperator() const noexcept {
return m_surfaceConstraintOperator;

View File

@@ -3,6 +3,7 @@ module;
#include <algorithm>
#include <cmath>
#include <limits>
#include <numbers>
#include <stdexcept>
#include <mfem.hpp>
@@ -133,21 +134,15 @@ namespace {
namespace mean_field::seed::detail {
ProjectedRadialFields projectRadialFields(
const equilibrium::StellarDiscretization &discretization,
fem::FEM &finiteElementModel,
const RadialProfile &profile,
const dimensions::MassValue targetMass,
const dimensions::PressureValue targetSurfacePressure,
const StellarEquilibriumProjectionOptions &options
) {
validate_profile(profile);
if (!std::isfinite(options.surfaceRadiusRelativeTolerance) || options.surfaceRadiusRelativeTolerance < 0.0) {
throw std::invalid_argument("The surface-radius projection tolerance must be finite and nonnegative.");
}
if (targetSurfacePressure.value() != 0.0) {
throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface.");
}
fem::FEM &finiteElementModel = discretization.finiteElementModel();
const SurfaceRadiusRange surfaceRadius = measure_surface_radius(finiteElementModel);
const double targetRadius = profile.stellarRadius.value();
const double comparisonScale = std::max({targetRadius, surfaceRadius.maximum, 1.0e-300});
@@ -185,6 +180,20 @@ namespace mean_field::seed::detail {
const physics::GravitySolution gravitySolution =
physics::solve_gravity_field(finiteElementModel, options.gravity, densityField, displacementField);
double radialMomentIntegral = 0.0;
for (int index = 0; index + 1 < profile.radius.Size(); ++index) {
const double leftRadius = profile.radius(index);
const double rightRadius = profile.radius(index + 1);
const double leftIntegrand = profile.density(index) * std::pow(leftRadius, 4);
const double rightIntegrand = profile.density(index + 1) * std::pow(rightRadius, 4);
radialMomentIntegral +=
0.5 * (rightRadius - leftRadius) * (leftIntegrand + rightIntegrand);
}
const double sphericalMomentOfInertia = (8.0 * std::numbers::pi / 3.0) * radialMomentIntegral;
if (!std::isfinite(sphericalMomentOfInertia) || sphericalMomentOfInertia <= 0.0) {
throw std::runtime_error("The radial profile has no finite, positive moment of inertia.");
}
const field::FieldDofGridFunctionAdapter densityAdapter =
field::make_field_dof_grid_function_adapter<field::Density, DomainSchema>(*finiteElementModel.densityFes);
const field::FieldDofGridFunctionAdapter enthalpyAdapter =
@@ -203,7 +212,8 @@ namespace mean_field::seed::detail {
.gravityGradient = gravityFluxAdapter.gather(gravitySolution.gradPhi),
.gravityPotential = gravityPotentialAdapter.gather(gravitySolution.phi),
.specificEnthalpy = enthalpyAdapter.gather(enthalpyField),
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius
.bernoulliConstant = -utils::G * targetMass.value() / targetRadius,
.sphericalMomentOfInertia = sphericalMomentOfInertia
};
}
} // namespace mean_field::seed::detail

View File

@@ -1,12 +1,16 @@
module;
#include <concepts>
#include <memory>
#include <stdexcept>
#include <type_traits>
#include <utility>
export module mean_field:equilibrium.stellar_discretization;
export import :fem;
export import :mapping.domain_mapper;
export import :normalization.physical_riesz;
export namespace mean_field::equilibrium {
/*
@@ -18,26 +22,78 @@ export namespace mean_field::equilibrium {
* mutable field workspaces. Separating those workspaces is a prerequisite
* for shared discretization ownership by solved Structure objects.
*/
class StellarDiscretization final {
template <normalization::NormalizationPrescription Normalization>
class StellarDiscretizationFor final {
public:
explicit StellarDiscretization(fem::FEM &finiteElementModel)
: StellarDiscretization(
using NormalizationPrescriptionType = std::remove_cvref_t<Normalization>;
explicit StellarDiscretizationFor(fem::FEM &finiteElementModel)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel)
RequireDomainMapper(finiteElementModel),
normalization::Unnormalized{}
) {
}
StellarDiscretization(
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper
)
requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized>
: StellarDiscretizationFor(
finiteElementModel,
domainMapper,
normalization::Unnormalized{}
) {
}
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&
) requires std::same_as<NormalizationPrescriptionType, normalization::Unnormalized> = delete;
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
NormalizationPrescriptionType normalizationPrescription
)
: StellarDiscretizationFor(
finiteElementModel,
RequireDomainMapper(finiteElementModel),
std::move(normalizationPrescription)
) {
}
StellarDiscretizationFor(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
NormalizationPrescriptionType normalizationPrescription
)
: m_finiteElementModel(std::addressof(finiteElementModel)),
m_domainMapper(std::addressof(domainMapper)) {
m_domainMapper(std::addressof(domainMapper)),
m_normalizationPrescription(std::move(normalizationPrescription)) {
if (!finiteElementModel.okay()) {
throw std::invalid_argument("A stellar discretization requires a complete finite-element model.");
}
}
StellarDiscretizationFor(
fem::FEM &,
mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
StellarDiscretizationFor(
fem::FEM &,
const mapping::DomainMapper &&,
NormalizationPrescriptionType
) = delete;
[[nodiscard]] fem::FEM &finiteElementModel() const noexcept {
return *m_finiteElementModel;
}
@@ -46,6 +102,10 @@ export namespace mean_field::equilibrium {
return *m_domainMapper;
}
[[nodiscard]] const NormalizationPrescriptionType &normalizationPrescription() const noexcept {
return m_normalizationPrescription;
}
[[nodiscard]] bool isCurrent() const noexcept {
return m_finiteElementModel != nullptr && m_domainMapper != nullptr && m_finiteElementModel->okay();
}
@@ -60,5 +120,64 @@ export namespace mean_field::equilibrium {
fem::FEM *m_finiteElementModel;
const mapping::DomainMapper *m_domainMapper;
NormalizationPrescriptionType m_normalizationPrescription;
};
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor(fem::FEM &, const mapping::DomainMapper &, Normalization)
-> StellarDiscretizationFor<std::remove_cvref_t<Normalization>>;
using StellarDiscretization = StellarDiscretizationFor<normalization::Unnormalized>;
template <typename Candidate> struct IsStellarDiscretization : std::false_type { };
template <normalization::NormalizationPrescription Normalization>
struct IsStellarDiscretization<StellarDiscretizationFor<Normalization>> : std::true_type { };
template <typename Candidate>
concept StellarDiscretizationType = IsStellarDiscretization<std::remove_cvref_t<Candidate>>::value;
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
[[nodiscard]] auto makeStellarDiscretization(
fem::FEM &finiteElementModel,
const mapping::DomainMapper &domainMapper,
Normalization normalizationPrescription
) {
return StellarDiscretizationFor<std::remove_cvref_t<Normalization>>{
finiteElementModel,
domainMapper,
std::move(normalizationPrescription)
};
}
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
fem::FEM &,
mapping::DomainMapper &&,
Normalization
) = delete;
template <normalization::NormalizationPrescription Normalization>
StellarDiscretizationFor<std::remove_cvref_t<Normalization>>
makeStellarDiscretization(
fem::FEM &,
const mapping::DomainMapper &&,
Normalization
) = delete;
} // namespace mean_field::equilibrium

View File

@@ -231,6 +231,28 @@ export namespace mean_field::field {
static_assert(constraintsAreValid);
};
// Scalar angular speed generated by FixedAngularMomentum. The axis and
// center belong to the compiled invariant, so the nonlinear coordinate
// contains only the signed speed along that fixed unit axis.
struct AngularVelocity {
static constexpr std::string_view name = "angular_velocity";
using PhysicalQuantity = dimensions::quantity::AngularVelocity;
using Support = NonSpatialSupport;
struct Scalar final : GlobalScalarQ {
static constexpr std::string_view symbol = "Omega";
};
using Quantities = TypeList<Scalar>;
using Constraints = TypeList<>;
using FormList = TypeList<>;
static constexpr bool constraintsAreValid = validate_constraints(Constraints{});
static_assert(constraintsAreValid);
};
// Solver border generated by FixedCentralDensity. This is deliberately a
// non-spatial numerical coordinate rather than a physical stellar field.
struct CentralDensityBorder {

View File

@@ -27,6 +27,7 @@ export import :quadrature.mfem;
export import :solver.fields;
export import :solver.preconditioning_diagnostics;
export import :preconditioning;
export import :normalization;
export import :utils.blocks;
export import :operators.gravity_field;
export import :operators.gravity_field_jacobian;
@@ -60,6 +61,7 @@ export import :model.structure.polytropic;
export import :model.specifications;
export import :model.typed_stellar;
export import :model.compiled_fixed_mass;
export import :model.compiled_fixed_angular_momentum;
export import :model.compiled_fixed_central_density;
export import :eos.quantities;
export import :eos.relations;
@@ -85,11 +87,13 @@ export import :model.stellar;
export import :operators.root_manifest;
export import :operators.prepared_constraint;
export import :operators.prepared_mass_normalization;
export import :operators.prepared_angular_momentum;
export import :operators.prepared_central_density;
export import :operators.prepared_centering_constraint;
export import :operators.prepared_surface_constraint;
export import :operators.prepared_stellar_equilibrium;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :operators.stellar_equilibrium_compiler;
export import :operators.prepared_variadic_stellar_equilibrium;
export import :equilibrium.stellar_discretization;
export import :operators.stellar_equilibrium_problem;
export import :seed.stellar_equilibrium_projection;

View File

@@ -0,0 +1,74 @@
module;
#include <concepts>
#include <cstddef>
#include <type_traits>
#include <mfem.hpp>
export module mean_field:model.compiled_fixed_angular_momentum;
export import :field.registry;
export import :model.compiled_fixed_mass;
export import :physics.rigid_rotation;
export import :utils.blocks;
export namespace mean_field::models {
using FixedAngularMomentumLayoutRequest = ConstraintLayoutRequest<
FixedAngularMomentum,
PhysicalCoordinateFor<FixedAngularMomentum>,
ResidualFor<FixedAngularMomentum>,
utils::blocks::fixed_angular_momentum::angular_velocity::value,
utils::blocks::fixed_angular_momentum::angular_velocity::residual,
utils::blocks::fixed_angular_momentum::angular_velocity,
utils::blocks::density::mass::value,
utils::blocks::surface_deformation::parameters::value,
utils::blocks::fixed_angular_momentum::angular_velocity::value>;
class CompiledFixedAngularMomentum final {
public:
using SpecificationType = FixedAngularMomentum;
using LayoutRequest = FixedAngularMomentumLayoutRequest;
using AngularVelocityType = typename LayoutRequest::GeneratedValueType;
using ResidualType = typename LayoutRequest::GeneratedResidualType;
using AngularVelocityField = field::AngularVelocity;
explicit CompiledFixedAngularMomentum(const FixedAngularMomentum specification) noexcept
: m_specification(specification) {
}
[[nodiscard]] const FixedAngularMomentum &specification() const noexcept {
return m_specification;
}
[[nodiscard]] dimensions::AngularMomentumValue targetAngularMomentum() const noexcept {
return m_specification.targetAngularMomentum();
}
[[nodiscard]] physics::RigidRotation makeRotation(const double angularVelocity) const {
mfem::Vector velocity(3);
mfem::Vector center(3);
for (int component = 0; component < 3; ++component) {
velocity(component) = angularVelocity * m_specification.axis()[static_cast<std::size_t>(component)];
center(component) = m_specification.center()[static_cast<std::size_t>(component)];
}
return {velocity, center};
}
[[nodiscard]] static consteval LayoutRequest layoutRequest() noexcept {
return {};
}
private:
FixedAngularMomentum m_specification;
};
[[nodiscard]] inline CompiledFixedAngularMomentum compileConstraint(
const FixedAngularMomentum specification
) noexcept {
return CompiledFixedAngularMomentum{specification};
}
static_assert(ConstraintLayoutRequestType<FixedAngularMomentumLayoutRequest>);
static_assert(CompiledConstraint<CompiledFixedAngularMomentum>);
} // namespace mean_field::models

File diff suppressed because it is too large Load Diff

View File

@@ -14,21 +14,41 @@ export namespace mean_field::model {
template <typename SpecificationSet> class StellarModel;
template <models::ModelSpecification... CanonicalSpecifications>
requires models::ValidModelSpecificationPack<CanonicalSpecifications...> &&
models::SpecificationOperatorSignature<
models::detail::SpecificationSetStorage<CanonicalSpecifications...>>::symbolicallySquare
class StellarModel<models::detail::SpecificationSetStorage<CanonicalSpecifications...>> final {
public:
using SpecificationTypes = models::detail::SpecificationSetStorage<CanonicalSpecifications...>;
using OperatorSignature = models::SpecificationOperatorSignature<SpecificationTypes>;
using Storage = models::Model<CanonicalSpecifications...>;
using EquationOfStateType =
models::SpecificationForRoleT<models::SpecificationRole::constitutive_law, SpecificationTypes>;
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
static constexpr bool hasCompleteEquilibriumCompiler = Storage::hasCompleteRootCompiler;
static constexpr models::EquilibriumSystemCompilation compilationClass = Storage::compilationClass;
static constexpr std::size_t specificationCount = sizeof...(CanonicalSpecifications);
static constexpr bool symbolicallySquare = Storage::symbolicallySquare;
static constexpr bool hasCompleteEquilibriumDeclaration =
Storage::hasCompleteEquilibriumDeclaration;
template <models::SpecificationRole Role>
using SpecificationsForRole = models::SpecificationsForRoleT<Role, SpecificationTypes>;
template <models::SpecificationRole Role>
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
using SpecificationForRole = models::SpecificationForRoleT<Role, SpecificationTypes>;
template <models::SpecificationRole Role>
static constexpr std::size_t specificationRoleCount = models::specificationRoleCount<Role, SpecificationTypes>;
template <models::SpecificationRole Role>
static constexpr bool hasSpecificationsForRole = models::HasSpecificationsForRole<Role, SpecificationTypes>;
template <models::SpecificationRole Role>
static constexpr bool hasUniqueSpecificationForRole =
models::HasUniqueSpecificationForRole<Role, SpecificationTypes>;
template <typename... Arguments>
requires std::constructible_from<
Storage,
Arguments...>
requires std::constructible_from<Storage, Arguments...>
explicit StellarModel(Arguments &&...arguments) : m_specifications(std::forward<Arguments>(arguments)...) {
}
@@ -41,6 +61,25 @@ export namespace mean_field::model {
template <models::ModelSpecification Specification>
static constexpr bool containsSpecification = Storage::template containsSpecification<Specification>;
template <models::SpecificationRole Role>
requires models::HasUniqueSpecificationForRole<Role, SpecificationTypes>
[[nodiscard]] const models::SpecificationForRoleT<Role, SpecificationTypes> &
specificationForRole() const noexcept {
using Specification = models::SpecificationForRoleT<Role, SpecificationTypes>;
return specification<Specification>();
}
[[nodiscard]] const EquationOfStateType &equationOfState() const noexcept {
return specificationForRole<models::SpecificationRole::constitutive_law>();
}
template <typename = void>
requires models::HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition,
SpecificationTypes>
[[nodiscard]] const auto &surfaceCondition() const noexcept {
return specificationForRole<models::SpecificationRole::boundary_condition>();
}
[[nodiscard]] static constexpr std::span<const models::RuntimeSpecificationDescriptor>
runtimeSpecificationDescriptors() noexcept {
return Storage::runtimeSpecificationDescriptors();
@@ -56,11 +95,69 @@ export namespace mean_field::model {
-> StellarModel<models::SpecificationSet<std::remove_cvref_t<Specifications>...>>;
namespace detail {
template <typename Candidate> struct IsStellarModel : std::false_type { };
template <typename Candidate, typename = void> struct IsStellarModel : std::false_type {};
template <typename SpecificationSet> struct IsStellarModel<StellarModel<SpecificationSet>> : std::true_type { };
template <typename SpecificationSet>
struct IsStellarModel<
StellarModel<SpecificationSet>,
std::void_t<typename StellarModel<SpecificationSet>::SpecificationTypes,
typename StellarModel<SpecificationSet>::OperatorSignature,
decltype(StellarModel<SpecificationSet>::specificationCount),
decltype(StellarModel<SpecificationSet>::hasCompleteEquilibriumDeclaration)>>
: std::true_type {};
template <models::SpecificationRole Role, typename Candidate, bool = IsStellarModel<Candidate>::value>
struct StellarModelRoleSelection {
using Types = models::ModelTypeList<>;
static constexpr std::size_t count = 0;
};
template <models::SpecificationRole Role, typename Candidate>
struct StellarModelRoleSelection<Role, Candidate, true> {
using Types = models::SpecificationsForRoleT<Role, typename Candidate::SpecificationTypes>;
static constexpr std::size_t count =
models::specificationRoleCount<Role, typename Candidate::SpecificationTypes>;
};
} // namespace detail
template <typename Candidate>
concept StellarModelType = detail::IsStellarModel<std::remove_cvref_t<Candidate>>::value;
template <models::SpecificationRole Role, typename Candidate>
inline constexpr std::size_t specificationRoleCount =
detail::StellarModelRoleSelection<Role, std::remove_cvref_t<Candidate>>::count;
template <models::SpecificationRole Role, typename Candidate>
concept HasSpecificationsForRole = StellarModelType<Candidate> && specificationRoleCount<Role, Candidate> > 0;
template <models::SpecificationRole Role, typename Candidate>
concept HasUniqueSpecificationForRole = StellarModelType<Candidate> && specificationRoleCount<Role, Candidate> == 1;
template <models::SpecificationRole Role, typename Candidate>
requires StellarModelType<Candidate>
using SpecificationsForRoleT =
typename detail::StellarModelRoleSelection<Role, std::remove_cvref_t<Candidate>>::Types;
template <models::SpecificationRole Role, typename Candidate>
requires HasUniqueSpecificationForRole<Role, Candidate>
using SpecificationForRoleT =
models::SpecificationForRoleT<Role, typename std::remove_cvref_t<Candidate>::SpecificationTypes>;
template <typename Candidate>
concept HasEquationOfState = HasUniqueSpecificationForRole<models::SpecificationRole::constitutive_law, Candidate>;
template <typename Candidate>
concept HasSurfaceCondition = HasSpecificationsForRole<models::SpecificationRole::boundary_condition, Candidate>;
template <typename Candidate>
concept HasUniqueSurfaceCondition =
HasUniqueSpecificationForRole<models::SpecificationRole::boundary_condition, Candidate>;
template <HasEquationOfState Candidate>
using EquationOfStateType = SpecificationForRoleT<models::SpecificationRole::constitutive_law, Candidate>;
template <HasUniqueSurfaceCondition Candidate>
using SurfaceConditionType = SpecificationForRoleT<models::SpecificationRole::boundary_condition, Candidate>;
} // namespace mean_field::model

View File

@@ -0,0 +1,6 @@
export module mean_field:normalization;
export import :normalization.plan;
export import :normalization.physical_riesz;
export import :normalization.operators;
export import :normalization.stellar_equilibrium;

View File

@@ -0,0 +1,621 @@
module;
#include <array>
#include <cmath>
#include <concepts>
#include <cstdint>
#include <span>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <mfem.hpp>
export module mean_field:normalization.operators;
export import :normalization.physical_riesz;
export namespace mean_field::normalization {
class DiagonalNormalization final {
public:
DiagonalNormalization(
mfem::Vector stateToNormalized,
mfem::Vector residualToNormalized
)
: m_stateToNormalized(std::move(stateToNormalized)),
m_residualToNormalized(std::move(residualToNormalized)) {
ValidateFactors(m_stateToNormalized, "state");
ValidateFactors(m_residualToNormalized, "residual");
}
[[nodiscard]] static DiagonalNormalization Identity(
const int stateSize,
const int residualSize
) {
if (stateSize < 0 || residualSize < 0) {
throw std::invalid_argument("Normalization dimensions cannot be negative.");
}
mfem::Vector state(stateSize);
mfem::Vector residual(residualSize);
state = 1.0;
residual = 1.0;
return {std::move(state), std::move(residual)};
}
[[nodiscard]] int StateSize() const noexcept {
return m_stateToNormalized.Size();
}
[[nodiscard]] int ResidualSize() const noexcept {
return m_residualToNormalized.Size();
}
[[nodiscard]] const mfem::Vector &StateFactors() const noexcept {
return m_stateToNormalized;
}
[[nodiscard]] const mfem::Vector &ResidualFactors() const noexcept {
return m_residualToNormalized;
}
void NormalizeState(
const mfem::Vector &physical,
mfem::Vector &normalized
) const {
Apply(m_stateToNormalized, physical, normalized, false, "state");
}
void DenormalizeState(
const mfem::Vector &normalized,
mfem::Vector &physical
) const {
Apply(m_stateToNormalized, normalized, physical, true, "state");
}
void NormalizeResidual(
const mfem::Vector &physical,
mfem::Vector &normalized
) const {
Apply(m_residualToNormalized, physical, normalized, false, "residual");
}
void DenormalizeResidual(
const mfem::Vector &normalized,
mfem::Vector &physical
) const {
Apply(m_residualToNormalized, normalized, physical, true, "residual");
}
[[nodiscard]] double LocalStateNormSquared(const mfem::Vector &physical) const {
return LocalNormSquared(m_stateToNormalized, physical, "state");
}
[[nodiscard]] double LocalResidualNormSquared(const mfem::Vector &physical) const {
return LocalNormSquared(m_residualToNormalized, physical, "residual");
}
private:
static void ValidateFactors(
const mfem::Vector &factors,
const char *role
) {
for (int index = 0; index < factors.Size(); ++index) {
if (!std::isfinite(factors(index)) || factors(index) <= 0.0) {
throw std::invalid_argument(
std::string("The ") + role + " normalization factors must be finite and positive."
);
}
}
}
static void Apply(
const mfem::Vector &factors,
const mfem::Vector &input,
mfem::Vector &output,
const bool inverse,
const char *role
) {
if (input.Size() != factors.Size()) {
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
}
const bool exactAlias = input.GetData() == output.GetData() && input.Size() == output.Size();
if (!exactAlias) {
output.SetSize(input.Size());
}
for (int index = 0; index < input.Size(); ++index) {
const double value = input(index);
output(index) = inverse ? value / factors(index) : factors(index) * value;
}
}
[[nodiscard]] static double LocalNormSquared(
const mfem::Vector &factors,
const mfem::Vector &physical,
const char *role
) {
if (physical.Size() != factors.Size()) {
throw std::invalid_argument(std::string("The ") + role + " vector has the wrong size.");
}
double normSquared = 0.0;
for (int index = 0; index < physical.Size(); ++index) {
const double normalized = factors(index) * physical(index);
normSquared += normalized * normalized;
}
return normSquared;
}
mfem::Vector m_stateToNormalized;
mfem::Vector m_residualToNormalized;
};
/* Detection-safe public operation for an ordinary third-party runtime
* policy. The exact policy is recovered from the problem type and must own
* every method in its compiled plan. Its implementation remains beside
* the policy and is found by ADL, so adding a normalization family does
* not edit a library registry or switch. */
template <typename Problem>
concept RuntimePreparedNormalizationOperation =
requires(const std::remove_cvref_t<Problem> &problem) {
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
typename std::remove_cvref_t<Problem>::FormType;
requires RuntimePreparedNormalizationFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType>;
{
problem.GetNormalizationPrescription()
} -> std::same_as<const typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType &>;
{
prepareStellarNormalization(
problem.GetNormalizationPrescription(),
problem)
} -> std::same_as<DiagonalNormalization>;
};
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
class DiagonalNormalizationBuilder final {
public:
explicit DiagonalNormalizationBuilder(const utils::blocks::form_layout<Form> &layout)
: m_layout(&layout),
m_stateFactors(layout.value_offsets().Last()),
m_residualFactors(layout.residual_offsets().Last()) {
}
explicit DiagonalNormalizationBuilder(
utils::blocks::form_layout<Form> &&
) = delete;
explicit DiagonalNormalizationBuilder(
const utils::blocks::form_layout<Form> &&
) = delete;
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
void SetValueBlock(
const double physicalScale,
const mfem::Vector &primalGramDiagonal
) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
RequireUnassigned(m_valueAssigned[block], "value");
AssignBlock(
m_stateFactors,
m_layout->value_offsets()[block],
m_layout->value_offsets()[block + 1] - m_layout->value_offsets()[block],
physicalScale,
primalGramDiagonal,
false
);
m_valueAssigned[block] = true;
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
void SetResidualBlock(
const double physicalScale,
const mfem::Vector &primalGramDiagonal
) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
RequireUnassigned(m_residualAssigned[block], "residual");
AssignBlock(
m_residualFactors,
m_layout->residual_offsets()[block],
m_layout->residual_offsets()[block + 1] - m_layout->residual_offsets()[block],
physicalScale,
primalGramDiagonal,
true
);
m_residualAssigned[block] = true;
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::value_blocks>
void SetValueGlobal(const double physicalScale) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::value_blocks>;
SetConstantMetricValueBlock<Block>(physicalScale, BlockSize(m_layout->value_offsets(), block));
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
void SetResidualGlobal(const double physicalScale) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
mfem::Vector metric(BlockSize(m_layout->residual_offsets(), block));
metric = 1.0;
SetResidualBlock<Block>(physicalScale, metric);
}
template <typename Block>
requires utils::blocks::contains_type_v<Block, typename Form::residual_blocks>
void SetHybridResidualBlock(
const double physicalScale,
const mfem::Vector &bulkPrimalGramDiagonal,
const std::span<const int> pointRows,
const double pointMetric = 1.0
) {
constexpr int block = utils::blocks::type_index_v<Block, typename Form::residual_blocks>;
const int size = BlockSize(m_layout->residual_offsets(), block);
if (bulkPrimalGramDiagonal.Size() != size) {
throw std::invalid_argument("The hybrid residual Gram diagonal has the wrong size.");
}
ValidateMetric(pointMetric);
std::vector<bool> isPointRow(static_cast<std::size_t>(size), false);
for (const int row : pointRows) {
if (row < 0 || row >= size) {
throw std::out_of_range("A hybrid point row lies outside its residual block.");
}
if (isPointRow[static_cast<std::size_t>(row)]) {
throw std::invalid_argument("A hybrid point row was supplied more than once.");
}
isPointRow[static_cast<std::size_t>(row)] = true;
}
mfem::Vector metric(size);
for (int row = 0; row < size; ++row) {
metric(row) = isPointRow[static_cast<std::size_t>(row)]
? pointMetric
: bulkPrimalGramDiagonal(row);
}
SetResidualBlock<Block>(physicalScale, metric);
}
[[nodiscard]] DiagonalNormalization Build() && {
for (const bool assigned : m_valueAssigned) {
if (!assigned) {
throw std::logic_error("The normalization is missing a value block.");
}
}
for (const bool assigned : m_residualAssigned) {
if (!assigned) {
throw std::logic_error("The normalization is missing a residual block.");
}
}
return {std::move(m_stateFactors), std::move(m_residualFactors)};
}
private:
template <typename Block>
void SetConstantMetricValueBlock(
const double physicalScale,
const int size
) {
mfem::Vector metric(size);
metric = 1.0;
SetValueBlock<Block>(physicalScale, metric);
}
[[nodiscard]] static int BlockSize(
const mfem::Array<int> &offsets,
const int block
) noexcept {
return offsets[block + 1] - offsets[block];
}
static void RequireUnassigned(
const bool assigned,
const char *role
) {
if (assigned) {
throw std::logic_error(std::string("The ") + role + " block normalization was assigned twice.");
}
}
static void ValidateMetric(const double metric) {
if (!std::isfinite(metric) || metric <= 0.0) {
throw std::invalid_argument("Every Riesz Gram diagonal entry must be finite and positive.");
}
}
static void AssignBlock(
mfem::Vector &factors,
const int offset,
const int size,
const double physicalScale,
const mfem::Vector &primalGramDiagonal,
const bool dual
) {
if (!std::isfinite(physicalScale) || physicalScale <= 0.0) {
throw std::invalid_argument("A physical normalization scale must be finite and positive.");
}
if (primalGramDiagonal.Size() != size) {
throw std::invalid_argument("A Riesz Gram diagonal has the wrong block size.");
}
for (int index = 0; index < size; ++index) {
const double metric = primalGramDiagonal(index);
ValidateMetric(metric);
const double rieszFactor = std::sqrt(metric);
const double factor = dual
? 1.0 / (physicalScale * rieszFactor)
: rieszFactor / physicalScale;
if (!std::isfinite(factor) || factor <= 0.0) {
throw std::overflow_error("A normalization factor is not finite and positive.");
}
factors(offset + index) = factor;
}
}
const utils::blocks::form_layout<Form> *m_layout;
mfem::Vector m_stateFactors;
mfem::Vector m_residualFactors;
std::array<bool, Form::value_block_count> m_valueAssigned{};
std::array<bool, Form::residual_block_count> m_residualAssigned{};
};
class ScaledJacobianOperator final : public mfem::Operator {
public:
ScaledJacobianOperator(
const mfem::Operator &physicalJacobian,
const DiagonalNormalization &normalization
)
: mfem::Operator(normalization.ResidualSize(), normalization.StateSize()),
m_physicalJacobian(&physicalJacobian),
m_normalization(&normalization),
m_physicalDirection(normalization.StateSize()),
m_physicalAction(normalization.ResidualSize()) {
if (physicalJacobian.Width() != normalization.StateSize() ||
physicalJacobian.Height() != normalization.ResidualSize()) {
throw std::invalid_argument("The physical Jacobian and normalization dimensions do not agree.");
}
}
ScaledJacobianOperator(
mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledJacobianOperator(
const mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledJacobianOperator(
const mfem::Operator &,
DiagonalNormalization &&
) = delete;
ScaledJacobianOperator(
const mfem::Operator &,
const DiagonalNormalization &&
) = delete;
void Mult(
const mfem::Vector &normalizedDirection,
mfem::Vector &normalizedAction
) const override {
m_normalization->DenormalizeState(normalizedDirection, m_physicalDirection);
m_physicalJacobian->Mult(m_physicalDirection, m_physicalAction);
m_normalization->NormalizeResidual(m_physicalAction, normalizedAction);
}
private:
const mfem::Operator *m_physicalJacobian;
const DiagonalNormalization *m_normalization;
mutable mfem::Vector m_physicalDirection;
mutable mfem::Vector m_physicalAction;
};
class ScaledInverseOperator final : public mfem::Operator {
public:
ScaledInverseOperator(
const mfem::Operator &physicalInverse,
const DiagonalNormalization &normalization
)
: mfem::Operator(normalization.StateSize(), normalization.ResidualSize()),
m_physicalInverse(&physicalInverse),
m_normalization(&normalization),
m_physicalResidual(normalization.ResidualSize()),
m_physicalCorrection(normalization.StateSize()) {
if (physicalInverse.Width() != normalization.ResidualSize() ||
physicalInverse.Height() != normalization.StateSize()) {
throw std::invalid_argument("The physical inverse and normalization dimensions do not agree.");
}
}
ScaledInverseOperator(
mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledInverseOperator(
const mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledInverseOperator(
const mfem::Operator &,
DiagonalNormalization &&
) = delete;
ScaledInverseOperator(
const mfem::Operator &,
const DiagonalNormalization &&
) = delete;
void Mult(
const mfem::Vector &normalizedResidual,
mfem::Vector &normalizedCorrection
) const override {
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
}
private:
const mfem::Operator *m_physicalInverse;
const DiagonalNormalization *m_normalization;
mutable mfem::Vector m_physicalResidual;
mutable mfem::Vector m_physicalCorrection;
};
struct ScaledPreconditionerStatistics final {
std::uint64_t operatorBindings{0};
std::uint64_t applications{0};
};
/*
* Solver-compatible realization of R^{-1} M^{-1} L^{-1}. The wrapped
* inverse always sees the dimensional Jacobian, even when an MFEM Krylov
* solver binds this object to the normalized Jacobian L J R.
*/
class ScaledPreconditioner final : public mfem::Solver {
public:
ScaledPreconditioner(
mfem::Solver &physicalInverse,
const mfem::Operator &physicalJacobian,
const mfem::Operator &normalizedJacobian,
const DiagonalNormalization &normalization
)
: mfem::Solver(
normalization.StateSize(),
normalization.ResidualSize(),
physicalInverse.iterative_mode
),
m_physicalInverse(&physicalInverse),
m_physicalJacobian(&physicalJacobian),
m_expectedNormalizedJacobian(&normalizedJacobian),
m_normalization(&normalization),
m_physicalResidual(normalization.ResidualSize()),
m_physicalCorrection(normalization.StateSize()) {
if (physicalInverse.Width() != normalization.ResidualSize() ||
physicalInverse.Height() != normalization.StateSize() ||
physicalJacobian.Width() != normalization.StateSize() ||
physicalJacobian.Height() != normalization.ResidualSize()) {
throw std::invalid_argument(
"The physical preconditioner, Jacobian, and normalization dimensions do not agree."
);
}
SetOperator(normalizedJacobian);
}
ScaledPreconditioner(
mfem::Solver &,
mfem::Operator &&,
const mfem::Operator &,
const DiagonalNormalization &
) = delete;
ScaledPreconditioner(
mfem::Solver &,
const mfem::Operator &&,
const mfem::Operator &,
const DiagonalNormalization &
) = delete;
ScaledPreconditioner(
mfem::Solver &,
const mfem::Operator &,
mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledPreconditioner(
mfem::Solver &,
const mfem::Operator &,
const mfem::Operator &&,
const DiagonalNormalization &
) = delete;
ScaledPreconditioner(
mfem::Solver &,
const mfem::Operator &,
const mfem::Operator &,
DiagonalNormalization &&
) = delete;
ScaledPreconditioner(
mfem::Solver &,
const mfem::Operator &,
const mfem::Operator &,
const DiagonalNormalization &&
) = delete;
ScaledPreconditioner(const ScaledPreconditioner &) = delete;
ScaledPreconditioner &operator=(const ScaledPreconditioner &) = delete;
ScaledPreconditioner(ScaledPreconditioner &&) = delete;
ScaledPreconditioner &operator=(ScaledPreconditioner &&) = delete;
void SetOperator(const mfem::Operator &normalizedJacobian) override {
if (normalizedJacobian.Width() != Width() || normalizedJacobian.Height() != Height()) {
throw std::invalid_argument(
"The scaled preconditioner received an incompatible normalized Jacobian."
);
}
if (&normalizedJacobian != m_expectedNormalizedJacobian) {
throw std::invalid_argument(
"The scaled preconditioner cannot be rebound to a different normalized Jacobian."
);
}
m_physicalInverse->SetOperator(*m_physicalJacobian);
m_normalizedJacobian = &normalizedJacobian;
++m_statistics.operatorBindings;
}
void Mult(
const mfem::Vector &normalizedResidual,
mfem::Vector &normalizedCorrection
) const override {
if (m_normalizedJacobian == nullptr) {
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
}
if (normalizedResidual.Size() != Width() || normalizedCorrection.Size() != Height()) {
throw std::invalid_argument(
"The scaled preconditioner requires compatible, preallocated normalized vectors."
);
}
m_normalization->DenormalizeResidual(normalizedResidual, m_physicalResidual);
m_physicalInverse->Mult(m_physicalResidual, m_physicalCorrection);
m_normalization->NormalizeState(m_physicalCorrection, normalizedCorrection);
++m_statistics.applications;
}
[[nodiscard]] const mfem::Solver &GetPhysicalInverse() const noexcept {
return *m_physicalInverse;
}
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
return *m_physicalJacobian;
}
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
if (m_normalizedJacobian == nullptr) {
throw std::logic_error("The scaled preconditioner has not been bound to a normalized Jacobian.");
}
return *m_normalizedJacobian;
}
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
mfem::Solver *m_physicalInverse;
const mfem::Operator *m_physicalJacobian;
const mfem::Operator *m_expectedNormalizedJacobian;
const mfem::Operator *m_normalizedJacobian{nullptr};
const DiagonalNormalization *m_normalization;
mutable mfem::Vector m_physicalResidual;
mutable mfem::Vector m_physicalCorrection;
mutable ScaledPreconditionerStatistics m_statistics;
};
} // namespace mean_field::normalization

View File

@@ -0,0 +1,728 @@
module;
#include <cmath>
#include <concepts>
#include <stdexcept>
#include <type_traits>
export module mean_field:normalization.physical_riesz;
export import :dimensions.quantities;
export import :field.mfem;
export import :model.specifications;
export import :normalization.plan;
export namespace mean_field::normalization {
struct Unnormalized final : NormalizationPrescriptionTag { };
struct ReferenceGeometry final { };
struct FixedMassBranchReference final { };
template <typename Candidate>
concept RieszGeometryPolicy = std::same_as<std::remove_cvref_t<Candidate>, ReferenceGeometry>;
template <typename Candidate>
concept ReferenceScalePolicy = std::same_as<std::remove_cvref_t<Candidate>, FixedMassBranchReference>;
template <
RieszGeometryPolicy GeometryPolicy = ReferenceGeometry,
ReferenceScalePolicy ScalePolicy = FixedMassBranchReference>
class PhysicalRieszDiagonal final : public NormalizationPrescriptionTag {
public:
using Geometry = GeometryPolicy;
using ScaleSource = ScalePolicy;
explicit PhysicalRieszDiagonal(
const dimensions::LengthValue referenceRadius,
const double gravitationalConstant = 1.0
)
: m_referenceRadius(referenceRadius),
m_gravitationalConstant(gravitationalConstant) {
if (!std::isfinite(referenceRadius.value()) || referenceRadius.value() <= 0.0) {
throw std::invalid_argument("Physical Riesz normalization requires a finite, positive branch radius.");
}
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
throw std::invalid_argument(
"Physical Riesz normalization requires a finite, positive gravitational constant."
);
}
}
[[nodiscard]] dimensions::LengthValue referenceRadius() const noexcept {
return m_referenceRadius;
}
[[nodiscard]] double gravitationalConstant() const noexcept {
return m_gravitationalConstant;
}
private:
dimensions::LengthValue m_referenceRadius;
double m_gravitationalConstant;
};
PhysicalRieszDiagonal(dimensions::LengthValue, double = 1.0)
-> PhysicalRieszDiagonal<ReferenceGeometry, FixedMassBranchReference>;
template <typename Candidate> struct IsPhysicalRieszDiagonal : std::false_type { };
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource>
struct IsPhysicalRieszDiagonal<PhysicalRieszDiagonal<Geometry, ScaleSource>> : std::true_type { };
template <typename Candidate>
concept PhysicalRieszDiagonalPrescription =
IsPhysicalRieszDiagonal<std::remove_cvref_t<Candidate>>::value;
struct StellarCharacteristicScales final {
dimensions::MassValue mass;
dimensions::LengthValue radius;
double gravitationalConstant;
double density;
double acceleration;
double inverseTimeSquared;
double specificEnergy;
double pressure;
double angularVelocity;
double angularMomentum;
double force;
};
[[nodiscard]] inline StellarCharacteristicScales deriveStellarCharacteristicScales(
const dimensions::MassValue mass,
const dimensions::LengthValue radius,
const double gravitationalConstant = 1.0
) {
const double massValue = mass.value();
const double radiusValue = radius.value();
if (!std::isfinite(massValue) || massValue <= 0.0) {
throw std::invalid_argument("Characteristic stellar scales require a finite, positive mass.");
}
if (!std::isfinite(radiusValue) || radiusValue <= 0.0) {
throw std::invalid_argument("Characteristic stellar scales require a finite, positive radius.");
}
if (!std::isfinite(gravitationalConstant) || gravitationalConstant <= 0.0) {
throw std::invalid_argument(
"Characteristic stellar scales require a finite, positive gravitational constant."
);
}
const double radiusSquared = radiusValue * radiusValue;
const double radiusCubed = radiusSquared * radiusValue;
const double density = massValue / radiusCubed;
const double acceleration = gravitationalConstant * massValue / radiusSquared;
const double inverseTimeSquared = gravitationalConstant * massValue / radiusCubed;
const double specificEnergy = gravitationalConstant * massValue / radiusValue;
const double pressure = gravitationalConstant * massValue * massValue /
(radiusSquared * radiusSquared);
const double angularVelocity = std::sqrt(inverseTimeSquared);
const double angularMomentum = massValue * std::sqrt(gravitationalConstant * massValue * radiusValue);
const double force = gravitationalConstant * massValue * massValue / radiusSquared;
const double derived[] = {
density,
acceleration,
inverseTimeSquared,
specificEnergy,
pressure,
angularVelocity,
angularMomentum,
force
};
for (const double value : derived) {
if (!std::isfinite(value) || value <= 0.0) {
throw std::overflow_error("A derived characteristic stellar scale is not finite and positive.");
}
}
return {
.mass = mass,
.radius = radius,
.gravitationalConstant = gravitationalConstant,
.density = density,
.acceleration = acceleration,
.inverseTimeSquared = inverseTimeSquared,
.specificEnergy = specificEnergy,
.pressure = pressure,
.angularVelocity = angularVelocity,
.angularMomentum = angularMomentum,
.force = force
};
}
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Model>
requires requires(const Model &model) {
{
model.template specification<models::FixedTotalMass>()
} -> std::same_as<const models::FixedTotalMass &>;
{
model.template specification<models::FixedTotalMass>().targetMass()
} -> std::same_as<dimensions::MassValue>;
}
[[nodiscard]] StellarCharacteristicScales deriveStellarCharacteristicScales(
const PhysicalRieszDiagonal<Geometry, ScaleSource> &prescription,
const Model &model
) {
return deriveStellarCharacteristicScales(
model.template specification<models::FixedTotalMass>().targetMass(),
prescription.referenceRadius(),
prescription.gravitationalConstant()
);
}
namespace detail {
/*
* Model definitions live below the numerical normalization layer so
* that a physics component can describe its generated coordinates
* without importing solver machinery. These two translations are the
* deliberately small boundary between that neutral declaration and the
* normalization plan used by the discretization.
*/
template <models::RieszTopology Topology> struct DeclaredRieszTopology {
static constexpr bool available = false;
static constexpr RieszTopology value = RieszTopology::identity;
};
#define MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(Name) \
template <> struct DeclaredRieszTopology<models::RieszTopology::Name> { \
static constexpr bool available = true; \
static constexpr RieszTopology value = RieszTopology::Name; \
}
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(identity);
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_volume_l2);
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(vector_volume_l2);
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(scalar_boundary_l2);
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(hybrid_scalar_volume_point_rows);
MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY(global_scalar);
#undef MEAN_FIELD_DECLARED_RIESZ_TOPOLOGY
template <models::PhysicalScaleLaw Scale> struct DeclaredPhysicalScale {
static constexpr bool available = false;
static constexpr PhysicalScaleKind value = PhysicalScaleKind::dimensionless;
};
#define MEAN_FIELD_DECLARED_PHYSICAL_SCALE(Name) \
template <> struct DeclaredPhysicalScale<models::PhysicalScaleLaw::Name> { \
static constexpr bool available = true; \
static constexpr PhysicalScaleKind value = PhysicalScaleKind::Name; \
}
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(dimensionless);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(density);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(length);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(acceleration);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(inverse_time_squared);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(specific_energy);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(pressure);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(mass);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(force);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_velocity);
MEAN_FIELD_DECLARED_PHYSICAL_SCALE(angular_momentum);
#undef MEAN_FIELD_DECLARED_PHYSICAL_SCALE
template <typename Declaration, typename = void>
struct CompileDeclaredPhysicalRieszCoordinate {
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
template <typename Declaration>
struct CompileDeclaredPhysicalRieszCoordinate<
Declaration,
std::void_t<
decltype(std::integral_constant<
models::RieszTopology,
static_cast<models::RieszTopology>(Declaration::topology)>{}),
decltype(std::integral_constant<
models::PhysicalScaleLaw,
static_cast<models::PhysicalScaleLaw>(Declaration::scale)>{}),
decltype(std::bool_constant<static_cast<bool>(Declaration::available)>{})>> {
private:
static constexpr models::RieszTopology declaredTopology =
static_cast<models::RieszTopology>(Declaration::topology);
static constexpr models::PhysicalScaleLaw declaredScale =
static_cast<models::PhysicalScaleLaw>(Declaration::scale);
using Topology = DeclaredRieszTopology<declaredTopology>;
using Scale = DeclaredPhysicalScale<declaredScale>;
public:
static constexpr bool registered = static_cast<bool>(Declaration::available) &&
Topology::available && Scale::available;
using Method = std::conditional_t<
registered,
PhysicalRieszCoordinate<Topology::value, Scale::value>,
UnsupportedPhysicalRieszCoordinate>;
};
template <typename Generated, CoordinateKind Kind, typename = void>
struct DeclaredGeneratedPhysicalRieszCoordinate {
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
template <typename Generated>
struct DeclaredGeneratedPhysicalRieszCoordinate<
Generated,
CoordinateKind::value,
std::void_t<
typename Generated::SpecificationType,
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Value>>
: CompileDeclaredPhysicalRieszCoordinate<
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Value> { };
template <typename Generated>
struct DeclaredGeneratedPhysicalRieszCoordinate<
Generated,
CoordinateKind::residual,
std::void_t<
typename Generated::SpecificationType,
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Residual>>
: CompileDeclaredPhysicalRieszCoordinate<
typename models::SpecificationContribution<
typename Generated::SpecificationType>::Normalization::Residual> { };
template <typename GeneratedValues, typename GeneratedResiduals>
struct GeneratedPhysicalRieszCoverage {
static constexpr bool complete = false;
};
template <typename... GeneratedValues, typename... GeneratedResiduals>
struct GeneratedPhysicalRieszCoverage<
models::ModelTypeList<GeneratedValues...>,
models::ModelTypeList<GeneratedResiduals...>> {
static constexpr bool complete =
(DeclaredGeneratedPhysicalRieszCoordinate<
GeneratedValues,
CoordinateKind::value>::registered && ...) &&
(DeclaredGeneratedPhysicalRieszCoordinate<
GeneratedResiduals,
CoordinateKind::residual>::registered && ...);
};
template <typename Specification, typename = void>
struct SpecificationPhysicalRieszCoverage {
static constexpr bool complete = false;
};
template <models::ModelSpecification Specification>
struct SpecificationPhysicalRieszCoverage<
Specification,
std::void_t<
typename models::SpecificationContribution<Specification>::GeneratedValues,
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
: GeneratedPhysicalRieszCoverage<
typename models::SpecificationContribution<Specification>::GeneratedValues,
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
} // namespace detail
/*
* All generated blocks are normalized from their generating physics
* specification. Adding another constraint therefore does not add a
* normalization specialization: its public ModelDefinition is the single
* source of both the value and residual Riesz laws.
*/
template <typename Generated>
struct PhysicalRieszBlockTraits<utils::blocks::generated_value_block<Generated>>
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value> { };
template <typename Generated>
struct PhysicalRieszBlockTraits<utils::blocks::generated_residual_block<Generated>>
: detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual> { };
template <typename Generated>
concept GeneratedValuePhysicalRieszNormalizable =
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::value>::registered;
template <typename Generated>
concept GeneratedResidualPhysicalRieszNormalizable =
detail::DeclaredGeneratedPhysicalRieszCoordinate<Generated, CoordinateKind::residual>::registered;
template <typename Specification>
concept CompleteGeneratedPhysicalRieszNormalizationFor =
detail::SpecificationPhysicalRieszCoverage<std::remove_cvref_t<Specification>>::complete;
/*
* Runtime Physical Riesz assembly needs more than a symbolically complete
* plan: it must be able to recover the finite-element maps owned by the
* selected physical core. Keep that structural capability in this low
* normalization module so both problem formation and the solver-facing
* adapter can consult the same authority without importing one another.
*/
template <typename Candidate>
concept PhysicalRieszCoreRuntime =
requires(const std::remove_cvref_t<Candidate> &core) {
{
core.GetGravityContext().GetDensityMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetGravityContext().GetGravityGradientMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetGravityContext().GetGravityPotentialMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetHydrostaticOperator().GetEnthalpyMap()
} -> std::same_as<const field::FieldDofMap &>;
{
core.GetDomainDeformation().parameterCount()
} -> std::same_as<int>;
};
namespace detail {
template <typename Generated, CoordinateKind Kind>
using GeneratedPhysicalRieszMethod =
typename DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::Method;
template <typename Generated, CoordinateKind Kind, typename = void>
struct GeneratedPhysicalRieszRuntimeCoordinate : std::false_type { };
template <typename Generated, CoordinateKind Kind>
struct GeneratedPhysicalRieszRuntimeCoordinate<
Generated,
Kind,
std::void_t<decltype(GeneratedPhysicalRieszMethod<Generated, Kind>::topology)>>
: std::bool_constant<
DeclaredGeneratedPhysicalRieszCoordinate<Generated, Kind>::registered &&
GeneratedPhysicalRieszMethod<Generated, Kind>::topology ==
RieszTopology::global_scalar> { };
template <typename Specification, typename = void>
struct SpecificationPhysicalRieszRuntimeCoverage : std::false_type { };
template <typename Values, typename Residuals>
struct GeneratedPhysicalRieszRuntimeCoverage : std::false_type { };
template <typename... Values, typename... Residuals>
struct GeneratedPhysicalRieszRuntimeCoverage<
models::ModelTypeList<Values...>,
models::ModelTypeList<Residuals...>>
: std::bool_constant<
(GeneratedPhysicalRieszRuntimeCoordinate<Values, CoordinateKind::value>::value && ...) &&
(GeneratedPhysicalRieszRuntimeCoordinate<Residuals, CoordinateKind::residual>::value && ...)> { };
template <models::ModelSpecification Specification>
struct SpecificationPhysicalRieszRuntimeCoverage<
Specification,
std::void_t<
typename models::SpecificationContribution<Specification>::GeneratedValues,
typename models::SpecificationContribution<Specification>::GeneratedResiduals>>
: GeneratedPhysicalRieszRuntimeCoverage<
typename models::SpecificationContribution<Specification>::GeneratedValues,
typename models::SpecificationContribution<Specification>::GeneratedResiduals> { };
template <typename SpecificationTypes>
struct SpecificationSetPhysicalRieszRuntimeCoverage : std::false_type { };
template <models::ModelSpecification... Specifications>
struct SpecificationSetPhysicalRieszRuntimeCoverage<
models::detail::SpecificationSetStorage<Specifications...>>
: std::bool_constant<
(SpecificationPhysicalRieszRuntimeCoverage<Specifications>::value && ...)> { };
} // namespace detail
template <typename Specification>
concept CompleteGeneratedPhysicalRieszRuntimeNormalizationFor =
detail::SpecificationPhysicalRieszRuntimeCoverage<
std::remove_cvref_t<Specification>>::value;
#define MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(BlockType, TopologyValue, ScaleValue) \
template <> struct PhysicalRieszBlockTraits<BlockType> { \
using Method = PhysicalRieszCoordinate<RieszTopology::TopologyValue, PhysicalScaleKind::ScaleValue>; \
static constexpr bool registered = true; \
}
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::density::mass::value,
scalar_volume_l2,
density
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::surface_deformation::parameters::value,
scalar_boundary_l2,
length
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::gravity::gradient::value,
vector_volume_l2,
acceleration
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::gravity::poisson::value,
scalar_volume_l2,
specific_energy
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::enthalpy::specific::value,
scalar_volume_l2,
specific_energy
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::gravity::gradient::residual,
vector_volume_l2,
acceleration
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::gravity::poisson::residual,
scalar_volume_l2,
inverse_time_squared
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::density::mass::residual,
scalar_volume_l2,
density
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::surface_deformation::shape_equilibrium::residual,
scalar_boundary_l2,
force
);
MEAN_FIELD_PHYSICAL_RIESZ_TRAIT(
utils::blocks::enthalpy::specific::residual,
hybrid_scalar_volume_point_rows,
specific_energy
);
#undef MEAN_FIELD_PHYSICAL_RIESZ_TRAIT
template <typename Block>
[[nodiscard]] double physicalScale(
const StellarCharacteristicScales &scales
) {
static_assert(PhysicalRieszBlockTraits<Block>::registered, "The block has no Physical Riesz normalization.");
using Method = typename PhysicalRieszBlockTraits<Block>::Method;
constexpr PhysicalScaleKind scale = Method::scale;
if constexpr (scale == PhysicalScaleKind::dimensionless) {
return 1.0;
} else if constexpr (scale == PhysicalScaleKind::density) {
return scales.density;
} else if constexpr (scale == PhysicalScaleKind::length) {
return scales.radius.value();
} else if constexpr (scale == PhysicalScaleKind::acceleration) {
return scales.acceleration;
} else if constexpr (scale == PhysicalScaleKind::inverse_time_squared) {
return scales.inverseTimeSquared;
} else if constexpr (scale == PhysicalScaleKind::specific_energy) {
return scales.specificEnergy;
} else if constexpr (scale == PhysicalScaleKind::pressure) {
return scales.pressure;
} else if constexpr (scale == PhysicalScaleKind::mass) {
return scales.mass.value();
} else if constexpr (scale == PhysicalScaleKind::force) {
return scales.force;
} else if constexpr (scale == PhysicalScaleKind::angular_velocity) {
return scales.angularVelocity;
} else {
static_assert(scale == PhysicalScaleKind::angular_momentum);
return scales.angularMomentum;
}
}
namespace detail {
template <typename Values, typename Residuals> struct MakePhysicalRieszPlan;
template <typename... Values, typename... Residuals>
struct MakePhysicalRieszPlan<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
using Type = NormalizationPlan<
CoordinateComponent<
CoordinateKind::value,
utils::blocks::type_list<Values>,
typename PhysicalRieszBlockTraits<Values>::Method>...,
CoordinateComponent<
CoordinateKind::residual,
utils::blocks::type_list<Residuals>,
typename PhysicalRieszBlockTraits<Residuals>::Method>...>;
};
} // namespace detail
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using PhysicalRieszNormalizationPlanFor = typename detail::MakePhysicalRieszPlan<
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
/*
* Public compile-time extension point for a normalization prescription.
* A specialization owns both the complete coordinate plan and the
* low-level runtime compatibility predicate used before a discretized
* problem type is formed. Keeping those declarations together prevents a
* policy from compiling a plan which the selected stellar core cannot
* actually prepare.
*/
template <typename Prescription, typename Form> struct NormalizationCompilation {
using Plan = NormalizationPlan<>;
static constexpr bool registered = false;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor = false;
};
/* Astronomy/numerics-facing package for a policy which prepares one
* runtime diagonal over the complete inferred form and needs no private
* facility of a particular stellar core. The generated plan truthfully
* labels every coordinate as runtime-prepared by this exact policy. */
template <NormalizationPrescription Prescription, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct RuntimePreparedNormalizationCompilation {
using Plan = RuntimePreparedNormalizationPlanFor<Prescription, Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor = registered;
};
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCompilation<Unnormalized, Form> {
using Plan = IdentityNormalizationPlanFor<Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor = registered;
};
template <RieszGeometryPolicy Geometry, ReferenceScalePolicy ScaleSource, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCompilation<PhysicalRieszDiagonal<Geometry, ScaleSource>, Form> {
using Plan = PhysicalRieszNormalizationPlanFor<Form>;
static constexpr bool registered = CompleteNormalizationFor<Plan, Form>;
template <typename PhysicalCore, typename SpecificationTypes>
static constexpr bool runtimeAvailableFor =
registered &&
PhysicalRieszCoreRuntime<std::remove_cvref_t<PhysicalCore>> &&
detail::SpecificationSetPhysicalRieszRuntimeCoverage<
std::remove_cvref_t<SpecificationTypes>>::value;
};
namespace detail {
template <typename Prescription, typename Form, typename = void>
struct NormalizationCompilationAudit {
using Plan = NormalizationPlan<>;
static constexpr bool registered = false;
};
template <typename Prescription, typename Form>
requires NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>>
struct NormalizationCompilationAudit<
Prescription,
Form,
std::void_t<
typename NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::Plan,
decltype(std::bool_constant<static_cast<bool>(
NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::registered)>{})>> {
using Compilation = NormalizationCompilation<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>;
using Plan = typename Compilation::Plan;
static constexpr bool registered =
static_cast<bool>(Compilation::registered) &&
CompleteNormalizationFor<Plan, std::remove_cvref_t<Form>>;
};
} // namespace detail
template <NormalizationPrescription Prescription, typename Form>
using NormalizationPlanFor = typename detail::NormalizationCompilationAudit<
std::remove_cvref_t<Prescription>,
Form>::Plan;
template <typename Prescription, typename Form>
concept CompilableNormalizationFor =
detail::NormalizationCompilationAudit<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>::registered;
/* The public runtime-preparation adapter is intentionally narrower than
* an arbitrary complete plan: every coordinate must name the exact policy
* which supplies its runtime factor. This prevents a custom policy from
* advertising IdentityCoordinate (or another policy's method) while
* silently installing a different diagonal at runtime. */
template <typename Prescription, typename Form>
concept RuntimePreparedNormalizationFor =
NormalizationPrescription<std::remove_cvref_t<Prescription>> &&
utils::blocks::block_form_is_valid_v<std::remove_cvref_t<Form>> &&
CompilableNormalizationFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>> &&
std::same_as<
NormalizationPlanFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>,
RuntimePreparedNormalizationPlanFor<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>>>;
namespace detail {
template <
typename Prescription,
typename Form,
typename PhysicalCore,
typename SpecificationTypes,
typename = void>
struct StellarNormalizationRuntimeAudit : std::false_type { };
template <
typename Prescription,
typename Form,
typename PhysicalCore,
typename SpecificationTypes>
struct StellarNormalizationRuntimeAudit<
Prescription,
Form,
PhysicalCore,
SpecificationTypes,
std::void_t<
std::enable_if_t<NormalizationCompilationAudit<
Prescription,
Form>::registered>,
decltype(std::bool_constant<static_cast<bool>(
NormalizationCompilation<
Prescription,
Form>::template runtimeAvailableFor<
PhysicalCore,
SpecificationTypes>)>{})>>
: std::bool_constant<
(std::same_as<Prescription, Unnormalized> ||
PhysicalRieszDiagonalPrescription<Prescription> ||
RuntimePreparedNormalizationFor<Prescription, Form>) &&
static_cast<bool>(NormalizationCompilation<
Prescription,
Form>::template runtimeAvailableFor<
PhysicalCore,
SpecificationTypes>)> { };
} // namespace detail
/*
* Single detection-safe authority for pairing a compiled stellar form,
* its selected physical core, and a runtime normalization prescription.
* Each public NormalizationCompilation specialization declares this
* compatibility alongside its plan. The identity policy needs only a
* complete plan. Physical Riesz also requires every map consumed during
* assembly and global-scalar runtime preparation for every generated
* coordinate in the specification pack.
*/
template <
typename Prescription,
typename Form,
typename PhysicalCore,
typename SpecificationTypes>
concept StellarNormalizationRuntimeAvailableFor =
detail::StellarNormalizationRuntimeAudit<
std::remove_cvref_t<Prescription>,
std::remove_cvref_t<Form>,
std::remove_cvref_t<PhysicalCore>,
std::remove_cvref_t<SpecificationTypes>>::value;
} // namespace mean_field::normalization

View File

@@ -0,0 +1,376 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:normalization.plan;
export import :utils.blocks;
export namespace mean_field::normalization {
struct NormalizationPrescriptionTag { };
template <typename Candidate>
concept NormalizationPrescription =
std::derived_from<
std::remove_cvref_t<Candidate>,
NormalizationPrescriptionTag>;
enum class CoordinateKind { value, residual };
enum class RieszTopology {
identity,
scalar_volume_l2,
vector_volume_l2,
scalar_boundary_l2,
hybrid_scalar_volume_point_rows,
global_scalar
};
enum class PhysicalScaleKind {
dimensionless,
density,
length,
acceleration,
inverse_time_squared,
specific_energy,
pressure,
mass,
force,
angular_velocity,
angular_momentum
};
struct IdentityCoordinate final { };
/*
* Honest compile-time method for a coordinate whose positive diagonal
* factor is supplied at runtime by one exact normalization prescription.
* Unlike IdentityCoordinate, this category makes no claim about the
* numerical value of that factor. The owner type prevents one policy from
* silently presenting another policy's runtime map as its own plan.
*/
template <NormalizationPrescription Prescription>
struct RuntimePreparedCoordinate final {
using PrescriptionType = std::remove_cvref_t<Prescription>;
};
template <RieszTopology Topology, PhysicalScaleKind Scale> struct PhysicalRieszCoordinate final {
static constexpr RieszTopology topology = Topology;
static constexpr PhysicalScaleKind scale = Scale;
};
struct UnsupportedPhysicalRieszCoordinate final { };
template <typename Block> struct PhysicalRieszBlockTraits {
using Method = UnsupportedPhysicalRieszCoordinate;
static constexpr bool registered = false;
};
template <CoordinateKind Kind, typename BlockList, typename MethodType>
struct CoordinateComponent final {
using Blocks = BlockList;
using Method = MethodType;
static constexpr CoordinateKind kind = Kind;
using ValueBlocks = std::conditional_t<
Kind == CoordinateKind::value,
BlockList,
utils::blocks::type_list<>>;
using ResidualBlocks = std::conditional_t<
Kind == CoordinateKind::residual,
BlockList,
utils::blocks::type_list<>>;
};
namespace detail {
template <typename Candidate> struct IsTypeList : std::false_type { };
template <typename... Types>
struct IsTypeList<utils::blocks::type_list<Types...>> : std::true_type { };
template <typename List, typename Base> struct IsUniqueDerivedBlockList : std::false_type { };
template <typename Base, typename... Blocks>
struct IsUniqueDerivedBlockList<utils::blocks::type_list<Blocks...>, Base>
: std::bool_constant<
(std::derived_from<Blocks, Base> && ...) &&
utils::blocks::types_are_unique_v<utils::blocks::type_list<Blocks...>>> { };
template <typename Method> struct IsCoordinateMethod : std::false_type { };
template <> struct IsCoordinateMethod<IdentityCoordinate> : std::true_type { };
template <NormalizationPrescription Prescription>
struct IsCoordinateMethod<RuntimePreparedCoordinate<Prescription>>
: std::true_type { };
template <RieszTopology Topology, PhysicalScaleKind Scale>
struct IsCoordinateMethod<PhysicalRieszCoordinate<Topology, Scale>> : std::true_type { };
template <typename Method, typename Block> struct MethodSupportsBlock : std::false_type { };
template <typename Block>
struct MethodSupportsBlock<IdentityCoordinate, Block>
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
template <NormalizationPrescription Prescription, typename Block>
struct MethodSupportsBlock<RuntimePreparedCoordinate<Prescription>, Block>
: std::bool_constant<std::derived_from<Block, utils::blocks::block>> { };
template <RieszTopology Topology, PhysicalScaleKind Scale, typename Block>
struct MethodSupportsBlock<PhysicalRieszCoordinate<Topology, Scale>, Block>
: std::bool_constant<
PhysicalRieszBlockTraits<Block>::registered &&
std::same_as<
typename PhysicalRieszBlockTraits<Block>::Method,
PhysicalRieszCoordinate<Topology, Scale>>> { };
template <typename Method, typename List> struct MethodSupportsEveryBlock : std::false_type { };
template <typename Method, typename... Blocks>
struct MethodSupportsEveryBlock<Method, utils::blocks::type_list<Blocks...>>
: std::bool_constant<(MethodSupportsBlock<Method, Blocks>::value && ...)> { };
template <typename Candidate, typename = void> struct ComponentTraits {
static constexpr bool valid = false;
};
template <typename Candidate>
struct ComponentTraits<
Candidate,
std::void_t<
typename Candidate::Blocks,
typename Candidate::Method,
typename Candidate::ValueBlocks,
typename Candidate::ResidualBlocks,
decltype(Candidate::kind)>> {
using Blocks = typename Candidate::Blocks;
using Method = typename Candidate::Method;
using ValueBlocks = typename Candidate::ValueBlocks;
using ResidualBlocks = typename Candidate::ResidualBlocks;
static constexpr bool hasValidKind =
std::same_as<std::remove_cv_t<decltype(Candidate::kind)>, CoordinateKind>;
static constexpr bool hasValidBlockList = [] {
if constexpr (!hasValidKind || !IsTypeList<Blocks>::value) {
return false;
} else if constexpr (Candidate::kind == CoordinateKind::value) {
return IsUniqueDerivedBlockList<Blocks, utils::blocks::value_block_base>::value;
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
return IsUniqueDerivedBlockList<Blocks, utils::blocks::residual_block_base>::value;
} else {
return false;
}
}();
static constexpr bool hasCoherentCoordinateLists = [] {
if constexpr (!hasValidKind || !IsTypeList<ValueBlocks>::value ||
!IsTypeList<ResidualBlocks>::value) {
return false;
} else if constexpr (Candidate::kind == CoordinateKind::value) {
return std::same_as<ValueBlocks, Blocks> &&
std::same_as<ResidualBlocks, utils::blocks::type_list<>>;
} else if constexpr (Candidate::kind == CoordinateKind::residual) {
return std::same_as<ValueBlocks, utils::blocks::type_list<>> &&
std::same_as<ResidualBlocks, Blocks>;
} else {
return false;
}
}();
static constexpr bool valid = hasValidKind && IsTypeList<Blocks>::value &&
IsCoordinateMethod<Method>::value && hasValidBlockList &&
hasCoherentCoordinateLists &&
MethodSupportsEveryBlock<Method, Blocks>::value;
};
template <typename... Lists> struct Concatenate;
template <> struct Concatenate<> {
using Type = utils::blocks::type_list<>;
};
template <typename... Types> struct Concatenate<utils::blocks::type_list<Types...>> {
using Type = utils::blocks::type_list<Types...>;
};
template <typename... Left, typename... Right, typename... Remaining>
struct Concatenate<utils::blocks::type_list<Left...>, utils::blocks::type_list<Right...>, Remaining...> {
using Type = typename Concatenate<utils::blocks::type_list<Left..., Right...>, Remaining...>::Type;
};
template <typename... Lists> using ConcatenateT = typename Concatenate<Lists...>::Type;
template <typename List, typename Type> struct Append;
template <typename... Types, typename Appended>
struct Append<utils::blocks::type_list<Types...>, Appended> {
using Type = utils::blocks::type_list<Types..., Appended>;
};
template <typename List, typename Type> using AppendT = typename Append<List, Type>::Type;
template <typename List, typename Type>
using AppendUniqueT = std::conditional_t<
utils::blocks::contains_type_v<Type, List>,
List,
AppendT<List, Type>>;
template <typename Source, typename Excluded> struct ListDifference;
template <typename Excluded>
struct ListDifference<utils::blocks::type_list<>, Excluded> {
using Type = utils::blocks::type_list<>;
};
template <typename Head, typename... Tail, typename Excluded>
struct ListDifference<utils::blocks::type_list<Head, Tail...>, Excluded> {
private:
using Remaining = typename ListDifference<utils::blocks::type_list<Tail...>, Excluded>::Type;
public:
using Type = std::conditional_t<
utils::blocks::contains_type_v<Head, Excluded>,
Remaining,
ConcatenateT<utils::blocks::type_list<Head>, Remaining>>;
};
template <typename Source, typename Excluded>
using ListDifferenceT = typename ListDifference<Source, Excluded>::Type;
template <typename Remaining, typename Original, typename Repeated> struct CollectRepeatedTypes;
template <typename Original, typename Repeated>
struct CollectRepeatedTypes<utils::blocks::type_list<>, Original, Repeated> {
using Type = Repeated;
};
template <typename Head, typename... Tail, typename Original, typename Repeated>
struct CollectRepeatedTypes<utils::blocks::type_list<Head, Tail...>, Original, Repeated> {
private:
using Next = std::conditional_t<
(utils::blocks::type_count_v<Head, Original> > 1),
AppendUniqueT<Repeated, Head>,
Repeated>;
public:
using Type = typename CollectRepeatedTypes<utils::blocks::type_list<Tail...>, Original, Next>::Type;
};
template <typename List>
using RepeatedTypesT = typename CollectRepeatedTypes<
List,
List,
utils::blocks::type_list<>>::Type;
template <typename Candidate, typename = void> struct PlanTraits {
static constexpr bool valid = false;
};
} // namespace detail
template <typename Candidate>
concept NormalizationComponent = detail::ComponentTraits<std::remove_cvref_t<Candidate>>::valid;
template <typename... Components> struct NormalizationPlan final {
using ComponentTypes = utils::blocks::type_list<Components...>;
using ValueBlocks = detail::ConcatenateT<typename Components::ValueBlocks...>;
using ResidualBlocks = detail::ConcatenateT<typename Components::ResidualBlocks...>;
};
namespace detail {
template <typename... Components>
struct PlanTraits<NormalizationPlan<Components...>> {
static constexpr bool valid = (ComponentTraits<Components>::valid && ...);
};
template <typename Values, typename Residuals> struct MakeIdentityPlan;
template <typename... Values, typename... Residuals>
struct MakeIdentityPlan<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
using Type = NormalizationPlan<
CoordinateComponent<CoordinateKind::value, utils::blocks::type_list<Values>, IdentityCoordinate>...,
CoordinateComponent<
CoordinateKind::residual,
utils::blocks::type_list<Residuals>,
IdentityCoordinate>...>;
};
template <
NormalizationPrescription Prescription,
typename Values,
typename Residuals>
struct MakeRuntimePreparedPlan;
template <
NormalizationPrescription Prescription,
typename... Values,
typename... Residuals>
struct MakeRuntimePreparedPlan<
Prescription,
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>> {
using Method = RuntimePreparedCoordinate<Prescription>;
using Type = NormalizationPlan<
CoordinateComponent<
CoordinateKind::value,
utils::blocks::type_list<Values>,
Method>...,
CoordinateComponent<
CoordinateKind::residual,
utils::blocks::type_list<Residuals>,
Method>...>;
};
} // namespace detail
template <typename Candidate>
concept NormalizationPlanType = detail::PlanTraits<std::remove_cvref_t<Candidate>>::valid;
template <typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using IdentityNormalizationPlanFor = typename detail::MakeIdentityPlan<
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
template <NormalizationPrescription Prescription, typename Form>
requires utils::blocks::block_form_is_valid_v<Form>
using RuntimePreparedNormalizationPlanFor =
typename detail::MakeRuntimePreparedPlan<
std::remove_cvref_t<Prescription>,
typename Form::value_blocks,
typename Form::residual_blocks>::Type;
template <typename Form, typename Plan>
requires utils::blocks::block_form_is_valid_v<Form>
struct NormalizationCoverage final {
using DeclaredValueBlocks = typename Plan::ValueBlocks;
using DeclaredResidualBlocks = typename Plan::ResidualBlocks;
using MissingValueBlocks = detail::ListDifferenceT<typename Form::value_blocks, DeclaredValueBlocks>;
using UnexpectedValueBlocks = detail::ListDifferenceT<DeclaredValueBlocks, typename Form::value_blocks>;
using RepeatedValueBlocks = detail::RepeatedTypesT<DeclaredValueBlocks>;
using MissingResidualBlocks = detail::ListDifferenceT<typename Form::residual_blocks, DeclaredResidualBlocks>;
using UnexpectedResidualBlocks = detail::ListDifferenceT<DeclaredResidualBlocks, typename Form::residual_blocks>;
using RepeatedResidualBlocks = detail::RepeatedTypesT<DeclaredResidualBlocks>;
static constexpr bool hasEveryValueBlock = MissingValueBlocks::size == 0;
static constexpr bool hasOnlyValueBlocks = UnexpectedValueBlocks::size == 0;
static constexpr bool hasUniqueValueOwners = RepeatedValueBlocks::size == 0;
static constexpr bool hasEveryResidualBlock = MissingResidualBlocks::size == 0;
static constexpr bool hasOnlyResidualBlocks = UnexpectedResidualBlocks::size == 0;
static constexpr bool hasUniqueResidualOwners = RepeatedResidualBlocks::size == 0;
static constexpr bool complete = hasEveryValueBlock && hasOnlyValueBlocks && hasUniqueValueOwners &&
hasEveryResidualBlock && hasOnlyResidualBlocks &&
hasUniqueResidualOwners;
};
template <typename Plan, typename Form>
concept CompleteNormalizationFor = utils::blocks::block_form_is_valid_v<Form> &&
NormalizationPlanType<Plan> &&
NormalizationCoverage<Form, std::remove_cvref_t<Plan>>::complete;
} // namespace mean_field::normalization

View File

@@ -0,0 +1,921 @@
module;
#include <concepts>
#include <cstdint>
#include <memory>
#include <span>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <mfem.hpp>
export module mean_field:normalization.stellar_equilibrium;
export import :normalization.operators;
export import :operators.stellar_equilibrium_compiler;
export import :operators.stellar_equilibrium_problem;
export import :utils.domain;
namespace mean_field::normalization::detail {
using DomainSchema = utils::domain::CoreEnvelopeVacuumDomainSchema;
[[nodiscard]] inline mfem::Vector AssembleScalarMassDiagonal(
mfem::ParFiniteElementSpace &space,
mfem::Array<int> *domainMarker = nullptr
) {
mfem::ParBilinearForm mass(&space);
if (domainMarker == nullptr) {
mass.AddDomainIntegrator(new mfem::MassIntegrator());
} else {
mass.AddDomainIntegrator(new mfem::MassIntegrator(), *domainMarker);
}
mass.Assemble();
mass.Finalize();
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
if (matrix == nullptr) {
throw std::runtime_error("Reference scalar Riesz mass assembly failed.");
}
mfem::Vector diagonal;
matrix->GetDiag(diagonal);
return diagonal;
}
[[nodiscard]] inline mfem::Vector AssembleHDivMassDiagonal(mfem::ParFiniteElementSpace &space) {
mfem::ParBilinearForm mass(&space);
mass.AddDomainIntegrator(new mfem::VectorFEMassIntegrator());
mass.Assemble();
mass.Finalize();
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
if (matrix == nullptr) {
throw std::runtime_error("Reference H(div) Riesz mass assembly failed.");
}
mfem::Vector diagonal;
matrix->GetDiag(diagonal);
return diagonal;
}
[[nodiscard]] inline mfem::Vector AssembleSurfaceMassDiagonal(
const fem::FEM &finiteElements,
const field::ScalarBoundaryDofMap &surfaceMap
) {
mfem::Array<int> marker(finiteElements.mesh->bdr_attributes.Max());
marker = 0;
constexpr int attribute = DomainSchema::template boundary_attribute<utils::domain::StellarSurface>();
if (attribute <= 0 || attribute > marker.Size()) {
throw std::invalid_argument("The reference mesh does not contain the stellar-surface boundary.");
}
marker[attribute - 1] = 1;
mfem::ParBilinearForm mass(finiteElements.surfaceDeformationFes.get());
mass.AddBoundaryIntegrator(new mfem::MassIntegrator(), marker);
mass.Assemble();
mass.Finalize();
std::unique_ptr<mfem::HypreParMatrix> matrix(mass.ParallelAssemble());
if (matrix == nullptr) {
throw std::runtime_error("Reference surface Riesz mass assembly failed.");
}
mfem::Vector ambientDiagonal;
matrix->GetDiag(ambientDiagonal);
return surfaceMap.gather(ambientDiagonal);
}
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
[[nodiscard]] const auto &PhysicalOperator(const Problem &problem) {
return problem.GetPhysicalOperator();
}
[[nodiscard]] inline mfem::Vector GatherDiagonal(
const mfem::Vector &fullDiagonal,
const field::FieldDofMap &map,
const char *role
) {
if (fullDiagonal.Size() != map.full_size()) {
throw std::logic_error(std::string("The reference ") + role + " Gram diagonal has an incompatible map.");
}
return map.gather(fullDiagonal);
}
} // namespace mean_field::normalization::detail
export namespace mean_field::normalization {
/*
* Runtime preparation paired with the compile-time normalization plan.
* The operator compiler is the authority for which blocks a specification
* generated, and PhysicalRieszBlockTraits is the authority for their
* declared physical laws. Keeping those responsibilities separate means
* this layer never names a concrete integral or phase constraint.
*/
namespace detail {
template <typename Block>
using PhysicalRieszMethodFor = typename PhysicalRieszBlockTraits<Block>::Method;
template <typename Block, typename = void>
struct IsGlobalGeneratedValueNormalization : std::false_type { };
template <typename Generated>
struct IsGlobalGeneratedValueNormalization<
utils::blocks::generated_value_block<Generated>,
std::void_t<
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::scale)>>
: std::bool_constant<
PhysicalRieszBlockTraits<
utils::blocks::generated_value_block<Generated>>::registered &&
PhysicalRieszMethodFor<
utils::blocks::generated_value_block<Generated>>::topology ==
RieszTopology::global_scalar> { };
template <typename Blocks, typename Specification>
struct GeneratedValueBlocksBelongToSpecification : std::false_type { };
template <typename Generated, typename Specification, typename = void>
struct GeneratedCoordinateBelongsToSpecification : std::false_type { };
template <typename Generated, typename Specification>
struct GeneratedCoordinateBelongsToSpecification<
Generated,
Specification,
std::void_t<typename Generated::SpecificationType>>
: std::bool_constant<
std::same_as<typename Generated::SpecificationType, Specification>> { };
template <typename Specification, typename... Generated>
struct GeneratedValueBlocksBelongToSpecification<
utils::blocks::type_list<utils::blocks::generated_value_block<Generated>...>,
Specification>
: std::bool_constant<
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
template <typename Block, typename = void>
struct IsGlobalGeneratedResidualNormalization : std::false_type { };
template <typename Generated>
struct IsGlobalGeneratedResidualNormalization<
utils::blocks::generated_residual_block<Generated>,
std::void_t<
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::topology),
decltype(PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::scale)>>
: std::bool_constant<
PhysicalRieszBlockTraits<
utils::blocks::generated_residual_block<Generated>>::registered &&
PhysicalRieszMethodFor<
utils::blocks::generated_residual_block<Generated>>::topology ==
RieszTopology::global_scalar> { };
template <typename Blocks, typename Specification>
struct GeneratedResidualBlocksBelongToSpecification : std::false_type { };
template <typename Specification, typename... Generated>
struct GeneratedResidualBlocksBelongToSpecification<
utils::blocks::type_list<utils::blocks::generated_residual_block<Generated>...>,
Specification>
: std::bool_constant<
(GeneratedCoordinateBelongsToSpecification<Generated, Specification>::value && ...)> { };
template <typename Blocks> struct PrepareGeneratedValueNormalizations {
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &,
const StellarCharacteristicScales &
) {
static_assert(registered, "Generated value-block normalization metadata is malformed.");
}
};
template <typename... Blocks>
struct PrepareGeneratedValueNormalizations<utils::blocks::type_list<Blocks...>> {
static constexpr bool registered =
(IsGlobalGeneratedValueNormalization<Blocks>::value && ...);
template <typename Form>
static constexpr bool completeFor = registered &&
utils::blocks::block_form_is_valid_v<Form> &&
(utils::blocks::contains_type_v<Blocks, typename Form::value_blocks> && ...);
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &builder,
const StellarCharacteristicScales &scales
) {
if constexpr (completeFor<Form>) {
(builder.template SetValueGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
} else {
static_assert(
completeFor<Form>,
"Every generated value block must have a declared global-scalar Physical Riesz law "
"and belong to the compiled equilibrium form."
);
}
}
};
template <typename Blocks> struct PrepareGeneratedResidualNormalizations {
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &,
const StellarCharacteristicScales &
) {
static_assert(registered, "Generated residual-block normalization metadata is malformed.");
}
};
template <typename... Blocks>
struct PrepareGeneratedResidualNormalizations<utils::blocks::type_list<Blocks...>> {
static constexpr bool registered =
(IsGlobalGeneratedResidualNormalization<Blocks>::value && ...);
template <typename Form>
static constexpr bool completeFor = registered &&
utils::blocks::block_form_is_valid_v<Form> &&
(utils::blocks::contains_type_v<Blocks, typename Form::residual_blocks> && ...);
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &builder,
const StellarCharacteristicScales &scales
) {
if constexpr (completeFor<Form>) {
(builder.template SetResidualGlobal<Blocks>(physicalScale<Blocks>(scales)), ...);
} else {
static_assert(
completeFor<Form>,
"Every generated residual block must have a declared global-scalar Physical Riesz law "
"and belong to the compiled equilibrium form."
);
}
}
};
template <typename Specification, typename = void>
struct CompileStellarSpecificationNormalization {
using ValuePreparation = PrepareGeneratedValueNormalizations<void>;
using ResidualPreparation = PrepareGeneratedResidualNormalizations<void>;
static constexpr bool registered = false;
template <typename Form>
static constexpr bool completeFor = false;
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &,
const StellarCharacteristicScales &
) {
static_assert(
completeFor<Form>,
"The specification has no complete generated-coordinate normalization."
);
}
};
template <models::ModelSpecification Specification>
struct CompileStellarSpecificationNormalization<
Specification,
std::void_t<
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedValueBlocks,
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks>> {
using OperatorCompilation =
operators::StellarEquilibriumSpecificationCompilation<Specification>;
using ValuePreparation = PrepareGeneratedValueNormalizations<
typename OperatorCompilation::GeneratedValueBlocks>;
using ResidualPreparation = PrepareGeneratedResidualNormalizations<
typename OperatorCompilation::GeneratedResidualBlocks>;
static constexpr bool registered = OperatorCompilation::complete &&
models::CompleteGeneratedNormalizationFor<
Specification> &&
GeneratedValueBlocksBelongToSpecification<
typename OperatorCompilation::GeneratedValueBlocks,
Specification>::value &&
GeneratedResidualBlocksBelongToSpecification<
typename OperatorCompilation::GeneratedResidualBlocks,
Specification>::value &&
ValuePreparation::registered &&
ResidualPreparation::registered;
template <typename Form>
static constexpr bool completeFor = registered &&
ValuePreparation::template completeFor<Form> &&
ResidualPreparation::template completeFor<Form>;
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &builder,
const StellarCharacteristicScales &scales
) {
if constexpr (completeFor<Form>) {
ValuePreparation::template Apply<Form>(builder, scales);
ResidualPreparation::template Apply<Form>(builder, scales);
} else {
static_assert(
completeFor<Form>,
"The specification's generated blocks do not have a complete runtime normalization."
);
}
}
};
template <typename SpecificationSet> struct PrepareSpecificationNormalizations;
template <models::ModelSpecification... Specifications>
struct PrepareSpecificationNormalizations<models::detail::SpecificationSetStorage<Specifications...>> {
static constexpr bool registered =
(CompileStellarSpecificationNormalization<Specifications>::registered && ...);
template <typename Form>
static constexpr bool completeFor =
(CompileStellarSpecificationNormalization<Specifications>::template completeFor<Form> && ...);
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &builder,
const StellarCharacteristicScales &scales
) {
static_assert(
completeFor<Form>,
"Every generated stellar-equilibrium coordinate requires a declared global-scalar "
"Physical Riesz normalization and compiler-owned root block."
);
(CompileStellarSpecificationNormalization<Specifications>::template Apply<Form>(builder, scales), ...);
}
};
template <typename Model, typename Form, typename = void>
struct StellarModelNormalizationCoverage : std::false_type { };
template <typename Model, typename Form>
requires model::StellarModelType<Model> && utils::blocks::block_form_is_valid_v<Form>
struct StellarModelNormalizationCoverage<
Model,
Form,
std::void_t<typename std::remove_cvref_t<Model>::SpecificationTypes>>
: std::bool_constant<
PrepareSpecificationNormalizations<
typename std::remove_cvref_t<Model>::SpecificationTypes>::template completeFor<Form>> { };
} // namespace detail
template <typename Specification>
struct StellarSpecificationNormalizationContribution
: detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>> {
using Base = detail::CompileStellarSpecificationNormalization<std::remove_cvref_t<Specification>>;
template <typename Form>
static void Apply(
DiagonalNormalizationBuilder<Form> &builder,
const StellarCharacteristicScales &scales
) {
static_assert(
Base::template completeFor<Form>,
"The specification's generated blocks do not have a complete runtime normalization."
);
Base::template Apply<Form>(builder, scales);
}
};
template <typename Specification>
concept RegisteredStellarSpecificationNormalization =
StellarSpecificationNormalizationContribution<Specification>::registered;
template <typename Specification, typename Form>
concept CompleteStellarSpecificationNormalizationFor =
utils::blocks::block_form_is_valid_v<Form> &&
StellarSpecificationNormalizationContribution<Specification>::template completeFor<Form>;
template <typename Model, typename Form>
concept CompleteStellarNormalizationFor =
detail::StellarModelNormalizationCoverage<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Form>>::value;
/*
* Physical Riesz preparation is an optional capability of a physical
* core, not part of the protocol needed by the variadic equilibrium root.
* Keeping this boundary structural lets a new EOS core opt in by exposing
* the same discretization maps without inheriting from, or otherwise
* naming, the Polytrope implementation.
*/
template <typename Candidate>
concept PhysicalRieszStellarEquilibriumCore =
operators::PreparedStellarEquilibriumPhysicalCore<std::remove_cvref_t<Candidate>> &&
PhysicalRieszCoreRuntime<std::remove_cvref_t<Candidate>>;
template <typename Problem>
concept PhysicalRieszStellarEquilibriumProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
requires {
typename std::remove_cvref_t<Problem>::ModelType;
typename std::remove_cvref_t<Problem>::FormType;
typename std::remove_cvref_t<Problem>::PhysicalCoreType;
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType;
requires PhysicalRieszDiagonalPrescription<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType>;
requires CompilableNormalizationFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType>;
requires CompleteStellarNormalizationFor<
typename std::remove_cvref_t<Problem>::ModelType,
typename std::remove_cvref_t<Problem>::FormType>;
requires StellarNormalizationRuntimeAvailableFor<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Problem>::FormType,
typename std::remove_cvref_t<Problem>::PhysicalCoreType,
typename std::remove_cvref_t<Problem>::ModelType::SpecificationTypes>;
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
requires std::same_as<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
Unnormalized>
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
return DiagonalNormalization::Identity(problem.StateSize(), problem.EquationSize());
}
template <PhysicalRieszStellarEquilibriumProblem Problem>
[[nodiscard]] DiagonalNormalization prepareNormalization(const Problem &problem) {
using ProblemType = std::remove_cvref_t<Problem>;
using Form = typename ProblemType::FormType;
const fem::FEM &finiteElements = problem.GetDiscretization().finiteElementModel();
if (!finiteElements.okay()) {
throw std::invalid_argument("Physical Riesz preparation requires a current finite-element model.");
}
const auto &physical = detail::PhysicalOperator(problem);
const auto &gravityContext = physical.GetGravityContext();
const auto &enthalpyMap = physical.GetHydrostaticOperator().GetEnthalpyMap();
const auto scales = deriveStellarCharacteristicScales(
problem.GetNormalizationPrescription(),
problem.GetStellarModel()
);
mfem::Array<int> stellarMarker =
utils::domain::make_attribute_marker<utils::domain::Stellar, detail::DomainSchema>(*finiteElements.mesh);
const mfem::Vector densityDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.densityFes, &stellarMarker),
gravityContext.GetDensityMap(),
"density"
);
const mfem::Vector enthalpyDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.enthalpyFes, &stellarMarker),
enthalpyMap,
"enthalpy"
);
const mfem::Vector gravityGradientDiagonal = detail::GatherDiagonal(
detail::AssembleHDivMassDiagonal(*finiteElements.gravityFluxFes),
gravityContext.GetGravityGradientMap(),
"gravity-gradient"
);
const mfem::Vector gravityPotentialDiagonal = detail::GatherDiagonal(
detail::AssembleScalarMassDiagonal(*finiteElements.gravityPotentialFes),
gravityContext.GetGravityPotentialMap(),
"gravity-potential"
);
const field::ScalarBoundaryDofMap surfaceMap =
field::make_stellar_surface_scalar_dof_map<detail::DomainSchema>(*finiteElements.surfaceDeformationFes);
const mfem::Vector surfaceDiagonal = detail::AssembleSurfaceMassDiagonal(finiteElements, surfaceMap);
if (surfaceDiagonal.Size() != physical.GetDomainDeformation().parameterCount()) {
throw std::logic_error("The reference surface Gram diagonal does not match the root surface block.");
}
DiagonalNormalizationBuilder<Form> builder(problem.GetManifest().layout());
builder.template SetValueBlock<utils::blocks::density::mass::value>(
physicalScale<utils::blocks::density::mass::value>(scales), densityDiagonal
);
builder.template SetValueBlock<utils::blocks::surface_deformation::parameters::value>(
physicalScale<utils::blocks::surface_deformation::parameters::value>(scales), surfaceDiagonal
);
builder.template SetValueBlock<utils::blocks::gravity::gradient::value>(
physicalScale<utils::blocks::gravity::gradient::value>(scales), gravityGradientDiagonal
);
builder.template SetValueBlock<utils::blocks::gravity::poisson::value>(
physicalScale<utils::blocks::gravity::poisson::value>(scales), gravityPotentialDiagonal
);
builder.template SetValueBlock<utils::blocks::enthalpy::specific::value>(
physicalScale<utils::blocks::enthalpy::specific::value>(scales), enthalpyDiagonal
);
builder.template SetResidualBlock<utils::blocks::gravity::gradient::residual>(
physicalScale<utils::blocks::gravity::gradient::residual>(scales), gravityGradientDiagonal
);
builder.template SetResidualBlock<utils::blocks::gravity::poisson::residual>(
physicalScale<utils::blocks::gravity::poisson::residual>(scales), gravityPotentialDiagonal
);
builder.template SetResidualBlock<utils::blocks::density::mass::residual>(
physicalScale<utils::blocks::density::mass::residual>(scales), densityDiagonal
);
builder.template SetResidualBlock<utils::blocks::surface_deformation::shape_equilibrium::residual>(
physicalScale<utils::blocks::surface_deformation::shape_equilibrium::residual>(scales), surfaceDiagonal
);
const mfem::Array<int> &surfaceRows = problem.GetPressureSurfaceRows().reduced_dofs();
builder.template SetHybridResidualBlock<utils::blocks::enthalpy::specific::residual>(
physicalScale<utils::blocks::enthalpy::specific::residual>(scales),
enthalpyDiagonal,
std::span<const int>{surfaceRows.GetData(), static_cast<std::size_t>(surfaceRows.Size())}
);
detail::PrepareSpecificationNormalizations<typename ProblemType::ModelType::SpecificationTypes>::Apply(
builder,
scales
);
return std::move(builder).Build();
}
/* Public adapter for a third-party prescription. The implementation stays
* beside the policy and has the readable signature
*
* prepareStellarNormalization(policy, problem)
*
* while every solver-facing caller continues to use the uniform
* prepareNormalization(problem) operation. */
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
requires(
!std::same_as<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType,
Unnormalized> &&
!PhysicalRieszDiagonalPrescription<
typename std::remove_cvref_t<Problem>::NormalizationPrescriptionType> &&
RuntimePreparedNormalizationOperation<Problem>)
[[nodiscard]] DiagonalNormalization prepareNormalization(
const Problem &problem
) {
return prepareStellarNormalization(
problem.GetNormalizationPrescription(),
problem
);
}
/*
* Solver-facing normalization exists exactly when runtime preparation for
* the problem's compile-time prescription is a valid operation. This
* folds future policy-owned preparation hooks into the same public contract and
* turns unsupported core/prescription pairs into ordinary constraint
* failure instead of an error in a constructor body.
*/
template <typename Problem>
concept NormalizableStellarEquilibriumProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
requires(const std::remove_cvref_t<Problem> &problem) {
{
prepareNormalization(problem)
} -> std::same_as<DiagonalNormalization>;
};
struct NormalizedStellarEquilibriumStatistics final {
std::uint64_t normalizationPreparations{0};
std::uint64_t physicalPreparations{0};
std::uint64_t residualRetrievals{0};
std::uint64_t jacobianApplications{0};
};
/*
* The high-level stellar adapter retains a pointer to a prepared inverse.
* Consequently that inverse must identify the exact physical problem and
* expose its lifecycle state. Generic MFEM solvers remain valid inputs to
* the lower-level ScaledPreconditioner, where no stellar association is
* implied.
*/
template <typename Candidate, typename Problem>
concept ProblemBoundStellarInverseFor =
NormalizableStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
std::derived_from<std::remove_cvref_t<Candidate>, mfem::Solver> &&
requires(const std::remove_cvref_t<Candidate> &inverse) {
{
inverse.GetProblem()
} -> std::same_as<const std::remove_cvref_t<Problem> &>;
{
inverse.IsCurrent()
} -> std::same_as<bool>;
};
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
class NormalizedStellarPreconditioner;
/*
* Solver-facing coordinates for a dimensional stellar problem. The
* physical problem remains the sole source of residual and Jacobian
* physics; this adapter performs only the coordinate maps
*
* x = R x_hat, F_hat = L F, J_hat = L J R.
*
* Its normalization is immutable during Prepare/BuildResidual/Mult and is
* changed only by an explicit RefreshNormalization call.
*/
template <NormalizableStellarEquilibriumProblem Problem>
class NormalizedStellarEquilibriumOperator final : public mfem::Operator {
private:
using ProblemType = std::remove_cvref_t<Problem>;
public:
explicit NormalizedStellarEquilibriumOperator(ProblemType &problem)
: mfem::Operator(problem.EquationSize(), problem.StateSize()),
m_problem(&problem),
m_normalization(prepareNormalization(problem)),
m_scaledJacobian(problem.GetLinearizationOperator(), m_normalization),
m_physicalState(problem.StateSize()),
m_physicalResidual(problem.EquationSize()),
m_normalizedResidual(problem.EquationSize()) {
if (Width() != Height()) {
throw std::invalid_argument("A normalized stellar-equilibrium operator must be square.");
}
m_statistics.normalizationPreparations = 1;
}
NormalizedStellarEquilibriumOperator(const NormalizedStellarEquilibriumOperator &) = delete;
NormalizedStellarEquilibriumOperator &operator=(const NormalizedStellarEquilibriumOperator &) = delete;
NormalizedStellarEquilibriumOperator(NormalizedStellarEquilibriumOperator &&) = delete;
NormalizedStellarEquilibriumOperator &operator=(NormalizedStellarEquilibriumOperator &&) = delete;
[[nodiscard]] auto Prepare(
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) requires(ProblemType::generatedRotationProviderCount == 0) {
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
m_isPrepared = false;
m_normalization.DenormalizeState(normalizedState, m_physicalState);
auto report = m_problem->Prepare(m_physicalState, dependencies, rotation);
m_problem->BuildResidual(m_physicalResidual);
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
m_isPrepared = true;
++m_statistics.physicalPreparations;
return report;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &normalizedState,
const operators::StellarEquilibriumDependencies &dependencies
) requires(ProblemType::generatedRotationProviderCount == 1) {
if (normalizedState.Size() != Width()) {
throw std::invalid_argument("The normalized stellar state has the wrong size.");
}
m_isPrepared = false;
m_normalization.DenormalizeState(normalizedState, m_physicalState);
auto report = m_problem->Prepare(m_physicalState, dependencies);
m_problem->BuildResidual(m_physicalResidual);
m_normalization.NormalizeResidual(m_physicalResidual, m_normalizedResidual);
m_physicalPreparationGeneration = m_problem->GetPreparationGeneration();
m_isPrepared = true;
++m_statistics.physicalPreparations;
return report;
}
void BuildResidual(mfem::Vector &normalizedResidual) const {
VerifyPrepared();
normalizedResidual = m_normalizedResidual;
++m_statistics.residualRetrievals;
}
void Mult(
const mfem::Vector &normalizedDirection,
mfem::Vector &normalizedAction
) const override {
VerifyPrepared();
if (normalizedDirection.Size() != Width()) {
throw std::invalid_argument("The normalized stellar direction has the wrong size.");
}
m_scaledJacobian.Mult(normalizedDirection, normalizedAction);
++m_statistics.jacobianApplications;
}
void RefreshNormalization() {
DiagonalNormalization refreshed = prepareNormalization(*m_problem);
m_normalization = std::move(refreshed);
m_isPrepared = false;
++m_statistics.normalizationPreparations;
}
void NormalizeState(
const mfem::Vector &physicalState,
mfem::Vector &normalizedState
) const {
m_normalization.NormalizeState(physicalState, normalizedState);
}
void DenormalizeState(
const mfem::Vector &normalizedState,
mfem::Vector &physicalState
) const {
m_normalization.DenormalizeState(normalizedState, physicalState);
}
void NormalizeResidual(
const mfem::Vector &physicalResidual,
mfem::Vector &normalizedResidual
) const {
m_normalization.NormalizeResidual(physicalResidual, normalizedResidual);
}
void DenormalizeResidual(
const mfem::Vector &normalizedResidual,
mfem::Vector &physicalResidual
) const {
m_normalization.DenormalizeResidual(normalizedResidual, physicalResidual);
}
template <typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
[[nodiscard]] NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const;
[[nodiscard]] bool IsPrepared() const noexcept {
return m_isPrepared && m_problem->IsPrepared() &&
m_physicalPreparationGeneration == m_problem->GetPreparationGeneration();
}
[[nodiscard]] ProblemType &GetPhysicalProblem() noexcept {
return *m_problem;
}
[[nodiscard]] const ProblemType &GetPhysicalProblem() const noexcept {
return *m_problem;
}
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
return m_problem->GetLinearizationOperator();
}
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
return *m_problem;
}
[[nodiscard]] const DiagonalNormalization &GetNormalization() const noexcept {
return m_normalization;
}
[[nodiscard]] const mfem::Vector &GetPhysicalState() const {
VerifyPrepared();
return m_physicalState;
}
[[nodiscard]] const mfem::Vector &GetPhysicalResidual() const {
VerifyPrepared();
return m_physicalResidual;
}
[[nodiscard]] const NormalizedStellarEquilibriumStatistics &GetStatistics() const noexcept {
return m_statistics;
}
private:
void VerifyPrepared() const {
if (!IsPrepared()) {
throw std::logic_error(
"The normalized stellar-equilibrium operator must be prepared and current before application."
);
}
}
ProblemType *m_problem;
DiagonalNormalization m_normalization;
ScaledJacobianOperator m_scaledJacobian;
mfem::Vector m_physicalState;
mfem::Vector m_physicalResidual;
mfem::Vector m_normalizedResidual;
std::uint64_t m_physicalPreparationGeneration{0};
mutable NormalizedStellarEquilibriumStatistics m_statistics;
bool m_isPrepared{false};
};
template <NormalizableStellarEquilibriumProblem Problem, typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
class NormalizedStellarPreconditioner final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
using NormalizedOperator = NormalizedStellarEquilibriumOperator<ProblemType>;
using PhysicalInverseType = std::remove_cvref_t<PhysicalInverse>;
[[nodiscard]] static PhysicalInverseType &RequireAssociatedPhysicalInverse(
const NormalizedOperator &normalizedOperator,
PhysicalInverseType &physicalInverse
) {
if (std::addressof(physicalInverse.GetProblem()) !=
std::addressof(normalizedOperator.GetProblem())) {
throw std::invalid_argument(
"A normalized stellar preconditioner and its physical inverse must belong to the same problem."
);
}
return physicalInverse;
}
public:
NormalizedStellarPreconditioner(
const NormalizedOperator &normalizedOperator,
PhysicalInverseType &physicalInverse
)
: mfem::Solver(
normalizedOperator.Width(),
normalizedOperator.Height(),
physicalInverse.iterative_mode
),
m_normalizedOperator(&normalizedOperator),
m_physicalInverse(&physicalInverse),
m_scaled(
RequireAssociatedPhysicalInverse(normalizedOperator, physicalInverse),
normalizedOperator.GetPhysicalJacobian(),
normalizedOperator,
normalizedOperator.GetNormalization()
) {
}
NormalizedStellarPreconditioner(const NormalizedStellarPreconditioner &) = delete;
NormalizedStellarPreconditioner &operator=(const NormalizedStellarPreconditioner &) = delete;
NormalizedStellarPreconditioner(NormalizedStellarPreconditioner &&) = delete;
NormalizedStellarPreconditioner &operator=(NormalizedStellarPreconditioner &&) = delete;
void SetOperator(const mfem::Operator &normalizedJacobian) override {
VerifyCurrent();
if (&normalizedJacobian != m_normalizedOperator) {
throw std::invalid_argument(
"The normalized stellar preconditioner cannot be rebound to a different Jacobian."
);
}
m_scaled.SetOperator(normalizedJacobian);
}
void Mult(
const mfem::Vector &normalizedResidual,
mfem::Vector &normalizedCorrection
) const override {
VerifyCurrent();
m_scaled.Mult(normalizedResidual, normalizedCorrection);
}
[[nodiscard]] bool IsCurrent() const {
return m_normalizedOperator->IsPrepared() &&
m_physicalInverse->IsCurrent();
}
[[nodiscard]] PhysicalInverseType &GetPhysicalInverse() noexcept {
return *m_physicalInverse;
}
[[nodiscard]] const PhysicalInverseType &GetPhysicalInverse() const noexcept {
return *m_physicalInverse;
}
[[nodiscard]] const mfem::Operator &GetPhysicalJacobian() const noexcept {
return m_scaled.GetPhysicalJacobian();
}
[[nodiscard]] const mfem::Operator &GetNormalizedJacobian() const {
return m_scaled.GetNormalizedJacobian();
}
[[nodiscard]] const ScaledPreconditionerStatistics &GetStatistics() const noexcept {
return m_scaled.GetStatistics();
}
private:
void VerifyCurrent() const {
if (!IsCurrent()) {
throw std::logic_error(
"The normalized stellar preconditioner cannot be used while its normalized operator or physical "
"inverse is stale."
);
}
}
const NormalizedOperator *m_normalizedOperator;
PhysicalInverseType *m_physicalInverse;
ScaledPreconditioner m_scaled;
};
template <NormalizableStellarEquilibriumProblem Problem>
template <typename PhysicalInverse>
requires ProblemBoundStellarInverseFor<PhysicalInverse, Problem>
NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>
NormalizedStellarEquilibriumOperator<Problem>::MakeScaledPreconditioner(PhysicalInverse &physicalInverse) const {
VerifyPrepared();
return NormalizedStellarPreconditioner<Problem, std::remove_cvref_t<PhysicalInverse>>{
*this,
physicalInverse
};
}
template <NormalizableStellarEquilibriumProblem Problem>
[[nodiscard]] auto makeNormalizedStellarEquilibriumOperator(Problem &problem) {
return NormalizedStellarEquilibriumOperator<Problem>{problem};
}
} // namespace mean_field::normalization

View File

@@ -0,0 +1,196 @@
module;
#include <compare>
#include <cstdint>
#include <vector>
#include <mfem.hpp>
export module mean_field:operators.prepared_angular_momentum;
export import :fem;
export import :mapping.domain_mapper;
export import :model.compiled_fixed_angular_momentum;
export import :operators.context.gravity_field;
export namespace mean_field::operators {
struct AngularMomentumDependencyStamp final {
std::uint64_t identity{0};
std::uint64_t revision{0};
constexpr auto operator<=>(const AngularMomentumDependencyStamp &) const = default;
};
struct AngularMomentumDependencies final {
AngularMomentumDependencyStamp discretization;
AngularMomentumDependencyStamp density;
AngularMomentumDependencyStamp displacement;
AngularMomentumDependencyStamp rotation;
constexpr auto operator<=>(const AngularMomentumDependencies &) const = default;
};
struct PreparedAngularMomentumReport final {
bool rebuiltStaticPlan{false};
bool refreshedGeometry{false};
bool refreshedDensity{false};
bool updatedAngularVelocity{false};
bool assembledResidual{false};
[[nodiscard]] bool DidAnyWork() const noexcept {
return rebuiltStaticPlan || refreshedGeometry || refreshedDensity || updatedAngularVelocity ||
assembledResidual;
}
constexpr auto operator<=>(const PreparedAngularMomentumReport &) const = default;
};
struct AngularMomentumConstraintReport final {
double targetAngularMomentum;
double achievedAngularMomentum;
double momentOfInertia;
double angularVelocity;
double dimensionalResidual;
double scaledResidual;
};
struct PreparedAngularMomentumActionStatistics final {
std::uint64_t densityApplications{0};
std::uint64_t displacementApplications{0};
std::uint64_t angularVelocityApplications{0};
std::uint64_t completeApplications{0};
constexpr auto operator<=>(const PreparedAngularMomentumActionStatistics &) const = default;
};
/*
* Prepared scalar invariant
*
* R_J(rho, d, Omega) = Omega I_axis(rho, d) - J_target,
* I_axis = integral rho |(x-x_0)_perp|^2 dV.
*
* The axis is normalized by CompiledFixedAngularMomentum. Density and
* geometry are borrowed from the shared gravity context, so this row is
* linearized at exactly the same mapped state as every physical equation.
*/
class PreparedAngularMomentumOperator final {
public:
using SpecificationType = models::FixedAngularMomentum;
using CompiledConstraintType = models::CompiledFixedAngularMomentum;
using Dependencies = AngularMomentumDependencies;
using Report = PreparedAngularMomentumReport;
PreparedAngularMomentumOperator(
const fem::FEM &f,
const mapping::DomainMapper &domainMapper,
const context::gravity_field::GravityFieldLinearizationContext &gravityContext,
models::CompiledFixedAngularMomentum constraint
);
PreparedAngularMomentumOperator(const PreparedAngularMomentumOperator &) = delete;
PreparedAngularMomentumOperator &operator=(const PreparedAngularMomentumOperator &) = delete;
PreparedAngularMomentumOperator(PreparedAngularMomentumOperator &&) = delete;
PreparedAngularMomentumOperator &operator=(PreparedAngularMomentumOperator &&) = delete;
PreparedAngularMomentumReport Prepare(
double angularVelocity,
const AngularMomentumDependencies &dependencies
);
void BuildResidual(mfem::Vector &residual) const;
void ApplyDensityJacobianAction(
const mfem::Vector &densityVariation,
mfem::Vector &action
) const;
void ApplyDisplacementJacobianAction(
const mfem::Vector &displacementVariation,
mfem::Vector &action
) const;
void ApplyAngularVelocityJacobianAction(
double angularVelocityVariation,
mfem::Vector &action
) const;
void ApplyCompleteJacobianAction(
const mfem::Vector &densityVariation,
const mfem::Vector &displacementVariation,
double angularVelocityVariation,
mfem::Vector &action
) const;
[[nodiscard]] bool IsPrepared() const noexcept;
[[nodiscard]] double GetMomentOfInertia() const;
[[nodiscard]] double GetAngularVelocity() const;
[[nodiscard]] double GetCurrentAngularMomentum() const;
[[nodiscard]] double GetTargetAngularMomentum() const noexcept;
[[nodiscard]] physics::RigidRotation GetRotation() const;
[[nodiscard]] AngularMomentumConstraintReport GetConstraintReport() const;
[[nodiscard]] std::uint64_t GetPreparationCount() const noexcept;
[[nodiscard]] std::uint64_t GetResidualApplicationCount() const noexcept;
[[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};
};
struct ElementPAData final {
int elementId{-1};
mfem::Array<int> densityDofs;
mfem::Array<int> displacementDofs;
mfem::Array<int> compactificationDofs;
mfem::DofTransformation *densityDofTransformation{nullptr};
mfem::DofTransformation *displacementDofTransformation{nullptr};
mfem::DofTransformation *compactificationDofTransformation{nullptr};
mfem::Vector baseDisplacement;
mfem::Vector compactification;
std::vector<QuadraturePointData> quadraturePoints;
};
void BuildStaticPlan();
void RefreshGeometry(const mfem::Vector &displacement);
void RefreshDensity(const mfem::Vector &density);
void AssembleResidual();
void VerifyPrepared() const;
[[nodiscard]] double EvaluateDensityMomentActionLocal(const mfem::Vector &densityVariation) const;
[[nodiscard]] double EvaluateDisplacementMomentActionLocal(const mfem::Vector &displacementVariation) const;
[[nodiscard]] double CylindricalRadiusSquared(const mfem::Vector &physicalPosition) const noexcept;
[[nodiscard]] double CylindricalRadiusSquaredVariation(
const mfem::Vector &physicalPosition,
const mfem::Vector &physicalPositionVariation
) const noexcept;
[[nodiscard]] double GlobalSum(double localValue) const;
const fem::FEM &m_fem;
const mapping::DomainMapper &m_domainMapper;
const context::gravity_field::GravityFieldLinearizationContext &m_gravityContext;
models::CompiledFixedAngularMomentum m_constraint;
std::vector<ElementPAData> m_elements;
AngularMomentumDependencies m_preparedDependencies;
mfem::Vector m_cachedResidual;
mutable mfem::Vector m_densityVariationTrue;
mutable mfem::Vector m_displacementVariationTrue;
mutable mfem::Vector m_densityVariationLocal;
mutable mfem::Vector m_displacementVariationLocal;
mutable mfem::Vector m_elementDensityVariation;
mutable mfem::Vector m_elementDisplacementVariation;
double m_momentOfInertia{0.0};
double m_angularVelocity{0.0};
double m_currentAngularMomentum{0.0};
std::uint64_t m_preparationCount{0};
mutable std::uint64_t m_residualApplicationCount{0};
mutable PreparedAngularMomentumActionStatistics m_actionStatistics;
bool m_isPrepared{false};
};
} // namespace mean_field::operators

View File

@@ -1,117 +0,0 @@
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

@@ -35,6 +35,7 @@ export namespace mean_field::operators {
std::uint64_t enthalpyApplications{0};
std::uint64_t gravityPotentialApplications{0};
std::uint64_t bernoulliConstantApplications{0};
std::uint64_t rotationAmplitudeApplications{0};
std::uint64_t combinedApplications{0};
constexpr auto operator<=>(const PreparedHydrostaticAlgebraicJacobianStatistics &) const = default;
@@ -118,6 +119,14 @@ export namespace mean_field::operators {
mfem::Vector &action
) const;
// Differentiates a multiplicative change Omega -> (1 + alpha) Omega
// at the frozen rigid rotation. Since Psi_rotation is quadratic in
// Omega, this contributes -2 alpha Psi_rotation to the hydrostatic row.
void ApplyRotationAmplitudeJacobianAction(
double fractionalAngularVelocityVariation,
mfem::Vector &action
) const;
void ApplyAlgebraicJacobianAction(
const mfem::Vector &enthalpyVariation,
const mfem::Vector &gravityPotentialVariation,

View File

@@ -96,6 +96,20 @@ export namespace mean_field::operators {
class PreparedStellarEquilibriumOperator final : public mfem::Operator {
public:
/*
* Privileged aggregate runtimes can inspect this complete numerical
* core. Keep their allow-list on the concrete core itself: a custom
* EOS may reuse this class, but it cannot extend the class's backend
* privileges. Ordinary specifications use restricted nested physics
* and never interact with this list.
*/
using BackendSpecifications = models::ModelTypeList<
eos::Polytrope,
surface::Isobaric,
models::FixedTotalMass,
models::FixedAngularMomentum,
models::FixedCentralDensity>;
template <models::StellarModelType Model>
requires std::same_as<
typename std::remove_cvref_t<Model>::EquationOfStateType,
@@ -174,6 +188,12 @@ export namespace mean_field::operators {
[[nodiscard]] const PreparedHydrostaticEquilibriumOperator &GetHydrostaticOperator() const noexcept;
[[nodiscard]] const PreparedDisplacementResidualOperator &GetDisplacementOperator() const noexcept;
[[nodiscard]] const PreparedMassNormalizationOperator &GetMassNormalizationOperator() const noexcept;
[[nodiscard]] double ApplyDensityVolumeIntegralDensityAction(
const mfem::Vector &densityDirection
) const;
[[nodiscard]] double ApplyDensityVolumeIntegralSurfaceShapeAction(
const mfem::Vector &surfaceShapeDirection
) const;
[[nodiscard]] const PreparedPressureSurfaceConstraint &GetSurfaceConstraintOperator() const noexcept;
[[nodiscard]] const deformation::PreparedDomainDeformationRuntime &GetDomainDeformation() const noexcept;
[[nodiscard]] const mfem::Vector &GetSurfaceDeformationParameters() const;
@@ -234,5 +254,6 @@ export namespace mean_field::operators {
mutable mfem::Vector m_fullMechanicalAction;
mutable mfem::Vector m_surfaceShapeAction;
mutable mfem::Vector m_pullbackDerivativeAction;
mutable mfem::Vector m_densityVolumeIntegralAction;
};
} // namespace mean_field::operators

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,815 @@
module;
#include <concepts>
#include <type_traits>
export module mean_field:operators.stellar_equilibrium_compiler;
export import :model.compiled_fixed_angular_momentum;
export import :model.compiled_fixed_central_density;
export import :model.typed_stellar;
export import :utils.blocks;
export namespace mean_field::operators {
/*
* A coupling is the symbolic statement that one Jacobian block may be
* nonzero. Specifications contribute these statements independently of
* the final row and column layout.
*/
template <typename ResidualBlock, typename ValueBlock>
struct StellarEquilibriumJacobianCoupling final {
using Residual = ResidualBlock;
using Value = ValueBlock;
using ResidualBlockType = ResidualBlock;
using ValueBlockType = ValueBlock;
};
template <typename ResidualBlock, typename ValueBlock>
using EquilibriumJacobianCoupling =
StellarEquilibriumJacobianCoupling<ResidualBlock, ValueBlock>;
namespace detail {
template <typename... Lists> struct ConcatenateBlockLists;
template <> struct ConcatenateBlockLists<> {
using Type = utils::blocks::type_list<>;
};
template <typename... Types>
struct ConcatenateBlockLists<utils::blocks::type_list<Types...>> {
using Type = utils::blocks::type_list<Types...>;
};
template <typename... First, typename... Second, typename... Remaining>
struct ConcatenateBlockLists<utils::blocks::type_list<First...>,
utils::blocks::type_list<Second...>,
Remaining...> {
using Type = typename ConcatenateBlockLists<
utils::blocks::type_list<First..., Second...>, Remaining...>::Type;
};
template <typename... Lists>
using ConcatenateBlockListsT = typename ConcatenateBlockLists<Lists...>::Type;
template <typename List, typename Type> struct AppendUniqueBlockType;
template <typename... Types, typename Type>
struct AppendUniqueBlockType<utils::blocks::type_list<Types...>, Type> {
using TypeValue = std::conditional_t<
utils::blocks::contains_type_v<Type, utils::blocks::type_list<Types...>>,
utils::blocks::type_list<Types...>,
utils::blocks::type_list<Types..., Type>>;
};
template <typename Accumulated, typename Remaining> struct UniqueBlockListImpl;
template <typename Accumulated>
struct UniqueBlockListImpl<Accumulated, utils::blocks::type_list<>> {
using Type = Accumulated;
};
template <typename Accumulated, typename Head, typename... Tail>
struct UniqueBlockListImpl<Accumulated,
utils::blocks::type_list<Head, Tail...>> {
using Type = typename UniqueBlockListImpl<
typename AppendUniqueBlockType<Accumulated, Head>::TypeValue,
utils::blocks::type_list<Tail...>>::Type;
};
template <typename List>
using UniqueBlockListT =
typename UniqueBlockListImpl<utils::blocks::type_list<>, List>::Type;
template <typename... Lists>
using UniqueConcatenateBlockListsT =
UniqueBlockListT<ConcatenateBlockListsT<Lists...>>;
template <typename Candidate> struct IsValueBlockList : std::false_type {};
template <typename... Blocks>
struct IsValueBlockList<utils::blocks::type_list<Blocks...>>
: std::bool_constant<
(std::derived_from<Blocks, utils::blocks::value_block_base> && ...) &&
utils::blocks::types_are_unique_v<
utils::blocks::type_list<Blocks...>>> {};
template <typename Candidate> struct IsResidualBlockList : std::false_type {};
template <typename... Blocks>
struct IsResidualBlockList<utils::blocks::type_list<Blocks...>>
: std::bool_constant<
(std::derived_from<Blocks, utils::blocks::residual_block_base> &&
...) &&
utils::blocks::types_are_unique_v<
utils::blocks::type_list<Blocks...>>> {};
template <typename GeneratedValues> struct GeneratedValueBlocksFor;
template <typename... GeneratedValues>
struct GeneratedValueBlocksFor<models::ModelTypeList<GeneratedValues...>> {
using Type = utils::blocks::type_list<
utils::blocks::generated_value_block<GeneratedValues>...>;
};
template <typename GeneratedResiduals> struct GeneratedResidualBlocksFor;
template <typename... GeneratedResiduals>
struct GeneratedResidualBlocksFor<
models::ModelTypeList<GeneratedResiduals...>> {
using Type = utils::blocks::type_list<
utils::blocks::generated_residual_block<GeneratedResiduals>...>;
};
/*
* One translation boundary turns physics-facing stellar names into backend
* blocks. Existing backend block types pass through unchanged, which keeps
* the advanced extension API open without making built-in physics declarations
* depend on utils.blocks.
*/
template <typename DeclaredDependency>
struct UnmappedStellarDependency final {};
template <typename Blocks, typename DeclaredDependency>
struct SingleGeneratedBlock {
using Type = UnmappedStellarDependency<DeclaredDependency>;
static constexpr bool available = false;
};
template <typename Block, typename DeclaredDependency>
struct SingleGeneratedBlock<utils::blocks::type_list<Block>,
DeclaredDependency> {
using Type = Block;
static constexpr bool available = true;
};
template <models::ModelSpecification Specification, typename Dependency>
struct StellarDependencyBlock {
using Type = UnmappedStellarDependency<Dependency>;
static constexpr bool mapped = false;
};
template <models::ModelSpecification Specification, typename Block>
requires(std::derived_from<Block, utils::blocks::value_block_base> ||
std::derived_from<Block, utils::blocks::residual_block_base>)
struct StellarDependencyBlock<Specification, Block> {
using Type = Block;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification, models::stellar::state::Density> {
using Type = utils::blocks::density::mass::value;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::state::SurfaceShape> {
using Type = utils::blocks::surface_deformation::parameters::value;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::state::GravityGradient> {
using Type = utils::blocks::gravity::gradient::value;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<
Specification, models::stellar::state::GravitationalPotential> {
using Type = utils::blocks::gravity::poisson::value;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::state::SpecificEnthalpy> {
using Type = utils::blocks::enthalpy::specific::value;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<
Specification, models::stellar::state::OwnGeneratedCoordinate> {
private:
using GeneratedBlocks = typename GeneratedValueBlocksFor<
typename models::SpecificationContribution<
Specification>::GeneratedValues>::Type;
using Selection = SingleGeneratedBlock<
GeneratedBlocks, models::stellar::state::OwnGeneratedCoordinate>;
public:
using Type = typename Selection::Type;
static constexpr bool mapped = Selection::available;
};
template <models::ModelSpecification Specification,
models::ModelSpecification Owner>
struct StellarDependencyBlock<
Specification, models::stellar::state::GeneratedCoordinateOf<Owner>> {
private:
using GeneratedBlocks = typename GeneratedValueBlocksFor<
typename models::SpecificationContribution<Owner>::GeneratedValues>::Type;
using Dependency = models::stellar::state::GeneratedCoordinateOf<Owner>;
using Selection = SingleGeneratedBlock<GeneratedBlocks, Dependency>;
public:
using Type = typename Selection::Type;
static constexpr bool mapped = Selection::available;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<
Specification, models::stellar::equation::GravityGradientDefinition> {
using Type = utils::blocks::gravity::gradient::residual;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::equation::PoissonEquation> {
using Type = utils::blocks::gravity::poisson::residual;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::equation::DensityClosure> {
using Type = utils::blocks::density::mass::residual;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<
Specification, models::stellar::equation::SurfaceShapeBalance> {
using Type =
utils::blocks::surface_deformation::shape_equilibrium::residual;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<
Specification, models::stellar::equation::HydrostaticBalance> {
using Type = utils::blocks::enthalpy::specific::residual;
static constexpr bool mapped = true;
};
template <models::ModelSpecification Specification>
struct StellarDependencyBlock<Specification,
models::stellar::equation::OwnConstraint> {
private:
using GeneratedBlocks = typename GeneratedResidualBlocksFor<
typename models::SpecificationContribution<
Specification>::GeneratedResiduals>::Type;
using Selection = SingleGeneratedBlock<
GeneratedBlocks, models::stellar::equation::OwnConstraint>;
public:
using Type = typename Selection::Type;
static constexpr bool mapped = Selection::available;
};
template <models::ModelSpecification Specification,
models::ModelSpecification Owner>
struct StellarDependencyBlock<
Specification, models::stellar::equation::ConstraintOf<Owner>> {
private:
using GeneratedBlocks = typename GeneratedResidualBlocksFor<
typename models::SpecificationContribution<Owner>::GeneratedResiduals>::Type;
using Dependency = models::stellar::equation::ConstraintOf<Owner>;
using Selection = SingleGeneratedBlock<GeneratedBlocks, Dependency>;
public:
using Type = typename Selection::Type;
static constexpr bool mapped = Selection::available;
};
template <models::ModelSpecification Specification, typename Dependencies>
struct CompileStellarDependencies {
using Type = utils::blocks::type_list<
UnmappedStellarDependency<Dependencies>>;
static constexpr bool complete = false;
};
template <models::ModelSpecification Specification, typename... Dependencies>
struct CompileStellarDependencies<Specification,
models::ModelTypeList<Dependencies...>> {
using Type = utils::blocks::type_list<
typename StellarDependencyBlock<Specification, Dependencies>::Type...>;
static constexpr bool complete =
(StellarDependencyBlock<Specification, Dependencies>::mapped && ...);
};
template <typename Residual, typename Values> struct CoupleResidualToValues;
template <typename Residual, typename... Values>
struct CoupleResidualToValues<Residual, utils::blocks::type_list<Values...>> {
using Type = utils::blocks::type_list<
StellarEquilibriumJacobianCoupling<Residual, Values>...>;
};
template <typename Residuals, typename Values>
struct CartesianJacobianCouplings;
template <typename... Residuals, typename Values>
struct CartesianJacobianCouplings<utils::blocks::type_list<Residuals...>,
Values> {
using Type = ConcatenateBlockListsT<
typename CoupleResidualToValues<Residuals, Values>::Type...>;
};
template <typename Candidate> struct IsJacobianCoupling : std::false_type {};
template <typename Residual, typename Value>
struct IsJacobianCoupling<StellarEquilibriumJacobianCoupling<Residual, Value>>
: std::bool_constant<
std::derived_from<Residual, utils::blocks::residual_block_base> &&
std::derived_from<Value, utils::blocks::value_block_base>> {};
template <typename Candidate>
struct IsJacobianCouplingList : std::false_type {};
template <typename... Couplings>
struct IsJacobianCouplingList<utils::blocks::type_list<Couplings...>>
: std::bool_constant<(IsJacobianCoupling<Couplings>::value && ...) &&
utils::blocks::types_are_unique_v<
utils::blocks::type_list<Couplings...>>> {};
template <typename Candidate> struct IsGeneratedValueBlock : std::false_type {};
template <typename Owner>
struct IsGeneratedValueBlock<utils::blocks::generated_value_block<Owner>>
: std::true_type {};
template <typename Candidate>
struct IsGeneratedResidualBlock : std::false_type {};
template <typename Owner>
struct IsGeneratedResidualBlock<utils::blocks::generated_residual_block<Owner>>
: std::true_type {};
template <typename Coupling>
inline constexpr bool isGeneratedBorderIncidentCoupling =
IsGeneratedValueBlock<typename Coupling::Value>::value ||
IsGeneratedResidualBlock<typename Coupling::Residual>::value;
template <typename Couplings> struct GeneratedBorderIncidentCouplings;
template <>
struct GeneratedBorderIncidentCouplings<utils::blocks::type_list<>> {
using Type = utils::blocks::type_list<>;
};
template <typename Head, typename... Tail>
struct GeneratedBorderIncidentCouplings<
utils::blocks::type_list<Head, Tail...>> {
private:
using Remaining = typename GeneratedBorderIncidentCouplings<
utils::blocks::type_list<Tail...>>::Type;
public:
using Type = std::conditional_t<
isGeneratedBorderIncidentCoupling<Head>,
ConcatenateBlockListsT<utils::blocks::type_list<Head>, Remaining>,
Remaining>;
};
template <bool Registered, typename GeneratedValues,
typename GeneratedResiduals, typename DependsOn, typename Affects>
struct DeclarativeStellarEquilibriumSpecificationCompilation {
using GeneratedValueBlocks = GeneratedValues;
using GeneratedResidualBlocks = GeneratedResiduals;
using DependsOnValueBlocks = DependsOn;
using AffectedResidualBlocks = Affects;
/*
* Preserve the two physical meanings in the declaration instead of
* flattening their endpoints into independent unions:
*
* constraint equation <- everything named in Reads
* changed equations <- Reads plus the generated coordinate
*
* The second group deliberately includes Affects x Reads. Nonlinear
* constraints and multiplier forces generally contribute Hessian-like
* state derivatives there. Linear contributions simply assemble zero on
* those structurally permitted edges.
*/
using ConstraintInputValueBlocks = DependsOnValueBlocks;
using ConstraintOutputResidualBlocks = GeneratedResidualBlocks;
using ChangedEquationInputValueBlocks = UniqueConcatenateBlockListsT<
DependsOnValueBlocks, GeneratedValueBlocks>;
using ChangedEquationOutputResidualBlocks = AffectedResidualBlocks;
using ConstraintJacobianCouplings =
typename CartesianJacobianCouplings<GeneratedResidualBlocks,
DependsOnValueBlocks>::Type;
using ChangedEquationJacobianCouplings =
typename CartesianJacobianCouplings<AffectedResidualBlocks,
ChangedEquationInputValueBlocks>::Type;
// Compatibility names retained for backend code that distinguishes the
// generated row from the generated-coordinate column.
using GeneratedRowJacobianCouplings = ConstraintJacobianCouplings;
using AffectedRowJacobianCouplings =
typename CartesianJacobianCouplings<AffectedResidualBlocks,
GeneratedValueBlocks>::Type;
using AffectedStateJacobianCouplings =
typename CartesianJacobianCouplings<AffectedResidualBlocks,
DependsOnValueBlocks>::Type;
using JacobianCouplings = UniqueConcatenateBlockListsT<
ConstraintJacobianCouplings, ChangedEquationJacobianCouplings>;
using IncidentJacobianCouplings =
typename GeneratedBorderIncidentCouplings<JacobianCouplings>::Type;
// Correction is the Newton-facing name for a value coordinate.
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
static constexpr bool registered = Registered;
static constexpr bool complete =
registered && IsValueBlockList<GeneratedValueBlocks>::value &&
IsResidualBlockList<GeneratedResidualBlocks>::value &&
IsValueBlockList<DependsOnValueBlocks>::value &&
IsResidualBlockList<AffectedResidualBlocks>::value &&
IsJacobianCouplingList<JacobianCouplings>::value &&
(GeneratedValueBlocks::size == GeneratedResidualBlocks::size) &&
((GeneratedValueBlocks::size == 0 && DependsOnValueBlocks::size == 0 &&
AffectedResidualBlocks::size == 0) ||
(GeneratedValueBlocks::size > 0 && DependsOnValueBlocks::size > 0 &&
AffectedResidualBlocks::size > 0));
};
using EmptySpecificationCompilation =
DeclarativeStellarEquilibriumSpecificationCompilation<
false, utils::blocks::type_list<>, utils::blocks::type_list<>,
utils::blocks::type_list<>, utils::blocks::type_list<>>;
template <models::ModelSpecification Specification>
struct SelfDescribingSpecificationCompilationInputs {
using Contribution = models::SpecificationContribution<Specification>;
using DependsOn =
CompileStellarDependencies<Specification, typename Contribution::DependsOn>;
using Affects =
CompileStellarDependencies<Specification, typename Contribution::Affects>;
using GeneratedValueBlocks = typename GeneratedValueBlocksFor<
typename Contribution::GeneratedValues>::Type;
using GeneratedResidualBlocks = typename GeneratedResidualBlocksFor<
typename Contribution::GeneratedResiduals>::Type;
using DependsOnValueBlocks = typename DependsOn::Type;
using AffectedResidualBlocks = typename Affects::Type;
static constexpr bool registered =
Contribution::hasDeclarativeDefinition && DependsOn::complete &&
Affects::complete;
};
template <models::ModelSpecification Specification>
struct SelfDescribingSpecificationCompilation
: DeclarativeStellarEquilibriumSpecificationCompilation<
SelfDescribingSpecificationCompilationInputs<Specification>::registered,
typename SelfDescribingSpecificationCompilationInputs<
Specification>::GeneratedValueBlocks,
typename SelfDescribingSpecificationCompilationInputs<
Specification>::GeneratedResidualBlocks,
typename SelfDescribingSpecificationCompilationInputs<
Specification>::DependsOnValueBlocks,
typename SelfDescribingSpecificationCompilationInputs<
Specification>::AffectedResidualBlocks> {};
} // namespace detail
/*
* Public, inspectable per-specification compilation metadata. The primary
* is deliberately well formed and incomplete, so testing an arbitrary type
* in a requires-expression never triggers a diagnostic.
*/
template <typename Specification>
struct StellarEquilibriumSpecificationCompilation
: detail::EmptySpecificationCompilation {};
template <models::ModelSpecification Specification>
struct StellarEquilibriumSpecificationCompilation<Specification>
: detail::SelfDescribingSpecificationCompilation<Specification> {};
namespace detail {
template <typename Specification, typename = void>
struct SpecificationCompilationIsComplete : std::false_type {};
template <typename Specification>
struct SpecificationCompilationIsComplete<
Specification,
std::void_t<typename StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedValueBlocks,
typename StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks,
typename StellarEquilibriumSpecificationCompilation<
Specification>::DependsOnValueBlocks,
typename StellarEquilibriumSpecificationCompilation<
Specification>::AffectedResidualBlocks,
typename StellarEquilibriumSpecificationCompilation<
Specification>::JacobianCouplings,
std::bool_constant<StellarEquilibriumSpecificationCompilation<
Specification>::registered>,
std::bool_constant<StellarEquilibriumSpecificationCompilation<
Specification>::complete>>>
: std::bool_constant<
StellarEquilibriumSpecificationCompilation<
Specification>::registered &&
StellarEquilibriumSpecificationCompilation<Specification>::complete &&
IsValueBlockList<typename StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedValueBlocks>::value &&
IsResidualBlockList<
typename StellarEquilibriumSpecificationCompilation<
Specification>::GeneratedResidualBlocks>::value &&
IsValueBlockList<typename StellarEquilibriumSpecificationCompilation<
Specification>::DependsOnValueBlocks>::value &&
IsResidualBlockList<
typename StellarEquilibriumSpecificationCompilation<
Specification>::AffectedResidualBlocks>::value &&
IsJacobianCouplingList<
typename StellarEquilibriumSpecificationCompilation<
Specification>::JacobianCouplings>::value> {};
} // namespace detail
template <typename Candidate>
inline constexpr bool stellarEquilibriumSpecificationCompilationComplete =
detail::SpecificationCompilationIsComplete<
std::remove_cvref_t<Candidate>>::value;
template <typename Candidate>
concept StellarEquilibriumSpecificationCompilable =
stellarEquilibriumSpecificationCompilationComplete<Candidate>;
namespace detail {
/*
* This five-by-five physical core is independent of global constraints.
* Even FixedTotalMass is compiled as a contribution, keeping C and R_M
* visible in that specification's metadata.
*/
using StellarPhysicsValueBlocks = utils::blocks::type_list<
utils::blocks::density::mass::value,
utils::blocks::surface_deformation::parameters::value,
utils::blocks::gravity::gradient::value,
utils::blocks::gravity::poisson::value,
utils::blocks::enthalpy::specific::value>;
using StellarPhysicsResidualBlocks = utils::blocks::type_list<
utils::blocks::gravity::gradient::residual,
utils::blocks::gravity::poisson::residual,
utils::blocks::density::mass::residual,
utils::blocks::surface_deformation::shape_equilibrium::residual,
utils::blocks::enthalpy::specific::residual>;
using StellarPhysicsJacobianRows = utils::blocks::type_list<
utils::blocks::block_row<
utils::blocks::gravity::gradient::residual,
utils::blocks::gravity::gradient::value,
utils::blocks::gravity::poisson::value,
utils::blocks::surface_deformation::parameters::value>,
utils::blocks::block_row<
utils::blocks::gravity::poisson::residual,
utils::blocks::gravity::gradient::value,
utils::blocks::density::mass::value,
utils::blocks::surface_deformation::parameters::value>,
utils::blocks::block_row<
utils::blocks::density::mass::residual,
utils::blocks::density::mass::value,
utils::blocks::enthalpy::specific::value,
utils::blocks::surface_deformation::parameters::value>,
utils::blocks::block_row<
utils::blocks::surface_deformation::shape_equilibrium::residual,
utils::blocks::density::mass::value,
utils::blocks::surface_deformation::parameters::value,
utils::blocks::gravity::gradient::value,
utils::blocks::enthalpy::specific::value>,
utils::blocks::block_row<
utils::blocks::enthalpy::specific::residual,
utils::blocks::enthalpy::specific::value,
utils::blocks::gravity::poisson::value,
utils::blocks::surface_deformation::parameters::value>>;
template <typename Row> struct JacobianRowCouplings;
template <typename Residual, typename... Values>
struct JacobianRowCouplings<utils::blocks::block_row<Residual, Values...>> {
using Type = utils::blocks::type_list<
StellarEquilibriumJacobianCoupling<Residual, Values>...>;
};
template <typename Rows> struct FlattenJacobianRows;
template <typename... Rows>
struct FlattenJacobianRows<utils::blocks::type_list<Rows...>> {
using Type =
ConcatenateBlockListsT<typename JacobianRowCouplings<Rows>::Type...>;
};
using StellarPhysicsJacobianCouplings =
typename FlattenJacobianRows<StellarPhysicsJacobianRows>::Type;
template <typename SpecificationSet>
struct SpecificationSetCompilationsAreComplete;
template <models::ModelSpecification... Specifications>
struct SpecificationSetCompilationsAreComplete<
models::detail::SpecificationSetStorage<Specifications...>>
: std::bool_constant<(
stellarEquilibriumSpecificationCompilationComplete<Specifications> &&
...)> {};
template <typename SpecificationSet, bool Complete>
struct CollectStellarEquilibriumContributionsImpl {
using GeneratedValueBlocks = utils::blocks::type_list<>;
using GeneratedResidualBlocks = utils::blocks::type_list<>;
using ContributionJacobianCouplings = utils::blocks::type_list<>;
using IncidentJacobianCouplings = ContributionJacobianCouplings;
static constexpr bool complete = false;
};
template <models::ModelSpecification... Specifications>
struct CollectStellarEquilibriumContributionsImpl<
models::detail::SpecificationSetStorage<Specifications...>, true> {
using GeneratedValueBlocks = ConcatenateBlockListsT<
typename StellarEquilibriumSpecificationCompilation<
Specifications>::GeneratedValueBlocks...>;
using GeneratedResidualBlocks = ConcatenateBlockListsT<
typename StellarEquilibriumSpecificationCompilation<
Specifications>::GeneratedResidualBlocks...>;
using ContributionJacobianCouplings = UniqueConcatenateBlockListsT<
typename StellarEquilibriumSpecificationCompilation<
Specifications>::JacobianCouplings...>;
using IncidentJacobianCouplings = UniqueConcatenateBlockListsT<
typename StellarEquilibriumSpecificationCompilation<
Specifications>::IncidentJacobianCouplings...>;
static constexpr bool complete = true;
};
template <typename SpecificationSet>
using CollectStellarEquilibriumContributions =
CollectStellarEquilibriumContributionsImpl<
SpecificationSet,
SpecificationSetCompilationsAreComplete<SpecificationSet>::value>;
template <typename Residual, typename Couplings> struct ValuesCoupledToResidual;
template <typename Residual>
struct ValuesCoupledToResidual<Residual, utils::blocks::type_list<>> {
using Type = utils::blocks::type_list<>;
};
template <typename Residual, typename HeadResidual, typename HeadValue,
typename... Tail>
struct ValuesCoupledToResidual<
Residual,
utils::blocks::type_list<
StellarEquilibriumJacobianCoupling<HeadResidual, HeadValue>, Tail...>> {
private:
using Remaining =
typename ValuesCoupledToResidual<Residual,
utils::blocks::type_list<Tail...>>::Type;
public:
using Type = std::conditional_t<
std::same_as<Residual, HeadResidual>,
ConcatenateBlockListsT<utils::blocks::type_list<HeadValue>, Remaining>,
Remaining>;
};
template <typename Residual, typename Values> struct MakeJacobianRow;
template <typename Residual, typename... Values>
struct MakeJacobianRow<Residual, utils::blocks::type_list<Values...>> {
using Type = utils::blocks::block_row<Residual, Values...>;
};
template <typename Residuals, typename Couplings> struct SynthesizeJacobianRows;
template <typename... Residuals, typename Couplings>
struct SynthesizeJacobianRows<utils::blocks::type_list<Residuals...>,
Couplings> {
using Type = utils::blocks::type_list<typename MakeJacobianRow<
Residuals,
typename ValuesCoupledToResidual<Residuals, Couplings>::Type>::Type...>;
};
template <typename Couplings, typename ValueBlocks, typename ResidualBlocks>
struct CouplingEndpointsBelongToForm : std::false_type {};
template <typename ValueBlocks, typename ResidualBlocks, typename... Couplings>
struct CouplingEndpointsBelongToForm<utils::blocks::type_list<Couplings...>,
ValueBlocks, ResidualBlocks>
: std::bool_constant<((utils::blocks::contains_type_v<
typename Couplings::Value, ValueBlocks> &&
utils::blocks::contains_type_v<
typename Couplings::Residual, ResidualBlocks>) &&
...)> {};
template <typename Candidate> struct CompileStellarEquilibriumSystem {
using GeneratedValueBlocks = utils::blocks::type_list<>;
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
using GeneratedResidualBlocks = utils::blocks::type_list<>;
using BaseJacobianCouplings = utils::blocks::type_list<>;
using ContributionJacobianCouplings = utils::blocks::type_list<>;
using IncidentJacobianCouplings = ContributionJacobianCouplings;
using JacobianCouplings = utils::blocks::type_list<>;
static constexpr bool compilable = false;
};
template <model::StellarModelType Model>
struct CompileStellarEquilibriumSystem<Model> {
using ModelType = std::remove_cvref_t<Model>;
using Contributions = CollectStellarEquilibriumContributions<
typename ModelType::SpecificationTypes>;
using GeneratedValueBlocks = typename Contributions::GeneratedValueBlocks;
using GeneratedCorrectionBlocks = GeneratedValueBlocks;
using GeneratedResidualBlocks =
typename Contributions::GeneratedResidualBlocks;
using ValueBlocks =
ConcatenateBlockListsT<StellarPhysicsValueBlocks, GeneratedValueBlocks>;
using ResidualBlocks = ConcatenateBlockListsT<StellarPhysicsResidualBlocks,
GeneratedResidualBlocks>;
using FormType = utils::blocks::block_form<ValueBlocks, ResidualBlocks>;
using BaseJacobianCouplings = StellarPhysicsJacobianCouplings;
using ContributionJacobianCouplings =
typename Contributions::ContributionJacobianCouplings;
using IncidentJacobianCouplings = ContributionJacobianCouplings;
using JacobianCouplings =
UniqueConcatenateBlockListsT<BaseJacobianCouplings,
ContributionJacobianCouplings>;
// Pass two: materialize rows only after all contributed blocks are
// present in the final form.
using JacobianType =
typename SynthesizeJacobianRows<ResidualBlocks, JacobianCouplings>::Type;
static constexpr bool compilable =
Contributions::complete &&
utils::blocks::block_form_is_valid_v<FormType> &&
IsJacobianCouplingList<JacobianCouplings>::value &&
CouplingEndpointsBelongToForm<JacobianCouplings, ValueBlocks,
ResidualBlocks>::value &&
utils::blocks::jacobian_form_is_valid_v<FormType, JacobianType>;
};
} // namespace detail
template <typename Candidate>
inline constexpr bool stellarEquilibriumSystemIsCompilable =
detail::CompileStellarEquilibriumSystem<
std::remove_cvref_t<Candidate>>::compilable;
/*
* This compiler proves the symbolic block topology only. Keep the explicit
* name available to extension authors and tests so that success here is not
* mistaken for an assembled numerical runtime. The established spelling is
* retained below as a compatibility alias.
*/
template <typename Candidate>
inline constexpr bool stellarEquilibriumIsSymbolicallyCompilable =
stellarEquilibriumSystemIsCompilable<Candidate>;
template <typename Candidate>
concept StellarEquilibriumSymbolicallyCompilable =
stellarEquilibriumIsSymbolicallyCompilable<Candidate>;
template <typename Candidate>
concept StellarEquilibriumSystemCompilable =
StellarEquilibriumSymbolicallyCompilable<Candidate>;
template <model::StellarModelType Model>
requires StellarEquilibriumSystemCompilable<Model>
struct CompiledStellarEquilibriumSystem final
: detail::CompileStellarEquilibriumSystem<std::remove_cvref_t<Model>> {
using Base =
detail::CompileStellarEquilibriumSystem<std::remove_cvref_t<Model>>;
using FormType = typename Base::FormType;
using JacobianType = typename Base::JacobianType;
// This classification is exposed only after the complete compiler concept
// has succeeded; model declarations intentionally do not predict it.
static constexpr models::EquilibriumSystemCompilation compilationClass =
models::EquilibriumSystemCompilation::complete_equilibrium_system;
static_assert(utils::blocks::block_form_is_valid_v<FormType>);
static_assert(utils::blocks::valid_jacobian_form<FormType, JacobianType>);
};
template <model::StellarModelType Model>
requires StellarEquilibriumSystemCompilable<Model>
using CompiledStellarEquilibriumForm =
typename CompiledStellarEquilibriumSystem<Model>::FormType;
template <model::StellarModelType Model>
requires StellarEquilibriumSystemCompilable<Model>
using CompiledStellarEquilibriumJacobianForm =
typename CompiledStellarEquilibriumSystem<Model>::JacobianType;
} // namespace mean_field::operators

View File

@@ -2,6 +2,8 @@ module;
#include <concepts>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <type_traits>
#include <utility>
@@ -13,46 +15,126 @@ export import :deformation.domain_deformation;
export import :equilibrium.stellar_discretization;
export import :material.thermodynamic_equations;
export import :model.typed_stellar;
export import :operators.prepared_central_density_stellar_equilibrium;
export import :normalization.operators;
export import :operators.prepared_variadic_stellar_equilibrium;
export import :surface.compiler;
export namespace mean_field::equilibrium {
namespace detail {
template <
model::StellarModelType Model,
bool SymbolicallyCompilable = operators::StellarEquilibriumSystemCompilable<Model>>
struct StellarSurfaceCompilationAudit {
static constexpr bool complete = false;
};
template <model::StellarModelType Model>
struct StellarSurfaceCompilationAudit<Model, true> {
private:
using ModelType = std::remove_cvref_t<Model>;
using EquationOfState = typename ModelType::EquationOfStateType;
using Form = operators::CompiledStellarEquilibriumForm<ModelType>;
using AvailableEquations = material::StellarEquilibriumThermodynamicEquations;
static constexpr bool thermodynamicsCompilable =
material::ThermodynamicEquationsCompilable<EquationOfState, Form, AvailableEquations>;
public:
static constexpr bool complete = [] {
if constexpr (!thermodynamicsCompilable) {
return false;
} else {
using ThermodynamicEquations =
material::CompiledThermodynamicEquationsT<EquationOfState, Form, AvailableEquations>;
using Formulation = typename ThermodynamicEquations::PressureSurfaceFormulation;
using CompiledSurface =
surface::CompiledPressureSurfaceConstraintT<Formulation, EquationOfState>;
return requires(const ModelType &model) {
{
surface::compilePressureSurfaceConstraint<Formulation>(
model.surfaceCondition(),
model.equationOfState()
)
} -> std::same_as<CompiledSurface>;
};
}
}();
};
} // namespace detail
template <model::StellarModelType Model>
inline constexpr bool hasStellarEquilibriumSurfaceCompilation =
detail::StellarSurfaceCompilationAudit<std::remove_cvref_t<Model>>::complete;
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>;
typename std::remove_cvref_t<Candidate>::EquationOfStateType;
requires(
std::remove_cvref_t<Candidate>::template specificationRoleCount<
models::SpecificationRole::boundary_condition> == 1
);
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>
);
requires operators::StellarEquilibriumSystemCompilable<std::remove_cvref_t<Candidate>>;
requires hasStellarEquilibriumSurfaceCompilation<std::remove_cvref_t<Candidate>>;
requires operators::CompilableRootManifestFor<
std::remove_cvref_t<Candidate>,
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Candidate>>>;
requires operators::hasStellarEquilibriumCoreRuntime<std::remove_cvref_t<Candidate>>;
requires operators::hasCompleteStellarEquilibriumRuntime<std::remove_cvref_t<Candidate>>;
requires operators::stellarEquilibriumRotationProviderCount<std::remove_cvref_t<Candidate>> <= 1;
};
template <StellarEquilibriumModel Model> class StellarEquilibriumProblem final {
namespace detail {
template <typename Model, typename Discretization, typename = void>
struct StellarEquilibriumModelDiscretizationStructureAudit : std::false_type { };
template <typename Model, typename Discretization>
requires StellarEquilibriumModel<std::remove_cvref_t<Model>> &&
StellarDiscretizationType<std::remove_cvref_t<Discretization>>
struct StellarEquilibriumModelDiscretizationStructureAudit<
Model,
Discretization,
std::void_t<
typename std::remove_cvref_t<Discretization>::NormalizationPrescriptionType,
typename std::remove_cvref_t<Model>::SpecificationTypes,
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Model>>,
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>>>
: std::bool_constant<normalization::StellarNormalizationRuntimeAvailableFor<
typename std::remove_cvref_t<Discretization>::NormalizationPrescriptionType,
operators::CompiledStellarEquilibriumForm<std::remove_cvref_t<Model>>,
operators::StellarEquilibriumPhysicalCoreType<std::remove_cvref_t<Model>>,
typename std::remove_cvref_t<Model>::SpecificationTypes>> { };
struct StellarEquilibriumProblemFactory;
} // namespace detail
template <
StellarEquilibriumModel Model,
StellarDiscretizationType Discretization = StellarDiscretization>
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value
class StellarEquilibriumProblem final {
public:
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using NormalizationPrescriptionType = typename DiscretizationType::NormalizationPrescriptionType;
static constexpr bool hasFixedCentralDensity =
ModelType::template containsSpecification<models::FixedCentralDensity>;
static constexpr bool hasFixedAngularMomentum =
ModelType::template containsSpecification<models::FixedAngularMomentum>;
static constexpr std::size_t generatedRotationProviderCount =
operators::stellarEquilibriumRotationProviderCount<ModelType>;
static constexpr bool symbolicallySquare = ModelType::symbolicallySquare;
using PreparedOperatorType = std::conditional_t<
hasFixedCentralDensity,
operators::PreparedCentralDensityStellarEquilibriumOperator,
operators::PreparedStellarEquilibriumOperator>;
using FormType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumForm,
utils::blocks::surface_deformed_stellar_equilibrium_form>;
using JacobianFormType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumJacobianForm,
utils::blocks::surface_deformed_stellar_equilibrium_jacobian_form>;
using ManifestType = std::conditional_t<
hasFixedCentralDensity,
operators::CentralDensityStellarEquilibriumSystemManifest,
operators::StellarEquilibriumSystemManifest>;
using EquationOfStateType = eos::Polytrope;
using PreparedOperatorType = operators::PreparedVariadicStellarEquilibriumOperator<ModelType>;
using PhysicalCoreType = typename PreparedOperatorType::PhysicalCoreType;
using FormType = operators::CompiledStellarEquilibriumForm<ModelType>;
using JacobianFormType = operators::CompiledStellarEquilibriumJacobianForm<ModelType>;
using ManifestType = operators::EquilibriumSystemManifest<ModelType, FormType, JacobianFormType>;
using EquationOfStateType = model::EquationOfStateType<ModelType>;
using SurfaceConditionType = model::SurfaceConditionType<ModelType>;
using AvailableThermodynamicEquations = material::StellarEquilibriumThermodynamicEquations;
using ThermodynamicEquationsType =
material::CompiledThermodynamicEquationsT<EquationOfStateType, FormType, AvailableThermodynamicEquations>;
@@ -60,44 +142,22 @@ export namespace mean_field::equilibrium {
typename ThermodynamicEquationsType::PressureSurfaceFormulation,
EquationOfStateType>;
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();
}
private:
friend struct detail::StellarEquilibriumProblemFactory;
StellarEquilibriumProblem(
ModelType stellarModel,
const StellarDiscretization discretization
DiscretizationType discretization
)
requires hasFixedCentralDensity
: m_stellarModel(std::move(stellarModel)),
m_discretization(discretization),
m_compiledSurfaceConstraint(CompileSurfaceConstraint(m_stellarModel)),
: m_stellarModel(std::make_shared<ModelType>(std::move(stellarModel))),
m_discretization(std::move(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>()),
m_stellarModel,
operators::PressureSurfaceConstraintView{m_compiledSurfaceConstraint},
CompileDefaultDomainDeformation(m_discretization.finiteElementModel()),
models::compileConstraint(
m_stellarModel.template specification<models::FixedCentralDensity>(),
m_stellarModel.template specification<eos::Polytrope>()
)
CompileDefaultDomainDeformation(m_discretization.finiteElementModel())
) {
VerifyProblem();
}
@@ -107,14 +167,19 @@ export namespace mean_field::equilibrium {
StellarEquilibriumProblem(StellarEquilibriumProblem &&) = delete;
StellarEquilibriumProblem &operator=(StellarEquilibriumProblem &&) = delete;
public:
[[nodiscard]] const ModelType &GetStellarModel() const noexcept {
return m_stellarModel;
return *m_stellarModel;
}
[[nodiscard]] const StellarDiscretization &GetDiscretization() const noexcept {
[[nodiscard]] const DiscretizationType &GetDiscretization() const noexcept {
return m_discretization;
}
[[nodiscard]] const NormalizationPrescriptionType &GetNormalizationPrescription() const noexcept {
return m_discretization.normalizationPrescription();
}
[[nodiscard]] const CompiledSurfaceConstraintType &GetCompiledSurfaceConstraint() const noexcept {
return m_compiledSurfaceConstraint;
}
@@ -127,6 +192,10 @@ export namespace mean_field::equilibrium {
return m_preparedOperator;
}
[[nodiscard]] const PhysicalCoreType &GetPhysicalOperator() const noexcept {
return m_preparedOperator.GetPhysicalOperator();
}
[[nodiscard]] const auto &GetManifest() const noexcept {
return m_preparedOperator.GetRootManifest();
}
@@ -135,28 +204,20 @@ export namespace mean_field::equilibrium {
return m_preparedOperator.IsPrepared();
}
[[nodiscard]] std::uint64_t GetPreparationGeneration() const noexcept {
return m_preparationGeneration;
}
[[nodiscard]] const operators::StellarEquilibriumDependencies &GetLinearizationDependencies() const {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetDependencies();
} else {
return m_preparedOperator.GetDependencies();
}
return GetPhysicalOperator().GetDependencies();
}
[[nodiscard]] const operators::StellarEquilibriumDependencyStamp &GetGeometryDependency() const {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetGeneratedDisplacementDependency();
} else {
return m_preparedOperator.GetGeneratedDisplacementDependency();
}
return GetPhysicalOperator().GetGeneratedDisplacementDependency();
}
[[nodiscard]] const field::FieldBoundaryDofMap &GetPressureSurfaceRows() const noexcept {
if constexpr (hasFixedCentralDensity) {
return m_preparedOperator.GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
} else {
return m_preparedOperator.GetSurfaceConstraintOperator().GetSurfaceRows();
}
return GetPhysicalOperator().GetSurfaceConstraintOperator().GetSurfaceRows();
}
[[nodiscard]] int StateSize() const noexcept {
@@ -175,8 +236,19 @@ export namespace mean_field::equilibrium {
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
return m_preparedOperator.Prepare(state, dependencies, rotation);
) requires(generatedRotationProviderCount == 0) {
auto report = m_preparedOperator.Prepare(state, dependencies, rotation);
++m_preparationGeneration;
return report;
}
[[nodiscard]] auto Prepare(
const mfem::Vector &state,
const operators::StellarEquilibriumDependencies &dependencies
) requires(generatedRotationProviderCount == 1) {
auto report = m_preparedOperator.Prepare(state, dependencies);
++m_preparationGeneration;
return report;
}
void BuildResidual(mfem::Vector &residual) const {
@@ -194,8 +266,8 @@ export namespace mean_field::equilibrium {
[[nodiscard]] static CompiledSurfaceConstraintType CompileSurfaceConstraint(const ModelType &stellarModel) {
return surface::compilePressureSurfaceConstraint<
typename ThermodynamicEquationsType::PressureSurfaceFormulation>(
stellarModel.template specification<surface::Isobaric>(),
stellarModel.template specification<EquationOfStateType>()
stellarModel.surfaceCondition(),
stellarModel.equationOfState()
);
}
@@ -224,34 +296,112 @@ export namespace mean_field::equilibrium {
MFEM_VERIFY(m_discretization.isCurrent(), "The stellar equilibrium problem has a stale discretization.");
}
ModelType m_stellarModel;
StellarDiscretization m_discretization;
std::shared_ptr<const ModelType> m_stellarModel;
DiscretizationType m_discretization;
CompiledSurfaceConstraintType m_compiledSurfaceConstraint;
PreparedOperatorType m_preparedOperator;
std::uint64_t m_preparationGeneration{0};
};
template <StellarEquilibriumModel Model>
template <typename Candidate> struct IsStellarEquilibriumProblem : std::false_type { };
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
requires detail::StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value
struct IsStellarEquilibriumProblem<StellarEquilibriumProblem<Model, Discretization>> : std::true_type { };
template <typename Candidate>
concept DiscretizedStellarEquilibriumProblem = IsStellarEquilibriumProblem<std::remove_cvref_t<Candidate>>::value;
namespace detail {
template <
typename Model,
typename Discretization,
bool StructurallyCompatible =
StellarEquilibriumModelDiscretizationStructureAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value>
struct StellarEquilibriumModelDiscretizationOperationAudit : std::false_type { };
template <typename Model, typename Discretization>
struct StellarEquilibriumModelDiscretizationOperationAudit<
Model,
Discretization,
true> {
private:
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
using Problem = StellarEquilibriumProblem<ModelType, DiscretizationType>;
using Prescription = typename DiscretizationType::NormalizationPrescriptionType;
public:
static constexpr bool value = [] {
if constexpr (
std::same_as<Prescription, normalization::Unnormalized> ||
normalization::PhysicalRieszDiagonalPrescription<Prescription>) {
return true;
} else {
return normalization::RuntimePreparedNormalizationOperation<Problem>;
}
}();
};
} // namespace detail
/*
* A model and a discretization are separate compile-time choices. Their
* pairing is valid only when the normalization plan covers the inferred
* form, the selected physical runtime supports it, and a third-party
* runtime policy provides its exact preparation operation. Keeping this
* as a detection-safe public factory boundary rejects incomplete policies
* at discretize(), before a solver-facing problem can be constructed.
*/
template <typename Model, typename Discretization>
concept StellarEquilibriumModelDiscretizationCompatible =
detail::StellarEquilibriumModelDiscretizationOperationAudit<
std::remove_cvref_t<Model>,
std::remove_cvref_t<Discretization>>::value;
namespace detail {
/* The structurally formed problem type is needed to probe the ADL
* operation without a recursive concept. Its constructor remains
* private, and this factory is the single construction authority after
* the complete public compatibility contract has succeeded. */
struct StellarEquilibriumProblemFactory final {
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
[[nodiscard]] static auto Create(
Model &&stellarModel,
Discretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
using DiscretizationType = std::remove_cvref_t<Discretization>;
return StellarEquilibriumProblem<ModelType, DiscretizationType>{
std::forward<Model>(stellarModel),
std::move(discretization)
};
}
};
} // namespace detail
template <StellarEquilibriumModel Model, StellarDiscretizationType Discretization>
requires StellarEquilibriumModelDiscretizationCompatible<Model, Discretization>
[[nodiscard]] auto discretize(
Model &&stellarModel,
const StellarDiscretization discretization
Discretization discretization
) {
using ModelType = std::remove_cvref_t<Model>;
return StellarEquilibriumProblem<ModelType>{std::forward<Model>(stellarModel), discretization};
return detail::StellarEquilibriumProblemFactory::Create(
std::forward<Model>(stellarModel),
std::move(discretization)
);
}
template <StellarEquilibriumModel Model>
requires StellarEquilibriumModelDiscretizationCompatible<Model, StellarDiscretization>
[[nodiscard]] auto discretize(
Model &&stellarModel,
fem::FEM &finiteElementModel
) {
return discretize(std::forward<Model>(stellarModel), StellarDiscretization{finiteElementModel});
}
template <typename Candidate> struct IsStellarEquilibriumProblem : std::false_type { };
template <StellarEquilibriumModel Model>
struct IsStellarEquilibriumProblem<StellarEquilibriumProblem<Model>> : std::true_type { };
template <typename Candidate>
concept DiscretizedStellarEquilibriumProblem = IsStellarEquilibriumProblem<std::remove_cvref_t<Candidate>>::value;
} // namespace mean_field::equilibrium

View File

@@ -292,7 +292,8 @@ export namespace mean_field::preconditioning {
};
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem, SpecificationBorderBlockType Block>
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType>
requires EquilibriumCoordinateComponentFor<Block, typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<Problem, Block>
class PreparedStellarPreconditioner final : public mfem::Solver {
private:
using ProblemType = std::remove_cvref_t<Problem>;
@@ -324,6 +325,9 @@ export namespace mean_field::preconditioning {
}
}
PreparedStellarPreconditioner(ProblemType &&, BlockType) = delete;
PreparedStellarPreconditioner(const ProblemType &&, BlockType) = delete;
PreparedStellarPreconditioner(const PreparedStellarPreconditioner &) = delete;
PreparedStellarPreconditioner &operator=(const PreparedStellarPreconditioner &) = delete;
PreparedStellarPreconditioner(PreparedStellarPreconditioner &&) = delete;
@@ -367,6 +371,10 @@ export namespace mean_field::preconditioning {
return m_grouped.GetBlock();
}
[[nodiscard]] const ProblemType &GetProblem() const noexcept {
return m_grouped.GetProblem();
}
[[nodiscard]] const GroupedPreconditioner &GetGroupedPreconditioner() const noexcept {
return m_grouped;
}
@@ -392,11 +400,26 @@ export namespace mean_field::preconditioning {
SpecificationBorderBlockType Block>
requires EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType>
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<Problem, Block>
[[nodiscard]] auto prepare(
const Problem &problem,
Block block
) {
return PreparedStellarPreconditioner<Problem, Block>{problem, std::move(block)};
}
template <typename Problem, SpecificationBorderBlockType Block>
requires (!std::is_lvalue_reference_v<Problem>) &&
equilibrium::DiscretizedStellarEquilibriumProblem<std::remove_cvref_t<Problem>> &&
EquilibriumCoordinateComponentFor<
Block,
typename std::remove_cvref_t<Problem>::FormType> &&
SpecificationBorderPreparableFor<std::remove_cvref_t<Problem>, Block>
[[nodiscard]] auto prepare(
Problem &&,
Block
) -> PreparedStellarPreconditioner<
std::remove_cvref_t<Problem>,
std::remove_cvref_t<Block>> = delete;
} // namespace mean_field::preconditioning

View File

@@ -231,10 +231,90 @@ export namespace mean_field::preconditioning {
template <typename Candidate>
concept MaterialSurfaceDescriptor = detail::IsMaterialSurfaceDescriptor<std::remove_cvref_t<Candidate>>::value;
/*
* Capability boundary for EOS-specific material/surface surrogate
* assembly. The current kernels remain polytropic, but selection no
* longer embeds that closed-world type test in the descriptor concept.
*/
template <typename EquationOfState>
struct MaterialSurfaceEquationOfStateBackend {
static constexpr bool registered = false;
};
template <>
struct MaterialSurfaceEquationOfStateBackend<eos::Polytrope> {
static constexpr bool registered = true;
using CoreType = operators::PreparedStellarEquilibriumOperator;
};
template <typename EquationOfState>
concept ImplementedMaterialSurfaceEquationOfState = requires {
{
MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::registered
} -> std::convertible_to<bool>;
requires MaterialSurfaceEquationOfStateBackend<
std::remove_cvref_t<EquationOfState>>::registered;
typename MaterialSurfaceEquationOfStateBackend<std::remove_cvref_t<EquationOfState>>::CoreType;
};
/*
* Registering an EOS-to-core association is intentionally not enough to
* claim that the material/surface preconditioner can execute it. Every
* implementation listed here must have matching prepared operators and
* prepare(...) overloads below. A future backend should add its pair only
* after those executable pieces exist; this keeps capability queries
* truthful while the current kernels still consume the legacy physical
* core directly.
*/
template <typename EquationOfState, typename PhysicalCore>
struct MaterialSurfaceExecutableRuntime {
static constexpr bool available = false;
};
template <>
struct MaterialSurfaceExecutableRuntime<eos::Polytrope, operators::PreparedStellarEquilibriumOperator> {
static constexpr bool available = true;
};
template <typename EquationOfState, typename PhysicalCore>
concept ExecutableMaterialSurfaceRuntimeFor = requires {
{
MaterialSurfaceExecutableRuntime<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<PhysicalCore>>::available
} -> std::convertible_to<bool>;
requires MaterialSurfaceExecutableRuntime<
std::remove_cvref_t<EquationOfState>,
std::remove_cvref_t<PhysicalCore>>::available;
};
template <typename Descriptor>
concept ImplementedMaterialSurfaceDescriptor =
MaterialSurfaceDescriptor<Descriptor> &&
std::same_as<typename Descriptor::ThermodynamicEquations::EquationOfStateType, eos::Polytrope>;
ImplementedMaterialSurfaceEquationOfState<
typename Descriptor::ThermodynamicEquations::EquationOfStateType>;
template <typename Descriptor, typename PhysicalCore>
concept MaterialSurfaceRuntimeFor =
ImplementedMaterialSurfaceDescriptor<Descriptor> && requires {
typename MaterialSurfaceEquationOfStateBackend<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType;
requires std::same_as<
std::remove_cvref_t<PhysicalCore>,
typename MaterialSurfaceEquationOfStateBackend<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType>::CoreType>;
requires ExecutableMaterialSurfaceRuntimeFor<
typename std::remove_cvref_t<Descriptor>::ThermodynamicEquations::EquationOfStateType,
PhysicalCore>;
};
template <typename Candidate>
concept MaterialSurfacePreconditionerProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> && requires {
requires MaterialSurfaceRuntimeFor<
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
typename std::remove_cvref_t<Candidate>::PhysicalCoreType>;
};
using DensityMassDiagonalCharacteristics = OperatorCharacteristics<
OperatorCategory::mass_like,
@@ -605,7 +685,7 @@ export namespace mean_field::preconditioning {
};
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
MaterialSurfacePreconditionerProblem Problem,
backend::Registered MaterialBackend = backend::Diagonal,
backend::Registered SurfaceBackend = backend::Diagonal,
MaterialSurfaceFactorizationPolicy Policy = SurfaceThenMaterialTriangular>
@@ -623,7 +703,7 @@ export namespace mean_field::preconditioning {
}
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
MaterialSurfacePreconditionerProblem Problem,
backend::Registered MaterialBackend,
backend::Registered SurfaceBackend,
MaterialSurfaceFactorizationPolicy Policy,
@@ -688,7 +768,7 @@ export namespace mean_field::preconditioning {
// direct coupling actions and does not pay for a full Jacobian
// application.
m_fullDirection = 0.0;
const auto fullDirectionView = m_operation->GetRootManifest().directionView(m_fullDirection);
const auto fullDirectionView = m_operation->GetRootManifest().stateView(m_fullDirection);
mfem::Vector fullDensityDirection = fullDirectionView.block(utils::blocks::density_field.mass_term);
mfem::Vector fullSurfaceDirection =
fullDirectionView.block(utils::blocks::surface_deformation_field.parameters_term);
@@ -699,10 +779,10 @@ export namespace mean_field::preconditioning {
m_operation->Mult(m_fullDirection, m_fullAction);
const auto fullActionView = m_operation->GetRootManifest().residualView(m_fullAction);
const mfem::Vector fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
const mfem::Vector fullSurfaceAction =
const auto fullDensityAction = fullActionView.block(utils::blocks::density_field.mass_term);
const auto fullSurfaceAction =
fullActionView.block(utils::blocks::surface_deformation_field.shape_equilibrium_term);
const mfem::Vector fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
const auto fullEnthalpyAction = fullActionView.block(utils::blocks::enthalpy_field.specific_term);
densityAction = fullDensityAction;
surfaceAction = fullSurfaceAction;
enthalpyAction = fullEnthalpyAction;
@@ -1108,7 +1188,8 @@ export namespace mean_field::preconditioning {
std::uint64_t surfaceH1Assemblies{0};
};
template <ImplementedMaterialSurfaceDescriptor Descriptor, MaterialSurfaceFactorizationPolicy Policy>
template <MaterialSurfaceDescriptor Descriptor, MaterialSurfaceFactorizationPolicy Policy>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
class PreparedMaterialSurfaceBlock final : public mfem::Solver {
public:
using Block = MaterialSurfaceBlock<Descriptor, backend::Diagonal, backend::Diagonal, Policy>;
@@ -1562,9 +1643,10 @@ export namespace mean_field::preconditioning {
};
template <
ImplementedMaterialSurfaceDescriptor Descriptor,
MaterialSurfaceDescriptor Descriptor,
MaterialSurfaceFactorizationPolicy Policy,
backend::ApplicationMode Mode>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
class PreparedH1MaterialSurfaceBlock final : public mfem::Solver {
public:
using SurfaceBackend = backend::HypreBoomerAMG<Mode>;
@@ -2027,8 +2109,9 @@ export namespace mean_field::preconditioning {
};
template <
ImplementedMaterialSurfaceDescriptor Descriptor,
MaterialSurfaceDescriptor Descriptor,
MaterialSurfaceFactorizationPolicy Policy>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
[[nodiscard]] auto prepare(
const operators::PreparedStellarEquilibriumOperator &operation,
MaterialSurfaceBlock<
@@ -2042,26 +2125,26 @@ export namespace mean_field::preconditioning {
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
MaterialSurfaceFactorizationPolicy Policy>
requires MaterialSurfacePreconditionerProblem<
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
MaterialSurfaceBlock<
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model>>,
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
backend::Diagonal,
backend::Diagonal,
Policy> block
) {
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
return prepare(problem.GetPreparedOperator().GetPhysicalOperator(), std::move(block));
} else {
return prepare(problem.GetPreparedOperator(), std::move(block));
}
return prepare(problem.GetPhysicalOperator(), std::move(block));
}
template <
ImplementedMaterialSurfaceDescriptor Descriptor,
MaterialSurfaceDescriptor Descriptor,
MaterialSurfaceFactorizationPolicy Policy,
backend::ApplicationMode Mode>
requires MaterialSurfaceRuntimeFor<Descriptor, operators::PreparedStellarEquilibriumOperator>
[[nodiscard]] auto prepare(
const operators::PreparedStellarEquilibriumOperator &operation,
MaterialSurfaceBlock<
@@ -2076,21 +2159,20 @@ export namespace mean_field::preconditioning {
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
MaterialSurfaceFactorizationPolicy Policy,
backend::ApplicationMode Mode>
requires MaterialSurfacePreconditionerProblem<
equilibrium::StellarEquilibriumProblem<Model, Discretization>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
MaterialSurfaceBlock<
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model>>,
MaterialSurfaceDescriptorFor<equilibrium::StellarEquilibriumProblem<Model, Discretization>>,
backend::Diagonal,
backend::HypreBoomerAMG<Mode>,
Policy,
SurfaceH1MassStiffness> block
) {
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
return prepare(problem.GetPreparedOperator().GetPhysicalOperator(), std::move(block));
} else {
return prepare(problem.GetPreparedOperator(), std::move(block));
}
return prepare(problem.GetPhysicalOperator(), std::move(block));
}
} // namespace mean_field::preconditioning

View File

@@ -24,6 +24,7 @@ export namespace mean_field::preconditioning {
operators::StellarEquilibriumDependencyStamp geometry;
const void *equationOfStateIdentity{nullptr};
operators::StellarEquilibriumDependencies linearization;
std::uint64_t preparedOperatorGeneration{0};
constexpr bool operator==(const StellarPreconditionerLifecycleSnapshot &) const = default;
};
@@ -61,7 +62,8 @@ export namespace mean_field::preconditioning {
.discretization = prepared.discretization != current.discretization,
.geometry = prepared.geometry != current.geometry,
.equationOfState = prepared.equationOfStateIdentity != current.equationOfStateIdentity,
.linearization = prepared.linearization != current.linearization
.linearization = prepared.linearization != current.linearization ||
prepared.preparedOperatorGeneration != current.preparedOperatorGeneration
};
}
@@ -95,9 +97,11 @@ export namespace mean_field::preconditioning {
static constexpr bool registered = false;
};
template <equilibrium::StellarEquilibriumModel Model>
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model>> {
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization>
struct StellarEquilibriumProblemTraits<equilibrium::StellarEquilibriumProblem<Model, Discretization>> {
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
using Form = typename Problem::FormType;
using JacobianForm = typename Problem::JacobianFormType;
using Manifest = typename Problem::ManifestType;
@@ -130,8 +134,9 @@ export namespace mean_field::preconditioning {
.discretization = dependencies.discretization,
.geometry = problem.GetGeometryDependency(),
.equationOfStateIdentity =
std::addressof(problem.GetStellarModel().template specification<eos::Polytrope>()),
.linearization = dependencies
std::addressof(problem.GetStellarModel().equationOfState()),
.linearization = dependencies,
.preparedOperatorGeneration = problem.GetPreparationGeneration()
};
}
};
@@ -139,6 +144,243 @@ export namespace mean_field::preconditioning {
template <typename Candidate>
concept StellarPreconditionerProblem = StellarEquilibriumProblemTraits<std::remove_cvref_t<Candidate>>::registered;
namespace detail {
template <typename Block>
struct IsGeneratedStellarValueBlock : std::false_type { };
template <typename Generated>
struct IsGeneratedStellarValueBlock<utils::blocks::generated_value_block<Generated>>
: std::true_type { };
template <typename Block>
struct IsGeneratedStellarResidualBlock : std::false_type { };
template <typename Generated>
struct IsGeneratedStellarResidualBlock<utils::blocks::generated_residual_block<Generated>>
: std::true_type { };
template <typename Coupling>
inline constexpr bool isPurePhysicalStellarCoupling =
!IsGeneratedStellarValueBlock<
std::remove_cvref_t<typename Coupling::Value>>::value &&
!IsGeneratedStellarResidualBlock<
std::remove_cvref_t<typename Coupling::Residual>>::value;
/* Pure structure contributions owned by a trusted backend are exact
* (core, specification, coupling) capabilities. Future cores and new
* edges start with no privilege: changing a built-in declaration must
* be accompanied by an explicit preconditioner decision. Generated-
* border terms remain the responsibility of specification-border
* machinery. */
template <typename PhysicalCore, typename Specification>
struct StellarStructureBackendHandledCouplings {
using Type = utils::blocks::type_list<>;
};
template <>
struct StellarStructureBackendHandledCouplings<
operators::PreparedStellarEquilibriumOperator,
models::FixedTotalMass> {
using Type = utils::blocks::type_list<
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::density::mass::value>,
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::surface_deformation::parameters::value>>;
};
template <>
struct StellarStructureBackendHandledCouplings<
operators::PreparedStellarEquilibriumOperator,
models::FixedAngularMomentum> {
using Type = utils::blocks::type_list<
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::surface_deformation::shape_equilibrium::residual,
utils::blocks::density::mass::value>,
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::surface_deformation::shape_equilibrium::residual,
utils::blocks::surface_deformation::parameters::value>,
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::density::mass::value>,
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::surface_deformation::parameters::value>>;
};
template <>
struct StellarStructureBackendHandledCouplings<
operators::PreparedStellarEquilibriumOperator,
models::FixedCentralDensity> {
using Type = utils::blocks::type_list<
operators::StellarEquilibriumJacobianCoupling<
utils::blocks::enthalpy::specific::residual,
utils::blocks::enthalpy::specific::value>>;
};
template <
typename Coupling,
typename Model,
typename PhysicalCore,
typename ModelSpecifications>
struct EveryCouplingContributionHandled;
template <
typename Coupling,
model::StellarModelType Model,
typename PhysicalCore,
models::ModelSpecification... Specifications>
struct EveryCouplingContributionHandled<
Coupling,
Model,
PhysicalCore,
models::detail::SpecificationSetStorage<Specifications...>> final {
private:
template <typename Specification>
static constexpr bool handled =
!utils::blocks::contains_type_v<
Coupling,
typename operators::StellarEquilibriumSpecificationCompilation<
Specification>::JacobianCouplings> ||
(operators::stellarEquilibriumBackendRuntimeAuthorized<
Specification,
Model> &&
utils::blocks::contains_type_v<
Coupling,
typename StellarStructureBackendHandledCouplings<
PhysicalCore,
Specification>::Type>) ||
operators::stellarEquilibriumSpecificationCouplingIsStructuralZero<
Specification,
Model,
Coupling>;
public:
static constexpr bool value =
(handled<Specifications> && ...);
};
template <
typename Remaining,
typename Model,
typename PhysicalCore,
typename ModelSpecifications,
typename Unsupported>
struct CollectUnsupportedStellarStructureCouplings;
template <
typename Model,
typename PhysicalCore,
typename ModelSpecifications,
typename Unsupported>
struct CollectUnsupportedStellarStructureCouplings<
utils::blocks::type_list<>,
Model,
PhysicalCore,
ModelSpecifications,
Unsupported> {
using Type = Unsupported;
};
template <
typename Head,
typename... Tail,
typename Model,
typename PhysicalCore,
typename ModelSpecifications,
typename... Unsupported>
struct CollectUnsupportedStellarStructureCouplings<
utils::blocks::type_list<Head, Tail...>,
Model,
PhysicalCore,
ModelSpecifications,
utils::blocks::type_list<Unsupported...>> {
private:
static constexpr bool supported =
!isPurePhysicalStellarCoupling<Head> ||
EveryCouplingContributionHandled<
Head,
Model,
PhysicalCore,
ModelSpecifications>::value;
using Next = std::conditional_t<
supported,
utils::blocks::type_list<Unsupported...>,
utils::blocks::type_list<Unsupported..., Head>>;
public:
using Type = typename CollectUnsupportedStellarStructureCouplings<
utils::blocks::type_list<Tail...>,
Model,
PhysicalCore,
ModelSpecifications,
Next>::Type;
};
template <typename Candidate, typename = void>
struct DefaultStellarStructurePhysicalTopologyAudit {
using ContributionCouplings = utils::blocks::type_list<>;
using UnsupportedCouplings = utils::blocks::type_list<>;
static constexpr bool supported = false;
};
template <model::StellarModelType Model>
requires(
operators::StellarEquilibriumSystemCompilable<
std::remove_cvref_t<Model>> &&
operators::hasStellarEquilibriumCoreRuntime<
std::remove_cvref_t<Model>>)
struct DefaultStellarStructurePhysicalTopologyAudit<
Model,
std::void_t<
typename operators::CompiledStellarEquilibriumSystem<
std::remove_cvref_t<Model>>::ContributionJacobianCouplings,
operators::StellarEquilibriumPhysicalCoreType<
std::remove_cvref_t<Model>>>> {
private:
using Compilation = operators::CompiledStellarEquilibriumSystem<
std::remove_cvref_t<Model>>;
using PhysicalCore = operators::StellarEquilibriumPhysicalCoreType<
std::remove_cvref_t<Model>>;
public:
using ContributionCouplings =
typename Compilation::ContributionJacobianCouplings;
using UnsupportedCouplings =
typename CollectUnsupportedStellarStructureCouplings<
ContributionCouplings,
std::remove_cvref_t<Model>,
PhysicalCore,
typename std::remove_cvref_t<Model>::SpecificationTypes,
utils::blocks::type_list<>>::Type;
static constexpr bool supported = UnsupportedCouplings::size == 0;
};
} // namespace detail
/* A generated-border edge is owned by specification-border machinery and
* is deliberately ignored here. Every pure physical edge contributed by a
* model must be owned by the selected numerical structure backend, or
* every non-backend provider of that edge must prove StructuralZero. In
* particular, merely overlapping an existing base-Jacobian edge is not
* sufficient: a custom nonzero coefficient on that edge would otherwise
* disappear silently from the default preconditioner. This audit is
* detection-safe and therefore suitable for constraining factories. */
template <typename Candidate>
struct DefaultStellarStructurePhysicalTopologySupport
: detail::DefaultStellarStructurePhysicalTopologyAudit<
std::remove_cvref_t<Candidate>> { };
template <typename Candidate>
inline constexpr bool defaultStellarStructurePhysicalTopologySupported =
DefaultStellarStructurePhysicalTopologySupport<
std::remove_cvref_t<Candidate>>::supported;
template <typename Candidate>
concept DefaultStellarStructurePhysicalTopologySupportedFor =
defaultStellarStructurePhysicalTopologySupported<Candidate>;
namespace backend {
template <typename Component, typename Problem, typename Backend = typename Component::BackendType>
class PreparedComponent;
@@ -174,56 +416,17 @@ export namespace mean_field::preconditioning {
} // namespace backend
namespace detail {
using DensityIdentity =
IdentityBlock<utils::blocks::density::mass::value, utils::blocks::density::mass::residual>;
using SurfaceIdentity = IdentityBlock<
utils::blocks::surface_deformation::parameters::value,
utils::blocks::surface_deformation::shape_equilibrium::residual>;
using GravityGradientIdentity =
IdentityBlock<utils::blocks::gravity::gradient::value, utils::blocks::gravity::gradient::residual>;
using GravityPotentialIdentity =
IdentityBlock<utils::blocks::gravity::poisson::value, utils::blocks::gravity::poisson::residual>;
using EnthalpyIdentity =
IdentityBlock<utils::blocks::enthalpy::specific::value, utils::blocks::enthalpy::specific::residual>;
using FixedMassIdentity = IdentityBlock<
utils::blocks::fixed_total_mass::mass_normalization::value,
utils::blocks::fixed_total_mass::mass_normalization::residual>;
using FixedCentralDensityIdentity = IdentityBlock<
utils::blocks::fixed_central_density::central_value::value,
utils::blocks::fixed_central_density::central_value::residual>;
template <typename Form> struct IdentityPlanForForm;
template <> struct IdentityPlanForForm<utils::blocks::surface_deformed_stellar_equilibrium_form> {
using Type = PreconditionerPlan<
DensityIdentity,
SurfaceIdentity,
GravityGradientIdentity,
GravityPotentialIdentity,
EnthalpyIdentity,
FixedMassIdentity>;
template <typename... Values, typename... Residuals>
struct IdentityPlanForForm<utils::blocks::block_form<
utils::blocks::type_list<Values...>,
utils::blocks::type_list<Residuals...>>> {
static_assert(sizeof...(Values) == sizeof...(Residuals));
using Type = PreconditionerPlan<IdentityBlock<Values, Residuals>...>;
[[nodiscard]] static constexpr Type Make() {
return Type{DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{},
GravityPotentialIdentity{}, EnthalpyIdentity{}, FixedMassIdentity{}};
}
};
template <> struct IdentityPlanForForm<utils::blocks::central_density_bordered_stellar_equilibrium_form> {
using Type = PreconditionerPlan<
DensityIdentity,
SurfaceIdentity,
GravityGradientIdentity,
GravityPotentialIdentity,
EnthalpyIdentity,
FixedMassIdentity,
FixedCentralDensityIdentity>;
[[nodiscard]] static constexpr Type Make() {
return Type{
DensityIdentity{}, SurfaceIdentity{}, GravityGradientIdentity{}, GravityPotentialIdentity{},
EnthalpyIdentity{}, FixedMassIdentity{}, FixedCentralDensityIdentity{}
};
return Type{IdentityBlock<Values, Residuals>{}...};
}
};

View File

@@ -14,6 +14,7 @@ export module mean_field:preconditioning.stellar_structure;
export import :preconditioning.gravity_field;
export import :preconditioning.material_surface;
export import :preconditioning.stellar_equilibrium;
export namespace mean_field::preconditioning {
struct IndependentStellarSubsystems final { };
@@ -626,28 +627,88 @@ export namespace mean_field::preconditioning {
}
};
/*
* Material/surface execution and stellar-structure execution are separate
* capabilities. The latter also owns the physical cross-Jacobian and
* gravity-context wiring, which currently target the legacy prepared core.
* Add future cores here only together with matching cross-coupling and
* preparation implementations.
*/
template <typename PhysicalCore>
struct StellarStructureExecutableRuntime {
static constexpr bool available = false;
};
template <>
struct StellarStructureExecutableRuntime<operators::PreparedStellarEquilibriumOperator> {
static constexpr bool available = true;
};
template <typename PhysicalCore>
concept ExecutableStellarStructureRuntimeFor = requires {
{
StellarStructureExecutableRuntime<std::remove_cvref_t<PhysicalCore>>::available
} -> std::convertible_to<bool>;
requires StellarStructureExecutableRuntime<std::remove_cvref_t<PhysicalCore>>::available;
};
template <typename Descriptor, typename PhysicalCore>
concept StellarStructureRuntimeFor =
MaterialSurfaceRuntimeFor<Descriptor, PhysicalCore> &&
ExecutableStellarStructureRuntimeFor<PhysicalCore>;
template <typename Candidate>
concept StellarStructurePreconditionerProblem =
equilibrium::DiscretizedStellarEquilibriumProblem<Candidate> &&
DefaultStellarStructurePhysicalTopologySupportedFor<
typename std::remove_cvref_t<Candidate>::ModelType> &&
requires {
requires StellarStructureRuntimeFor<
MaterialSurfaceDescriptorFor<std::remove_cvref_t<Candidate>>,
typename std::remove_cvref_t<Candidate>::PhysicalCoreType>;
};
namespace detail {
template <equilibrium::StellarEquilibriumModel Model>
[[nodiscard]] const operators::PreparedStellarEquilibriumOperator &
physicalOperator(const equilibrium::StellarEquilibriumProblem<Model> &problem) {
if constexpr (equilibrium::StellarEquilibriumProblem<Model>::hasFixedCentralDensity) {
return problem.GetPreparedOperator().GetPhysicalOperator();
} else {
return problem.GetPreparedOperator();
}
template <StellarStructurePreconditionerProblem Problem>
[[nodiscard]] const auto &physicalOperator(const Problem &problem) {
return problem.GetPhysicalOperator();
}
} // namespace detail
template <typename Problem, typename MaterialComponent, typename GravityComponent>
concept StellarStructurePreparableFor =
StellarStructurePreconditionerProblem<std::remove_cvref_t<Problem>> &&
PreconditionerComponent<std::remove_cvref_t<MaterialComponent>> &&
PreconditionerComponent<std::remove_cvref_t<GravityComponent>> &&
requires(
const std::remove_cvref_t<Problem> &problem,
std::remove_cvref_t<MaterialComponent> materialComponent,
std::remove_cvref_t<GravityComponent> gravityComponent
) {
preconditioning::prepare(problem, std::move(materialComponent));
preconditioning::prepare(
problem.GetPhysicalOperator().GetHydrostaticOperator().GetFEM(),
problem.GetPhysicalOperator().GetGravityContext().GetGeometryContext(),
std::move(gravityComponent)
);
StellarStructureCrossJacobianOperator{problem.GetPhysicalOperator()};
};
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
requires StellarStructurePreparableFor<
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
MaterialComponent,
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
class PreparedStellarStructureBlock final : public mfem::Solver {
private:
using Problem = equilibrium::StellarEquilibriumProblem<Model>;
using Problem = equilibrium::StellarEquilibriumProblem<Model, Discretization>;
using GravityComponent = GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>;
using Structure = StellarStructureBlock<
MaterialComponent,
@@ -769,7 +830,7 @@ export namespace mean_field::preconditioning {
};
template <
equilibrium::DiscretizedStellarEquilibriumProblem Problem,
StellarStructurePreconditionerProblem Problem,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
@@ -792,7 +853,7 @@ export namespace mean_field::preconditioning {
};
}
template <equilibrium::DiscretizedStellarEquilibriumProblem Problem>
template <StellarStructurePreconditionerProblem Problem>
[[nodiscard]] constexpr auto stellarStructureBlock(const Problem &problem) {
using FixedAMG = backend::HypreBoomerAMG<backend::FixedCycles>;
auto material = materialSurfaceBlock(problem);
@@ -805,25 +866,30 @@ export namespace mean_field::preconditioning {
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
typename MaterialComponent,
backend::Registered GravityMassBackend,
backend::ApplicationMode Mode,
GravityFactorizationPolicy GravityPolicy,
StellarStructureFactorizationPolicy StructurePolicy>
requires StellarStructurePreparableFor<
equilibrium::StellarEquilibriumProblem<Model, Discretization>,
MaterialComponent,
GravityFieldBlock<GravityMassBackend, backend::HypreBoomerAMG<Mode>, GravityPolicy>>
[[nodiscard]] auto prepare(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
StellarStructureBlock<
MaterialComponent,
GravityFieldBlock<
GravityMassBackend,
backend::HypreBoomerAMG<Mode>,
GravityPolicy>,
typename equilibrium::StellarEquilibriumProblem<Model>::FormType,
typename equilibrium::StellarEquilibriumProblem<Model>::JacobianFormType,
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::FormType,
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::JacobianFormType,
StructurePolicy> structure
) {
return PreparedStellarStructureBlock<
Model, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
Model, Discretization, MaterialComponent, GravityMassBackend, Mode, GravityPolicy, StructurePolicy>{
problem, std::move(structure)
};
}

View File

@@ -1,5 +1,7 @@
module;
#include <cmath>
#include <cstddef>
#include <concepts>
#include <stdexcept>
#include <type_traits>
@@ -25,6 +27,65 @@ export namespace mean_field::seed {
mfem::Vector values;
};
/*
* The public radial-projection extension boundary deliberately speaks in
* physical state names. A specification author supplies one small rule
* and opts in with
*
* using RadialProjection = seed::projection::Use<MyProjectionPhysics>;
*
* There is no registry ordinal and no model-combination specialization.
*/
struct RadialProjectionScales final {
dimensions::MassValue targetMass;
dimensions::LengthValue stellarRadius;
double bernoulliConstant;
double sphericalMomentOfInertia;
const mfem::Array<int> *surfaceCarrierRows;
};
struct RadialProjectionState final {
mfem::Vector density;
mfem::Vector surfaceShape;
mfem::Vector gravityGradient;
mfem::Vector gravityPotential;
mfem::Vector specificEnthalpy;
};
namespace projection {
template <typename Physics> struct Use final {
using PhysicsType = Physics;
};
/* An explicit opt-in for a specification that leaves a radial seed unchanged. */
struct NoStateChange {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Specification, typename Model>
static void validate(
const Specification &,
const Model &,
const RadialProfile &,
const StellarEquilibriumProjectionOptions &
) noexcept {
}
template <typename Specification, typename Model>
static void initialize(
const Specification &,
const Model &,
const RadialProjectionScales &,
RadialProjectionState &,
mfem::Vector
) noexcept {
}
};
} // namespace projection
namespace detail {
struct ProjectedRadialFields final {
mfem::Vector density;
@@ -32,13 +93,13 @@ export namespace mean_field::seed {
mfem::Vector gravityPotential;
mfem::Vector specificEnthalpy;
double bernoulliConstant;
double sphericalMomentOfInertia;
};
[[nodiscard]] ProjectedRadialFields projectRadialFields(
const equilibrium::StellarDiscretization &discretization,
fem::FEM &finiteElementModel,
const RadialProfile &profile,
dimensions::MassValue targetMass,
dimensions::PressureValue targetSurfacePressure,
const StellarEquilibriumProjectionOptions &options
);
@@ -52,18 +113,402 @@ export namespace mean_field::seed {
}
destination = source;
}
struct UnavailableRadialProjectionPhysics final {
static constexpr bool registered = false;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = false;
};
struct FixedTotalMassRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = true;
template <typename Model>
static constexpr bool supports = true;
[[nodiscard]] static dimensions::MassValue targetMass(const models::FixedTotalMass &specification) {
return specification.targetMass();
}
template <typename Model>
static void validate(
const models::FixedTotalMass &,
const Model &,
const RadialProfile &,
const StellarEquilibriumProjectionOptions &
) noexcept {
}
template <typename Model>
static void initialize(
const models::FixedTotalMass &,
const Model &,
const RadialProjectionScales &scales,
RadialProjectionState &,
mfem::Vector coordinate
) {
if (coordinate.Size() != 1) {
throw std::invalid_argument(
"A fixed-total-mass radial projection requires exactly one generated multiplier."
);
}
coordinate(0) = scales.bernoulliConstant;
}
};
struct IsobaricRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = requires(
const Model &model,
const surface::Isobaric &condition
) {
{
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
model.equationOfState(), condition.targetPressure()
)
} -> std::same_as<dimensions::SpecificEnthalpyValue>;
};
template <typename Model>
static void validate(
const surface::Isobaric &condition,
const Model &,
const RadialProfile &,
const StellarEquilibriumProjectionOptions &
) {
if (condition.targetPressure().value() != 0.0) {
throw std::invalid_argument("A Lane-Emden radial seed requires a zero-pressure isobaric surface.");
}
}
template <typename Model>
static void initialize(
const surface::Isobaric &condition,
const Model &model,
const RadialProjectionScales &scales,
RadialProjectionState &state,
mfem::Vector coordinate
) {
if (coordinate.Size() != 0) {
throw std::logic_error("An isobaric surface must not generate a radial-seed coordinate.");
}
if (scales.surfaceCarrierRows == nullptr) {
throw std::logic_error("An isobaric radial projection requires compiled surface-carrier rows.");
}
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
model.equationOfState(), condition.targetPressure()
);
for (const int surfaceRow : *scales.surfaceCarrierRows) {
state.specificEnthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
}
}
};
struct FixedCentralDensityRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model>
static void validate(
const models::FixedCentralDensity &,
const Model &,
const RadialProfile &,
const StellarEquilibriumProjectionOptions &
) noexcept {
}
template <typename Model>
static void initialize(
const models::FixedCentralDensity &,
const Model &,
const RadialProjectionScales &,
RadialProjectionState &,
mfem::Vector coordinate
) {
if (coordinate.Size() != 1) {
throw std::invalid_argument(
"A fixed-central-density radial projection requires exactly one generated phase coordinate."
);
}
coordinate(0) = 0.0;
}
};
struct FixedAngularMomentumRadialProjectionPhysics final {
static constexpr bool registered = true;
static constexpr bool providesRadialMass = false;
template <typename Model>
static constexpr bool supports = true;
template <typename Model>
static void validate(
const models::FixedAngularMomentum &,
const Model &,
const RadialProfile &,
const StellarEquilibriumProjectionOptions &
) noexcept {
}
template <typename Model>
static void initialize(
const models::FixedAngularMomentum &constraint,
const Model &,
const RadialProjectionScales &scales,
RadialProjectionState &,
mfem::Vector coordinate
) {
if (coordinate.Size() != 1) {
throw std::logic_error(
"A fixed-angular-momentum radial projection requires one angular-velocity coordinate."
);
}
double centerSquared = 0.0;
double centerAlongAxis = 0.0;
for (std::size_t component = 0; component < constraint.center().size(); ++component) {
centerSquared += constraint.center()[component] * constraint.center()[component];
centerAlongAxis += constraint.center()[component] * constraint.axis()[component];
}
const double parallelAxisCorrection =
scales.targetMass.value() * (centerSquared - centerAlongAxis * centerAlongAxis);
const double momentOfInertia = scales.sphericalMomentOfInertia + parallelAxisCorrection;
if (!std::isfinite(momentOfInertia) || momentOfInertia <= 0.0) {
throw std::runtime_error("The radial seed has no finite, positive axial moment of inertia.");
}
coordinate(0) = constraint.targetAngularMomentum().value() / momentOfInertia;
}
};
template <typename Specification> struct BuiltinRadialProjectionPhysics {
using Type = UnavailableRadialProjectionPhysics;
};
template <> struct BuiltinRadialProjectionPhysics<eos::Polytrope> {
using Type = projection::NoStateChange;
};
template <> struct BuiltinRadialProjectionPhysics<surface::Isobaric> {
using Type = IsobaricRadialProjectionPhysics;
};
template <> struct BuiltinRadialProjectionPhysics<models::FixedTotalMass> {
using Type = FixedTotalMassRadialProjectionPhysics;
};
template <> struct BuiltinRadialProjectionPhysics<models::FixedCentralDensity> {
using Type = FixedCentralDensityRadialProjectionPhysics;
};
template <> struct BuiltinRadialProjectionPhysics<models::FixedAngularMomentum> {
using Type = FixedAngularMomentumRadialProjectionPhysics;
};
template <typename Candidate> struct UnwrapRadialProjectionPhysics {
using Type = UnavailableRadialProjectionPhysics;
static constexpr bool valid = false;
};
template <typename Physics> struct UnwrapRadialProjectionPhysics<projection::Use<Physics>> {
using Type = Physics;
static constexpr bool valid = true;
};
template <typename Specification, typename = void> struct SelectRadialProjectionPhysics {
using Type = typename BuiltinRadialProjectionPhysics<Specification>::Type;
};
template <typename Specification>
struct SelectRadialProjectionPhysics<Specification, std::void_t<typename Specification::RadialProjection>> {
private:
using Wrapped = UnwrapRadialProjectionPhysics<typename Specification::RadialProjection>;
public:
using Type = std::conditional_t<Wrapped::valid, typename Wrapped::Type, UnavailableRadialProjectionPhysics>;
};
template <typename Physics> [[nodiscard]] consteval bool radialProjectionPhysicsRegistered() {
if constexpr (requires {
{ Physics::registered } -> std::convertible_to<bool>;
}) {
return static_cast<bool>(Physics::registered);
} else {
return false;
}
}
template <typename Physics> [[nodiscard]] consteval bool radialProjectionPhysicsProvidesMass() {
if constexpr (requires {
{ Physics::providesRadialMass } -> std::convertible_to<bool>;
}) {
return static_cast<bool>(Physics::providesRadialMass);
} else {
return false;
}
}
template <typename Specification, typename Model>
[[nodiscard]] consteval bool radialProjectionPhysicsIsComplete() {
using Physics = typename SelectRadialProjectionPhysics<Specification>::Type;
if constexpr (!radialProjectionPhysicsRegistered<Physics>()) {
return false;
} else if constexpr (!requires {
{ Physics::template supports<Model> } -> std::convertible_to<bool>;
}) {
return false;
} else if constexpr (!static_cast<bool>(Physics::template supports<Model>)) {
return false;
} else if constexpr (!requires(
const Specification &specification,
const Model &model,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options,
const RadialProjectionScales &scales,
RadialProjectionState &state,
mfem::Vector coordinate
) {
Physics::validate(specification, model, profile, options);
Physics::initialize(specification, model, scales, state, coordinate);
}) {
return false;
} else if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
return requires(const Specification &specification) {
{ Physics::targetMass(specification) } -> std::same_as<dimensions::MassValue>;
};
} else {
return true;
}
}
template <models::ModelSpecification Specification, models::GeneratedStateKind Kind>
struct RadialProjectionCoordinateTerm;
template <models::ModelSpecification Specification>
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::multiplier> final {
using value = utils::blocks::generated_value_block<models::MultiplierFor<Specification>>;
};
template <models::ModelSpecification Specification>
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::physical_coordinate> final {
using value = utils::blocks::generated_value_block<models::PhysicalCoordinateFor<Specification>>;
};
template <models::ModelSpecification Specification>
struct RadialProjectionCoordinateTerm<Specification, models::GeneratedStateKind::solver_border> final {
using value = utils::blocks::generated_value_block<models::BorderFor<Specification>>;
};
template <typename Model, typename SpecificationSet> struct CompileRadialProjection;
template <model::StellarModelType Model, models::ModelSpecification... Specifications>
struct CompileRadialProjection<Model, models::detail::SpecificationSetStorage<Specifications...>> {
using ModelType = std::remove_cvref_t<Model>;
static constexpr std::size_t radialMassProviderCount =
(std::size_t{0} + ... +
(radialProjectionPhysicsProvidesMass<
typename SelectRadialProjectionPhysics<Specifications>::Type>()
? std::size_t{1}
: std::size_t{0}));
static constexpr bool complete = radialMassProviderCount == 1 &&
(radialProjectionPhysicsIsComplete<Specifications, ModelType>() && ...);
[[nodiscard]] static dimensions::MassValue targetMass(const ModelType &model) requires complete {
dimensions::MassValue result{0.0};
([&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (radialProjectionPhysicsProvidesMass<Physics>()) {
result = Physics::targetMass(model.template specification<Specifications>());
}
}(), ...);
return result;
}
static void validate(
const ModelType &model,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options
) requires complete {
([&] {
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
Physics::validate(model.template specification<Specifications>(), model, profile, options);
}(), ...);
}
template <typename StateView>
static void initialize(
const ModelType &model,
const RadialProjectionScales &scales,
RadialProjectionState &state,
const StateView &stateView
) requires complete {
([&] {
using Contribution = models::SpecificationContribution<Specifications>;
using Physics = typename SelectRadialProjectionPhysics<Specifications>::Type;
if constexpr (Contribution::generatedValueArity == 0) {
Physics::initialize(
model.template specification<Specifications>(), model, scales, state, mfem::Vector{}
);
} else {
static_assert(
Contribution::generatedValueArity == 1,
"Radial projection currently requires each specification contribution to generate at "
"most one scalar coordinate."
);
using Term = RadialProjectionCoordinateTerm<Specifications, Contribution::generatedStateKind>;
Physics::initialize(
model.template specification<Specifications>(), model, scales, state,
stateView.block(Term{})
);
}
}(), ...);
}
};
template <typename Candidate, bool = model::StellarModelType<Candidate>>
struct RadialProjectionCompilationAudit {
static constexpr bool complete = false;
};
template <typename Candidate>
struct RadialProjectionCompilationAudit<Candidate, true>
: CompileRadialProjection<Candidate, typename Candidate::SpecificationTypes> { };
} // namespace detail
template <equilibrium::StellarEquilibriumModel Model>
template <typename Candidate>
inline constexpr bool radialProjectionIsCompilable =
detail::RadialProjectionCompilationAudit<std::remove_cvref_t<Candidate>>::complete;
template <typename Candidate>
concept RadialProfileProjectableModel =
model::StellarModelType<Candidate> && radialProjectionIsCompilable<std::remove_cvref_t<Candidate>>;
template <equilibrium::StellarEquilibriumModel Model, equilibrium::StellarDiscretizationType Discretization>
requires RadialProfileProjectableModel<Model>
[[nodiscard]] ProjectedEquilibriumState<Model> projectRadialProfile(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
const RadialProfile &profile,
const StellarEquilibriumProjectionOptions &options = {}
) {
using Projection = detail::CompileRadialProjection<
std::remove_cvref_t<Model>,
typename std::remove_cvref_t<Model>::SpecificationTypes>;
const auto &stellarModel = problem.GetStellarModel();
Projection::validate(stellarModel, profile, options);
const dimensions::MassValue targetMass = Projection::targetMass(stellarModel);
const detail::ProjectedRadialFields fields = detail::projectRadialFields(
problem.GetDiscretization(), profile,
problem.GetStellarModel().template specification<models::FixedTotalMass>().targetMass(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure(), options
problem.GetDiscretization().finiteElementModel(), profile, targetMass, options
);
mfem::Vector values(problem.StateSize());
@@ -89,45 +534,40 @@ export namespace mean_field::seed {
);
/*
* Projection of a continuous spherical profile onto a faceted
* reference mesh generally leaves a small trace error on the physical
* surface. The pressure condition replaces these carrier rows in the
* compiled equilibrium problem, so impose its required carrier value
* exactly after bulk projection instead of treating that geometric
* mismatch as part of the initial residual.
* Each specification now initializes only its inferred contribution.
* In particular, the surface rule imposes the exact carrier trace and
* generated constraints obtain their coordinate by type, not by a
* hard-coded whole-model layout.
*/
mfem::Vector enthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term);
const dimensions::SpecificEnthalpyValue requiredSurfaceEnthalpy =
eos::evaluate<dimensions::quantity::SpecificEnthalpy>(
problem.GetStellarModel().template specification<eos::Polytrope>(),
problem.GetStellarModel().template specification<surface::Isobaric>().targetPressure()
);
for (const int surfaceRow : problem.GetPressureSurfaceRows().reduced_dofs()) {
enthalpy(surfaceRow) = requiredSurfaceEnthalpy.value();
}
mfem::Vector fixedMassCoordinate =
stateView.block(utils::blocks::fixed_total_mass_constraint.mass_normalization_term);
if (fixedMassCoordinate.Size() != 1) {
throw std::invalid_argument("FixedTotalMass must generate exactly one equilibrium-state coordinate.");
}
fixedMassCoordinate(0) = fields.bernoulliConstant;
if constexpr (std::remove_cvref_t<Model>::template containsSpecification<models::FixedCentralDensity>) {
stateView.block(utils::blocks::fixed_central_density_phase.central_value_term) = 0.0;
}
RadialProjectionState projectedState{
.density = stateView.block(utils::blocks::density_field.mass_term),
.surfaceShape = stateView.block(utils::blocks::surface_deformation_field.parameters_term),
.gravityGradient = stateView.block(utils::blocks::gravity_field.gradient_term),
.gravityPotential = stateView.block(utils::blocks::gravity_field.poisson_term),
.specificEnthalpy = stateView.block(utils::blocks::enthalpy_field.specific_term)
};
const RadialProjectionScales scales{
.targetMass = targetMass,
.stellarRadius = profile.stellarRadius,
.bernoulliConstant = fields.bernoulliConstant,
.sphericalMomentOfInertia = fields.sphericalMomentOfInertia,
.surfaceCarrierRows = &problem.GetPressureSurfaceRows().reduced_dofs()
};
Projection::initialize(stellarModel, scales, projectedState, stateView);
return {.values = std::move(values)};
}
template <
equilibrium::StellarEquilibriumModel Model,
equilibrium::StellarDiscretizationType Discretization,
typename Strategy>
requires RadialSeedStrategyFor<
Strategy,
typename equilibrium::StellarEquilibriumProblem<Model>::ModelType>
typename equilibrium::StellarEquilibriumProblem<Model, Discretization>::ModelType> &&
RadialProfileProjectableModel<Model>
[[nodiscard]] ProjectedEquilibriumState<Model> makeProjectedEquilibriumState(
const equilibrium::StellarEquilibriumProblem<Model> &problem,
const equilibrium::StellarEquilibriumProblem<Model, Discretization> &problem,
const Strategy &strategy,
const StellarEquilibriumProjectionOptions &options = {}
) {

View File

@@ -136,6 +136,19 @@ export namespace mean_field::utils::blocks {
static inline constexpr central_value central_value_term{};
};
struct fixed_angular_momentum final : field {
using SpecificationType = models::FixedAngularMomentum;
using CoordinateType = models::PhysicalCoordinateFor<SpecificationType>;
using ResidualType = models::ResidualFor<SpecificationType>;
struct angular_velocity final : term {
using value = generated_value_block<CoordinateType>;
using residual = generated_residual_block<ResidualType>;
};
static inline constexpr angular_velocity angular_velocity_term{};
};
// Compatibility name for the current barotropic formulation. The scalar
// is generated by FixedTotalMass; its realization in this formulation is
// the historical C coordinate.
@@ -148,6 +161,7 @@ export namespace mean_field::utils::blocks {
inline constexpr enthalpy enthalpy_field{};
inline constexpr fixed_total_mass fixed_total_mass_constraint{};
inline constexpr fixed_central_density fixed_central_density_phase{};
inline constexpr fixed_angular_momentum fixed_angular_momentum_constraint{};
inline constexpr barotropic_constant barotropic_constant_field{};
template <typename... Types> struct type_list {
@@ -516,6 +530,7 @@ export namespace mean_field::utils::blocks {
enthalpy::specific::value,
gravity::poisson::value,
surface_deformation::parameters::value,
density::mass::value,
barotropic_constant::mass_normalization::value>,
block_row<
barotropic_constant::mass_normalization::residual,
@@ -569,6 +584,7 @@ export namespace mean_field::utils::blocks {
enthalpy::specific::value,
gravity::poisson::value,
surface_deformation::parameters::value,
density::mass::value,
barotropic_constant::mass_normalization::value,
fixed_central_density::central_value::value>,
block_row<