Alright, I am now very pleased to say that I have a working version for a single radius, that goes like this:
using DynamicalSystemsBase
using OrdinaryDiffEq: Tsit5
using DynamicalSystemsBase.DiffEqBase: ContinuousCallback, ODEProblem, solve
using Distances
εdistance(u, u0, ε::Real) = euclidean(u, u0) - ε
function transit_time_statistics(ds::ContinuousDynamicalSystem, u0, εs, T;
alg = Tsit5(), diffeq...
)
eT = eltype(ds.t0)
check_εs_sorting(εs, length(u0))
exits = [eT[] for _ in 1:length(εs)]
entries = [eT[] for _ in 1:length(εs)]
# Make the magic callback:
crossing(u, t, integ) = ChaosTools.εdistance(u, u0, εs[1])
negative_affect!(integ) = push!(entries[1], integ.t)
positive_affect!(integ) = push!(exits[1], integ.t)
cb = ContinuousCallback(crossing, positive_affect!, negative_affect!;
save_positions = (false, false)
)
prob = ODEProblem(ds, (eT(0), eT(T)); u0 = u0)
sol = solve(prob, alg;
callback=cb, save_everystep = false, dense = false,
save_start=false, save_end = false, diffeq...
)
return exits, entries
end
This works so far without problem. You will notice that εs
is a vector of ball radii, so I need to extend this framework to use a CallbackSet
. So I change my callback generating code to:
callbacks = ContinuousCallback[]
for i in eachindex(εs)
crossing(u, t, integ) = ChaosTools.εdistance(u, u0, εs[i])
negative_affect!(integ) = push!(entries[i], integ.t)
positive_affect!(integ) = push!(exits[i], integ.t)
cb = ContinuousCallback(crossing, positive_affect!, negative_affect!;
save_positions = (false, false)
)
push!(callbacks, cb)
end
cb = CallbackSet(callbacks...)
which, sometimes, works excellently. There are edge cases that I haven’t been able to identify yet where the integrator and/or interpolation seems to get stuck when exiting the ε-ball and or entering the inner most ε-ball and the callback positive_affect!
gets called non-stop.
For example if I have εs = sort!([1.0, 0.1, 0.01]; rev=true)
, I can get resulting in output arrays like (fully reproducible code at the end):
julia> exits[2]
885033-element Array{Float64,1}:
0.009521130925491618
105.1285060629573
227.79608608298875
456.0488702006206
561.549220491485
954.0609666798003
1610.4113223742547
1645.4402235743025
1680.468316963271
1949.7593549776773
2658.4544078395106
⋮
27017.53815060816
27017.53815060816
27017.53815060816
27017.53815060816
27017.53815060816
27017.53815060816
while the corresponding entries[2]
, which, by the definition of continuity of the trajectory, can have at most 1 more entry that exits[2]
, is:
julia> entries[2]
80-element Array{Float64,1}:
105.10947207509459
227.7840886145673
456.03680102588226
561.5319922039274
⋮
23077.10816055895
24143.055169575884
24992.380689658865
25009.884596962012
25062.475812273526
26355.939325594318
26508.239300245827
26783.405955508315
27017.519471225707
So I guess something happens when exiting one of the inner balls. @ChrisRackauckas do you have any suggestions? In the example above I attempted to integrate for total of T = 100000.0
so the algorithm shouldn’t stop at 27017.519471225707
( I guess it hit the max number of steps and thus terminated)
I can also say that I’ve noticed that when this stuck happens, it was the first time that my trajectory crossed the innermost ball of radius εs[3]
, which has the smallest radius.
Reproducible code (but unfortunatley, needs fixodprob
branch of DynamicalSystemsBase
)
using DynamicalSystemsBase
using OrdinaryDiffEq: Tsit5
using DynamicalSystemsBase.DiffEqBase: ContinuousCallback, ODEProblem, solve
using Distances
function transit_time_statistics(ds::ContinuousDynamicalSystem, u0, εs, T;
alg = Tsit5(), diffeq...
)
eT = eltype(ds.t0)
check_εs_sorting(εs, length(u0))
exits = [eT[] for _ in 1:length(εs)]
entries = [eT[] for _ in 1:length(εs)]
# Make the magic callbacks:
callbacks = ContinuousCallback[]
for i in eachindex(εs)
crossing(u, t, integ) = ChaosTools.εdistance(u, u0, εs[i])
negative_affect!(integ) = push!(entries[i], integ.t)
positive_affect!(integ) = push!(exits[i], integ.t)
cb = ContinuousCallback(crossing, positive_affect!, negative_affect!;
save_positions = (false, false)
)
push!(callbacks, cb)
end
cb = CallbackSet(callbacks...)
prob = ODEProblem(ds, (eT(0), eT(T)); u0 = u0)
sol = solve(prob, alg;
callback=cb, save_everystep = false, dense = false,
save_start=false, save_end = false, diffeq...
)
return exits, entries
end
ro = Systems.roessler(ones(3))
tr = trajectory(ro, 5000; Ttr = 100)
u0 = trajectory(ro, 11; Ttr = 100)[end] # return center
εs = sort!([1.0, 0.1, 0.01]; rev=true)
T = 100000.0 # maximum time
exits, entries = transit_time_statistics(ro, u0, εs, T)