Skip to content

Commit 126e7cf

Browse files
disco optimizations
1 parent bdd44ca commit 126e7cf

7 files changed

Lines changed: 165 additions & 109 deletions

File tree

lib/OrdinaryDiffEqBDF/src/controllers.jl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,11 @@ 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+
if integrator.curr_discontinuity != -1 &&
87+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
88+
integrator.curr_discontinuity = -1
89+
cache.nconsteps = 0
90+
end
8691
cache.nconsteps += 1
8792
if iszero(OrdinaryDiffEqCore.get_EEst(integrator))
8893
return integrator.dt * get_current_qmax(integrator, get_qmax(integrator))
@@ -174,8 +179,12 @@ function bdf_step_reject_controller!(integrator, cache, EEst1)
174179
end
175180

176181
if discontinuity_detection
182+
no_prior_discontinuity = (integrator.curr_discontinuity == -1)
177183
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
178-
if disco_dt != -1
184+
if no_prior_discontinuity && disco_dt > zero(disco_dt)
185+
add_tstop!(integrator, integrator.disco_checkpoint)
186+
end
187+
if disco_dt > zero(disco_dt)
179188
integrator.dt = disco_dt
180189
return integrator.dt
181190
end
@@ -416,6 +425,11 @@ function step_accept_controller!(
416425
q
417426
) where {max_order}
418427
cache.consfailcnt = 0
428+
if integrator.curr_discontinuity != -1 &&
429+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
430+
integrator.curr_discontinuity = -1
431+
cache.nconsteps = 0
432+
end
419433
if q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
420434
q = one(q)
421435
end
@@ -579,6 +593,11 @@ function step_accept_controller!(
579593
q
580594
) where {max_order}
581595
cache.consfailcnt = 0
596+
if integrator.curr_discontinuity != -1 &&
597+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
598+
integrator.curr_discontinuity = -1
599+
cache.nconsteps = 0
600+
end
582601
if q <= get_qsteady_max(integrator) && q >= get_qsteady_min(integrator)
583602
q = one(q)
584603
end

lib/OrdinaryDiffEqCore/src/OrdinaryDiffEqCore.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ import EnzymeCore
112112
Tableau # tableau-derived predictor (α / const_stage_guess)
113113
end
114114
export Predictor
115+
export set_discontinuity
115116

116117
const CompiledFloats = Union{Float32, Float64}
117118
import Preferences

lib/OrdinaryDiffEqCore/src/disco.jl

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,24 +38,32 @@ function find_discontinuity(u, uprev, integrator)
3838
len_cb = i.len
3939
i.condition(disco_zero.out_low, uprev, t, integrator)
4040
i.condition(disco_zero.out_high, u, t + dt, integrator)
41+
_ode_addsteps!(disco_zero.k, disco_zero.tprev, disco_zero.uprev, disco_zero.u,
42+
disco_zero.dt, disco_zero.f, disco_zero.p, disco_zero.cache, false, true, false)
4143
for j in 1:len_cb
4244
if (disco_zero.out_low[j] * disco_zero.out_high[j] < zero(disco_zero.out_low[j]))
4345
disco_zero.ind = j
44-
sol = solve(disco_prob)
46+
sol = solve(disco_prob, tspan = (0.0, breakpointθ == -one(dt) ? 1.0 : breakpointθ); save_everystep = false)
4547
tmp = sol[]
4648
if (!isnan(tmp) && (breakpointθ < zero(breakpointθ) || tmp < breakpointθ))
4749
breakpointθ = tmp
50+
integrator.curr_discontinuity = idx
51+
integrator.disco_checkpoint = integrator.t + tmp * dt
4852
end
4953
end
5054
end
5155
else
5256
out_prev = i.condition(uprev, t, integrator)
5357
out_curr = i.condition(u, t + dt, integrator)
5458
if (out_prev * out_curr < zero(out_prev))
55-
sol = solve(disco_prob)
59+
_ode_addsteps!(disco_zero.k, disco_zero.tprev, disco_zero.uprev, disco_zero.u,
60+
disco_zero.dt, disco_zero.f, disco_zero.p, disco_zero.cache, false, true, false)
61+
sol = solve(disco_prob, tspan = (0.0, breakpointθ == -one(dt) ? 1.0 : breakpointθ); save_everystep = false)
5662
tmp = sol[]
5763
if (!isnan(tmp) && (breakpointθ < zero(breakpointθ) || tmp < breakpointθ))
5864
breakpointθ = tmp
65+
integrator.curr_discontinuity = idx
66+
integrator.disco_checkpoint = integrator.t + tmp * dt
5967
end
6068
end
6169
end

