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

@@ -1,6 +1,13 @@
module;
#include <cmath>
#include <expected>
#include <mfem.hpp>
#include <optional>
#include <stdexcept>
#include <utility>
#include <mpi.h>
module mean_field;
@@ -8,6 +15,75 @@ import :operators.prepared_displacement_residual;
namespace {
using Dependencies = mean_field::operators::DisplacementResidualDependencies;
using Rejection = mean_field::operators::DisplacementResidualPreparationRejection;
using Source = mean_field::operators::DisplacementResidualPreparationRejectionSource;
using Reason = mean_field::operators::DisplacementResidualPreparationRejectionReason;
[[nodiscard]] Rejection
pressure_rejection(const mean_field::operators::PressureForcePreparationRejection &rejection) noexcept {
using PressureReason = mean_field::operators::PressureForcePreparationRejectionReason;
switch (rejection.reason) {
case PressureReason::equation_of_state:
return {
.source = Source::pressure,
.reason = Reason::equation_of_state,
.equationOfStateCode = rejection.equationOfStateCode
};
case PressureReason::invalid_mapping:
return {
.source = Source::pressure, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
};
case PressureReason::non_finite_arithmetic:
default:
return {.source = Source::pressure, .reason = Reason::non_finite_arithmetic};
}
}
[[nodiscard]] Rejection
gravity_rejection(const mean_field::operators::kernels::GravityDisplacementForceRejection &rejection) noexcept {
if (rejection.reason ==
mean_field::operators::kernels::GravityDisplacementForceRejectionReason::invalid_mapping) {
return {
.source = Source::gravity, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
};
}
return {.source = Source::gravity, .reason = Reason::non_finite_arithmetic};
}
[[nodiscard]] Rejection
rotation_rejection(const mean_field::operators::kernels::RotationalDisplacementForceRejection &rejection) noexcept {
if (rejection.reason ==
mean_field::operators::kernels::RotationalDisplacementForceRejectionReason::invalid_mapping) {
return {
.source = Source::rotation, .reason = Reason::invalid_mapping, .mappingStatus = rejection.mappingStatus
};
}
return {.source = Source::rotation, .reason = Reason::non_finite_arithmetic};
}
[[nodiscard]] bool vector_is_finite(const mfem::Vector &vector) noexcept {
for (int index = 0; index < vector.Size(); ++index) {
if (!std::isfinite(vector(index))) {
return false;
}
}
return true;
}
[[noreturn]] void throw_rejection(const Rejection &rejection) {
switch (rejection.reason) {
case Reason::equation_of_state:
throw mean_field::eos::EvaluationError(
rejection.equationOfStateCode,
"PreparedDisplacementResidualOperator encountered invalid thermodynamic data."
);
case Reason::invalid_mapping:
throw std::domain_error("PreparedDisplacementResidualOperator encountered an invalid mapped domain.");
case Reason::non_finite_arithmetic:
default:
throw std::domain_error("PreparedDisplacementResidualOperator produced non-finite arithmetic.");
}
}
[[nodiscard]] mean_field::operators::context::pressure_force::PressureForceDependencies
make_pressure_dependencies(const Dependencies &dependencies) {
@@ -118,6 +194,21 @@ namespace mean_field::operators {
const DisplacementResidualStateView &state,
const DisplacementResidualDependencies &dependencies,
const physics::RigidRotation &rotation
) {
auto result = TryPrepare(state, dependencies, rotation);
if (!result.has_value()) {
throw_rejection(result.error());
}
return std::move(result).value();
}
std::expected<
PreparedDisplacementResidualReport,
DisplacementResidualPreparationRejection>
PreparedDisplacementResidualOperator::TryPrepare(
const DisplacementResidualStateView &state,
const DisplacementResidualDependencies &dependencies,
const physics::RigidRotation &rotation
) {
validate_shared_gravity_revisions(m_gravityContext, dependencies);
@@ -161,20 +252,47 @@ namespace mean_field::operators {
PreparedDisplacementResidualReport report;
report.pressure = m_pressureOperator.Prepare(
auto pressureResult = m_pressureOperator.TryPrepare(
{.enthalpy = state.enthalpy, .displacement = displacement}, make_pressure_dependencies(dependencies)
);
if (!pressureResult.has_value()) {
return std::unexpected(pressure_rejection(pressureResult.error()));
}
report.pressure = std::move(pressureResult).value();
report.gravity = m_gravityOperator.Prepare();
auto gravityResult = m_gravityOperator.TryPrepare();
if (!gravityResult.has_value()) {
return std::unexpected(gravity_rejection(gravityResult.error()));
}
report.gravity = std::move(gravityResult).value();
report.rotation = m_rotationalOperator.Prepare(
auto rotationResult = m_rotationalOperator.TryPrepare(
{.density = density, .displacement = displacement}, make_rotational_dependencies(dependencies), rotation
);
if (!rotationResult.has_value()) {
return std::unexpected(rotation_rejection(rotationResult.error()));
}
report.rotation = std::move(rotationResult).value();
if (report.DidAnyChildWork() ||
m_cachedResidual.Size() != m_gravityContext.GetDisplacementMap().reduced_size()) {
AssembleResidual();
const auto localAssemblyRejection = AssembleResidual();
const int localRejected = localAssemblyRejection.has_value() ? 1 : 0;
int globallyRejected = 0;
if (MPI_Allreduce(
&localRejected, &globallyRejected, 1, MPI_INT, MPI_MAX, m_fem.displacementFes->GetComm()
) != MPI_SUCCESS) {
throw std::runtime_error(
"PreparedDisplacementResidualOperator could not synchronize residual validity."
);
}
if (globallyRejected != 0) {
return std::unexpected(
Rejection{.source = Source::composition, .reason = Reason::non_finite_arithmetic}
);
}
report.assembledResidual = true;
++m_residualPreparationCount;
}
MFEM_VERIFY(
@@ -188,7 +306,7 @@ namespace mean_field::operators {
return report;
}
void PreparedDisplacementResidualOperator::AssembleResidual() {
std::optional<DisplacementResidualPreparationRejection> PreparedDisplacementResidualOperator::AssembleResidual() {
mfem::Vector pressureResidual;
mfem::Vector gravityResidual;
mfem::Vector rotationalResidual;
@@ -211,7 +329,10 @@ namespace mean_field::operators {
"different sizes."
);
++m_residualPreparationCount;
if (!vector_is_finite(m_cachedResidual)) {
return Rejection{.source = Source::composition, .reason = Reason::non_finite_arithmetic};
}
return std::nullopt;
}
void PreparedDisplacementResidualOperator::BuildResidual(mfem::Vector &residual) const {