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

@@ -3,10 +3,15 @@ module;
#include <array>
#include <cmath>
#include <cstdint>
#include <expected>
#include <limits>
#include <mfem.hpp>
#include <optional>
#include <stdexcept>
#include <utility>
#include <mpi.h>
module mean_field;
import :operators.prepared_barotropic_closure;
@@ -148,6 +153,154 @@ namespace {
return *resolution.integration_rule;
}
using BarotropicRejection = mean_field::operators::BarotropicClosurePreparationRejection;
using BarotropicRejectionReason = mean_field::operators::BarotropicClosurePreparationRejectionReason;
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
return false;
}
}
return true;
}
/*
* Rejections are selected by preparation phase, then by an explicit
* detail priority. An earlier phase wins: mapping, quadrature algebra,
* then EOS evaluation. Never depend on the declaration order or the
* underlying integer representation of either public enum.
*/
[[nodiscard]] int mapping_status_priority(const mean_field::mapping::MappingStatus status) {
using Status = mean_field::mapping::MappingStatus;
switch (status) {
case Status::non_positive_determinant:
return 7;
case Status::non_finite_result:
return 6;
case Status::non_finite_input:
return 5;
case Status::outside_reference_domain:
return 4;
case Status::at_compactified_infinity:
return 3;
case Status::invalid_reference_radius:
return 2;
case Status::valid:
throw std::logic_error("A valid mapping cannot be a barotropic candidate rejection.");
case Status::invalid_dimension:
throw std::logic_error("A mapping dimension error cannot be a barotropic candidate rejection.");
}
throw std::logic_error("Unknown mapping status in barotropic candidate rejection.");
}
[[nodiscard]] mean_field::mapping::MappingStatus mapping_status_from_priority(const int priority) {
using Status = mean_field::mapping::MappingStatus;
switch (priority) {
case 7:
return Status::non_positive_determinant;
case 6:
return Status::non_finite_result;
case 5:
return Status::non_finite_input;
case 4:
return Status::outside_reference_domain;
case 3:
return Status::at_compactified_infinity;
case 2:
return Status::invalid_reference_radius;
default:
throw std::logic_error("Invalid synchronized mapping priority for barotropic preparation.");
}
}
[[nodiscard]] int eos_error_priority(const mean_field::eos::EvaluationErrorCode code) {
using Code = mean_field::eos::EvaluationErrorCode;
switch (code) {
case Code::outside_domain:
return 3;
case Code::nonfinite_input:
return 2;
case Code::nonfinite_result:
return 1;
case Code::unsupported_relation:
case Code::unsupported_derivative:
case Code::wrong_input_count:
case Code::wrong_input_quantity:
throw std::logic_error("A structural EOS error cannot be a barotropic candidate rejection.");
}
throw std::logic_error("Unknown EOS error in barotropic candidate rejection.");
}
[[nodiscard]] mean_field::eos::EvaluationErrorCode eos_error_from_priority(const int priority) {
using Code = mean_field::eos::EvaluationErrorCode;
switch (priority) {
case 3:
return Code::outside_domain;
case 2:
return Code::nonfinite_input;
case 1:
return Code::nonfinite_result;
default:
throw std::logic_error("Invalid synchronized EOS priority for barotropic preparation.");
}
}
[[nodiscard]] int rejection_priority(const BarotropicRejection &rejection) {
switch (rejection.reason) {
case BarotropicRejectionReason::mapping_failure:
return 300 + mapping_status_priority(rejection.mappingStatus);
case BarotropicRejectionReason::invalid_quadrature_data:
return 200;
case BarotropicRejectionReason::equation_of_state:
return 100 + eos_error_priority(rejection.equationOfStateError);
}
throw std::logic_error("Unknown barotropic candidate-rejection reason.");
}
[[nodiscard]] BarotropicRejection rejection_from_priority(const int priority) {
if (priority >= 300) {
return {
.reason = BarotropicRejectionReason::mapping_failure,
.mappingStatus = mapping_status_from_priority(priority - 300)
};
}
if (priority == 200) {
return {.reason = BarotropicRejectionReason::invalid_quadrature_data};
}
if (priority >= 100) {
return {
.reason = BarotropicRejectionReason::equation_of_state,
.equationOfStateError = eos_error_from_priority(priority - 100)
};
}
throw std::logic_error("Invalid synchronized barotropic candidate-rejection priority.");
}
void retain_higher_priority_rejection(
std::optional<BarotropicRejection> &current,
const BarotropicRejection candidate
) {
if (!current.has_value() || rejection_priority(candidate) > rejection_priority(*current)) {
current = candidate;
}
}
[[nodiscard]] std::optional<BarotropicRejection> synchronize_rejection(
const std::optional<BarotropicRejection> &local,
const MPI_Comm communicator
) {
const int localPriority = local.has_value() ? rejection_priority(*local) : 0;
int globalPriority = 0;
if (MPI_Allreduce(&localPriority, &globalPriority, 1, MPI_INT, MPI_MAX, communicator) != MPI_SUCCESS) {
throw std::runtime_error("PreparedBarotropicClosureOperator could not synchronize candidate validity.");
}
if (globalPriority == 0) {
return std::nullopt;
}
return rejection_from_priority(globalPriority);
}
} // namespace
namespace mean_field::operators {
@@ -256,6 +409,29 @@ namespace mean_field::operators {
PreparedBarotropicClosureReport PreparedBarotropicClosureOperator::Prepare(
const context::barotropic::BarotropicClosureStateView &state,
const context::barotropic::BarotropicClosureDependencies &dependencies
) {
auto result = TryPrepare(state, dependencies);
if (!result.has_value()) {
const BarotropicClosurePreparationRejection &rejection = result.error();
switch (rejection.reason) {
case BarotropicClosurePreparationRejectionReason::equation_of_state:
throw eos::EvaluationError(
rejection.equationOfStateError,
"PreparedBarotropicClosureOperator encountered invalid thermodynamic data."
);
case BarotropicClosurePreparationRejectionReason::invalid_quadrature_data:
throw std::domain_error("PreparedBarotropicClosureOperator encountered non-finite quadrature data.");
case BarotropicClosurePreparationRejectionReason::mapping_failure:
throw std::domain_error("PreparedBarotropicClosureOperator could not map the candidate geometry.");
}
throw std::logic_error("Unknown barotropic candidate-rejection reason.");
}
return std::move(result).value();
}
BarotropicClosurePreparationResult PreparedBarotropicClosureOperator::TryPrepare(
const context::barotropic::BarotropicClosureStateView &state,
const context::barotropic::BarotropicClosureDependencies &dependencies
) {
PreparedBarotropicClosureReport report;
report.contextReport = m_context.Prepare(state, dependencies);
@@ -296,6 +472,7 @@ namespace mean_field::operators {
mfem::Vector densityShape;
mfem::Vector enthalpyShape;
std::optional<BarotropicClosurePreparationRejection> localRejection;
for (int elementId = 0; elementId < m_fem.mesh->GetNE(); ++elementId) {
mfem::ElementTransformation *transformation = m_fem.mesh->GetElementTransformation(elementId);
@@ -385,11 +562,16 @@ namespace mean_field::operators {
);
MFEM_VERIFY(
mappingStatus == mapping::MappingStatus::valid,
"Stateless mapping failed while preparing the barotropic closure operator. Element: "
<< elementId << ", attribute: " << transformation->Attribute
<< ", quadrature point: " << quadraturePoint << ", status: " << static_cast<int>(mappingStatus)
mappingStatus != mapping::MappingStatus::invalid_dimension,
"Stateless mapping reported a dimension error while preparing the barotropic closure operator."
);
if (mappingStatus != mapping::MappingStatus::valid) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::mapping_failure,
.mappingStatus = mappingStatus}
);
continue;
}
MFEM_VERIFY(
!mappingContext.mapping.compactified,
@@ -406,6 +588,13 @@ namespace mean_field::operators {
densityElement.CalcShape(integrationPoint, densityShape);
enthalpyElement.CalcShape(integrationPoint, enthalpyShape);
if (!vector_is_finite(densityShape) || !vector_is_finite(enthalpyShape)) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
);
continue;
}
for (int densityDof = 0; densityDof < densityDofCount; ++densityDof) {
data.densityBasis(quadraturePoint, densityDof) = densityShape(densityDof);
}
@@ -416,6 +605,35 @@ namespace mean_field::operators {
const double density = elementBaseDensity * densityShape;
const double enthalpy = elementBaseEnthalpy * enthalpyShape;
const double quadratureWeight = mappingContext.quadrature.weight;
if (!std::isfinite(quadratureWeight) || quadratureWeight <= 0.0) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
);
continue;
}
if (!std::isfinite(density)) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
);
continue;
}
if (!std::isfinite(enthalpy)) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
.equationOfStateError = eos::EvaluationErrorCode::nonfinite_input}
);
continue;
}
if (enthalpy < 0.0) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
.equationOfStateError = eos::EvaluationErrorCode::outside_domain}
);
continue;
}
const dimensions::SpecificEnthalpyValue specificEnthalpy{enthalpy};
const double eosDensity =
eos::evaluate<eos::quantity::Density>(m_equationOfState, specificEnthalpy).value();
@@ -425,18 +643,34 @@ namespace mean_field::operators {
)
.value();
MFEM_VERIFY(
std::isfinite(quadratureWeight) && quadratureWeight > 0.0 && std::isfinite(eosDensity) &&
std::isfinite(enthalpyDerivative),
"PreparedBarotropicClosureOperator encountered invalid quadrature data."
);
if (!std::isfinite(eosDensity) || !std::isfinite(enthalpyDerivative)) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::equation_of_state,
.equationOfStateError = eos::EvaluationErrorCode::nonfinite_result}
);
continue;
}
const double weightedResidual = quadratureWeight * (density - eosDensity);
const double weightedEnthalpyDerivative = quadratureWeight * enthalpyDerivative;
if (!std::isfinite(weightedResidual) || !std::isfinite(weightedEnthalpyDerivative)) {
retain_higher_priority_rejection(
localRejection, {.reason = BarotropicClosurePreparationRejectionReason::invalid_quadrature_data}
);
continue;
}
data.quadratureWeights(quadraturePoint) = quadratureWeight;
data.weightedResidual(quadraturePoint) = quadratureWeight * (density - eosDensity);
data.weightedEnthalpyDerivative(quadraturePoint) = quadratureWeight * enthalpyDerivative;
data.weightedResidual(quadraturePoint) = weightedResidual;
data.weightedEnthalpyDerivative(quadraturePoint) = weightedEnthalpyDerivative;
}
}
if (auto globalRejection = synchronize_rejection(localRejection, m_fem.densityFes->GetComm());
globalRejection.has_value()) {
return std::unexpected(*globalRejection);
}
MFEM_VERIFY(!m_elements.empty(), "PreparedBarotropicClosureOperator found no elements in Density::Support.");
m_isPrepared = true;