Distinguish strategy failures and expose the actual trial tangent

This commit is contained in:
2026-09-17 10:09:22 -04:00
parent 5f3eb0b18c
commit 2e31917269
6 changed files with 152 additions and 13 deletions

View File

@@ -265,6 +265,9 @@ export namespace mean_field::solver::nonlinear {
std::span<const mfem::real_t> candidatePhysicalState{};
std::span<const mfem::real_t> candidateNormalizedState{};
std::span<const mfem::real_t> candidateNormalizedResidual{};
// A curved trial path cannot recover its Newton tangent by dividing
// the candidate displacement by alpha. Observers borrow it directly.
std::span<const mfem::real_t> normalizedDirection{};
};
struct AfterIteration final {

View File

@@ -1,6 +1,7 @@
module;
#include <concepts>
#include <cstdint>
#include <type_traits>
#include <utility>
@@ -22,7 +23,7 @@ export namespace mean_field::solver::nonlinear {
struct GravitySolveOptions final {
LinearSolveControl control{.relativeTolerance = 1.0e-10, .absoluteTolerance = 1.0e-14,
.maximumIterations = 300};
linear::FGMRESOptions backend{.restartLength = 80};
linear::FGMRESOptions backend{.restartLength = 80, .printLevel = -1};
void Validate() const { control.Validate(); backend.Validate(); }
};
@@ -64,14 +65,18 @@ export namespace mean_field::solver::nonlinear {
template <typename Candidate>
concept NewtonNumericalConfiguration = IsNewtonNumerics<std::remove_cvref_t<Candidate>>::value;
enum class LinearReportSpace : std::uint8_t { full_system, reduced_system, gravity_subsystem };
struct NumericalStrategyDiagnostics final {
int polynomialChecks{0};
int directionRepairs{0};
double lastRepairFraction{0.0};
double lastMeritDirectionalDerivative{0.0};
int gravitySolves{0};
int gravityIterations{0};
int gravityFailures{0};
std::uint64_t gravitySolves{0};
std::uint64_t gravityIterations{0};
std::uint64_t gravityFailures{0};
std::uint64_t gravityPreparedFailures{0};
LinearReportSpace lastLinearReportSpace{LinearReportSpace::full_system};
double gravitySolveSeconds{0.0};
double lastGravityTrueResidual{0.0};
double lastGravityCorrectionNorm{0.0};

View File

@@ -1307,9 +1307,14 @@ export namespace mean_field::solver {
}
if (!strategy.CaptureDirection(state, diagnostics.numericalStrategies)) {
NotifyAfter(iteration, nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
const bool linearFailure = strategy.DirectionSolveFailed();
NotifyAfter(iteration, linearFailure ? nonlinear::IterationDisposition::linear_solve_failure :
nonlinear::IterationDisposition::globalization_failure, false, 0.0, 0,
diagnostics.initialResidualNorm, acceptedMetric, acceptedMetric, linearReport, timings, state);
return Failure(state, std::move(diagnostics), StellarEquilibriumFailureReason::globalization_failure,
return Failure(state, std::move(diagnostics), linearFailure ?
StellarEquilibriumFailureReason::linear_solve_failure :
StellarEquilibriumFailureReason::globalization_failure,
linearFailure ? "Lifting the repaired direction failed its checked gravity solve." :
"No certified enthalpy repair with a decreasing linear merit was available.");
}
@@ -1780,7 +1785,8 @@ export namespace mean_field::solver {
.candidateNormalizedState = detail::ReadOnlySpan(state.trialNormalizedState),
.candidateNormalizedResidual = residualAvailable
? detail::ReadOnlySpan(state.trialNormalizedResidual)
: std::span<const mfem::real_t>{}
: std::span<const mfem::real_t>{},
.normalizedDirection = detail::ReadOnlySpan(state.normalizedCorrection)
};
const Clock::time_point observerStart = Clock::now();
nonlinear::detail::InvokeAfterLineSearchTrial(m_observer, event);

View File

@@ -118,6 +118,8 @@ namespace mean_field::solver::detail {
report.gravitySolves = subsystem.GetSolveCount();
report.gravityIterations = subsystem.GetIterationCount();
report.gravityFailures = subsystem.GetFailureCount();
report.gravityPreparedFailures = preparedFailures;
report.lastLinearReportSpace = lastLinearReportSpace;
report.gravitySolveSeconds = subsystem.GetSolveSeconds();
if (const auto &last = subsystem.GetLastSolveReport(); last.has_value()) {
report.lastGravityTrueResidual = last->trueResidualNorm;
@@ -174,6 +176,7 @@ namespace mean_field::solver::detail {
std::isfinite(targetDefect) && targetDefect <= tolerance;
lastPreparedDefect = targetDefect;
}
if (!preparation || !trialSolveSucceeded) { ++preparedFailures; }
return preparation;
}
@@ -186,6 +189,8 @@ namespace mean_field::solver::detail {
double lastCorrectionNorm{0.0};
double lastPreparedDefect{0.0};
double liftedRelativeDefect{0.0};
std::uint64_t preparedFailures{0};
nonlinear::LinearReportSpace lastLinearReportSpace{nonlinear::LinearReportSpace::full_system};
};
template <typename State>
@@ -259,13 +264,18 @@ namespace mean_field::solver::detail {
LinearSolveReport Solve(State &state, const LinearSolveControl &control) {
this->subsystem.Refresh();
try {
this->lastLinearReportSpace = nonlinear::LinearReportSpace::gravity_subsystem;
auto inner = m_operator.TryCondenseRightHandSide(state.linearRightHandSide, m_rhs);
if (!inner.Converged()) { return inner; }
this->subsystem.GetPartition().GatherRetainedValues(state.normalizedCorrection, m_correction);
this->lastLinearReportSpace = nonlinear::LinearReportSpace::reduced_system;
auto report = m_backend.Solve(m_rhs, m_correction, control);
if (!report.Converged()) { return report; }
inner = m_operator.TryLiftCorrection(m_correction, state.linearRightHandSide, state.normalizedCorrection);
if (!inner.Converged()) { return inner; }
if (!inner.Converged()) {
this->lastLinearReportSpace = nonlinear::LinearReportSpace::gravity_subsystem;
return inner;
}
state.normalizedOperator->Mult(state.normalizedCorrection, this->fullAction);
this->fullAction -= state.linearRightHandSide;
const auto communicator = this->subsystem.GetCommunicator();
@@ -283,6 +293,7 @@ namespace mean_field::solver::detail {
}
return report;
} catch (const GravityReductionFailure &failure) {
this->lastLinearReportSpace = nonlinear::LinearReportSpace::gravity_subsystem;
return failure.GetReport();
}
}
@@ -368,9 +379,14 @@ namespace mean_field::solver::detail {
return report;
}
bool CaptureDirection(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
m_directionSolveFailed = false;
if (!m_enthalpy.Repair(state, diagnostics)) { return false; }
if constexpr (!std::same_as<Gravity, nonlinear::EliminatedGravity> || altersDirection) {
if (!m_gravity.CaptureDirection(state)) { m_gravity.CopyDiagnostics(diagnostics); return false; }
if (!m_gravity.CaptureDirection(state)) {
m_directionSolveFailed = true;
m_gravity.CopyDiagnostics(diagnostics);
return false;
}
}
if constexpr (altersDirection && std::same_as<Gravity, nonlinear::EliminatedGravity>) {
mfem::Vector action(state.Problem().EquationSize());
@@ -389,7 +405,9 @@ namespace mean_field::solver::detail {
return preparation;
}
bool TrialSolveSucceeded() const noexcept { return m_gravity.TrialSolveSucceeded(); }
bool DirectionSolveFailed() const noexcept { return m_directionSolveFailed; }
private:
bool m_directionSolveFailed{false};
PreparedEnthalpyStrategy<State, Enthalpy> m_enthalpy;
PreparedGravityStrategy<State, Gravity> m_gravity;
};