Skip to content

Commit c1e9972

Browse files
Replace Hairer-Wanner initdt with CVODE CVHin for deterministic solvers
Replace the Hairer-Wanner initial step size algorithm with the CVODE CVHin algorithm (Hindmarsh et al., 2005) for all deterministic ODE solvers. This fixes issue #1496 where zero initial conditions with tiny abstol produced catastrophically small initial timesteps (~5e-25). CVHin uses component-wise iterative estimation: - Upper bound from most restrictive component: min_i(tol_i / |f₀_i|) - Geometric mean of lower/upper bounds as initial guess - Up to 4 refinement iterations with finite-difference second derivatives - Order-dependent step proposal: h ~ (2/yddnrm)^(1/(p+1)) - 0.5 safety factor with bounds clamping For stochastic problems (g !== nothing), the Hairer-Wanner algorithm with diffusion terms is preserved unchanged. Fixes #1496 Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
1 parent 9539ed3 commit c1e9972

3 files changed

Lines changed: 525 additions & 130 deletions

File tree

lib/OrdinaryDiffEqBDF/test/runtests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ if TEST_GROUP != "QA"
1313
@time @safetestset "BDF Inference Tests" include("inference_tests.jl")
1414
@time @safetestset "BDF Convergence Tests" include("bdf_convergence_tests.jl")
1515
@time @safetestset "BDF Regression Tests" include("bdf_regression_tests.jl")
16+
@time @safetestset "CVHin InitDt Tests" include("stiff_initdt_tests.jl")
1617
end
1718

