Skip to content

Commit 39be3dd

Browse files
committed
Fix stepping past domain error for NoiseGrid when dt doesn't evenly divide time span
When solving a NoiseProblem with a NoiseGrid where the dt doesn't evenly divide the time span, floating point errors could cause the solver to report "Stepped past the defined domain for the NoiseGrid". This occurred because: 1. After many steps with dt=0.01 over tspan [0.0, 10.0], floating point errors accumulate and W.curt ends up at ~9.9999999999 instead of exactly 10.0 2. The step_setup flag gets set to false when W.curt + dt > W.t[end] 3. The while loop continues because W.curt < prob.tspan[2] (floating point) 4. accept_step! errors because step_setup is false The fix adds two checks in the solve loop: 1. If we're essentially at the end (within floating point tolerance), exit 2. If the next step would overshoot, take a partial final step to reach exactly the end, resetting step_setup if necessary Fixes #136 Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent d0f998d commit 39be3dd

File tree

2 files changed

+46
-1
lines changed

2 files changed

+46
-1
lines changed

src/solve.jl

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,23 @@ function DiffEqBase.__solve(
4444
setup_next_step!(W, nothing, nothing)
4545
tType = typeof(W.curt)
4646
while W.curt < prob.tspan[2]
47-
if tType <: AbstractFloat && abs(W.curt + dt - prob.tspan[2]) < 100 * eps(dt) # Correct the end due to floating point error
47+
# Check if we're essentially at the end due to floating point error
48+
if tType <: AbstractFloat &&
49+
abs(W.curt - prob.tspan[2]) < 100 * eps(typeof(W.curt)(prob.tspan[2]))
50+
W.curt = prob.tspan[2]
51+
break
52+
end
53+
# Check if taking the full step would overshoot the end
54+
if W.curt + dt > prob.tspan[2]
55+
# Take a final partial step to reach exactly the end
56+
final_dt = prob.tspan[2] - W.curt
57+
# For NoiseGrid, we need to reset step_setup if it was set to false
58+
if hasproperty(W, :step_setup) && !W.step_setup
59+
W.step_setup = true
60+
end
61+
accept_step!(W, final_dt, nothing, nothing)
62+
W.curt = prob.tspan[2]
63+
elseif tType <: AbstractFloat && abs(W.curt + dt - prob.tspan[2]) < 100 * eps(dt) # Correct the end due to floating point error
4864
dt = prob.tspan[2] - W.curt
4965
accept_step!(W, dt, nothing, nothing)
5066
W.curt = prob.tspan[2]

test/noise_grid.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,33 @@
4747
W = NoiseGrid(t, brownian_values)
4848
prob_rational = NoiseProblem(W, (0, 1))
4949
sol = solve(prob_rational; dt = 1 // 10)
50+
51+
# Test for issue #136: stepping past domain with dt that doesn't evenly divide the time span
52+
# When dt doesn't evenly divide (tspan[2] - tspan[1]), floating point errors can cause
53+
# the solver to think it needs to step past the domain boundary
54+
@testset "Issue #136 - stepping past domain" begin
55+
# Original issue case: dt=0.01 with grid step 0.1
56+
tgrid = 0.0:0.1:10.0
57+
brownian_noise = [[0.0, 0.0] for _ in 1:length(tgrid)]
58+
W = NoiseGrid(collect(tgrid), brownian_noise)
59+
prob = NoiseProblem(W, (tgrid[begin], tgrid[end]))
60+
sol = solve(prob; dt = 0.01)
61+
@test sol.curt == 10.0
62+
63+
# Case where dt does not evenly divide the time span
64+
tgrid2 = 0.0:0.1:1.0
65+
brownian_noise2 = [[0.0, 0.0] for _ in 1:length(tgrid2)]
66+
W2 = NoiseGrid(collect(tgrid2), brownian_noise2)
67+
prob2 = NoiseProblem(W2, (0.0, 1.0))
68+
sol2 = solve(prob2; dt = 0.03)
69+
@test sol2.curt == 1.0
70+
71+
# Case with scalar noise
72+
tgrid3 = 0.0:0.1:10.0
73+
brownian_noise3 = [0.0 for _ in 1:length(tgrid3)]
74+
W3 = NoiseGrid(collect(tgrid3), brownian_noise3)
75+
prob3 = NoiseProblem(W3, (tgrid3[begin], tgrid3[end]))
76+
sol3 = solve(prob3; dt = 0.01)
77+
@test sol3.curt == 10.0
78+
end
5079
end

0 commit comments

Comments
 (0)