Skip to content

Commit 33d9c81

Browse files
Default Newton-Krylov integrators to Hegedus warm starting
Resolves a Krylov linear solver left at the new LinearSolve.WarmStart.Auto default to WarmStart.Hegedus on the Newton nonlinear-solver path, so implicit integrators using KrylovJL_GMRES()/KrylovJL_FGMRES() reuse the previous solution (Hegedus-scaled) across the correlated linear solves of a Newton iteration. Rosenbrock/W-method integrators never call the resolver, so their Auto solver stays a cold start -- warm starting is unsafe there (no outer Newton iteration absorbs the within-tolerance stage-solve perturbation, which can trigger step-rejection feedback loops). Explicit WarmStart.None/Previous/Hegedus are always respected. The resolver is guarded on isdefined(LinearSolve, :WarmStart), so it is a no-op on LinearSolve < 5.1 and this compiles unchanged there. Bumps the LinearSolve compat of the two touched sublibraries to allow 5; umbrella-wide activation additionally requires the OrdinaryDiffEq LinearSolve 5 compat migration across the remaining sublibraries. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d2577da commit 33d9c81

7 files changed

Lines changed: 65 additions & 7 deletions

File tree

lib/OrdinaryDiffEqDifferentiation/Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name = "OrdinaryDiffEqDifferentiation"
2-
version = "3.4.1"
2+
version = "3.5.0"
33
uuid = "4302a76b-040a-498a-8c04-15b101fed76b"
44
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>", "Yingbo Ma <mayingbo5@gmail.com>"]
55

@@ -21,7 +21,7 @@ DiffEqDevTools = "3"
2121
Test = "<0.0.1, 1"
2222
FiniteDiff = "2.27"
2323
DifferentiationInterface = "0.7.18"
24-
LinearSolve = "3.75.0, 4"
24+
LinearSolve = "3.75.0, 4, 5"
2525
ConstructionBase = "1.5.8"
2626
LinearAlgebra = "1.10"
2727
SciMLBase = "3.35.1"

