|
| 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