@@ -282,6 +282,86 @@ function _create_irrational(irrational::Irrational, start::Int, res_size::Int,
282282 return T .(input_matrix)
283283end
284284
285+ """
286+ chebyshev_mapping([rng], [T], dims...;
287+ amplitude=one(T), sine_divisor=one(T),
288+ chebyshev_parameter=one(T), return_sparse=true)
289+
290+ Generate a Chebyshev-mapped matrix [^xie2024]. The first row is initialized
291+ using a sine function and subsequent rows are iteratively generated
292+ via the Chebyshev mapping. The first row is defined as:
293+
294+ ```math
295+ w(1, j) = amplitude * sin(j * π / (sine_divisor * n_cols))
296+ ```
297+
298+ for j = 1, 2, …, n_cols (with n_cols typically equal to K+1, where K is the number of input layer neurons).
299+ Subsequent rows are generated by applying the mapping:
300+
301+ ```math
302+ w(i+1, j) = cos(chebyshev_parameter * acos(w(i, j)))
303+ ```
304+
305+ # Arguments
306+
307+ - `rng`: Random number generator. Default is `Utils.default_rng()`
308+ from WeightInitializers.
309+ - `T`: Type of the elements in the reservoir matrix.
310+ Default is `Float32`.
311+ - `dims`: Dimensions of the matrix. Should follow `res_size x in_size`. Here, res_size is assumed to be K+1.
312+
313+ # Keyword arguments
314+
315+ - `amplitude`: Scaling factor used to initialize the first row.
316+ This parameter adjusts the amplitude of the sine function. Default value is one.
317+ - `sine_divisor`: Divisor applied in the sine function's phase. Default value is one.
318+ - `chebyshev_parameter`: Control parameter for the Chebyshev mapping in
319+ subsequent rows. This parameter influences the distribution of the
320+ matrix elements. Default is one.
321+ - `return_sparse`: If `true`, the function returns the matrix as a sparse matrix. Default is `false`.
322+
323+ # Examples
324+
325+ ```jldoctest
326+ julia> input_matrix = chebyshev_mapping(10, 3)
327+ 10×3 Matrix{Float32}:
328+ 0.866025 0.866025 1.22465f-16
329+ 0.866025 0.866025 -4.37114f-8
330+ 0.866025 0.866025 -4.37114f-8
331+ 0.866025 0.866025 -4.37114f-8
332+ 0.866025 0.866025 -4.37114f-8
333+ 0.866025 0.866025 -4.37114f-8
334+ 0.866025 0.866025 -4.37114f-8
335+ 0.866025 0.866025 -4.37114f-8
336+ 0.866025 0.866025 -4.37114f-8
337+ 0.866025 0.866025 -4.37114f-8
338+ ```
339+
340+ [^xie2024]: Xie, Minzhi, Qianxue Wang, and Simin Yu.
341+ "Time Series Prediction of ESN Based on Chebyshev Mapping and Strongly
342+ Connected Topology."
343+ Neural Processing Letters 56.1 (2024): 30.
344+ """
345+ function chebyshev_mapping (rng:: AbstractRNG , :: Type{T} , dims:: Integer... ;
346+ amplitude:: AbstractFloat = one (T), sine_divisor:: AbstractFloat = one (T),
347+ chebyshev_parameter:: AbstractFloat = one (T),
348+ return_sparse:: Bool = false ) where {T <: Number }
349+ input_matrix = DeviceAgnostic. zeros (rng, T, dims... )
350+ n_rows, n_cols = dims[1 ], dims[2 ]
351+
352+ for idx_cols in 1 : n_cols
353+ input_matrix[1 , idx_cols] = amplitude * sin (idx_cols * pi / (sine_divisor * n_cols))
354+ end
355+ for idx_rows in 2 : n_rows
356+ for idx_cols in 1 : n_cols
357+ input_matrix[idx_rows, idx_cols] = cos (chebyshev_parameter * acos (input_matrix[
358+ idx_rows - 1 , idx_cols]))
359+ end
360+ end
361+
362+ return return_sparse ? sparse (input_matrix) : input_matrix
363+ end
364+
285365# ## reservoirs
286366
287367"""
@@ -672,11 +752,118 @@ function get_sparsity(M, dim)
672752 return size (M[M .!= 0 ], 1 ) / (dim * dim - size (M[M .!= 0 ], 1 )) # nonzero/zero elements
673753end
674754
755+ function digital_chaotic_adjacency (rng:: AbstractRNG , bit_precision:: Integer ;
756+ extra_edge_probability:: AbstractFloat = 0.1 )
757+ matrix_order = 2 ^ (2 * bit_precision)
758+ adjacency_matrix = zeros (Int, matrix_order, matrix_order)
759+ for row_index in 1 : (matrix_order - 1 )
760+ adjacency_matrix[row_index, row_index + 1 ] = 1
761+ end
762+ adjacency_matrix[matrix_order, 1 ] = 1
763+ for row_index in 1 : matrix_order, column_index in 1 : matrix_order
764+ if row_index != column_index && rand (rng) < extra_edge_probability
765+ adjacency_matrix[row_index, column_index] = 1
766+ end
767+ end
768+
769+ return adjacency_matrix
770+ end
771+
772+ """
773+ chaotic_init([rng], [T], dims...;
774+ extra_edge_probability=T(0.1), spectral_radius=one(T),
775+ return_sparse_matrix=true)
776+
777+ Construct a chaotic reservoir matrix using a digital chaotic system [^xie2024].
778+
779+ The matrix topology is derived from a strongly connected adjacency
780+ matrix based on a digital chaotic system operating at finite precision.
781+ If the requested matrix order does not exactly match a valid order the
782+ closest valid order is used.
783+
784+ # Arguments
785+
786+ - `rng`: Random number generator. Default is `Utils.default_rng()`
787+ from WeightInitializers.
788+ - `T`: Type of the elements in the reservoir matrix.
789+ Default is `Float32`.
790+ - `dims`: Dimensions of the reservoir matrix.
791+
792+ # Keyword arguments
793+
794+ - `extra_edge_probability`: Probability of adding extra random edges in
795+ the adjacency matrix to enhance connectivity. Default is 0.1.
796+ - `desired_spectral_radius`: The target spectral radius for the
797+ reservoir matrix. Default is one.
798+ - `return_sparse_matrix`: If `true`, the function returns the
799+ reservoir matrix as a sparse matrix. Default is `true`.
800+
801+ # Examples
802+
803+ ```jldoctest
804+ julia> res_matrix = chaotic_init(8, 8)
805+ ┌ Warning:
806+ │
807+ │ Adjusting reservoir matrix order:
808+ │ from 8 (requested) to 4
809+ │ based on computed bit precision = 1.
810+ │
811+ └ @ ReservoirComputing ~/.julia/dev/ReservoirComputing/src/esn/esn_inits.jl:805
812+ 4×4 SparseArrays.SparseMatrixCSC{Float32, Int64} with 6 stored entries:
813+ ⋅ -0.600945 ⋅ ⋅
814+ ⋅ ⋅ 0.132667 2.21354
815+ ⋅ -2.60383 ⋅ -2.90391
816+ -0.578156 ⋅ ⋅ ⋅
817+ ```
818+
819+ [^xie2024]: Xie, Minzhi, Qianxue Wang, and Simin Yu.
820+ "Time Series Prediction of ESN Based on Chebyshev Mapping and Strongly
821+ Connected Topology."
822+ Neural Processing Letters 56.1 (2024): 30.
823+ """
824+ function chaotic_init (rng:: AbstractRNG , :: Type{T} , dims:: Integer... ;
825+ extra_edge_probability:: AbstractFloat = T (0.1 ), spectral_radius:: AbstractFloat = one (T),
826+ return_sparse_matrix:: Bool = true ) where {T <: Number }
827+ requested_order = first (dims)
828+ if length (dims) > 1 && dims[2 ] != requested_order
829+ @warn """ \n
830+ Using dims[1] = $requested_order for the chaotic reservoir matrix order.\n
831+ """
832+ end
833+ d_estimate = log2 (requested_order) / 2
834+ d_floor = max (floor (Int, d_estimate), 1 )
835+ d_ceil = ceil (Int, d_estimate)
836+ candidate_order_floor = 2 ^ (2 * d_floor)
837+ candidate_order_ceil = 2 ^ (2 * d_ceil)
838+ chosen_bit_precision = abs (candidate_order_floor - requested_order) <=
839+ abs (candidate_order_ceil - requested_order) ? d_floor : d_ceil
840+ actual_matrix_order = 2 ^ (2 * chosen_bit_precision)
841+ if actual_matrix_order != requested_order
842+ @warn """ \n
843+ Adjusting reservoir matrix order:
844+ from $requested_order (requested) to $actual_matrix_order
845+ based on computed bit precision = $chosen_bit_precision . \n
846+ """
847+ end
848+
849+ random_weight_matrix = T (2 ) * rand (rng, T, actual_matrix_order, actual_matrix_order) .-
850+ T (1 )
851+ adjacency_matrix = digital_chaotic_adjacency (
852+ rng, chosen_bit_precision; extra_edge_probability= extra_edge_probability)
853+ reservoir_matrix = random_weight_matrix .* adjacency_matrix
854+ current_spectral_radius = maximum (abs, eigvals (reservoir_matrix))
855+ if current_spectral_radius != 0
856+ reservoir_matrix .*= spectral_radius / current_spectral_radius
857+ end
858+
859+ return return_sparse_matrix ? sparse (reservoir_matrix) : reservoir_matrix
860+ end
861+
675862# ## fallbacks
676863# fallbacks for initializers #eventually to remove once migrated to WeightInitializers.jl
677864for initializer in (:rand_sparse , :delay_line , :delay_line_backward , :cycle_jumps ,
678- :simple_cycle , :pseudo_svd ,
679- :scaled_rand , :weighted_init , :informed_init , :minimal_init )
865+ :simple_cycle , :pseudo_svd , :chaotic_init ,
866+ :scaled_rand , :weighted_init , :informed_init , :minimal_init , :chebyshev_mapping )
680867 @eval begin
681868 function ($ initializer)(dims:: Integer... ; kwargs... )
682869 return $ initializer (Utils. default_rng (), Float32, dims... ; kwargs... )
0 commit comments