diff --git a/docs/make.jl b/docs/make.jl index 99e06512..3d928d49 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,13 +6,17 @@ cp("./docs/Project.toml", "./docs/src/assets/Project.toml"; force=true) ENV["PLOTS_TEST"] = "true" ENV["GKSwstype"] = "100" include("pages.jl") +mathengine = Documenter.MathJax() makedocs(; modules=[ReservoirComputing], sitename="ReservoirComputing.jl", clean=true, doctest=false, linkcheck=true, - format=Documenter.HTML(; assets=["assets/favicon.ico"], + format=Documenter.HTML(; + mathengine, + assets=["assets/favicon.ico"], canonical="https://docs.sciml.ai/ReservoirComputing/stable/"), - pages=pages) + pages=pages +) deploydocs(; repo="github.com/SciML/ReservoirComputing.jl.git", push_preview=true) diff --git a/src/states.jl b/src/states.jl index d240ff34..9f78d993 100644 --- a/src/states.jl +++ b/src/states.jl @@ -16,190 +16,639 @@ end """ StandardStates() -When this struct is employed, the states of the reservoir are not modified. It represents the default behavior -in scenarios where no specific state modification is required. This approach is ideal for applications -where the inherent dynamics of the reservoir are sufficient, and no external manipulation of the states -is necessary. It maintains the original state representation, ensuring that the reservoir's natural properties -are preserved and utilized in computations. +When this struct is employed, the states of the reservoir are not modified. + +# Example + +```jldoctest +julia> states = StandardStates() +StandardStates() + +julia> test_vec = zeros(Float32, 5) +5-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + +julia> new_vec = states(test_vec) +5-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + +julia> test_mat = zeros(Float32, 5, 5) +5×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + +julia> new_mat = states(test_mat) +5×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 +``` """ struct StandardStates <: AbstractStates end +function (::StandardStates)(nla_type::NonLinearAlgorithm, + state, inp) + return nla(nla_type, state) +end + +(::StandardStates)(state) = state """ ExtendedStates() -The `ExtendedStates` struct is used to extend the reservoir states by -vertically concatenating the input data (during training) and the prediction data (during the prediction phase). -This method enriches the state representation by integrating external data, enhancing the model's capability -to capture and utilize complex patterns in both training and prediction stages. +The `ExtendedStates` struct is used to extend the reservoir +states by vertically concatenating the input data (during training) +and the prediction data (during the prediction phase). + +# Example + +```jldoctest +julia> states = ExtendedStates() +ExtendedStates() + +julia> test_vec = zeros(Float32, 5) +5-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + +julia> new_vec = states(test_vec, fill(3.0f0, 3)) +8-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 3.0 + 3.0 + 3.0 + +julia> test_mat = zeros(Float32, 5, 5) +5×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + +julia> new_mat = states(test_mat, fill(3.0f0, 3)) +8×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 3.0 3.0 3.0 3.0 3.0 + 3.0 3.0 3.0 3.0 3.0 + 3.0 3.0 3.0 3.0 3.0 +``` """ struct ExtendedStates <: AbstractStates end -struct PaddedStates{T} <: AbstractPaddedStates - padding::T +function (states_type::ExtendedStates)(mat::AbstractMatrix, inp::AbstractMatrix) + results = states_type.(eachcol(mat), eachcol(inp)) + return hcat(results...) end -struct PaddedExtendedStates{T} <: AbstractPaddedStates - padding::T +function (states_type::ExtendedStates)(mat::AbstractMatrix, inp::AbstractVector) + results = Vector{Vector{eltype(mat)}}(undef, size(mat, 2)) + for (idx, col) in enumerate(eachcol(mat)) + results[idx] = states_type(col, inp) + end + return hcat(results...) +end + +function (::ExtendedStates)(vect::AbstractVector, inp::AbstractVector) + return x_tmp = vcat(vect, inp) +end + +function (states_type::ExtendedStates)(nla_type::NonLinearAlgorithm, + state::AbstractVecOrMat, inp::AbstractVecOrMat) + return nla(nla_type, states_type(state, inp)) end """ PaddedStates(padding) PaddedStates(;padding=1.0) -Creates an instance of the `PaddedStates` struct with specified padding value. -This padding is typically set to 1.0 by default but can be customized. -The states of the reservoir are padded by vertically concatenating this padding value, -enhancing the dimensionality and potentially improving the performance of the reservoir computing model. -This function is particularly useful in scenarios where adding a constant baseline to the states is necessary -for the desired computational task. +Creates an instance of the `PaddedStates` struct with specified +padding value (default 1.0). The states of the reservoir are padded +by vertically concatenating the padding value. + +# Example + +```jldoctest +julia> states = PaddedStates(1.0) +PaddedStates{Float64}(1.0) + +julia> test_vec = zeros(Float32, 5) +5-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + +julia> new_vec = states(test_vec) +6-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 1.0 + +julia> test_mat = zeros(Float32, 5, 5) +5×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + +julia> new_mat = states(test_mat) +6×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 1.0 1.0 1.0 1.0 1.0 +``` """ +struct PaddedStates{T} <: AbstractPaddedStates + padding::T +end + function PaddedStates(; padding=1.0) return PaddedStates(padding) end +function (states_type::PaddedStates)(mat::AbstractMatrix) + results = states_type.(eachcol(mat)) + return hcat(results...) +end + +function (states_type::PaddedStates)(vect::AbstractVector) + tt = eltype(vect) + return vcat(vect, tt(states_type.padding)) +end + +function (states_type::PaddedStates)(nla_type::NonLinearAlgorithm, + state::AbstractVecOrMat, inp::AbstractVecOrMat) + return nla(nla_type, states_type(state)) +end + """ PaddedExtendedStates(padding) PaddedExtendedStates(;padding=1.0) -Constructs a `PaddedExtendedStates` struct, which first extends the reservoir states with training or prediction data, -then pads them with a specified value (defaulting to 1.0). This process is achieved through vertical concatenation, -combining the padding value, data, and states. -This function is particularly useful for enhancing the reservoir's state representation in more complex scenarios, -where both extended contextual information and consistent baseline padding are crucial for the computational -effectiveness of the reservoir computing model. +Constructs a `PaddedExtendedStates` struct, which first extends +the reservoir states with training or prediction data,then pads them +with a specified value (defaulting to 1.0). + +# Example + +```jldoctest +julia> states = PaddedExtendedStates(1.0) +PaddedExtendedStates{Float64}(1.0) + +julia> test_vec = zeros(Float32, 5) +5-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + +julia> new_vec = states(test_vec, fill(3.0f0, 3)) +9-element Vector{Float32}: + 0.0 + 0.0 + 0.0 + 0.0 + 0.0 + 1.0 + 3.0 + 3.0 + 3.0 + +julia> test_mat = zeros(Float32, 5, 5) +5×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + +julia> new_mat = states(test_mat, fill(3.0f0, 3)) +9×5 Matrix{Float32}: + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 0.0 0.0 0.0 0.0 0.0 + 1.0 1.0 1.0 1.0 1.0 + 3.0 3.0 3.0 3.0 3.0 + 3.0 3.0 3.0 3.0 3.0 + 3.0 3.0 3.0 3.0 3.0 +``` """ +struct PaddedExtendedStates{T} <: AbstractPaddedStates + padding::T +end + function PaddedExtendedStates(; padding=1.0) return PaddedExtendedStates(padding) end -#functions of the states to apply modifications -function (::StandardStates)(nla_type, x, y) - return nla(nla_type, x) +function (states_type::PaddedExtendedStates)(nla_type::NonLinearAlgorithm, + state::AbstractVecOrMat, inp::AbstractVecOrMat) + return nla(nla_type, states_type(state, inp)) end -function (::ExtendedStates)(nla_type, x, y) - x_tmp = vcat(y, x) - return nla(nla_type, x_tmp) +function (states_type::PaddedExtendedStates)(state::AbstractVecOrMat, + inp::AbstractVecOrMat) + x_pad = PaddedStates(states_type.padding)(state) + x_ext = ExtendedStates()(x_pad, inp) + return x_ext end -#check matrix/vector -function (states_type::PaddedStates)(nla_type, x, y) - tt = typeof(first(x)) - x_tmp = vcat(fill(tt(states_type.padding), (1, size(x, 2))), x) - #x_tmp = reduce(vcat, x_tmp) - return nla(nla_type, x_tmp) -end +#### non linear algorithms ### +## to conform to current (0.10.5) approach +nla(nlat::NonLinearAlgorithm, x_old::AbstractVecOrMat) = nlat(x_old) -#check matrix/vector -function (states_type::PaddedExtendedStates)(nla_type, x, y) - tt = typeof(first(x)) - x_tmp = vcat(y, x) - x_tmp = vcat(fill(tt(states_type.padding), (1, size(x, 2))), x_tmp) - #x_tmp = reduce(vcat, x_tmp) - return nla(nla_type, x_tmp) +# dispatch over matrices for all nonlin algorithms +function (nlat::NonLinearAlgorithm)(x_old::AbstractMatrix) + results = nlat.(eachcol(x_old)) + return hcat(results...) end -#non linear algorithms """ NLADefault() `NLADefault` represents the default non-linear algorithm option. When used, it leaves the input array unchanged. -This option is suitable in cases where no non-linear transformation of the data is required, -maintaining the original state of the input array for further processing. -It's the go-to choice for preserving the raw data integrity within the computational pipeline -of the reservoir computing model. + +# Example + +```jldoctest +julia> nlat = NLADefault() +NLADefault() + +julia> x_old = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +10-element Vector{Int64}: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + +julia> n_new = nlat(x_old) +10-element Vector{Int64}: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + +julia> mat_old = [1 2 3; + 4 5 6; + 7 8 9; + 10 11 12; + 13 14 15; + 16 17 18; + 19 20 21] +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + 10 11 12 + 13 14 15 + 16 17 18 + 19 20 21 + +julia> mat_new = nlat(mat_old) +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + 10 11 12 + 13 14 15 + 16 17 18 + 19 20 21 +``` """ struct NLADefault <: NonLinearAlgorithm end -function nla(::NLADefault, x) - return x -end +(::NLADefault)(x::AbstractVector) = x +(::NLADefault)(x::AbstractMatrix) = x -""" +@doc raw""" NLAT1() -`NLAT1` implements the T₁ transformation algorithm introduced in [^Chattopadhyay] and [^Pathak]. -The T₁ algorithm selectively squares elements of the input array, -specifically targeting every second row. This non-linear transformation enhances certain data characteristics, -making it a valuable tool in analyzing chaotic systems and improving the performance of reservoir computing models. -The T₁ transformation's uniqueness lies in its selective approach, allowing for a more nuanced manipulation of the input data. - -References: +`NLAT1` implements the T₁ transformation algorithm introduced +in [^Chattopadhyay] and [^Pathak]. The T₁ algorithm squares +elements of the input array, targeting every second row. + + +```math +\tilde{r}_{i,j} = +\begin{cases} + r_{i,j} \times r_{i,j}, & \text{if } j \text{ is odd}; \\ + r_{i,j}, & \text{if } j \text{ is even}. +\end{cases} +``` +# Example + +```jldoctest +julia> nlat = NLAT1() +NLAT1() + +julia> x_old = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +10-element Vector{Int64}: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + +julia> n_new = nlat(x_old) +10-element Vector{Int64}: + 0 + 1 + 4 + 3 + 16 + 5 + 36 + 7 + 64 + 9 + +julia> mat_old = [1 2 3; + 4 5 6; + 7 8 9; + 10 11 12; + 13 14 15; + 16 17 18; + 19 20 21] +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + 10 11 12 + 13 14 15 + 16 17 18 + 19 20 21 + +julia> mat_new = nlat(mat_old) +7×3 Matrix{Int64}: + 1 4 9 + 4 5 6 + 49 64 81 + 10 11 12 + 169 196 225 + 16 17 18 + 361 400 441 + +``` [^Chattopadhyay]: Chattopadhyay, Ashesh, et al. "Data-driven prediction of a multi-scale Lorenz 96 chaotic system using a - hierarchy of deep learning methods: Reservoir computing, ANN, and RNN-LSTM." (2019). + hierarchy of deep learning methods: Reservoir computing, ANN, and RNN-LSTM." + (2019). + [^Pathak]: Pathak, Jaideep, et al. - "Model-free prediction of large spatiotemporally chaotic systems from data: - A reservoir computing approach." + "Model-free prediction of large spatiotemporally chaotic systems + from data: A reservoir computing approach." Physical review letters 120.2 (2018): 024102. """ struct NLAT1 <: NonLinearAlgorithm end -function nla(::NLAT1, x_old) +function (::NLAT1)(x_old::AbstractVector) x_new = copy(x_old) - for i in 1:size(x_new, 1) - if mod(i, 2) != 0 - x_new[i, :] = copy(x_old[i, :] .* x_old[i, :]) + + for idx in eachindex(x_old) + if isodd(idx) + x_new[idx] = x_old[idx] * x_old[idx] end end return x_new end -""" +@doc raw""" NLAT2() -`NLAT2` implements the T₂ transformation algorithm as defined in [^Chattopadhyay]. -This transformation algorithm modifies the reservoir states by multiplying each odd-indexed -row (starting from the second row) with the product of its two preceding rows. -This specific approach to non-linear transformation is useful for capturing and -enhancing complex patterns in the data, particularly beneficial in the analysis of chaotic -systems and in improving the dynamics within reservoir computing models. - -Reference: +`NLAT2` implements the T₂ transformation algorithm as defined +in [^Chattopadhyay]. This transformation algorithm modifies the +reservoir states by multiplying each odd-indexed row +(starting from the second row) with the product of its two preceding rows. + +```math +\tilde{r}_{i,j} = +\begin{cases} + r_{i,j-1} \times r_{i,j-2}, & \text{if } j > 1 \text{ is odd}; \\ + r_{i,j}, & \text{if } j \text{ is 1 or even}. +\end{cases} +``` +# Example + +```jldoctest +julia> nlat = NLAT2() +NLAT2() + +julia> x_old = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +10-element Vector{Int64}: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + +julia> n_new = nlat(x_old) +10-element Vector{Int64}: + 0 + 1 + 0 + 3 + 6 + 5 + 20 + 7 + 42 + 9 + +julia> mat_old = [1 2 3; + 4 5 6; + 7 8 9; + 10 11 12; + 13 14 15; + 16 17 18; + 19 20 21] +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + 10 11 12 + 13 14 15 + 16 17 18 + 19 20 21 + +julia> mat_new = nlat(mat_old) +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 4 10 18 + 10 11 12 + 70 88 108 + 16 17 18 + 19 20 21 + +``` [^Chattopadhyay]: Chattopadhyay, Ashesh, et al. "Data-driven prediction of a multi-scale Lorenz 96 chaotic system using a - hierarchy of deep learning methods: Reservoir computing, ANN, and RNN-LSTM." (2019). + hierarchy of deep learning methods: Reservoir computing, ANN, and RNN-LSTM." + (2019). """ struct NLAT2 <: NonLinearAlgorithm end -function nla(::NLAT2, x_old) +function (::NLAT2)(x_old::AbstractVector) x_new = copy(x_old) - for i in 2:(size(x_new, 1) - 1) - if mod(i, 2) != 0 - x_new[i, :] = copy(x_old[i - 1, :] .* x_old[i - 2, :]) + + for idx in eachindex(x_old) + if firstindex(x_old) < idx < lastindex(x_old) && isodd(idx) + x_new[idx, :] .= x_old[idx - 1, :] .* x_old[idx - 2, :] end end return x_new end -""" +@doc raw""" NLAT3() -The `NLAT3` struct implements the T₃ transformation algorithm as detailed in [^Chattopadhyay]. -This algorithm modifies the reservoir's states by multiplying each odd-indexed row -(beginning from the second row) with the product of the immediately preceding and the -immediately following rows. T₃'s unique approach to data transformation makes it particularly -useful for enhancing complex data patterns, thereby improving the modeling and analysis -capabilities within reservoir computing, especially for chaotic and dynamic systems. - -Reference: +Implements the T₃ transformation algorithm as detailed +in [^Chattopadhyay]. This algorithm modifies the reservoir's states by +multiplying each odd-indexed row (beginning from the second row) with the +product of the immediately preceding and the immediately following rows. + +```math +\tilde{r}_{i,j} = +\begin{cases} +r_{i,j-1} \times r_{i,j+1}, & \text{if } j > 1 \text{ is odd}; \\ +r_{i,j}, & \text{if } j = 1 \text{ or even.} +\end{cases} +``` +# Example + +```jldoctest +julia> nlat = NLAT3() +NLAT3() + +julia> x_old = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +10-element Vector{Int64}: + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + +julia> n_new = nlat(x_old) +10-element Vector{Int64}: + 0 + 1 + 3 + 3 + 15 + 5 + 35 + 7 + 63 + 9 + +julia> mat_old = [1 2 3; + 4 5 6; + 7 8 9; + 10 11 12; + 13 14 15; + 16 17 18; + 19 20 21] +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 7 8 9 + 10 11 12 + 13 14 15 + 16 17 18 + 19 20 21 + +julia> mat_new = nlat(mat_old) +7×3 Matrix{Int64}: + 1 2 3 + 4 5 6 + 40 55 72 + 10 11 12 + 160 187 216 + 16 17 18 + 19 20 21 + +``` [^Chattopadhyay]: Chattopadhyay, Ashesh, et al. - "Data-driven prediction of a multi-scale Lorenz 96 chaotic system using a hierarchy of deep learning methods: - Reservoir computing, ANN, and RNN-LSTM." (2019). + "Data-driven predictions of a multiscale Lorenz 96 chaotic system using + machine-learning methods: reservoir computing, artificial neural network, + and long short-term memory network." (2019). """ struct NLAT3 <: NonLinearAlgorithm end -function nla(::NLAT3, x_old) +function (::NLAT3)(x_old::AbstractVector) x_new = copy(x_old) - for i in 2:(size(x_new, 1) - 1) - if mod(i, 2) != 0 - x_new[i, :] = copy(x_old[i - 1, :] .* x_old[i + 1, :]) + + for idx in eachindex(x_old) + if firstindex(x_old) < idx < lastindex(x_old) && isodd(idx) + x_new[idx] = x_old[idx - 1] * x_old[idx + 1] end end diff --git a/test/test_states.jl b/test/test_states.jl index 2eaab5fb..1a191c4f 100644 --- a/test/test_states.jl +++ b/test/test_states.jl @@ -12,12 +12,10 @@ nlas = [(NLADefault(), test_array), pes = [(StandardStates(), test_array), (PaddedStates(; padding=padding), - reshape(vcat(padding, test_array), length(test_array) + 1, 1)), + vcat(test_array, padding)), (PaddedExtendedStates(; padding=padding), - reshape(vcat(padding, extension, test_array), - length(test_array) + length(extension) + 1, - 1)), - (ExtendedStates(), vcat(extension, test_array))] + vcat(test_array, padding, extension)), + (ExtendedStates(), vcat(test_array, extension))] @testset "States Testing" for T in test_types @testset "Nonlinear Algorithms Testing: $algo $T" for (algo, expected_output) in nlas