-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #23 from tkoolen/periodic-callback
Add PeriodicCallback.
- Loading branch information
Showing
5 changed files
with
106 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
function PeriodicCallback(f, Δt::Number; initialize = DiffEqBase.INITIALIZE_DEFAULT, kwargs...) | ||
# Value of `t` at which `f` should be called next: | ||
tnext = Ref(typemax(Δt)) | ||
condition = (t, u, integrator) -> t == tnext[] | ||
|
||
# Call f, update tnext, and make sure we stop at the new tnext | ||
affect! = function (integrator) | ||
f(integrator) | ||
|
||
# Schedule next call to `f` using `add_tstops!`, but be careful not to keep integrating forever | ||
tnew = tnext[] + Δt | ||
tstops = integrator.opts.tstops | ||
for i in length(tstops) : -1 : 1 # reverse iterate to encounter large elements earlier | ||
if DataStructures.compare(tstops.comparer, tnew, tstops.valtree[i]) # TODO: relying on implementation details | ||
tnext[] = tnew | ||
add_tstop!(integrator, tnew) | ||
break | ||
end | ||
end | ||
end | ||
|
||
# Initialization: first call to `f` should be *before* any time steps have been taken: | ||
initialize_periodic = function (c, t, u, integrator) | ||
@assert integrator.tdir == sign(Δt) | ||
initialize(c, t, u, integrator) | ||
tnext[] = t | ||
affect!(integrator) | ||
end | ||
|
||
DiscreteCallback(condition, affect!; initialize = initialize_periodic, kwargs...) | ||
end | ||
|
||
export PeriodicCallback |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
using Base.Test, OrdinaryDiffEq, DiffEqCallbacks | ||
|
||
tmin = 0.1 | ||
tmax = 5.2 | ||
|
||
for tmax_problem in [tmax; Inf] | ||
# Test with both a finite and an infinite tspan for the ODEProblem. | ||
# | ||
# Having support for infinite tspans is one of the main reasons for implementing PeriodicCallback | ||
# using add_tstop!, instead of just passing in a linspace as the tstops solve argument. | ||
# (the other being that the length of the internal tstops collection would otherwise become | ||
# linear in the length of the integration time interval. | ||
# | ||
# Testing a finite tspan is necessary because a naive implementation could add tstops after | ||
# tmax and thus integrate for too long (or even indefinitely). | ||
|
||
# Dynamics: two independent single integrators: | ||
du = [0; 0] | ||
u0 = [0.; 0.] | ||
dynamics = (t, u) -> eltype(u).(du) | ||
prob = ODEProblem(dynamics, u0, (tmin, tmax_problem)) | ||
|
||
# Callbacks periodically increase the input to the integrators: | ||
Δt1 = 0.5 | ||
increase_du_1 = integrator -> du[1] += 1 | ||
periodic1_initialized = Ref(false) | ||
initialize1 = (c, t, u, integrator) -> periodic1_initialized[] = true | ||
periodic1 = PeriodicCallback(increase_du_1, Δt1; initialize = initialize1) | ||
|
||
Δt2 = 1. | ||
increase_du_2 = integrator -> du[2] += 1 | ||
periodic2 = PeriodicCallback(increase_du_2, Δt2) | ||
|
||
# Terminate at tmax (regardless of whether the tspan of the ODE problem is infinite). | ||
terminator = DiscreteCallback((t, u, integrator) -> t == tmax, terminate!) | ||
|
||
# Solve. | ||
sol = solve(prob, Tsit5(); callback = CallbackSet(terminator, periodic1, periodic2), tstops = [tmax]) | ||
|
||
# Ensure that initialize1 has been called | ||
@test periodic1_initialized[] | ||
|
||
# Make sure we haven't integrated past tmax: | ||
@test sol.t[end] == tmax | ||
|
||
# Make sure that the components of du have been incremented the appropriate number of times. | ||
Δts = [Δt1, Δt2] | ||
expected_num_calls = map(Δts) do Δt | ||
floor(Int, (tmax - tmin) / Δt) + 1 | ||
end | ||
@test du == expected_num_calls | ||
|
||
# Make sure that the final state matches manual integration of the piecewise linear function | ||
foreach(Δts, sol.u[end], du) do Δt, u_i, du_i | ||
@test u_i ≈ Δt * sum(1 : du_i - 1) + rem(tmax - tmin, Δt) * du_i atol = 1e-5 | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters