1+ using OrdinaryDiffEqVerner, StaticArrays, Test
2+ using OrdinaryDiffEqCore: handle_tstop_step!
3+
4+ # Test cases for the tstop robustness fix with next_step_tstop flag
5+ @testset " Tstop Robustness Tests" begin
6+
7+ @testset " Basic tstop flag functionality" begin
8+ # Simple ODE problem to test flag behavior
9+ function simple_ode! (du, u, p, t)
10+ du[1 ] = - u[1 ]
11+ du[2 ] = u[1 ] - u[2 ]
12+ end
13+
14+ function simple_ode (u, p, t)
15+ [- u[1 ], u[1 ] - u[2 ]]
16+ end
17+
18+ u0_array = [1.0 , 0.0 ]
19+ u0_static = SVector {2} (1.0 , 0.0 )
20+ tspan = (0.0 , 1.0 )
21+
22+ # Test with regular arrays
23+ prob_array = ODEProblem (simple_ode!, u0_array, tspan)
24+ sol_array = solve (prob_array, Vern9 (); reltol= 1e-12 , abstol= 1e-12 ,
25+ tstops= [0.5 ], save_everystep= false )
26+ @test sol_array. retcode == :Success
27+ @test 0.5 in sol_array. t # Should have saved at tstop
28+
29+ # Test with StaticArrays - should work now without tstop error
30+ prob_static = ODEProblem (simple_ode, u0_static, tspan)
31+ sol_static = solve (prob_static, Vern9 (); reltol= 1e-12 , abstol= 1e-12 ,
32+ tstops= [0.5 ], save_everystep= false )
33+ @test sol_static. retcode == :Success
34+ @test 0.5 in sol_static. t # Should have saved at tstop
35+
36+ # Solutions should be very close despite different array types
37+ @test isapprox (sol_array (1.0 ), sol_static (1.0 ), rtol= 1e-10 )
38+ end
39+
40+ @testset " Tiny tstop step handling" begin
41+ # Test case where tstop is very close to current time
42+ function test_ode (u, p, t)
43+ [u[1 ]] # Simple growth
44+ end
45+
46+ u0 = SVector {1} (1.0 )
47+ tspan = (0.0 , 1.0 )
48+
49+ # Create tstop very close to start time (would cause tiny dt)
50+ tiny_tstop = 1e-15
51+
52+ prob = ODEProblem (test_ode, u0, tspan)
53+ sol = solve (prob, Vern9 (); tstops= [tiny_tstop], save_everystep= false )
54+
55+ @test sol. retcode == :Success
56+ @test tiny_tstop in sol. t # Should handle tiny tstop correctly
57+ end
58+
59+ @testset " Multiple close tstops" begin
60+ # Test with multiple tstops that are very close together
61+ function growth_ode (u, p, t)
62+ [0.1 * u[1 ]]
63+ end
64+
65+ u0 = SVector {1} (1.0 )
66+ tspan = (0.0 , 2.0 )
67+
68+ # Multiple tstops close together
69+ close_tstops = [0.5 , 0.5 + 1e-14 , 0.5 + 2e-14 , 1.0 ]
70+
71+ prob = ODEProblem (growth_ode, u0, tspan)
72+ sol = solve (prob, Vern9 (); tstops= close_tstops, reltol= 1e-12 , abstol= 1e-12 )
73+
74+ @test sol. retcode == :Success
75+ # All tstops should be handled correctly
76+ for tstop in close_tstops
77+ @test any (abs .(sol. t .- tstop) .< 1e-12 ) # Should have hit each tstop
78+ end
79+ end
80+
81+ @testset " Extreme precision with StaticArrays" begin
82+ # Test the specific case that was failing: extreme precision + StaticArrays
83+ function precise_dynamics (u, p, t)
84+ # Simplified electromagnetic-like dynamics
85+ x = @view u[1 : 2 ]
86+ v = @view u[3 : 4 ]
87+
88+ # Simple force model
89+ dv = - 0.01 * x + 1e-6 * sin (1000 * t) * [1 , 1 ]
90+
91+ return SVector {4} (v[1 ], v[2 ], dv[1 ], dv[2 ])
92+ end
93+
94+ # Initial conditions similar to the original issue
95+ u0 = SVector {4} (1.0 , - 0.5 , 0.01 , 0.01 )
96+ tspan = (- 1.0 , 1.0 )
97+
98+ # Test with extreme tolerances that originally caused issues
99+ prob = ODEProblem (precise_dynamics, u0, tspan)
100+ sol = solve (prob, Vern9 (); reltol= 1e-12 , abstol= 1e-15 ,
101+ tstops= [0.0 ], save_everystep= false , maxiters= 10 ^ 6 )
102+
103+ @test sol. retcode == :Success
104+ @test 0.0 in sol. t
105+ end
106+
107+ @testset " Flag state management" begin
108+ # Test that flags are properly set and reset
109+ function flag_test_ode (u, p, t)
110+ [u[1 ]]
111+ end
112+
113+ u0 = SVector {1} (1.0 )
114+ prob = ODEProblem (flag_test_ode, u0, (0.0 , 2.0 ))
115+
116+ # Create integrator manually to inspect flag states
117+ integrator = init (prob, Vern9 (); tstops= [1.0 ])
118+
119+ # Initially, flag should be false
120+ @test integrator. next_step_tstop == false
121+
122+ # Step until we approach the tstop
123+ while integrator. t < 0.9
124+ step! (integrator)
125+ # Flag should still be false when not near tstop
126+ @test integrator. next_step_tstop == false
127+ end
128+
129+ # Take steps near tstop - flag should get set
130+ while integrator. t < 1.0
131+ step! (integrator)
132+ # When dt is reduced for tstop, flag should be set
133+ if integrator. next_step_tstop
134+ @test integrator. tstop_target ≈ 1.0
135+ break
136+ end
137+ end
138+
139+ # After hitting tstop, flag should be reset
140+ step! (integrator)
141+ @test integrator. next_step_tstop == false
142+
143+ finalize! (integrator)
144+ end
145+
146+ @testset " Backward time integration" begin
147+ # Test that the fix works for backward time integration too
148+ function backward_ode (u, p, t)
149+ [- u[1 ]] # Decay
150+ end
151+
152+ u0 = SVector {1} (1.0 )
153+ tspan = (1.0 , 0.0 ) # Backward integration
154+
155+ prob = ODEProblem (backward_ode, u0, tspan)
156+ sol = solve (prob, Vern9 (); tstops= [0.5 ], reltol= 1e-12 , abstol= 1e-12 )
157+
158+ @test sol. retcode == :Success
159+ @test 0.5 in sol. t
160+ end
161+
162+ end
0 commit comments