Skip to content

Commit df0a9a9

Browse files
authored
Support for recurrence networks based on LightGraphs (#112)
* add SimpleGraph and SimpleDiGraph * add Matrix constructores * interface for SimpleGraphs from RP * remove option to keep diagonal in SimpleGraph * Revert "remove option to keep diagonal in SimpleGraph" This reverts commit d365ec1. * fix calculation of SimpleGraph * rna function, do not export LightGraph names * v1.5 * add LightGraphs in tests * fix diameter
1 parent de51940 commit df0a9a9

File tree

11 files changed

+192
-73
lines changed

11 files changed

+192
-73
lines changed

Project.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
name = "RecurrenceAnalysis"
22
uuid = "639c3291-70d9-5ea2-8c5b-839eba1ee399"
33
repo = "https://github.com/JuliaDynamics/RecurrenceAnalysis.jl.git"
4-
version = "1.4.0"
4+
version = "1.5.0"
55

66
[deps]
77
DelayEmbeddings = "5732040d-69e3-5649-938a-b6b4f237613f"
88
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
99
Distances = "b4f34e82-e78d-54a5-968a-f98e89d6e8f7"
10+
LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d"
1011
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1112
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
1213
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
@@ -17,16 +18,18 @@ UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228"
1718
[compat]
1819
DelayEmbeddings = "1.17, 1.18, 1.19"
1920
Distances = "0.8, 0.9, 0.10"
21+
LightGraphs = "1"
2022
StaticArrays = "0.8,0.9,0.10,0.11,0.12, 1.0"
2123
UnicodePlots = "0.3,1"
2224
julia = "1.5"
2325

2426
[extras]
2527
DynamicalSystemsBase = "6e36e845-645a-534a-86f2-f5d4aa5a06b4"
28+
LightGraphs = "093fc24a-ae57-5d10-9952-331d41423f4d"
2629
Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c"
2730
SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf"
2831
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
2932
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
3033

3134
[targets]
32-
test = ["Statistics", "Test", "SparseArrays", "Random", "DynamicalSystemsBase"]
35+
test = ["Statistics", "Test", "SparseArrays", "Random", "DynamicalSystemsBase", "LightGraphs"]

src/RecurrenceAnalysis.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,15 +45,19 @@ export embed,
4545
nmprt,
4646
rqa,
4747
sorteddistances,
48-
transitivity,
49-
@windowed
48+
@windowed,
49+
rna,
50+
# deprecated:
51+
transitivity
5052

5153
include("distance_matrix.jl")
5254
include("matrices.jl")
5355
include("plot.jl")
5456
include("histograms.jl")
5557
include("rqa.jl")
5658
include("radius.jl")
59+
include("graphs.jl")
60+
include("rna.jl")
5761
include("windowed.jl")
5862
include("deprecate.jl")
5963

src/deprecate.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,20 @@ function rqa(::Type{NamedTuple}, R; onlydiagonal=false, kwargs...)
6464
)
6565
end
6666
end
67+
68+
function transitivity(R::AbstractRecurrenceMatrix)
69+
@warn "`transitivity(x::AbstractRecurrenceMatrix)` is deprecated`, use `rna` to analyse network parameters"
70+
if size(R, 1) size(R, 2)
71+
@warn "Computing network transitivity of a non-square adjacency matrix is impossible"
72+
return NaN
73+
end
74+
= R.data * R.data
75+
numerator = zero(eltype(R²))
76+
for col = 1:size(R,2)
77+
rows = view(rowvals(R), nzrange(R,col))
78+
for r = rows
79+
numerator += R²[r, col]
80+
end
81+
end
82+
trans = numerator / (sum(R²) - LinearAlgebra.tr(R²))
83+
end

