Skip to content

NSA: gate iter-1 convergence on inner solver retcode (#3817) - #3893

Merged
ChrisRackauckas merged 1 commit into
SciML:masterfrom
singhharsh1708:nsa-trustregion-ndz-convergence-3817
Jul 31, 2026
Merged

NSA: gate iter-1 convergence on inner solver retcode (#3817)#3893
ChrisRackauckas merged 1 commit into
SciML:masterfrom
singhharsh1708:nsa-trustregion-ndz-convergence-3817

Conversation

@singhharsh1708

@singhharsh1708 singhharsh1708 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Fixes #3817.

A globalized inner solver like TrustRegion can reject its trial step: step! returns with the iterate exactly unmoved and the cache not terminated, having only shrunk its trust region for the next attempt. The outer Newton loop measures displacement, so that rejected step reads as ndz == 0 — which both convergence tests treat as a perfect solve (ndz < 1e-5 on the first iteration, eta*ndz = 0 < kappa afterwards). The stage gets accepted with no correction applied. On fixed-dt FBDF + TrustRegion Robertson, master returns Success with the state frozen at u0.

#4020 already gates on a terminated inner cache; the case it cannot see is rejected-but-not-terminated. This PR adds exactly that discriminator, for NLSolver{<:NonlinearSolveAlg} only:

_rejected_trial_step(nlsolver, ndz) =
    iszero(ndz) && NonlinearSolveBase.not_terminated(nlsolver.cache.cache)

When it fires, nlsolve! skips the convergence/divergence bookkeeping for that iteration (a zero ndz would also poison the next iteration's theta) and lets the inner solver retry with its shrunken region. If it never moves, the loop exhausts and the step is rejected as unconverged. A cache that terminated at zero displacement either converged exactly or already failed (failures become Inf in compute_step!), so termination is the right discriminator between "at the root" and "undecided". NLNewton and the other legacy solvers hit the false fallback and are untouched.

Measured on Robertson (0, 1e5) against an FBDF reltol=1e-12 reference:

  • fixed-dt FBDF + TrustRegion: Success frozen at u0 -> ConvergenceFailure (matches what NewtonRaphson reports there)
  • default tolerances: relative error 1.7e-2 -> 1.6e-5 (NLNewton gets 2.1e-5, NSA NewtonRaphson 4.0e-6 on the same problem)
  • reltol=1e-8: 2.4e-8 -> 1.6e-9
  • NSA NewtonRaphson runs are bit-identical before/after (the predicate never fires for it), naccept/nreject unchanged

This replaces the earlier iter-1 retcode gate from the first version of this PR, which #4020 made partially redundant. The testset that asserted a 1e-4 error bound the old gate could not meet now measures 1.6e-9 and asserts honestly; a second testset covers the fixed-dt frozen case. Also drops a non-public SimpleNonlinearSolve import the QA lane flagged.

AI Disclosure

Claude assisted with this work.

@ChrisRackauckas

Copy link
Copy Markdown
Member

don't stack this

@singhharsh1708
singhharsh1708 force-pushed the nsa-trustregion-ndz-convergence-3817 branch from 4f63a59 to eae4139 Compare July 13, 2026 17:30
@singhharsh1708

Copy link
Copy Markdown
Contributor Author

don't stack this

done.

@singhharsh1708
singhharsh1708 force-pushed the nsa-trustregion-ndz-convergence-3817 branch 4 times, most recently from 8628e6d to 7718a44 Compare July 18, 2026 07:33
@singhharsh1708
singhharsh1708 force-pushed the nsa-trustregion-ndz-convergence-3817 branch from 7718a44 to 9ae749a Compare July 21, 2026 14:31
A globalized inner solver (TrustRegion and friends) can reject its trial
step: `step!` returns with the iterate exactly unmoved and the cache not
terminated, having only shrunk its trust region for the next attempt.
The outer convergence test read that zero displacement as a perfect
solve - `ndz < 1e-5` on the first iteration, `eta*ndz = 0 < kappa` on
later ones - and accepted the stage with no correction applied. SciML#4020
surfaces inner caches that *terminated* unsuccessfully; a rejected step
terminates nothing, so it slipped through both branches.

`nlsolve!` now skips the convergence and divergence bookkeeping for an
iteration whose `NonlinearSolveAlg` step left the iterate unmoved
without terminating: no new iterate exists to judge, and a zero `ndz`
would also poison the next iteration's theta. The inner solver retries
with its shrunken radius; if it never moves, the loop runs out and the
step is rejected as unconverged. A cache that terminated at zero
displacement converged exactly (failures already return `Inf` from
`compute_step!`), so that case still counts as convergence.

ROBER with FBDF and a TrustRegion inner solver:

- fixed dt = 1.0 (transient unresolvable): was ReturnCode.Success with
  the state frozen at u0; now ConvergenceFailure, matching NewtonRaphson.
- adaptive, default tolerances: relerr 1.7e-2 -> 1.8e-5 (NLNewton 2.1e-5,
  NSA NewtonRaphson 4.0e-6). Same for RobustMultiNewton.
- adaptive, reltol=1e-8: relerr 2.4e-8 -> 1.6e-9 (NewtonRaphson 1.3e-9).

NewtonRaphson inner solves are unchanged step-for-step (their iterates
always move or terminate), and NLNewton never enters the predicate.
@singhharsh1708
singhharsh1708 force-pushed the nsa-trustregion-ndz-convergence-3817 branch from 9ae749a to 8d994e3 Compare July 30, 2026 20:26
@ChrisRackauckas
ChrisRackauckas merged commit eea042e into SciML:master Jul 31, 2026
131 of 171 checks passed
@singhharsh1708
singhharsh1708 deleted the nsa-trustregion-ndz-convergence-3817 branch July 31, 2026 07:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NonlinearSolveAlg(TrustRegion) + W-reuse: displacement-based ndz declares convergence on rejected TR steps → silently wrong solutions

2 participants