lib/OrdinaryDiffEqDifferentiation/src/linsolve_utils.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ function dolinsolve(
5252
return linres
5353
end
5454

55+
"""
56+
default_krylov_warm_start(linsolver) -> linsolver
57+
58+
Resolve a Krylov linear solver left at `LinearSolve.WarmStart.Auto` to the mode
59+
appropriate for a Newton-based integrator, `LinearSolve.WarmStart.Hegedus`
60+
(Hegedüs-scaled reuse of the previous solution — the recommended mode for the
61+
sequence of correlated preconditioned solves inside a Newton iteration).
62+
63+
Only the `Auto` default is resolved: an explicit `WarmStart.None`/`Previous`/
64+
`Hegedus`, a non-Krylov solver, or a LinearSolve too old to define `WarmStart`
65+
is returned unchanged. This is called on the Newton nonlinear-solver path only;
66+
Rosenbrock/W-method integrators never call it, so their `Auto` solver stays a
67+
cold start (warm starting is unsafe there — no outer Newton iteration absorbs
68+
the within-tolerance stage-solve perturbation).
69+
"""
70+
function default_krylov_warm_start(linsolver)
71+
isdefined(LinearSolve, :WarmStart) || return linsolver
72+
(
73+
linsolver isa LinearSolve.KrylovJL &&
74+
linsolver.warm_start === LinearSolve.WarmStart.Auto
75+
) || return linsolver
76+
return SciMLBase.remake(linsolver; warm_start = LinearSolve.WarmStart.Hegedus)
77+
end
78+
5579
"""
5680
wrapprecs(linsolver, W, weight) -> linsolver
5781

lib/OrdinaryDiffEqDifferentiation/test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ if TEST_GROUP ∉ ("QA", "Sparse", "ModelingToolkit")
3636
@time @safetestset "Autodiff Error Tests" include("autodiff_error_tests.jl")
3737
@time @safetestset "No Jac Tests" include("nojac_tests.jl")
3838
@time @safetestset "Stale W Linear Operator Tests" include("stale_w_linear_operator_tests.jl")
39+
@time @safetestset "Krylov warm_start default" include("warm_start_default_tests.jl")
3940
end
4041

4142
# Run sparse tests (separate environment due to ComponentArrays dep conflicts)
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
using OrdinaryDiffEqDifferentiation: default_krylov_warm_start
2+
using LinearSolve
3+
using Test
4+
5+
@testset "default_krylov_warm_start resolves Auto for the Newton path" begin
6+
# Non-Krylov solvers are always returned unchanged.
7+
@test default_krylov_warm_start(LUFactorization()) isa LUFactorization
8+
9+
if isdefined(LinearSolve, :WarmStart)
10+
# Auto (the KrylovJL default) resolves to Hegedus on the Newton path.
11+
@test default_krylov_warm_start(KrylovJL_GMRES()).warm_start ===
12+
LinearSolve.WarmStart.Hegedus
13+
# Explicit choices are respected, never overridden.
14+
for ws in (
15+
LinearSolve.WarmStart.None, LinearSolve.WarmStart.Previous,
16+
LinearSolve.WarmStart.Hegedus,
17+
)
18+
@test default_krylov_warm_start(KrylovJL_GMRES(warm_start = ws)).warm_start === ws
19+
end
20+
# Other fields survive the remake.
21+
precs = (A, p) -> (I, I)
22+
resolved = default_krylov_warm_start(KrylovJL_GMRES(; precs))
23+
@test resolved.warm_start === LinearSolve.WarmStart.Hegedus
24+
@test resolved.precs === precs
25+
else
26+
# LinearSolve too old to define WarmStart: strictly a no-op.
27+
k = KrylovJL_GMRES()
28+
@test default_krylov_warm_start(k) === k
29+
end
30+
end

lib/OrdinaryDiffEqNonlinearSolve/Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OrdinaryDiffEqNonlinearSolve"
22
uuid = "127b3ac7-2247-4354-8eb6-78cf4e7c58e8"
33
authors = ["Chris Rackauckas <accounts@chrisrackauckas.com>", "Yingbo Ma <mayingbo5@gmail.com>"]
4-
version = "2.4.0"
4+
version = "2.5.0"
55

66
[deps]
77
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
@@ -58,7 +58,7 @@ FastBroadcast = "1.3"
5858
Random = "<0.0.1, 1"
5959
DiffEqDevTools = "3"
6060
MuladdMacro = "0.2.4"
61-
LinearSolve = "3.75.0, 4"
61+
LinearSolve = "3.75.0, 4, 5"
6262
LineSearches = "7.5.1"
6363
LinearAlgebra = "1.10"
6464
OrdinaryDiffEqDifferentiation = "3"

lib/OrdinaryDiffEqNonlinearSolve/src/OrdinaryDiffEqNonlinearSolve.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ import OrdinaryDiffEqCore: _initialize_dae!,
5555
@SciMLMessage
5656

5757
import OrdinaryDiffEqDifferentiation: update_W!, is_always_new, build_uf, build_J_W,
58-
WOperator, StaticWOperator, wrapprecs,
58+
WOperator, StaticWOperator, wrapprecs, default_krylov_warm_start,
5959
build_jac_config, dolinsolve,
6060
resize_jac_config!, jacobian2W!, jacobian!
6161

lib/OrdinaryDiffEqNonlinearSolve/src/utils.jl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,10 @@ function build_nlsolver(
432432
)
433433
end
434434
end
435-
inner_alg = _nlalg_with_linsolve(nlalg.alg, wrapprecs(alg.linsolve, W, weight))
435+
inner_alg = _nlalg_with_linsolve(
436+
nlalg.alg,
437+
wrapprecs(default_krylov_warm_start(alg.linsolve), W, weight)
438+
)
436439
cache = init(prob, inner_alg, verbose = verbose.nonlinear_verbosity)
437440
nlcache = NonlinearSolveCache(
438441
ustep, tstep, k, atmp, invγdt, prob, cache,
@@ -448,7 +451,7 @@ function build_nlsolver(
448451
du = isdae ? k : nothing # k will be overwritten at solve time, but has the right type.
449452
linprob = LinearProblem(W, _vec(k), (du, u, p, t); u0 = _vec(dz))
450453
linsolve = init(
451-
linprob, wrapprecs(alg.linsolve, W, weight),
454+
linprob, wrapprecs(default_krylov_warm_start(alg.linsolve), W, weight),
452455
alias = LinearAliasSpecifier(alias_A = true, alias_b = true),
453456
assumptions = LinearSolve.OperatorAssumptions(true),
454457
verbose = verbose.linear_verbosity

0 commit comments

Comments
 (0)