src/graphs.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using LightGraphs
2+
3+
"""
4+
SimpleGraph(R::AbstractRecurrenceMatrix)
5+
6+
Create a recurrent network as a `SimpleGraph`, from the symmetric recurrence matrix `R`.
7+
That matrix is typically the result of calculating a [`RecurrenceMatrix`](@ref)
8+
or a [`JointRecurrenceMatrix`](@ref).
9+
10+
The recurrence structure of `R` is interpreted as the adjacency matrix of an
11+
undirected complex network, where two different vertices are connected if they are
12+
neighbors in the embedded phase space, i.e.
13+
14+
```math
15+
A_{i,j} = R_{i,j} - \\delta_{i,j}
16+
```
17+
18+
Following this definition, diagonal points of `R` are ommited, i.e.
19+
the graph does not contain self-connected nodes.
20+
21+
See the package [LightGraphs.jl](https://juliagraphs.org/LightGraphs.jl/stable/)
22+
for further options to work with `SimpleGraph` objects, besides the functions
23+
for Recurrence Network Analysis provided in this package.
24+
25+
# References
26+
27+
[1] : R.V. Donner *et al.* "Recurrence networks — a novel paradigm for nonlinear time series analysis",
28+
*New Journal of Physics* 12, 033025 (2010).
29+
30+
[2] : R.V. Donner *et al.* "Complex Network Analysis of Recurrences", in:
31+
Webber, C.L. & Marwan N. (eds.) *Recurrence Quantification Analysis.
32+
Theory and Best Practices*, Springer, pp. 101-165 (2015).
33+
"""
34+
function LightGraphs.SimpleGraphs.SimpleGraph(R::AbstractRecurrenceMatrix)
35+
graph = SimpleGraph(R.data)
36+
delta = SimpleGraphFromIterator(Edge(v,v) for v=1:size(graph, 1))
37+
graph = difference(graph, delta)
38+
return graph
39+
end

src/matrices.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ function colvals(x::SparseMatrixCSC)
5959
end
6060
colvals(x::ARM) = colvals(x.data)
6161

62+
# Convert to matrices
63+
Base.Array{T}(R::ARM) where T = Matrix{T}(R.data)
64+
Base.Matrix{T}(R::ARM) where T = Matrix{T}(R.data)
65+
Base.Array(R::ARM) = Matrix(R.data)
66+
Base.Matrix(R::ARM) = Matrix(R.data)
67+
SparseArrays.SparseMatrixCSC{T}(R::ARM) where T = SparseMatrixCSC{T}(R.data)
68+
SparseArrays.SparseMatrixCSC(R::ARM) = SparseMatrixCSC(R.data)
6269

6370
"""
6471
RecurrenceMatrix(x, ε; kwargs...)
@@ -209,6 +216,7 @@ JointRecurrenceMatrix(args...; kwargs...) = JointRecurrenceMatrix{WithinRange}(a
209216

210217
"""
211218
JointRecurrenceMatrix(R1, R2; kwargs...)
219+
212220
Create a joint recurrence matrix from given recurrence matrices `R1, R2`.
213221
"""
214222
function JointRecurrenceMatrix(

src/rna.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
rna(R)
3+
rna(args...; kwargs...)
4+
5+
Calculate a set of Recurrence Network parameters.
6+
The input `R` can be a symmetric recurrence matrix that is
7+
interpreted as the adjacency matrix of an undirected complex network,
8+
such that linked vertices are neighboring points in the phase space.
9+
10+
Alternatively, the inputs can be a graph object or any
11+
valid inputs to the `SimpleGraph` constructor of the
12+
[LightGraphs](https://github.com/JuliaGraphs/LightGraphs.jl) package.
13+
14+
## Return
15+
16+
The returned value is a dictionary that contains the following entries,
17+
with the corresponding global network properties[^1][^2]:
18+
* `:density`: edge density, approximately equivalent to the global recurrence rate in the phase space.
19+
* `:transitivity`: network transitivity, which describes the
20+
global clustering of points following Barrat's and Weigt's formulation [^3].
21+
* `:averagepath`: mean value of the shortest path lengths taken over
22+
all pairs of connected vertices, related to the average separation
23+
between points in the phase.
24+
* `:diameter`: maximum value of the shortest path lengths between
25+
pairs of connected vertices, related to the phase space diameter.
26+
27+
## References
28+
29+
[^1]: R.V. Donner *et al.* ["Recurrence networks — a novel paradigm for nonlinear time series analysis",
30+
*New Journal of Physics* 12, 033025 (2010)](https://doi.org/10.1088/1367-2630/12/3/033025)
31+
32+
[^2]: R.V. Donner *et al.*, [The geometry of chaotic dynamics — a complex network perspective,
33+
*Eur. Phys. J.* B 84, 653–672 (2011)](https://doi.org/10.1140/epjb/e2011-10899-1)
34+
35+
[^3]: A. Barrat & M. Weight, ["On the properties of small-world network models",
36+
*The European Physical Journal B* 13, 547–560 (2000)](https://doi.org/10.1007/s100510050067)
37+
"""
38+
function rna(args...; kwargs...)
39+
graph = SimpleGraph(args...; kwargs...)
40+
return Dict{Symbol, Float64}(
41+
:density => density(graph),
42+
:transitivity => global_clustering_coefficient(graph),
43+
:averagepath => mean(1 ./ closeness_centrality(graph)),
44+
:diameter => diameter(graph)
45+
)
46+
end

