7979# ###############################################################################
8080abstract type AbstractRecurrenceMatrix end
8181const ARM = AbstractRecurrenceMatrix
82+
8283struct RecurrenceMatrix <: AbstractRecurrenceMatrix
8384 data:: SparseMatrixCSC{Bool,Int}
8485end
@@ -133,7 +134,7 @@ Objects of type `<:AbstractRecurrenceMatrix` are displayed as a [`recurrenceplot
133134
134135## Description
135136
136- The recurrence matrix is a numeric representation of a "recurrence plot" [1, 2],
137+ The recurrence matrix is a numeric representation of a "recurrence plot" [^ 1, ^ 2],
137138in the form of a sparse square matrix of Boolean values.
138139
139140`x` must be a `Vector` or a `Dataset` or a `Matrix` with data points in rows
@@ -155,22 +156,26 @@ by the following keyword arguments:
155156 and `scale` is ignored.
156157* `metric="euclidean"` : metric of the distances, either `Metric` or a string,
157158 as in [`distancematrix`](@ref).
159+ * `parallel=false` : whether to parallelize the computation of the recurrence
160+ matrix. This will split the computation of the matrix across multiple threads.
161+ This **may not work on Julia versions before v1.3**, so be warned!
158162
159163See also: [`CrossRecurrenceMatrix`](@ref), [`JointRecurrenceMatrix`](@ref) and
160164use [`recurrenceplot`](@ref) to turn the result of these functions into a plottable format.
161165
162166## References
163- [1] : N. Marwan *et al.*, "Recurrence plots for the analysis of complex systems",
167+ [^ 1] : N. Marwan *et al.*, "Recurrence plots for the analysis of complex systems",
164168*Phys. Reports 438*(5-6), 237-329 (2007).
165169
166- [2] : N. Marwan & C.L. Webber, "Mathematical and computational foundations of
170+ [^ 2] : N. Marwan & C.L. Webber, "Mathematical and computational foundations of
167171recurrence quantifications", in: Webber, C.L. & N. Marwan (eds.), *Recurrence
168172Quantification Analysis. Theory and Best Practices*, Springer, pp. 3-43 (2015).
169173"""
170- function RecurrenceMatrix (x, ε; metric = DEFAULT_METRIC, kwargs... )
174+ function RecurrenceMatrix (x, ε; metric = DEFAULT_METRIC,
175+ parallel:: Bool = length (x) > 500 && Threads. nthreads () > 1 , kwargs... )
171176 m = getmetric (metric)
172177 s = resolve_scale (x, m, ε; kwargs... )
173- m = recurrence_matrix (x, m, s)
178+ m = recurrence_matrix (x, m, s, Val (parallel) )
174179 return RecurrenceMatrix (m)
175180end
176181
@@ -187,10 +192,11 @@ then the cell `(i, j)` of the matrix will have a `true` value.
187192See [`RecurrenceMatrix`](@ref) for details, references and keywords.
188193See also: [`JointRecurrenceMatrix`](@ref).
189194"""
190- function CrossRecurrenceMatrix (x, y, ε; metric = DEFAULT_METRIC, kwargs... )
195+ function CrossRecurrenceMatrix (x, y, ε; metric = DEFAULT_METRIC,
196+ parallel:: Bool = length (x) > 500 && Threads. nthreads () > 1 , kwargs... )
191197 m = getmetric (metric)
192198 s = resolve_scale (x, y, m, ε; kwargs... )
193- m = recurrence_matrix (x, y, m, s)
199+ m = recurrence_matrix (x, y, m, s, Val (parallel) )
194200 return CrossRecurrenceMatrix (m)
195201end
196202
@@ -293,18 +299,28 @@ end
293299# ###############################################################################
294300# recurrence_matrix - Low level interface
295301# ###############################################################################
296- # TODO : increase the efficiency here by not computing everything:
297- recurrence_matrix (x, metric:: Metric , ε:: Real ) =
298- recurrence_matrix (x, x, metric, ε)
302+ # TODO : increase the efficiency here by not computing everything!
303+
304+ """
305+ recurrence_matrix(x, y, metric::Metric, ε::Real, parallel::Val)
306+
307+ Return a sparse matrix which encodes recurrence points.
308+
309+ Note that `parallel` may be either `Val(true)` or `Val(false)`.
310+ """
311+ recurrence_matrix (x, metric:: Metric , ε:: Real , parallel:: Val ) =
312+ recurrence_matrix (x, x, metric, ε, parallel)
299313
300314# Convert Matrices to Datasets (better performance in all cases)
301315function recurrence_matrix (x:: AbstractMatrix , y:: AbstractMatrix ,
302- metric:: Metric , ε)
303- return recurrence_matrix (Dataset (x), Dataset (y), metric, ε)
316+ metric:: Metric , ε, parallel :: Val )
317+ return recurrence_matrix (Dataset (x), Dataset (y), metric, ε, parallel )
304318end
305319
320+ # First, we define the non-parallel versions.
321+
306322# Core function
307- function recurrence_matrix (xx:: Dataset , yy:: Dataset , metric:: Metric , ε)
323+ function recurrence_matrix (xx:: Dataset , yy:: Dataset , metric:: Metric , ε, parallel :: Val{false} )
308324 x = xx. data
309325 y = yy. data
310326 rowvals = Vector {Int} ()
@@ -324,7 +340,7 @@ function recurrence_matrix(xx::Dataset, yy::Dataset, metric::Metric, ε)
324340end
325341
326342# Vector version can be more specialized (and metric is irrelevant)
327- function recurrence_matrix (x:: AbstractVector , y:: AbstractVector , metric, ε)
343+ function recurrence_matrix (x:: AbstractVector , y:: AbstractVector , metric, ε, parallel :: Val{false} )
328344 rowvals = Vector {Int} ()
329345 colvals = Vector {Int} ()
330346 for j in 1 : length (y)
@@ -340,3 +356,59 @@ function recurrence_matrix(x::AbstractVector, y::AbstractVector, metric, ε)
340356 nzvals = fill (true , (length (rowvals),))
341357 return sparse (rowvals, colvals, nzvals, length (x), length (y))
342358end
359+
360+ # Now, we define the parallel versions of these functions.
361+
362+ # Core function
363+ function recurrence_matrix (xx:: Dataset , yy:: Dataset , metric:: Metric , ε, parallel:: Val{true} )
364+ x = xx. data
365+ y = yy. data
366+ # We create an `Array` of `Array`s, for each thread to have its
367+ # own array to push to. This avoids race conditions with
368+ # multiple threads pushing to the same `Array` (`Array`s are not atomic).
369+ rowvals = [Vector {Int} () for _ in 1 : Threads. nthreads ()]
370+ colvals = [Vector {Int} () for _ in 1 : Threads. nthreads ()]
371+
372+ # This is the same logic as the serial function, but parallelized.
373+ Threads. @threads for j in 1 : length (y)
374+ threadn = Threads. threadid ()
375+ nzcol = 0
376+ for i in 1 : length (x)
377+ @inbounds if evaluate (metric, x[i], y[j]) ≤ ε
378+ push! (rowvals[threadn], i) # push to the thread-specific row array
379+ nzcol += 1
380+ end
381+ end
382+ append! (colvals[threadn], fill (j, (nzcol,)))
383+ end
384+ finalrows = vcat (rowvals... ) # merge into one array
385+ finalcols = vcat (colvals... ) # merge into one array
386+ nzvals = fill (true , (length (finalrows),))
387+ return sparse (finalrows, finalcols, nzvals, length (x), length (y))
388+ end
389+
390+ # Vector version can be more specialized (and metric is irrelevant)
391+ function recurrence_matrix (x:: AbstractVector , y:: AbstractVector , metric, ε, parallel:: Val{true} )
392+ # We create an `Array` of `Array`s, for each thread to have its
393+ # own array to push to. This avoids race conditions with
394+ # multiple threads pushing to the same `Array` (`Array`s are not atomic).
395+ rowvals = [Vector {Int} () for _ in 1 : Threads. nthreads ()]
396+ colvals = [Vector {Int} () for _ in 1 : Threads. nthreads ()]
397+
398+ # This is the same logic as the serial function, but parallelized.
399+ Threads. @threads for j in 1 : length (y)
400+ threadn = Threads. threadid ()
401+ nzcol = 0
402+ for i in 1 : length (x)
403+ @inbounds if abs (x[i] - y[j]) ≤ ε
404+ push! (rowvals[threadn], i) # push to the thread-specific row array
405+ nzcol += 1
406+ end
407+ end
408+ append! (colvals[threadn], fill (j, (nzcol,)))
409+ end
410+ finalrows = vcat (rowvals... ) # merge into one array
411+ finalcols = vcat (colvals... ) # merge into one array
412+ nzvals = fill (true , (length (finalrows),))
413+ return sparse (finalrows, finalcols, nzvals, length (x), length (y))
414+ end
0 commit comments