lib/OrdinaryDiffEqCore/src/integrators/controllers.jl

Lines changed: 61 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,13 @@ The fields are:
4343
inside this interval, dt is held constant.
4444
- `failfactor`: post-Newton-failure shrink factor used by
4545
[`post_newton_controller!`](@ref).
46+
- 'discontinuity_detection': If `discontinuity_detection` is set to true, the algorithm will run the autonomous
47+
discontinuity detection to predict the best next timestep after step rejection.
48+
Otherwise, it follows the default step rejection algorithm. This feature is currently
49+
defaulted off.
50+
- `disco_probs`: If `discontinuity_detection` is set to true, this field holds the vector of
51+
`IntervalNonlinearProblem`s used for discontinuity detection. Otherwise, it can be left empty.
52+
4653
4754
User-supplied overrides flow through controllers as a `NamedTuple` of
4855
keyword arguments (whatever subset the user passed). At
@@ -506,10 +513,6 @@ the interval `[qmin, qmax]`.
506513
A step will be accepted whenever the estimated error `get_EEst(integrator)` is
507514
less than or equal to unity. Otherwise, the step is rejected and re-tried with
508515
the predicted step size.
509-
If `discontinuity_detection` is set to true, the algorithm will run the autonomous
510-
discontinuity detection to predict the best next timestep after step rejection.
511-
Otherwise, it follows the default step rejection algorithm. This feature is currently
512-
defaulted off.
513516
514517
## References
515518
@@ -563,18 +566,26 @@ end
563566

564567
# TODO change signature to remove the q input
565568
function step_accept_controller!(integrator, cache::IControllerCache, alg, q)
566-
(; qsteady_min, qsteady_max) = cache.controller.basic
569+
(; qsteady_min, qsteady_max, discontinuity_detection) = cache.controller.basic
567570

568571
if qsteady_min <= q <= qsteady_max
569572
q = one(q)
570573
end
574+
if discontinuity_detection && integrator.curr_discontinuity != -1 &&
575+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
576+
integrator.curr_discontinuity = -1
577+
end
571578
return integrator.dt / q # new dt
572579
end
573580

574581
function step_reject_controller!(integrator, cache::IControllerCache, alg)
575582
discontinuity_detection = cache.controller.basic.discontinuity_detection
576583
if discontinuity_detection
584+
no_prior_discontinuity = (integrator.curr_discontinuity == -1)
577585
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
586+
if no_prior_discontinuity && disco_dt > zero(disco_dt)
587+
add_tstop!(integrator, integrator.disco_checkpoint)
588+
end
578589
if disco_dt > zero(disco_dt)
579590
integrator.dt = disco_dt
580591
return integrator.dt
@@ -611,10 +622,7 @@ the interval `[qmin, qmax]`.
611622
A step will be accepted whenever the estimated error `get_EEst(integrator)` is
612623
less than or equal to unity. Otherwise, the step is rejected and re-tried with
613624
the predicted step size.
614-
If `discontinuity_detection` is set to true, the algorithm will run the autonomous
615-
discontinuity detection to predict the best next timestep after step rejection.
616-
Otherwise, it follows the default step rejection algorithm. This feature is currently
617-
defaulted off.
625+
618626
!!! note
619627
620628
The coefficients `beta1, beta2` are not scaled by the order of the method,
@@ -703,25 +711,35 @@ end
703711