src/rqa.jl

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -76,59 +76,6 @@ function _rrdenominator(R::M; theiler=0, kwargs...) where
7676
return k*(k+1)
7777
end
7878

79-
"""
80-
transitivity(R::AbstractRecurrenceMatrix) → T
81-
Returns the network transitivity `T` of the ε-recurrence network `R`. Here the
82-
recurrence plot `R` is identified as the network adjacency matrix `A`.
83-
84-
## Description
85-
86-
We quote from [^Donner2011], where the authors provide a complete description:
87-
Transitivity is related to fundamental algebraic relationships between triples
88-
of discrete objects. Specifically, in graph-theoretical terms, we identify the
89-
set `X` with the set of vertices `V` , and the relation `R` with the mutual
90-
adjacency of pairs of vertices. Hence, for a given vertex `i ∈ V` , transitivity
91-
refers to the fact that for two other vertices `j, k ∈ V` with
92-
`A_ij = A_ik = 1, A_jk = 1` also holds. In a general network, this is typically
93-
not the case for all vertices. Consequently, characterising the degree of
94-
transitivity (or, alternatively, the relative frequency of closed 3-loops, which
95-
are commonly referred to as triangles) with respect to
96-
the whole network provides important information on the structural graph
97-
properties, which may be related to important general features of the underlying
98-
system.
99-
100-
The network transitivity measures the fraction of closed triangles with respect to
101-
the number of linked triples of vertices in the whole network:
102-
103-
```math
104-
\\mathcal{T} = \\frac{3 \\times \\textrm{number of triangles in the network}}{\\textrm{number of linked triples of vertices}}
105-
```
106-
or given the adjacency matrix `A`
107-
```math
108-
\\mathcal{T} = \\frac{trace(A^3)}{\\sum(A^2) - trace(A^2)}
109-
```
110-
111-
## References
112-
113-
[^Donner2011]: R.V. Donner *et al.*, [The geometry of chaotic dynamics — a complex network perspective, Eur. Phys. J. B 84, 653–672 (2011)](https://doi.org/10.1140/epjb/e2011-10899-1)
114-
"""
115-
function transitivity(R::ARM)
116-
if size(R, 1) size(R, 2)
117-
@warn "Computing network transitivity of a non-square adjacency matrix is impossible"
118-
return NaN
119-
end
120-
= R.data * R.data
121-
numerator = zero(eltype(R²))
122-
for col = 1:size(R,2)
123-
rows = view(rowvals(R), nzrange(R,col))
124-
for r = rows
125-
numerator += R²[r, col]
126-
end
127-
end
128-
trans = numerator / (sum(R²) - LinearAlgebra.tr(R²))
129-
end
130-
131-
13279
###########################################################################################
13380
# 0. Histograms
13481
###########################################################################################
@@ -444,7 +391,6 @@ The returned value contains the following entries,
444391
which can be retrieved as from a dictionary (e.g. `results[:RR]`, etc.):
445392
446393
* `:RR`: recurrence rate (see [`recurrencerate`](@ref))
447-
* `:TRANS`: transitivity (see [`transitivity`](@ref))
448394
* `:DET`: determinsm (see [`determinism`](@ref))
449395
* `:L`: average length of diagonal structures (see [`dl_average`](@ref))
450396
* `:Lmax`: maximum length of diagonal structures (see [`dl_max`](@ref))
@@ -466,7 +412,6 @@ less than the keyword `lminvert`) the average and maximum values
466412
(`:L`, `:Lmax`, `:TT`, `:Vmax`, `:MRT`)
467413
are returned as `0.0` but their respective entropies (`:ENTR`, `:VENTR`, `:RTE`)
468414
are returned as `NaN`.
469-
`:TRANS` is only returned when the input is a square matrix.
470415
471416
## Keyword Arguments
472417
@@ -567,9 +512,6 @@ function rqa(::Type{Dict}, R; onlydiagonal=false, kwargs...)
567512
:RTE => _rt_entropy(rthist),
568513
:NMPRT => maximum(rthist)
569514
)
570-
if size(R, 1) == size(R, 2)
571-
rqa_dict[:TRANS] = transitivity(R)
572-
end
573515
return rqa_dict
574516
end
575517
end

