Skip to content

Commit 3177aa4

Browse files
authored
Histograms (#35)
* clean rqa code * error > throw(ErrorException()) * optimization of histogram functions * Poincare recurrence times and RT parameters * minimum lenght line for histograms * add `recurrencestructures` * add tests
1 parent 726c49c commit 3177aa4

File tree

6 files changed

+425
-204
lines changed

6 files changed

+425
-204
lines changed

src/RecurrenceAnalysis.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@ export embed,
1313
jointrecurrencematrix,
1414
recurrenceplot,
1515
recurrencerate,
16+
recurrencestructures,
17+
dl_average, dl_max, dl_entropy,
18+
vl_average, vl_max, vl_entropy,
19+
rt_average, rt_max, rt_entropy,
1620
determinism,
1721
avgdiag,
1822
maxdiag,
@@ -28,6 +32,7 @@ export embed,
2832

2933
include("matrices.jl")
3034
include("plot.jl")
35+
include("histograms.jl")
3136
include("rqa.jl")
3237
include("radius.jl")
3338
include("windowed.jl")

src/histograms.jl

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
### Histograms of recurrence structures
2+
3+
# Add one item to position `p` in the histogram `h` that has precalculated length `n`
4+
# - update the histogram and return its new length
5+
@inline function extendhistogram!(h::Vector{Int}, n::Int, p::Int)
6+
if p > n
7+
append!(h, zeros(p-n))
8+
n = p
9+
end
10+
h[p] += 1
11+
return n
12+
end
13+
14+
# Calculate the histograms of segments and distances between segments
15+
# from the indices of rows and columns/diagonals of the matrix
16+
# `theiler` is used for histograms of vertical structures
17+
# `distances` is used to simplify calculations of the distances are not wanted
18+
function _linehistograms(rows::T, cols::T, lmin::Integer=1, theiler::Integer=0,
19+
distances::Bool=true) where {T<:AbstractVector{Int}}
20+
21+
# check bounds
22+
n = length(rows)
23+
if length(cols) != n
24+
throw(ErrorException("mismatch between number of row and column indices"))
25+
end
26+
# histogram for segments
27+
bins = [0]
28+
nbins = 1
29+
# histogram for distances between segments
30+
bins_d = [0]
31+
nbins_d = 1
32+
# find the first point outside the Theiler window
33+
firstindex = 1
34+
while (firstindex<=n) && (abs(rows[firstindex]-cols[firstindex])<theiler)
35+
firstindex += 1
36+
end
37+
if firstindex > n
38+
return ([0],[0])
39+
end
40+
# Iterate over columns
41+
cprev = cols[firstindex]
42+
r1 = rows[firstindex]
43+
rprev = r1 - 1 # coerce that (a) is not hit in the first iteration
44+
dist = 0 # placeholder for distances between segments
45+
@inbounds for i=firstindex:n
46+
r = rows[i]
47+
c = cols[i]
48+
if abs(r-c)>=theiler
49+
# Search the second and later segments in the column
50+
if c == cprev
51+
if r-rprev !=1 # (a): there is a separation between rprev and r
52+
# update histogram of segments
53+
current_vert = rprev-r1+1
54+
if current_vert >= lmin
55+
nbins = extendhistogram!(bins, nbins, current_vert)
56+
end
57+
# update histogram of distances if it there were at least
58+
# two previous segments in the column
59+
if distances
60+
if current_vert >= lmin
61+
halfline = div(current_vert, 2)
62+
if dist != 0
63+
nbins_d = extendhistogram!(bins_d, nbins_d, dist+halfline)
64+
end
65+
# update the distance
66+
dist = r-rprev+halfline-1
67+
else
68+
dist += r - r1
69+
end
70+
end
71+
r1 = r # update the start of the next segment
72+
end
73+
rprev = r # update the previous position
74+
else # hit in the first point of a new column
75+
# process the last fragment of the previous column
76+
current_vert = rprev-r1+1
77+
if current_vert >= lmin
78+
nbins = extendhistogram!(bins, nbins, current_vert)
79+
end
80+
if distances
81+
if dist!= 0 && current_vert >= lmin
82+
halfline = div(current_vert, 2)
83+
nbins_d = extendhistogram!(bins_d, nbins_d, dist+halfline)
84+
end
85+
dist = 0
86+
end
87+
# initialize values for searching new fragments
88+
cprev = c
89+
r1 = r
90+
rprev = r
91+
end
92+
end
93+
end
94+
# Process the latest fragment
95+
current_vert = rprev-r1+1
96+
if current_vert >= lmin
97+
nbins = extendhistogram!(bins, nbins, current_vert)
98+
if distances && dist != 0
99+
halfline = div(current_vert, 2)
100+
nbins_d = extendhistogram!(bins_d, nbins_d, dist+halfline)
101+
end
102+
end
103+
return (bins, bins_d)
104+
end
105+
106+
function diagonalhistogram(x::ARM; lmin::Integer=2, theiler::Integer=0, kwargs...)
107+
(theiler < 0) && throw(ErrorException(
108+
"Theiler window length must be greater than or equal to 0"))
109+
(lmin < 1) && throw(ErrorException("lmin must be 1 or greater"))
110+
m,n=size(x)
111+
rv = rowvals(x)[:]
112+
dv = colvals(x) .- rowvals(x)
113+
loi_hist = Int[]
114+
if issymmetric(x)
115+
# If theiler==0, the LOI is counted separately to avoid duplication
116+
if theiler == 0
117+
loi_hist = verticalhistograms(CrossRecurrenceMatrix(hcat(diag(x,0))),lmin=lmin)[1]
118+
end
119+
inside = (dv .< max(theiler,1))
120+
f = 2
121+
else
122+
inside = (abs.(dv) .< theiler)
123+
f = 1
124+
end
125+
# remove Theiler window and short by diagonals
126+
deleteat!(rv, inside)
127+
deleteat!(dv, inside)
128+
rv = rv[sortperm(dv)]
129+
dv = sort!(dv)
130+
dh = f.*_linehistograms(rv, dv, lmin, 0, false)[1]
131+
# Add frequencies of LOI if suitable
132+
if (nbins_loi = length(loi_hist)) > 0
133+
nbins = length(dh)
134+
if nbins_loi > nbins
135+
loi_hist[1:nbins] .+= dh
136+
dh = loi_hist
137+
else
138+
dh[1:nbins_loi] .+= loi_hist
139+
end
140+
end
141+
return dh
142+
end
143+
144+
function verticalhistograms(x::ARM;
145+
lmin::Integer=2, theiler::Integer=0, distances=true, kwargs...)
146+
(theiler < 0) && throw(ErrorException(
147+
"Theiler window length must be greater than or equal to 0"))
148+
(lmin < 1) && throw(ErrorException("lmin must be 1 or greater"))
149+
m,n=size(x)
150+
rv = rowvals(x)
151+
cv = colvals(x)
152+
return _linehistograms(rv,cv,lmin,theiler,distances)
153+
end
154+
155+
vl_histogram(x; kwargs...) = verticalhistograms(x; kwargs...)[1]
156+
rt_histogram(x; kwargs...) = verticalhistograms(x; kwargs...)[2]
157+
158+
"""
159+
recurrencestructures(x::AbstractRecurrenceMatrix;
160+
diagonal=true,
161+
vertical=true,
162+
recurrencetimes=true,
163+
kwargs...)
164+
165+
Histograms of the recurrence structures contained in the recurrence matrix `x`.
166+
167+
## Description:
168+
169+
Returns a dictionary with the keys `"diagonal"`, `"vertical"` or
170+
`"recurrencetimes"`, depending on what keyword arguments are given as `true`.
171+
Each item of the dictionary is a vector of integers, such that the `i`-th
172+
element of the vector is the number of lines of length `i` contained in `x`.
173+
174+
* `"diagonal"` counts the diagonal lines, i.e. the recurrent trajectories.
175+
* `"vertical"` counts the vertical lines, i.e. the laminar states.
176+
* `"recurrencetimes"` counts the vertical distances between recurrent states,
177+
i.e. the recurrence times. These are calculated as the distance between
178+
the middle points of consecutive vertical lines.
179+
180+
All the points of the matrix are counted by default. Extra keyword arguments can
181+
be passed to rule out the lines shorter than a minimum length or around the main
182+
diagonal. See the arguments of the function [`rqa`](@ref) for further details.
183+
184+
## References:
185+
186+
N. Marwan & C.L. Webber, "Mathematical and computational foundations of
187+
recurrence quantifications", in: Webber, C.L. & N. Marwan (eds.), *Recurrence
188+
Quantification Analysis. Theory and Best Practices*, Springer, pp. 3-43 (2015).
189+
"""
190+
function recurrencestructures(x::ARM;
191+
diagonal=true, vertical=true, recurrencetimes=true, lmin=1, theiler=0, kwargs...)
192+
193+
# Parse arguments for diagonal and vertical structures
194+
histograms = Dict{String,Vector{Int}}()
195+
if diagonal
196+
kw_d = Dict(kwargs)
197+
haskey(kw_d, :theilerdiag) && (kw_d[:theiler] = kw_d[:theilerdiag])
198+
haskey(kw_d, :lmindiag) && (kw_d[:lmin] = kw_d[:lmindiag])
199+
histograms["diagonal"] = diagonalhistogram(x; lmin=lmin, theiler=theiler, kw_d...)
200+
end
201+
if vertical || recurrencetimes
202+
kw_v = Dict(kwargs)
203+
haskey(kw_v, :theilervert) && (kw_v[:theiler] = kw_v[:theilervert])
204+
haskey(kw_v, :lminvert) && (kw_v[:lmin] = kw_v[:lminvert])
205+
vhist = verticalhistograms(x; lmin=lmin, theiler=theiler, kw_v...)
206+
vertical && (histograms["vertical"] = vhist[1])
207+
recurrencetimes && (histograms["recurrencetimes"] = vhist[2])
208+
end
209+
return histograms
210+
end
211+

0 commit comments

Comments
 (0)