704712
function step_accept_controller!(integrator, cache::PIControllerCache, alg, q)
705713
(; controller) = cache
706-
(; qsteady_min, qsteady_max) = controller.basic
714+
(; qsteady_min, qsteady_max, discontinuity_detection) = controller.basic
707715
qoldinit = controller.qoldinit
708716
EEst = DiffEqBase.value(get_EEst(integrator))
709717

710718
if qsteady_min <= q <= qsteady_max
711719
q = one(q)
712720
end
713-
cache.errold = max(EEst, qoldinit)
721+
722+
if discontinuity_detection && integrator.curr_discontinuity != -1 &&
723+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
724+
integrator.curr_discontinuity = -1
725+
cache.errold = qoldinit
726+
else
727+
cache.errold = max(EEst, qoldinit)
728+
end
714729
return integrator.dt / q # new dt
715730
end
716731

717732
function step_reject_controller!(integrator, cache::PIControllerCache, alg)
718733
(; controller, q11) = cache
719734
(; qmin, gamma, discontinuity_detection) = controller.basic
720735
if discontinuity_detection
736+
no_prior_discontinuity = (integrator.curr_discontinuity == -1)
721737
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
738+
if no_prior_discontinuity && disco_dt > zero(disco_dt) #found a discontinuity
739+
add_tstop!(integrator, integrator.disco_checkpoint)
740+
end
722741
if disco_dt > zero(disco_dt)
723-
integrator.dt = disco_dt
724-
return integrator.dt
742+
return integrator.dt = disco_dt
725743
end
726744
end
727745
return integrator.dt /= min(inv(qmin), q11 / gamma)
@@ -776,11 +794,6 @@ Some standard controller parameters suggested in the literature are
776794
| H211PI | `1//6` | `1//6` | `0` |
777795
| H312PID | `1//18` | `1//9` | `1//18` |
778796
779-
If `discontinuity_detection` is set to true, the algorithm will run the autonomous
780-
discontinuity detection to predict the best next timestep after step rejection.
781-
Otherwise, it follows the default step rejection algorithm. This feature is currently
782-
defaulted off.
783-
784797
!!! note
785798
786799
In contrast to the [`PIController`](@ref), the coefficients `beta1, beta2, beta3`
@@ -940,22 +953,35 @@ end
940953

941954
function step_accept_controller!(integrator, cache::PIDControllerCache, alg, dt_factor)
942955
(; controller) = cache
943-
(; qsteady_min, qsteady_max) = controller.basic
956+
(; qsteady_min, qsteady_max, discontinuity_detection) = controller.basic
944957

945958
if qsteady_min <= inv(dt_factor) <= qsteady_max
946959
dt_factor = one(dt_factor)
947960
end
948-
@inbounds begin
949-
cache.err[3] = cache.err[2]
950-
cache.err[2] = cache.err[1]
961+
if discontinuity_detection && integrator.curr_discontinuity != -1 &&
962+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
963+
integrator.curr_discontinuity = -1
964+
@inbounds begin
965+
cache.err[3] = one(eltype(cache.err))
966+
cache.err[2] = cache.err[1]
967+
end
968+
else
969+
@inbounds begin
970+
cache.err[3] = cache.err[2]
971+
cache.err[2] = cache.err[1]
972+
end
951973
end
952974
return integrator.dt * dt_factor # new dt
953975
end
954976