src/windowed.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
const rqa_funs = [
22
:recurrencerate,
3-
:transitivity,
43
:determinism,
54
:dl_average,
65
:dl_max,

test/deprecations.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ v = Dataset(rand(10, 2))
2121
@test_logs (:warn, "`JointRecurrenceMatrix{WithinRange}(x::AbstractMatrix, y, ε; kwargs...)` is deprecated, use `JointRecurrenceMatrix{WithinRange}(Dataset(x), y, ε; kwargs...)`") JointRecurrenceMatrix(xm, v, 0.5)
2222
@test_logs (:warn, "`JointRecurrenceMatrix{WithinRange}(x, y::AbstractMatrix, ε; kwargs...)` is deprecated, use `JointRecurrenceMatrix{WithinRange}(x, Dataset(y), ε; kwargs...)`") JointRecurrenceMatrix(v, ym, 0.5)
2323
@test_logs (:warn, "`JointRecurrenceMatrix{WithinRange}(x::AbstractMatrix, y::AbstractMatrix, ε; kwargs...)` is deprecated, use `JointRecurrenceMatrix{WithinRange}(Dataset(x), Dataset(y), ε; kwargs...)`") JointRecurrenceMatrix(xm, ym, 0.5)
24+
# transitivity
25+
@test_logs (:warn, "`transitivity(x::AbstractRecurrenceMatrix)` is deprecated`, use `rna` to analyse network parameters") transitivity(R)
2426
end

test/dynamicalsystems.jl

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using RecurrenceAnalysis
22
using DynamicalSystemsBase, Random, Statistics, SparseArrays
3+
using LightGraphs, LinearAlgebra
34
using Test
45

56
RA = RecurrenceAnalysis
@@ -48,7 +49,7 @@ dict_keys = ["Sine wave","White noise","Hénon (chaotic)","Hénon (periodic)"]
4849
ε = rqa_threshold[k]
4950
dmat = distancematrix(xe, ye)
5051
crmat = CrossRecurrenceMatrix(xe, ye, ε)
51-
@test Matrix(crmat.data) == (dmat .≤ ε)
52+
@test Matrix(crmat) == (dmat .≤ ε)
5253
rmat = RecurrenceMatrix(xe, ε; parallel = false)
5354
rmat_p = RecurrenceMatrix(xe, ε; parallel = true)
5455
jrmat = JointRecurrenceMatrix(xe, ye, ε; parallel = false)
@@ -117,4 +118,10 @@ dict_keys = ["Sine wave","White noise","Hénon (chaotic)","Hénon (periodic)"]
117118
@windowed rqaw = rqa(rmatw) width=50 step=40
118119
@test rqaw[:RR] == rrw
119120

121+
# Recurrence networks
122+
graph = SimpleGraph(rmat)
123+
amat = adjacency_matrix(graph)
124+
@test amat == Matrix(rmat) - I
125+
rna_dict = rna(rmat)
126+
rna_dict[:density] == recurrencerate(rmat)
120127
end

0 commit comments

Comments
 (0)