Found while investigating the master nlstep_tests.jl FBDF+NSA(TrustRegion) order failures (see #3807 discussion; the order-test failure itself is fixed separately).
Symptom
Fixed-dt FBDF + NonlinearSolveAlg(TrustRegion()) on Robertson (analytic jac, plain W-reuse path, no MTK nlstep) produces a final-state error of 4.741e-2, constant across dt = 2^-15 … 2^-18 — a dt-independent error floor, i.e. silently wrong results with Success retcodes. Stats show exactly 1 inner iteration and 1 f-eval per step.
Mechanism
compute_step! for NonlinearSolveAlg computes the convergence norm from the displacement: ndz = internalnorm(calculate_residuals(z .- nlcache.u, ...)) (newton.jl, from #3693). When TrustRegion rejects or truncates its internal step, nlcache.u barely moves, so ndz ≈ 0 — and the iter-1 η-convergence shortcut (iter == 1 && ndz < 1e-5, plus the first-iter trust gate enabled for cache.W !== nothing in nlsolve.jl) declares the stage converged with essentially no correction applied. A rejected globalization step reads as "converged" to the outer loop.
Two related consequences observed with the same mechanism:
- The θ≈1 stall detection (
eps_around_one branch in nlsolve.jl) misfires for globalized inner solvers: a rejected TR step leaves fu unchanged → θ = 1 exactly → premature Divergence (after 2 iterations) or premature convergence depending on ndz.
- On a freshly built MTK nlstep problem,
FBDF + NSA(TrustRegion) at dt=2^-15 aborts at step 0 with ConvergenceFailure after 2 inner iterations via that θ≈1 path (test files only pass because earlier testsets warm the shared nlprob; see also the companion NonlinearSolve.jl issue about reinit! resetting the trust radius to its build-time value).
Proposed fix direction
Outer convergence must not be declared from displacement-ndz when the inner solver did not actually accept a step / make progress: consult the inner cache (descent success / step acceptance / retcode) before treating a small ndz as convergence — or, longer-term, drive NonlinearSolveAlg inner solvers by their own residual-based termination instead of NLNewton's θ/η heuristics, which assume a plain (modified-)Newton contraction.
Repro scripts from the investigation are available; error table:
| dt |
final-state l∞ error (FBDF+NSA(TrustRegion), fixed dt) |
| 2^-15 |
4.741e-2 |
| 2^-16 |
4.741e-2 |
| 2^-17 |
4.741e-2 |
| 2^-18 |
4.741e-2 |
(For comparison, FBDF+NSA(NewtonRaphson) and FBDF+NLNewton halve cleanly.)
🤖 Filed by an agent after local reproduction on master 72706ab, Julia 1.12.6, NonlinearSolve 4.20.1.
Found while investigating the master
nlstep_tests.jlFBDF+NSA(TrustRegion) order failures (see #3807 discussion; the order-test failure itself is fixed separately).Symptom
Fixed-dt
FBDF + NonlinearSolveAlg(TrustRegion())on Robertson (analyticjac, plain W-reuse path, no MTK nlstep) produces a final-state error of 4.741e-2, constant across dt = 2^-15 … 2^-18 — a dt-independent error floor, i.e. silently wrong results withSuccessretcodes. Stats show exactly 1 inner iteration and 1 f-eval per step.Mechanism
compute_step!forNonlinearSolveAlgcomputes the convergence norm from the displacement:ndz = internalnorm(calculate_residuals(z .- nlcache.u, ...))(newton.jl, from #3693). When TrustRegion rejects or truncates its internal step,nlcache.ubarely moves, sondz ≈ 0— and the iter-1 η-convergence shortcut (iter == 1 && ndz < 1e-5, plus the first-iter trust gate enabled forcache.W !== nothingin nlsolve.jl) declares the stage converged with essentially no correction applied. A rejected globalization step reads as "converged" to the outer loop.Two related consequences observed with the same mechanism:
eps_around_onebranch in nlsolve.jl) misfires for globalized inner solvers: a rejected TR step leavesfuunchanged → θ = 1 exactly → premature Divergence (after 2 iterations) or premature convergence depending onndz.FBDF + NSA(TrustRegion)at dt=2^-15 aborts at step 0 withConvergenceFailureafter 2 inner iterations via that θ≈1 path (test files only pass because earlier testsets warm the sharednlprob; see also the companion NonlinearSolve.jl issue aboutreinit!resetting the trust radius to its build-time value).Proposed fix direction
Outer convergence must not be declared from displacement-
ndzwhen the inner solver did not actually accept a step / make progress: consult the inner cache (descent success / step acceptance /retcode) before treating a smallndzas convergence — or, longer-term, driveNonlinearSolveAlginner solvers by their own residual-based termination instead of NLNewton's θ/η heuristics, which assume a plain (modified-)Newton contraction.Repro scripts from the investigation are available; error table:
(For comparison, FBDF+NSA(NewtonRaphson) and FBDF+NLNewton halve cleanly.)
🤖 Filed by an agent after local reproduction on master 72706ab, Julia 1.12.6, NonlinearSolve 4.20.1.