955977
function step_reject_controller!(integrator, cache::PIDControllerCache, alg)
956978
discontinuity_detection = cache.controller.basic.discontinuity_detection
957979
if discontinuity_detection
980+
no_prior_discontinuity = (integrator.curr_discontinuity == -1)
958981
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
982+
if no_prior_discontinuity && disco_dt > zero(disco_dt)
983+
add_tstop!(integrator, integrator.disco_checkpoint)
984+
end
959985
if disco_dt > zero(disco_dt)
960986
integrator.dt = disco_dt
961987
return integrator.dt
@@ -1020,10 +1046,6 @@ integrator.dt / qacc
10201046
```
10211047
10221048
When it rejects, it's the same as the [`IController`](@ref):
1023-
If `discontinuity_detection` is set to true, the algorithm will run the autonomous
1024-
discontinuity detection to predict the best next timestep after step rejection.
1025-
Otherwise, it follows the default step rejection algorithm. This feature is currently
1026-
defaulted off.
10271049
```julia
10281050
if integrator.success_iter == 0
10291051
integrator.dt *= 0.1
@@ -1119,8 +1141,15 @@ function step_accept_controller!(integrator, cache::PredictiveControllerCache, a
11191141
if qsteady_min <= qacc <= qsteady_max
11201142
qacc = one(qacc)
11211143
end
1122-
cache.dtacc = DiffEqBase.value(integrator.dt)
1123-
cache.erracc = max(1.0e-2, EEst)
1144+
if cache.controller.basic.discontinuity_detection && integrator.curr_discontinuity != -1 &&
1145+
integrator.tdir * integrator.t >= integrator.tdir * integrator.disco_checkpoint
1146+
integrator.curr_discontinuity = -1
1147+
cache.dtacc = DiffEqBase.value(integrator.dt)
1148+
cache.erracc = one(EEst)
1149+
else
1150+
cache.dtacc = DiffEqBase.value(integrator.dt)
1151+
cache.erracc = max(1.0e-2, EEst)
1152+
end
11241153

11251154
return integrator.dt / qacc
11261155
end
@@ -1130,7 +1159,11 @@ function step_reject_controller!(integrator, cache::PredictiveControllerCache, a
11301159
(; qold) = cache
11311160
discontinuity_detection = cache.controller.basic.discontinuity_detection
11321161
if discontinuity_detection
1162+
no_prior_discontinuity = (integrator.curr_discontinuity == -1)
11331163
disco_dt = set_discontinuity(integrator.u, integrator.uprev, integrator)
1164+
if no_prior_discontinuity && disco_dt > zero(disco_dt)
1165+
add_tstop!(integrator, integrator.disco_checkpoint)
1166+
end
11341167
if disco_dt > zero(disco_dt)
11351168
integrator.dt = disco_dt
11361169
return integrator.dt

lib/OrdinaryDiffEqCore/src/integrators/type.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,8 @@ mutable struct ODEIntegrator{
175175
fsalfirst::FSALType
176176
fsallast::FSALType
177177
rng::RNGType
178+
curr_discontinuity::Int
179+
disco_checkpoint::tType
178180
W::WType
179181
P::PType
180182
sqdt::SqdtType

lib/OrdinaryDiffEqCore/src/solve.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ end
3838
parameter_values(z::zero_func_struct) = z.p
3939

4040
function (z::zero_func_struct)(θ, p)
41-
_ode_addsteps!(z.k, z.tprev, z.uprev, z.u, z.dt, z.f, z.p, z.cache, false, true, false)
4241
ode_interpolant!(z.u₁, θ, z.dt, z.uprev, z.u, z.k, z.cache, z.idxs, Val{0}, z.differential_vars)
4342
return zero_condition(z.callback, z.out, z.u₁, z.tprev + θ * z.dt, z, z.ind)
4443
end
@@ -688,6 +687,8 @@ Base.@constprop :aggressive function _ode_init(
688687

689688
controller_cache = setup_controller_cache(_alg, cache, controller, EEstT, disco_probs)
690689

690+
curr_discontinuity = -1
691+
disco_checkpoint = zero(tType)
691692
# Seed the initial EEst on the controller cache (was previously
692693
# `integrator.EEst = oneunit(EEstT)`).
693694
set_EEst!(controller_cache, EEst)
@@ -721,7 +722,7 @@ Base.@constprop :aggressive function _ode_init(
721722
isout, reeval_fsal,
722723
derivative_discontinuity, reinitialize, isdae,
723724
opts, stats, initializealg, differential_vars,
724-
fsalfirst, fsallast, _rng,
725+
fsalfirst, fsallast, _rng, curr_discontinuity, disco_checkpoint,
725726
W, P, sqdt,
726727
noise, c, rate_constants
727728
)

0 commit comments

Comments
 (0)