Add warm_start option to KrylovJL GMRES/FGMRES#1100
Conversation
CI triage (automated)Failing checks on this PR, all verified as pre-existing on unmodified
Everything else is green so far, including Background investigations (bisect + root-cause) of the pre-existing main failures are running separately and will be reported when complete. 🤖 Generated with Claude Code |
Follow-up: all 5 pre-existing failures root-caused; fixes existBackground investigation of the failures this PR inherits from
Merging #1093, #1094, #1095 (disjoint files) should turn main's Tests workflow green, after which this PR's checks should be fully green on a rebase/rerun. 🤖 Generated with Claude Code |
|
Downstream fix is now up: SciML/NonlinearSolve.jl#1091 (draft) restores the pre-NonlinearSolve#1039 exact-inverse fast paths in Complete unblock path for this PR's CI: merge LinearSolve #1093, #1094, #1095 + NonlinearSolve #1091, then rerun checks here. 🤖 Generated with Claude Code |
39652b4 to
26f2714
Compare
Post-rebase CI: 58/59 greenRebased onto main with #1093/#1094/#1095 — AD, QA, and Trim now pass. The single remaining failure is NonlinearSolve.jl/Core downstream, and it is a newly unmasked pre-existing issue, again unrelated to this diff: NonlinearSolve#1055 (2026-07-15) changed A sweep of the full NonlinearSolve master Core suite against the registered stack is running to enumerate any further latent test-vs-release mismatches, followed by a version-bump PR to NonlinearSolve.jl covering every lib whose unreleased changes master's tests depend on. Once those libs are released, this job should go green with no changes here. 🤖 Generated with Claude Code |
|
Final piece: full sweep of NonlinearSolve master's Core suite (73 files) against the registered stack found exactly one latent mismatch — the known Unblock: merge SciML/NonlinearSolve.jl#1093 (two-line version bumps: NLSBase 2.35.0→2.35.1, FirstOrder 2.2.1→2.2.2) and register; the downstream job here then resolves NLSBase 2.35.1 automatically and this PR should be 59/59. 🤖 Generated with Claude Code |
Expanded benchmark study (4 PDEs × 4 solvers × 2 tols × 3 modes, 150 cells)Full data, scripts, and analysis: https://github.com/ChrisRackauckas/InternalJunk/pull/71 ( Across 37 Newton-Krylov cells: Rosenbrock hazard (new finding): Pushed a docs commit tempering the guidance accordingly (iteration reduction reliable; time win specifically for ≳5 iters/solve Newton-Krylov; warning box against Rosenbrock use). Defaults unchanged ( Follow-ups worth considering: Krylov.jl API for precomputed 🤖 Generated with Claude Code |
API change:
|
Adds warm_start = :none | :previous | :hegedus to the Krylov.jl wrapper algorithms. When the same LinearCache is solved repeatedly, the previous solution cache.u seeds the next GMRES/FGMRES solve via Krylov.warm_start!, either raw (:previous) or rescaled by the Hegedus trick (:hegedus), xi = <Au, b>/||Au||^2, which guarantees the initial residual is no larger than a cold start's. Since Krylov.jl measures rtol against the warm initial residual instead of ||M b||, rtol * ||M b|| is folded into atol for warm-started solves so the stopping threshold matches cold-start semantics. On the ILU-preconditioned 2D Brusselator with KenCarp47/FBDF Newton-Krylov, :hegedus reduces total GMRES iterations by 19-44%; downstream solvers need no changes since their caches already carry the previous solution. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Documents the measured behavior: :hegedus reliably reduces GMRES iterations (median -17% across 37 Newton-Krylov cells on Brusselator/Allen-Cahn/Burgers/ advection-diffusion) but only improves wall time when solves perform >~5 iterations each; adds a warning against use in Rosenbrock-type integrators, where warm starting can degrade accuracy and trigger step-rejection feedback loops since there is no outer Newton iteration to absorb within-tolerance solve differences. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces the Symbol-valued warm_start option (:none/:previous/:hegedus) with an exported, documented EnumX enum WarmStart (None/Previous/Hegedus), matching the OperatorCondition/NonstructuralZeros pattern already used in the package. This gives compile-time-checked values and rendered docs for each variant. WarmStart.None remains the default; WarmStart.Hegedus is the recommended mode and WarmStart.Previous is documented as rarely appropriate for Newton loops. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ntext WarmStart.Auto becomes the default warm_start value. In standalone LinearSolve it behaves exactly as WarmStart.None (cold start), so merging changes no behavior on its own. A context-aware caller that knows the surrounding algorithm can resolve Auto to a concrete mode -- OrdinaryDiffEq will resolve it to WarmStart.Hegedus for Newton-based integrators and leave it a cold start for Rosenbrock/W-methods, without clobbering a user's explicit None/Previous/Hegedus. Mirrors the existing NonstructuralZeros.Auto sentinel pattern. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
a0bb247 to
7eb8eb1
Compare
|
Rebased onto main (5.0.1 → 5.1.0) and marked ready for review. Added Final API: 🤖 Generated with Claude Code |
ExplicitImports (via SciMLTesting run_qa) cannot analyze EnumX-generated modules, so the new WarmStart enum joins OperatorCondition/NonstructuralZeros/ DefaultAlgorithmChoice in allow_unanalyzable. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
CI follow-up: two failing checks after the EnumX/docs commits.
With the QA fix pushed, this PR should be green except the pre-existing umbrella 🤖 Generated with Claude Code |
Important
This PR should be ignored until reviewed by @ChrisRackauckas.
Closes #70.
What
Adds a
warm_startoption to the Krylov.jl wrapper algorithms, currently applied to GMRES and FGMRES (other methods accept the option but ignore it):When the same
LinearCacheis solved repeatedly (cache.b = newb; solve!(cache)), the previous solutioncache.uis used as the initial guess viaKrylov.warm_start!::previous— raw previous solution.:hegedus— previous solution rescaled by the Hegedüs trickx₀ = ξu,ξ = ⟨Au, b⟩/‖Au‖², which minimizes the initial residual along the previous solution's direction and guarantees‖b - Ax₀‖ ≤ ‖b‖(never worse than a cold start, up to the cost of one extra operator application).Tolerance semantics are preserved: Krylov.jl measures
rtolagainst the warm-start residual‖M(b - Ax₀)‖instead of‖Mb‖, which would silently change whatreltolmeans. The implementation foldsrtol·‖Mb‖intoatol(and zeroesrtol) for warm-started solves so the stopping threshold is identical to the cold start's.Guards: the warm start is skipped (silent cold start) for zero or nonfinite previous solutions, non-vector
u(batched RHS), and non-GMRES/FGMRES workspaces.Why / benchmarks
Newton-Krylov in OrdinaryDiffEq/NonlinearSolve currently cold-starts every GMRES solve from zero even though
cache.uholds the previous increment. Benchmarks on the 2D Brusselator (full investigation with scripts and data: https://github.com/ChrisRackauckas/InternalJunk/pull/71)::hegeduswith ILU-preconditioned Newton-Krylov: 19–44% fewer GMRES iterations across KenCarp47/FBDF, reltol 1e-3/1e-6, N=32/64. End-to-end with this branch (N=32, reltol=1e-6, ILU): KenCarp47 wall time 1.06s → 0.75s, nf 1130 → 914; FBDF nf 919 → 909.:previousis consistently worse than cold start for Newton increments (+8–30% iterations) — successive increments shrink in magnitude, so the raw previous solution overshoots. It is included because it is the right choice when successive solutions are close (e.g. sequences of parameterized solves, projection steps), but:hegedusis the recommended mode for Newton-type loops and:nonestays the default.:hegedusis neutral (±1%).No downstream changes are needed: OrdinaryDiffEq's
dolinsolveand NonlinearSolve's linear-solve caches already carry the previous solution incache.u, soFBDF(linsolve = KrylovJL_GMRES(warm_start = :hegedus))works as-is (verified end-to-end against this branch with locally-widened compat).Notes
:hegedusallocates one vector per solve forAu(plusξ.*u), and preconditioned warm starts allocate one vector for‖Mb‖. A follow-up could add scratch buffers; kept simple here for correctness. A Krylov.jl API accepting a precomputedA*x₀would remove the duplicated matvec insideKrylov.warm_start!-based solves and halve the per-solve overhead, which matters when a strong preconditioner leaves only ~2-3 iterations per solve.test/Core/warm_start.jl— validation, repeated-solve correctness for all modes on GMRES/FGMRES (including operator changes), warm-start engagement (identical re-solve takes 0 iterations), the Hegedüs scaling-invariance property, preconditioned solves, complex systems, batched-RHS fallback, and zero-ufallback. Local run: 31/31 pass;GROUP=Corerun locally before pushing.Process log
:none/:previous/:hegeduson Brusselator via a patched LinearSolve v4.3.0 with a global toggle (see the InternalJunk PR for scripts + full tables), then implemented the winning design onmainas an opt-in per-algorithm option.🤖 Generated with Claude Code