Skip to content

Commit fd3f95b

Browse files
authored
Merge pull request #3720 from Shreyas-Ekanathan/disco-optimizations
Optimizations to Discontinuity Detection Scheme
2 parents b7e332c + da80135 commit fd3f95b

11 files changed

Lines changed: 241 additions & 182 deletions

File tree

lib/OrdinaryDiffEqBDF/src/controllers.jl

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,14 @@ end
8383
function step_accept_controller!(integrator, cache::Union{QNDFCache, QNDFConstantCache}, alg::QNDF{max_order}, q) where {max_order}
8484
#step is accepted, reset count of consecutive failed steps
8585
cache.consfailcnt = 0
86+
is_disco = integrator.is_disco_step
87+
if is_disco
88+
integrator.is_disco_step = false
89+
cache.nconsteps = 0
90+
end
8691
cache.nconsteps += 1
87-
if iszero(OrdinaryDiffEqCore.get_EEst(integrator))
88-
return integrator.dt * get_current_qmax(integrator, get_qmax(integrator))
92+
new_dt = if iszero(OrdinaryDiffEqCore.get_EEst(integrator))
93+
integrator.dt * get_current_qmax(integrator, get_qmax(integrator))
8994
else
9095
est = OrdinaryDiffEqCore.get_EEst(integrator)
9196
estₖ₋₁ = cache.EEst1
@@ -147,16 +152,36 @@ function step_accept_controller!(integrator, cache::Union{QNDFCache, QNDFConstan
147152
end
148153
cache.order = kₙ
149154
q = integrator.dt / hₙ
150-
end
151-
if prefer_const_step
152-
if q < 1.2 && q > 0.6
153-
return integrator.dt
155+
156+
if prefer_const_step && 0.6 < q < 1.2
157+
h
158+
elseif q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
159+
h
160+
else
161+
h / q
154162
end
155163
end
156-
if q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
157-
return integrator.dt
164+
if is_disco
165+
return min((integrator.disco_checkpoint - integrator.t) / 4, new_dt)
158166
end
159-
return integrator.dt / q
167+
return new_dt
168+
end
169+
170+
"""
171+
bdf_restart_estimates!(cache)
172+
173+
Restart the order and constant-step estimates for a step that straddles a derivative
174+
discontinuity, where the history behind it and the solution ahead belong to different
175+
regimes. We do this because the BDF step-size and order logic is based on the history of the solution,
176+
and we have effectively entered a new regime where old estimates no longer apply.
177+
"""
178+
function bdf_restart_estimates!(cache)
179+
cache.order = 1
180+
cache.nconsteps = 0
181+
if hasfield(typeof(cache), :qwait)
182+
cache.qwait = 3
183+
end
184+
return nothing
160185
end
161186

162187
function bdf_step_reject_controller!(integrator, cache, EEst1)
@@ -174,8 +199,8 @@ function bdf_step_reject_controller!(integrator, cache, EEst1)
174199
end
175200

176201
if discontinuity_detection
177-
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
178-
if disco_dt != -1
202+
disco_dt = set_discontinuity(integrator)
203+
if disco_dt > zero(disco_dt)
179204
integrator.dt = disco_dt
180205
return integrator.dt
181206
end
@@ -416,6 +441,11 @@ function step_accept_controller!(
416441
q
417442
) where {max_order}
418443
cache.consfailcnt = 0
444+
is_disco = integrator.is_disco_step
445+
if is_disco
446+
integrator.is_disco_step = false
447+
cache.nconsteps = 0
448+
end
419449
if q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
420450
q = one(q)
421451
end
@@ -427,7 +457,12 @@ function step_accept_controller!(
427457
elseif cache.qwait > 0
428458
cache.qwait -= 1 # countdown
429459
end
430-
return integrator.dt / q
460+
new_dt = integrator.dt / q
461+
if is_disco
462+
bdf_restart_estimates!(cache)
463+
return min((integrator.disco_checkpoint - integrator.t) / 4, new_dt)
464+
end
465+
return new_dt
431466
end
432467

433468
function step_reject_controller!(integrator, alg::DFBDF)
@@ -579,6 +614,11 @@ function step_accept_controller!(
579614
q
580615
) where {max_order}
581616
cache.consfailcnt = 0
617+
is_disco = integrator.is_disco_step
618+
if is_disco
619+
integrator.is_disco_step = false
620+
cache.nconsteps = 0
621+
end
582622
if q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
583623
q = one(q)
584624
end
@@ -590,5 +630,10 @@ function step_accept_controller!(
590630
elseif cache.qwait > 0
591631
cache.qwait -= 1 # countdown
592632
end
593-
return integrator.dt / q
633+
new_dt = integrator.dt / q
634+
if is_disco
635+
bdf_restart_estimates!(cache)
636+
return min((integrator.disco_checkpoint - integrator.t) / 4, new_dt)
637+
end
638+
return new_dt
594639
end

lib/OrdinaryDiffEqCore/src/OrdinaryDiffEqCore.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ methods.
149149
Tableau # tableau-derived predictor (α / const_stage_guess)
150150
end
151151
export Predictor
152+
export set_discontinuity
152153

153154
"""
154155
CompiledFloats
@@ -476,7 +477,7 @@ include("precompilation_setup.jl")
476477
# Integrator step / cache / initialization hooks.
477478
:_ode_init, :_determine_initdt, :ode_determine_initdt, :_initialize_dae!,
478479
:find_algebraic_vars_eqs, :postamble!, :apply_step!, :last_step_failed, :reset_alg_dependent_opts!,
479-
:set_discontinuity, :resolve_basic,
480+
:resolve_basic,
480481
# Noise hooks used by the SDE/RODE solver sublibs.
481482
:accept_noise!, :reinit_noise!, :reject_noise!, :save_noise!, :noise_curt,
482483
:is_noise_saveable,

lib/OrdinaryDiffEqCore/src/disco.jl

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
"""
2-
set_discontinuity(u, uprev, integrator) -> dt
2+
set_discontinuity(integrator) -> dt
33
44
Return the sub-step `Δt` at which a continuous callback's root lies within the
55
current step (a discontinuity to stop at), or a negative value if there is none in
66
`(0, 1)·dt`.
77
"""
8-
function set_discontinuity(u, uprev, integrator)
9-
breakpointθ = find_discontinuity(u, uprev, integrator)
8+
function set_discontinuity(integrator)
9+
breakpointθ = find_discontinuity(integrator)
1010
dt = integrator.dt
11-
if 1.0e-10 < breakpointθ < 1.0
12-
return breakpointθ * dt
11+
if breakpointθ < one(breakpointθ)
12+
# below formula is equivalent to (0.5 + 0.4 * sin(π * (breakpointθ - 0.5))) * dt
13+
return (0.5 + 1.2372 * (breakpointθ - 0.5) - 1.7487 * (breakpointθ - 0.5)^3) * dt
1314
end
1415
return -one(dt)
1516
end
@@ -18,17 +19,21 @@ get_disco_probs(cache::AbstractControllerCache) = cache.controller.basic.disco_p
1819
get_disco_probs(cache::DummyControllerCache) = cache.disco_probs
1920
get_disco_probs(cache::CompositeControllerCache) = get_disco_probs(first(cache.caches))
2021

21-
function find_discontinuity(u, uprev, integrator)
22+
function find_discontinuity(integrator)
2223
cb = integrator.opts.callback
2324
dt = integrator.dt
24-
cb === nothing && return -one(dt)
25-
isempty(cb.continuous_callbacks) && return -one(dt)
25+
u = integrator.u
26+
uprev = integrator.uprev
27+
integrator.is_disco_step = false
28+
cb === nothing && return one(dt)
29+
isempty(cb.continuous_callbacks) && return one(dt)
2630
p = integrator.p
2731
t = integrator.t
2832
k = integrator.k
29-
breakpointθ = -one(dt)
33+
breakpointθ = one(dt)
3034
disco_probs = get_disco_probs(integrator.controller_cache)
3135
idx = 1
36+
addsteps_called = false
3237
for i in cb.continuous_callbacks
3338
if (!(i.maybe_discontinuity))
3439
continue
@@ -47,22 +52,38 @@ function find_discontinuity(u, uprev, integrator)
4752
i.condition(disco_zero.out_high, u, t + dt, integrator)
4853
for j in 1:len_cb
4954
if (disco_zero.out_low[j] * disco_zero.out_high[j] < zero(disco_zero.out_low[j]))
55+
if (!addsteps_called)
56+
addsteps_called = true
57+
_ode_addsteps!(disco_zero.k, disco_zero.tprev, disco_zero.uprev, disco_zero.u,
58+
disco_zero.dt, disco_zero.f, disco_zero.p, disco_zero.cache, false, true, false)
59+
end
5060
disco_zero.ind = j
61+
disco_prob.tspan[2] = breakpointθ
5162
sol = solve(disco_prob)
5263
tmp = sol[]
53-
if (!isnan(tmp) && (breakpointθ < zero(breakpointθ) || tmp < breakpointθ))
64+
if (!isnan(tmp) && tmp < breakpointθ)
5465
breakpointθ = tmp
66+
integrator.is_disco_step = true
67+
integrator.disco_checkpoint = integrator.t + dt #our prev rejected step, we shouldn't step too far past this
5568
end
5669
end
5770
end
5871
else
59-
out_prev = i.condition(uprev, t, integrator)
60-
out_curr = i.condition(u, t + dt, integrator)
61-
if (out_prev * out_curr < zero(out_prev))
72+
disco_zero.out_low[1] = i.condition(uprev, t, integrator)
73+
disco_zero.out_high[1] = i.condition(u, t + dt, integrator)
74+
if (disco_zero.out_low[1] * disco_zero.out_high[1] < zero(disco_zero.out_low[1]))
75+
if (!addsteps_called)
76+
addsteps_called = true
77+
_ode_addsteps!(disco_zero.k, disco_zero.tprev, disco_zero.uprev, disco_zero.u,
78+
disco_zero.dt, disco_zero.f, disco_zero.p, disco_zero.cache, false, true, false)
79+
end
80+
disco_prob.tspan[2] = breakpointθ
6281
sol = solve(disco_prob)
6382
tmp = sol[]
64-
if (!isnan(tmp) && (breakpointθ < zero(breakpointθ) || tmp < breakpointθ))
83+
if (!isnan(tmp) && tmp < breakpointθ)
6584
breakpointθ = tmp
85+
integrator.is_disco_step = true
86+
integrator.disco_checkpoint = integrator.t + dt #our prev rejected step, we shouldn't step past this
6687
end
6788
end
6889
end

0 commit comments

Comments
 (0)