|
191 | 191 | @test_throws ErrorException solve(prob, alg, dt = 0.1; stage_limiter = slim!) |
192 | 192 | end |
193 | 193 | end |
| 194 | + |
| 195 | +# The stage limiter contract is `limiter!(u, integrator, p, t)` |
| 196 | +# (`OrdinaryDiffEqCore.trivial_limiter!`). The limiters above ignore their |
| 197 | +# arguments, so they accept whatever is handed to them; that is how `Tsit5` shipped |
| 198 | +# with the `ODEFunction` in the integrator slot, `Midpoint` with a stage buffer, and |
| 199 | +# `QPRK98` limiting `uprev`. These testsets pin down which objects get passed rather |
| 200 | +# than how many times the limiter is called. |
| 201 | +const LIMITER_ALGS = [ |
| 202 | + Euler, Heun, Ralston, Midpoint, RK4, BS3, OwrenZen3, DP5, Tsit5, |
| 203 | + Vern6, Vern9, DP8, TanYam7, TsitPap8, QPRK98, |
| 204 | + SSPRK22, SSPRK43, SSPRK104, SSPRK932, |
| 205 | + CarpenterKennedy2N54, ORK256, RDPK3Sp35, NDBLSRK124, |
| 206 | + Rosenbrock23, ROS3P, Rodas4, Rodas5P, |
| 207 | +] |
| 208 | + |
| 209 | +@testset "stage limiter receives the integrator" begin |
| 210 | + prob = ODEProblem((du, u, p, t) -> du .= u, [1.0, 1.0], (0.0, 1.0)) |
| 211 | + for A in LIMITER_ALGS |
| 212 | + ok = Ref(true) |
| 213 | + calls = Ref(0) |
| 214 | + limiter! = function (u, integrator, p, t) |
| 215 | + calls[] += 1 |
| 216 | + integrator isa SciMLBase.DEIntegrator || (ok[] = false) |
| 217 | + return nothing |
| 218 | + end |
| 219 | + solve(prob, A(), dt = 0.1; stage_limiter = limiter!) |
| 220 | + @test calls[] > 0 |
| 221 | + @test ok[] |
| 222 | + end |
| 223 | +end |
| 224 | + |
| 225 | +# The limiter mutates whatever it is given, so handing it `uprev` corrupts every |
| 226 | +# later stage and the step update. `QPRK98` stage 5 did exactly that. |
| 227 | +@testset "stage limiter is never handed uprev" begin |
| 228 | + prob = ODEProblem((du, u, p, t) -> du .= u, [1.0, 1.0], (0.0, 1.0)) |
| 229 | + for A in LIMITER_ALGS |
| 230 | + ok = Ref(true) |
| 231 | + limiter! = function (u, integrator, p, t) |
| 232 | + u === integrator.uprev && (ok[] = false) |
| 233 | + return nothing |
| 234 | + end |
| 235 | + solve(prob, A(), dt = 0.1; stage_limiter = limiter!) |
| 236 | + @test ok[] |
| 237 | + end |
| 238 | +end |
| 239 | + |
| 240 | +# End-to-end: with `QPRK98` stage 5 clamping `uprev` in place, `uprev` changed |
| 241 | +# value partway through a single step. |
| 242 | +@testset "uprev is stable across one step" begin |
| 243 | + prob = ODEProblem((du, u, p, t) -> du .= u, [-1.0], (0.0, 0.1)) |
| 244 | + seen = Float64[] |
| 245 | + positivity! = function (u, integrator, p, t) |
| 246 | + push!(seen, integrator.uprev[1]) |
| 247 | + @. u = max(u, 0.0) |
| 248 | + return nothing |
| 249 | + end |
| 250 | + solve(prob, QPRK98(), dt = 0.1, adaptive = false; stage_limiter = positivity!) |
| 251 | + @test !isempty(seen) |
| 252 | + @test all(==(seen[1]), seen) |
| 253 | +end |
0 commit comments