Clear unavailable gravity measurements before each attempt

This commit is contained in:
2026-09-17 11:01:13 -04:00
parent 7e1ab69fcd
commit a2871e11e2
4 changed files with 77 additions and 7 deletions

View File

@@ -33,9 +33,30 @@ Two separate requirements remain:
Diagnostics retain the raw affine discrepancy, raw target defect, arithmetic
estimate, and resulting target threshold separately. Nonfinite or negative
norm inputs fail the assessment. A failed preparation leaves these measurements
unavailable rather than reusing the preceding trial's values. Diagnostics also
report whether compact gravity operator and inverse applications were selected.
norm inputs fail the assessment. Each gravity attempt clears its correction
norm and prepared-assessment measurements before the raw candidate preparation.
An early preparation or inner-solve failure therefore leaves unmeasured values
as NaN, rather than reusing the preceding attempt's values. Initial gravity
completion follows the same rule. A failure in the corrected preparation can
still retain that attempt's measured correction norm.
The last completed inner solve's true defect and cumulative solve/iteration/
failure/time counters have a different lifetime: a candidate that fails before
calling the inner solver does not erase them. A line-search candidate rejected
before invoking the gravity strategy does not begin an auxiliary attempt and
leaves all last-attempt diagnostics intact. Diagnostic consumers must distinguish
these meanings. Diagnostics also report whether compact gravity operator and
inverse applications were selected.
`lastLinearReportSpace` labels the retained `lastLinearSolve` report from the
outer strategy's `Solve`. A later positivity-direction relift can fail while
that outer report remains converged in reduced coordinates. The evaluation then
reports `linear_solve_failure`; the auxiliary failure is reflected by the gravity
failure counter and last completed inner true defect. Changing only the report
space would incorrectly relabel the retained reduced-system norms. The full
lifted Newton gate precedes feasibility repair; a repaired direction must pass
its relift, descent check, and nonlinear acceptance, but is not claimed to solve
the original Newton equations.
Three focused tests, appended to `tests/solver/gravity_reduction.cpp`, cover
large cancelling contributions, rejection of defects beyond either gate,
@@ -43,3 +64,17 @@ nonfinite measurements, and scaling over twenty-four orders of magnitude.
The real stellar rejection must still be reproduced and reassessed; passing a
synthetic arithmetic test alone establishes neither the hypothesis nor stellar
convergence.
## Reporting regression
Branch `research/gravity-trial-diagnostics-20260917` moves this reset to a shared
attempt-entry helper, used by both trial strategies and initial completion.
Existing production rollback fixtures now assert finite measurements after a
completed curvature assessment, and unavailable prepared/correction fields after
a failed initial inner solve while retaining its finite true defect and counters.
No new stellar fixture or numerical behavior is introduced. The source-level
success-then-early-failure path is covered by the unconditional shared reset;
a separate expensive campaign is not justified for this reporting change.
Build/run of the added assertions is deferred until the running immutable
production campaign completes.

View File

