-
-
Notifications
You must be signed in to change notification settings - Fork 27
/
Copy pathtrack.jl
146 lines (122 loc) · 5.29 KB
/
track.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
"""
track_propagated_discontinuities!(integrator::DDEIntegrator)
Try to find a propagated discontinuity in the time interval `[integrator.t, integrator.t +
integrator.dt]` and add it to the set of discontinuities and grid points of the
`integrator`.
"""
function track_propagated_discontinuities!(integrator::DDEIntegrator)
# calculate interpolation points
interp_points = integrator.discontinuity_interp_points
Θs = range(zero(integrator.t); stop = oneunit(integrator.t), length = interp_points)
# for dependent lags and previous discontinuities
for lag in integrator.sol.prob.dependent_lags,
discontinuity in integrator.tracked_discontinuities
# obtain time of previous discontinuity
T = discontinuity.t
# estimate subinterval of current integration step that contains a propagated
# discontinuity induced by the lag and the previous discontinuity
interval = discontinuity_interval(integrator, lag, T, Θs)
# if a discontinuity exists in the current integration step
if interval !== nothing
# estimate time point of discontinuity
t = discontinuity_time(integrator, lag, T, interval)
# add new discontinuity of correct order at the estimated time point
if integrator.sol.prob.neutral
d = Discontinuity(t, discontinuity.order)
else
d = Discontinuity(t, discontinuity.order + 1)
end
push!(integrator.opts.d_discontinuities, d)
push!(integrator.opts.tstops, t)
# analogously to RADAR5 we do not strive for finding the first discontinuity
break
end
end
nothing
end
"""
discontinuity_function(integrator::DDEIntegrator, lag, T, t)
Evaluate function ``f(x) = T + lag(u(x), p, x) - x`` at time point `t`, where `T` is time
point of a previous discontinuity and `lag` is a dependent delay.
"""
function discontinuity_function(integrator::DDEIntegrator, lag, T, t)
tmp = get_tmp_cache(integrator)
cache = tmp === nothing ? nothing : first(tmp)
# estimate state at the given time
if cache === nothing
ut = integrator(t, Val{0})
else
integrator(cache, t, Val{0})
ut = cache
end
T + lag(ut, integrator.p, t) - t
end
"""
discontinuity_interval(integrator::DDEIntegrator, lag, T, Θs)
Return an estimated subinterval of the current integration step of the `integrator` that
contains a propagated discontinuity induced by the dependent delay `lag` and the
discontinuity at time point `T`, or `nothing`.
The interval is estimated by checking the signs of `T + lag(u(t), p, t) - t` for time points
`integrator.t .+ θs` in the interval `[integrator.t, integrator.t + integrator.dt]`.
"""
function discontinuity_interval(integrator::DDEIntegrator, lag, T, Θs)
# use start and end point of last time interval to check for discontinuities
previous_condition = discontinuity_function(integrator, lag, T, integrator.t)
if isapprox(previous_condition, 0;
rtol = integrator.discontinuity_reltol,
atol = integrator.discontinuity_abstol)
prev_sign = 0
else
prev_sign = cmp(previous_condition, zero(previous_condition))
end
new_condition = discontinuity_function(integrator, lag, T, integrator.t + integrator.dt)
new_sign = cmp(new_condition, zero(new_condition))
# if signs are different we already know that a discontinuity exists
if prev_sign * new_sign < 0
return (zero(eltype(Θs)), one(eltype(Θs)))
end
# recheck interpolation intervals if no discontinuity found yet
prev_Θ = zero(eltype(Θs))
for i in 2:length(Θs)
# evaluate sign at next interpolation point
new_Θ = Θs[i]
new_t = integrator.t + new_Θ * integrator.dt
new_condition = discontinuity_function(integrator, lag, T, new_t)
new_sign = cmp(new_condition, zero(new_condition))
# return estimated interval if we find a root or observe a switch of signs
if new_sign == 0
return (new_Θ, new_Θ)
elseif prev_sign * new_sign < 0
return (prev_Θ, new_Θ)
else
# otherwise update lower estimate of subinterval
prev_sign = new_sign
prev_Θ = new_Θ
end
end
nothing
end
"""
discontinuity_time(integrator::DDEIntegrator, lag, T, interval)
Estimate time point of the propagated discontinuity induced by the dependent delay
`lag` and the discontinuity at time point `T` inside the `interval` of the current
integration step of the `integrator`.
"""
function discontinuity_time(integrator::DDEIntegrator, lag, T, (bottom_Θ, top_Θ))
if bottom_Θ == top_Θ
# in that case we have already found the time point of a discontinuity
Θ = top_Θ
else
# define function for root finding
zero_func = let integrator = integrator, lag = lag, T = T, t = integrator.t,
dt = integrator.dt
(θ, p = nothing) -> discontinuity_function(integrator, lag, T, t + θ * dt)
end
Θ = SimpleNonlinearSolve.solve(
SimpleNonlinearSolve.IntervalNonlinearProblem{false}(zero_func,
(bottom_Θ,
top_Θ)),
SimpleNonlinearSolve.Falsi()).left
end
integrator.t + Θ * integrator.dt
end