1819
# Run QA tests (AllocCheck, JET, Aqua) - skip on pre-release Julia
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using Test
2+
using OrdinaryDiffEqBDF
3+
using OrdinaryDiffEqSDIRK
4+
5+
@testset "CVHin Initial Step Size Algorithm" begin
6+
7+
@testset "Simple exponential decay (in-place)" begin
8+
function f_exp!(du, u, p, t)
9+
du[1] = -u[1]
10+
end
11+
prob = ODEProblem(f_exp!, [1.0], (0.0, 10.0))
12+
sol = solve(prob, FBDF())
13+
@test sol.retcode == ReturnCode.Success
14+
@test isapprox(sol.u[end][1], exp(-10.0), rtol = 1.0e-2)
15+
end
16+
17+
@testset "Simple exponential decay (out-of-place)" begin
18+
f_exp(u, p, t) = [-u[1]]
19+
prob = ODEProblem(f_exp, [1.0], (0.0, 10.0))
20+
sol = solve(prob, FBDF())
21+
@test sol.retcode == ReturnCode.Success
22+
@test isapprox(sol.u[end][1], exp(-10.0), rtol = 1.0e-2)
23+
end
24+
25+
@testset "Multi-scale with zero states and tiny abstol (in-place)" begin
26+
# This is the pathological case from issue #1496:
27+
# Some species start at 0 with tiny abstol, causing the Hairer algorithm
28+
# to produce catastrophically small initial dt.
29+
function f_ms!(du, u, p, t)
30+
du[1] = -100.0 * u[1] + 1.0
31+
du[2] = 0.1 * u[1]
32+
du[3] = 1.84 # mimics TH2S
33+
end
34+
u0 = [1.0, 0.0, 0.0]
35+
prob = ODEProblem(f_ms!, u0, (0.0, 100.0))
36+
37+
# With FBDF - should succeed efficiently
38+
sol_fbdf = solve(prob, FBDF(), abstol = 1.0e-15, reltol = 1.0e-8)
39+
@test sol_fbdf.retcode == ReturnCode.Success
40+
@test sol_fbdf.t[end] == 100.0
41+
42+
# The CVHin algorithm should produce an initial dt much larger than the
43+
# catastrophically small ~5e-25 that the old Hairer algorithm would give.
44+
# With abstol=1e-15 and u0=0, it computes ~3.5e-15
45+
# (10 orders of magnitude larger).
46+
dt_fbdf = sol_fbdf.t[2] - sol_fbdf.t[1]
47+
@test dt_fbdf > 1.0e-20 # Much larger than the ~5e-25 Hairer would give
48+
end
49+
50+
@testset "Multi-scale with zero states and tiny abstol (out-of-place)" begin
51+
function f_ms(u, p, t)
52+
return [-100.0 * u[1] + 1.0, 0.1 * u[1], 1.84]
53+
end
54+
u0 = [1.0, 0.0, 0.0]
55+
prob = ODEProblem(f_ms, u0, (0.0, 100.0))
56+
57+
sol = solve(prob, FBDF(), abstol = 1.0e-15, reltol = 1.0e-8)
58+
@test sol.retcode == ReturnCode.Success
59+
@test sol.t[end] == 100.0
60+
end
61+
62+
@testset "Stiff Robertson problem" begin
63+
# Classic stiff test problem
64+
function robertson!(du, u, p, t)
65+
du[1] = -0.04 * u[1] + 1.0e4 * u[2] * u[3]
66+
du[2] = 0.04 * u[1] - 1.0e4 * u[2] * u[3] - 3.0e7 * u[2]^2
67+
du[3] = 3.0e7 * u[2]^2
68+
end
69+
u0 = [1.0, 0.0, 0.0]
70+
prob = ODEProblem(robertson!, u0, (0.0, 1.0e5))
71+
72+
sol = solve(prob, FBDF(), abstol = 1.0e-8, reltol = 1.0e-8)
73+
@test sol.retcode == ReturnCode.Success
74+
@test sol.t[end] == 1.0e5
75+
76+
# Conservation law: u1 + u2 + u3 = 1
77+
@test isapprox(sum(sol.u[end]), 1.0, atol = 1.0e-6)
78+
end
79+
80+
@testset "Backward integration" begin
81+
function f_back!(du, u, p, t)
82+
du[1] = -u[1]
83+
end
84+
prob = ODEProblem(f_back!, [exp(-10.0)], (10.0, 0.0))
85+
sol = solve(prob, FBDF())
86+
@test sol.retcode == ReturnCode.Success
87+
@test isapprox(sol.u[end][1], 1.0, rtol = 2.0e-2)
88+
end
89+
90+
@testset "User-specified dt still works" begin
91+
function f_dt!(du, u, p, t)
92+
du[1] = -u[1]
93+
end
94+
prob = ODEProblem(f_dt!, [1.0], (0.0, 1.0))
95+
# When user specifies dt, initdt should not be called
96+
sol = solve(prob, FBDF(), dt = 0.01)
97+
@test sol.retcode == ReturnCode.Success
98+
end
99+
100+
@testset "QNDF with multi-scale problem" begin
101+
function f_qndf!(du, u, p, t)
102+
du[1] = -100.0 * u[1] + 1.0
103+
du[2] = 0.0 # starts at 0, stays at 0
104+
end
105+
u0 = [1.0, 0.0]
106+
prob = ODEProblem(f_qndf!, u0, (0.0, 1.0))
107+
108+
sol = solve(prob, QNDF(), abstol = 1.0e-12, reltol = 1.0e-8)
109+
@test sol.retcode == ReturnCode.Success
110+
@test sol.t[end] == 1.0
111+
end
112+
113+
@testset "ImplicitEuler initial step size" begin
114+
function f_ie!(du, u, p, t)
115+
du[1] = -100.0 * u[1] + 1.0
116+
du[2] = 0.0
117+
end
118+
u0 = [1.0, 0.0]
119+
prob = ODEProblem(f_ie!, u0, (0.0, 1.0))
120+
121+
sol = solve(prob, ImplicitEuler(), abstol = 1.0e-12, reltol = 1.0e-8)
122+
@test sol.retcode == ReturnCode.Success
123+
@test sol.t[end] == 1.0
124+
end
125+
end

0 commit comments

Comments
 (0)