feat(newton): first newton solver implementation

This commit is contained in:
2026-09-08 06:36:39 -04:00
parent 76818f2f82
commit b3c04d507a
98 changed files with 20397 additions and 11040 deletions

View File

@@ -4,6 +4,11 @@ module;
#include <array>
#include <cmath>
#include <cstdint>
#include <expected>
#include <optional>
#include <stdexcept>
#include <string>
#include <utility>
#include <mfem.hpp>
@@ -18,6 +23,82 @@ namespace {
return DomainSchema::template attribute_belongs_to<mean_field::utils::domain::Vacuum>(attribute);
}
[[nodiscard]] bool is_candidate_mapping_failure(const mean_field::mapping::MappingStatus status) {
using mean_field::mapping::MappingStatus;
return status == MappingStatus::non_finite_input || status == MappingStatus::non_finite_result ||
status == MappingStatus::non_positive_determinant;
}
[[nodiscard]] std::optional<mean_field::mapping::MappingStatus> synchronize_mapping_failure(
const std::optional<mean_field::mapping::MappingStatus> localFailure,
const MPI_Comm communicator
) {
int localFailures[2]{0, 0};
if (localFailure.has_value()) {
const int encodedStatus = static_cast<int>(*localFailure) + 1;
if (is_candidate_mapping_failure(*localFailure)) {
localFailures[0] = encodedStatus;
} else {
localFailures[1] = encodedStatus;
}
}
int globalFailures[2]{0, 0};
if (MPI_Allreduce(localFailures, globalFailures, 2, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
throw std::runtime_error(
"PreparedHydrostaticEquilibriumOperator could not synchronize mapped-geometry validity."
);
}
if (globalFailures[1] != 0) {
throw std::runtime_error(
"PreparedHydrostaticEquilibriumOperator encountered a structural mapping failure with status " +
std::to_string(globalFailures[1] - 1) + "."
);
}
if (globalFailures[0] == 0) {
return std::nullopt;
}
return static_cast<mean_field::mapping::MappingStatus>(globalFailures[0] - 1);
}
[[nodiscard]] bool synchronize_non_finite_failure(
const bool localFailure,
const MPI_Comm communicator
) {
const int localStatus = localFailure ? 1 : 0;
int globalStatus = 0;
if (MPI_Allreduce(&localStatus, &globalStatus, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
throw std::runtime_error(
"PreparedHydrostaticEquilibriumOperator could not synchronize finite-arithmetic validity."
);
}
return globalStatus != 0;
}
[[nodiscard]] bool is_finite(const mfem::Vector &vector) {
for (int entry = 0; entry < vector.Size(); ++entry) {
if (!std::isfinite(vector(entry))) {
return false;
}
}
return true;
}
[[nodiscard]] bool is_finite(const mfem::DenseMatrix &matrix) {
for (int row = 0; row < matrix.Height(); ++row) {
for (int column = 0; column < matrix.Width(); ++column) {
if (!std::isfinite(matrix(row, column))) {
return false;
}
}
}
return true;
}
void true_to_local(
const mfem::ParFiniteElementSpace &finiteElementSpace,
const mfem::Vector &trueVector,
@@ -291,8 +372,21 @@ namespace mean_field::operators {
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
auto result = TryPrepare(state, dependencies, rotation);
if (!result.has_value()) {
throwHydrostaticEquilibriumPreparationRejection(result.error());
}
return std::move(result).value();
}
HydrostaticEquilibriumPreparationResult PreparedHydrostaticEquilibriumOperator::TryPrepare(
const context::hydrostatic::HydrostaticEquilibriumStateView &state,
const context::hydrostatic::HydrostaticEquilibriumDependencies &dependencies,
const physics::RigidRotation &rotation
) {
const bool wasPrepared = m_isPrepared;
const bool rotationObjectChanged =
!m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
!wasPrepared || !m_context.IsPrepared() || dependencies.rotation != m_context.GetDependencies().rotation;
PreparedHydrostaticEquilibriumReport report;
@@ -310,25 +404,58 @@ namespace mean_field::operators {
m_isPrepared = false;
if (report.contextReport.preparedStaticDependencies) {
if (report.contextReport.preparedStaticDependencies || !wasPrepared) {
PrepareStaticPlan();
}
if (report.contextReport.preparedGeometryState) {
PrepareGeometry();
PrepareAlgebraicJacobianBlocks();
if (report.contextReport.preparedGeometryState || !wasPrepared) {
const auto mappingFailure = synchronize_mapping_failure(PrepareGeometry(), m_fem.mesh->GetComm());
if (mappingFailure.has_value()) {
const auto reason = *mappingFailure == mapping::MappingStatus::non_positive_determinant
? HydrostaticEquilibriumPreparationRejectionReason::inverted_geometry
: HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry;
return std::unexpected(
HydrostaticEquilibriumPreparationRejection{.reason = reason, .mappingStatus = *mappingFailure}
);
}
if (synchronize_non_finite_failure(PrepareAlgebraicJacobianBlocks(), m_fem.mesh->GetComm())) {
return std::unexpected(
HydrostaticEquilibriumPreparationRejection{
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_geometry,
.mappingStatus = mapping::MappingStatus::non_finite_result
}
);
}
report.preparedAlgebraicJacobianBlocks = true;
}
if (report.contextReport.preparedRotationDependencies) {
PrepareRotation();
if (report.contextReport.preparedRotationDependencies || !wasPrepared) {
if (synchronize_non_finite_failure(PrepareRotation(), m_fem.mesh->GetComm())) {
return std::unexpected(
HydrostaticEquilibriumPreparationRejection{
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
}
);
}
}
if (report.contextReport.preparedBaseState) {
PrepareBaseState();
if (report.contextReport.preparedBaseState || !wasPrepared) {
if (synchronize_non_finite_failure(PrepareBaseState(), m_fem.mesh->GetComm())) {
return std::unexpected(
HydrostaticEquilibriumPreparationRejection{
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
}
);
}
FinalizeDisplacementJacobianPreparation();
AssembleCachedResidual();
++m_residualPreparationCount;
if (synchronize_non_finite_failure(AssembleCachedResidual(), m_fem.mesh->GetComm())) {
return std::unexpected(
HydrostaticEquilibriumPreparationRejection{
.reason = HydrostaticEquilibriumPreparationRejectionReason::non_finite_residual
}
);
}
report.preparedDisplacementJacobianData = true;
report.preparedResidual = true;
}
@@ -343,6 +470,16 @@ namespace mean_field::operators {
"The prepared hydrostatic residual has the wrong supported size."
);
if (report.preparedAlgebraicJacobianBlocks) {
++m_algebraicJacobianStatistics.preparations;
}
if (report.preparedDisplacementJacobianData) {
++m_displacementJacobianStatistics.preparations;
}
if (report.preparedResidual) {
++m_residualPreparationCount;
}
m_isPrepared = true;
return report;
}
@@ -422,7 +559,7 @@ namespace mean_field::operators {
}
}
void PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
std::optional<mapping::MappingStatus> PreparedHydrostaticEquilibriumOperator::PrepareGeometry() {
mfem::Vector displacementLocal;
true_to_local(*m_fem.displacementFes, m_context.GetDisplacementTrue(), displacementLocal);
@@ -491,39 +628,37 @@ namespace mean_field::operators {
mappingData, *transformation, integrationPoint, workspace, mappingContext
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed while preparing "
"hydrostatic geometry. Element: "
<< data.elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
);
if (mappingStatus != mapping::MappingStatus::valid) {
return mappingStatus;
}
const double quadratureWeight = mappingContext.quadrature.weight;
MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0,
"Prepared hydrostatic geometry encountered "
"an invalid quadrature weight."
);
if (!std::isfinite(quadratureWeight)) {
return mapping::MappingStatus::non_finite_result;
}
if (quadratureWeight <= 0.0) {
return mapping::MappingStatus::non_positive_determinant;
}
data.quadratureWeights(quadraturePoint) = quadratureWeight;
for (int component = 0; component < m_fem.mesh->Dimension(); ++component) {
const double position = mappingContext.mapping.physical_position(component);
MFEM_VERIFY(
std::isfinite(position), "Prepared hydrostatic geometry encountered "
"a non-finite physical position."
);
if (!std::isfinite(position)) {
return mapping::MappingStatus::non_finite_result;
}
data.physicalPositions(quadraturePoint, component) = position;
}
}
}
return std::nullopt;
}
void PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
bool PreparedHydrostaticEquilibriumOperator::PrepareAlgebraicJacobianBlocks() {
for (ElementPAData &data : m_elements) {
const int quadraturePointCount = data.quadratureWeights.Size();
@@ -567,12 +702,17 @@ namespace mean_field::operators {
}
}
}
if (!is_finite(data.enthalpyJacobian) || !is_finite(data.gravityPotentialJacobian) ||
!is_finite(data.bernoulliConstantJacobian)) {
return true;
}
}
++m_algebraicJacobianStatistics.preparations;
return false;
}
void PreparedHydrostaticEquilibriumOperator::PrepareRotation() {
bool PreparedHydrostaticEquilibriumOperator::PrepareRotation() {
MFEM_VERIFY(m_rotation.has_value(), "Prepared hydrostatic rotation has no frozen state.");
mfem::Vector physicalPosition(m_fem.mesh->Dimension());
@@ -598,10 +738,9 @@ namespace mean_field::operators {
const double rotationPotential = m_rotation->potential(physicalPosition);
MFEM_VERIFY(
std::isfinite(rotationPotential), "Prepared hydrostatic rotation encountered "
"a non-finite potential."
);
if (!std::isfinite(rotationPotential)) {
return true;
}
data.rotationPotential(quadraturePoint) = rotationPotential;
@@ -612,18 +751,19 @@ namespace mean_field::operators {
const double gradientComponent =
m_rotation->potential_directional_derivative(physicalPosition, coordinateDirection);
MFEM_VERIFY(
std::isfinite(gradientComponent), "Prepared hydrostatic rotation encountered "
"a non-finite potential gradient."
);
if (!std::isfinite(gradientComponent)) {
return true;
}
data.rotationGradient(quadraturePoint, component) = gradientComponent;
}
}
}
return false;
}
void PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
bool PreparedHydrostaticEquilibriumOperator::PrepareBaseState() {
mfem::Vector enthalpyLocal;
mfem::Vector gravityPotentialLocal;
@@ -675,16 +815,17 @@ namespace mean_field::operators {
const double weightedResidual = data.quadratureWeights(quadraturePoint) * imbalance;
MFEM_VERIFY(
std::isfinite(weightedResidual), "Prepared hydrostatic base state encountered "
"a non-finite residual value."
);
if (!std::isfinite(imbalance) || !std::isfinite(weightedResidual)) {
return true;
}
data.weightedResidual(quadraturePoint) = weightedResidual;
data.hydrostaticImbalance(quadraturePoint) = imbalance;
}
}
return false;
}
void PreparedHydrostaticEquilibriumOperator::FinalizeDisplacementJacobianPreparation() {
@@ -703,11 +844,9 @@ namespace mean_field::operators {
"has inconsistent frozen data."
);
}
++m_displacementJacobianStatistics.preparations;
}
void PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
bool PreparedHydrostaticEquilibriumOperator::AssembleCachedResidual() {
mfem::Vector localResidual(m_fem.enthalpyFes->GetVSize());
localResidual = 0.0;
@@ -729,6 +868,8 @@ namespace mean_field::operators {
m_cachedResidual.SetSize(m_context.GetEnthalpyMap().reduced_size());
m_context.GetEnthalpyMap().gather(m_fullEnthalpyAction, m_cachedResidual);
return !is_finite(m_cachedResidual);
}
void PreparedHydrostaticEquilibriumOperator::BuildResidual(mfem::Vector &residual) const {
@@ -926,9 +1067,9 @@ namespace mean_field::operators {
);
weightedVariation.SetSize(quadraturePointCount);
for (int quadraturePoint = 0; quadraturePoint < quadraturePointCount; ++quadraturePoint) {
weightedVariation(quadraturePoint) =
-2.0 * fractionalAngularVelocityVariation * data.quadratureWeights(quadraturePoint) *
data.rotationPotential(quadraturePoint);
weightedVariation(quadraturePoint) = -2.0 * fractionalAngularVelocityVariation *
data.quadratureWeights(quadraturePoint) *
data.rotationPotential(quadraturePoint);
}
elementAction.SetSize(data.enthalpyDofs.Size());
data.enthalpyBasis.MultTranspose(weightedVariation, elementAction);