feat(newton): first newton solver implementation
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user