@@ -81,9 +81,17 @@ export namespace mean_field::solver::nonlinear {
std::uint64_t gravityPreparedFailures{0};
bool compactGravityOperator{false};
bool compactGravityInverse{false};
// Coordinates of the evaluation's lastLinearSolve report. A later
// feasibility relift or trial solve has its own auxiliary diagnostics
// and does not replace that report or relabel its stored norms.
LinearReportSpace lastLinearReportSpace{LinearReportSpace::full_system};
double gravitySolveSeconds{0.0};
// Last completed auxiliary solve; a later failure before Solve leaves
// this value and the cumulative counters unchanged.
double lastGravityTrueResidual{0.0};
// Current gravity attempt: NaN until that attempt measures the value.
// Candidates rejected before invoking gravity leave its prior attempt
// untouched because no auxiliary work was attempted.
double lastGravityCorrectionNorm{0.0};
double lastGravityPreparedDefect{0.0};
double lastGravityAffineDisagreement{0.0};

View File

@@ -140,6 +140,17 @@ namespace mean_field::solver::detail {
}
protected:
void BeginGravityAttempt() noexcept {
trialSolveSucceeded = true;
// These measurements belong to this attempt. An early preparation
// or inner-solve failure must not publish the preceding trial's
// successful assessment. The last completed solve and cumulative
// counters deliberately retain their separate lifetime.
lastCorrectionNorm = lastPreparedDefect = lastAffineDisagreement =
lastArithmeticAllowance = lastPreparedTargetThreshold =
std::numeric_limits<double>::quiet_NaN();
}
void Refresh(State &state) {
// Preparing a trial invalidates both normalized and physical
// inverses. Refreshing only the restricted wrapper would hide this.
@@ -157,8 +168,6 @@ namespace mean_field::solver::detail {
}
auto ApplyCorrection(State &state) {
lastPreparedDefect = lastAffineDisagreement = lastArithmeticAllowance =
lastPreparedTargetThreshold = std::numeric_limits<double>::quiet_NaN();
const auto &partition = subsystem.GetPartition();
partition.InjectGravityValues(gravityCorrection, fullCorrection);
subsystem.GetOperator().Mult(gravityCorrection, gravityAction);
@@ -249,7 +258,7 @@ namespace mean_field::solver::detail {
return true;
}
auto PrepareTrial(State &state, const double alpha) {
this->trialSolveSucceeded = true;
this->BeginGravityAttempt();
auto preparation = state.PrepareTrial();
if (!preparation) { return preparation; }
this->Refresh(state);
@@ -281,6 +290,7 @@ namespace mean_field::solver::detail {
m_rhs(m_operator.Height()), m_correction(m_operator.Width()) { }
bool Initialize(State &state, nonlinear::NumericalStrategyDiagnostics &diagnostics) {
this->BeginGravityAttempt();
const auto &partition = this->subsystem.GetPartition();
partition.GatherGravityResiduals(state.acceptedNormalizedResidual, this->gravityResidual);
diagnostics.initialGravityResidual = StrategyNorm(this->gravityResidual, this->subsystem.GetCommunicator());
@@ -343,11 +353,14 @@ namespace mean_field::solver::detail {
this->subsystem.GetPartition().GatherRetainedValues(state.normalizedCorrection, m_correction);
this->lastSolve = m_operator.TryLiftCorrection(m_correction, state.linearRightHandSide,
state.normalizedCorrection);
// Keep the report space attached to the outer Solve result; this
// later auxiliary failure is reported through DirectionSolveFailed
// and the gravity counters/last completed inner defect.
return this->lastSolve.Converged();
}
auto PrepareTrial(State &state, double) {
this->trialSolveSucceeded = true;
this->BeginGravityAttempt();
auto preparation = state.PrepareTrial();
if (!preparation) { return preparation; }
this->Refresh(state);

View File

@@ -196,6 +196,11 @@ TEST_CASE("Production gravity strategies retain optional model borders and resto
CHECK(report.diagnostics().lastLinearSolve->iterations == 0);
CHECK(report.diagnostics().numericalStrategies.gravitySolves > 0);
CHECK(report.diagnostics().numericalStrategies.gravityFailures == 0);
CHECK(std::isfinite(report.diagnostics().numericalStrategies.lastGravityCorrectionNorm));
CHECK(std::isfinite(report.diagnostics().numericalStrategies.lastGravityPreparedDefect));
CHECK(std::isfinite(report.diagnostics().numericalStrategies.lastGravityAffineDisagreement));
CHECK(std::isfinite(report.diagnostics().numericalStrategies.lastGravityArithmeticAllowance));
CHECK(std::isfinite(report.diagnostics().numericalStrategies.lastGravityPreparedTargetThreshold));
CHECK(report.lastAcceptedCheckpointView().valid());
}
REQUIRE(context.isReady());
@@ -237,6 +242,15 @@ TEST_CASE("A failed initial gravity completion preserves the accepted stellar ch
CHECK(report.diagnostics().numericalStrategies.initialGravityResidual > 0.0);
CHECK(report.diagnostics().numericalStrategies.gravitySolves == 1);
CHECK(report.diagnostics().numericalStrategies.gravityFailures == 1);
const auto &auxiliary = report.diagnostics().numericalStrategies;
// A completed but unsuccessful inner solve has a meaningful true
// defect. No correction/prepared assessment occurred in this attempt.
CHECK(std::isfinite(auxiliary.lastGravityTrueResidual));
CHECK(std::isnan(auxiliary.lastGravityCorrectionNorm));
CHECK(std::isnan(auxiliary.lastGravityPreparedDefect));
CHECK(std::isnan(auxiliary.lastGravityAffineDisagreement));
CHECK(std::isnan(auxiliary.lastGravityArithmeticAllowance));
CHECK(std::isnan(auxiliary.lastGravityPreparedTargetThreshold));
CHECK(report.diagnostics().acceptedNonlinearIterations == 0);
CHECK(report.lastAcceptedCheckpointView().valid());
}