|
| 1 | +""" |
| 2 | +$(TYPEDEF) |
| 3 | +
|
| 4 | +`WyNDA` implements the Wide-Array of Nonlinear Dynamics Approximation update as an |
| 5 | +online recursive least-squares estimator with exponential forgetting. Given a |
| 6 | +matrix of evaluated basis functions `X` and targets `Y`, it updates the |
| 7 | +coefficient matrix sample-by-sample so that `Y ≈ coefficients * X`. |
| 8 | +
|
| 9 | +The forgetting factor `λ` controls how quickly older samples are discounted. |
| 10 | +Values close to one recover a batch least-squares-like fit, while smaller values |
| 11 | +adapt faster to parameter drift. |
| 12 | +
|
| 13 | +# Fields |
| 14 | +
|
| 15 | +$(FIELDS) |
| 16 | +
|
| 17 | +# Example |
| 18 | +
|
| 19 | +```julia |
| 20 | +opt = WyNDA() |
| 21 | +opt = WyNDA(0.998) |
| 22 | +opt = WyNDA(0.998; initial_covariance = 1.0e4) |
| 23 | +``` |
| 24 | +""" |
| 25 | +struct WyNDA{T <: Number, C, IC} <: AbstractSparseRegressionAlgorithm |
| 26 | + """Exponential forgetting factor.""" |
| 27 | + λ::T |
| 28 | + """Initial inverse covariance scale or matrix.""" |
| 29 | + initial_covariance::C |
| 30 | + """Optional initial coefficient vector or matrix.""" |
| 31 | + initial_coefficients::IC |
| 32 | + |
| 33 | + function WyNDA( |
| 34 | + λ::T = 1.0; |
| 35 | + initial_covariance::C = 1.0e6, |
| 36 | + initial_coefficients::IC = nothing |
| 37 | + ) where {T <: Number, C, IC} |
| 38 | + @assert zero(T) < λ <= one(T) "Forgetting factor λ must satisfy 0 < λ <= 1" |
| 39 | + if initial_covariance isa Number |
| 40 | + @assert initial_covariance > zero(initial_covariance) "Initial covariance must be positive" |
| 41 | + end |
| 42 | + return new{T, C, IC}(λ, initial_covariance, initial_coefficients) |
| 43 | + end |
| 44 | +end |
| 45 | + |
| 46 | +Base.summary(::WyNDA) = "WyNDA" |
| 47 | + |
| 48 | +function _initial_covariance(alg::WyNDA, ::Type{T}, n_features::Int) where {T} |
| 49 | + covariance = alg.initial_covariance |
| 50 | + if covariance isa Number |
| 51 | + return Matrix{T}(I, n_features, n_features) .* T(covariance) |
| 52 | + end |
| 53 | + @assert size(covariance) == (n_features, n_features) "Initial covariance matrix size must match the number of features" |
| 54 | + return Matrix{T}(covariance) |
| 55 | +end |
| 56 | + |
| 57 | +function _initial_coefficients( |
| 58 | + alg::WyNDA, ::Type{T}, n_targets::Int, n_features::Int |
| 59 | + ) where {T} |
| 60 | + coefficients = alg.initial_coefficients |
| 61 | + if coefficients === nothing |
| 62 | + return zeros(T, n_targets, n_features) |
| 63 | + end |
| 64 | + if coefficients isa AbstractVector |
| 65 | + @assert n_targets == 1 "Initial coefficient vectors are only valid for single-target problems" |
| 66 | + @assert length(coefficients) == n_features "Initial coefficient vector length must match the number of features" |
| 67 | + return reshape(T.(coefficients), 1, n_features) |
| 68 | + end |
| 69 | + @assert size(coefficients) == (n_targets, n_features) "Initial coefficient matrix size must match targets by features" |
| 70 | + return Matrix{T}(coefficients) |
| 71 | +end |
| 72 | + |
| 73 | +function (alg::WyNDA)( |
| 74 | + X::AbstractMatrix, Y::AbstractVecOrMat; |
| 75 | + options::DataDrivenCommonOptions = DataDrivenCommonOptions(), |
| 76 | + kwargs... |
| 77 | + ) |
| 78 | + Y_matrix = Y isa AbstractVector ? reshape(Y, 1, :) : Y |
| 79 | + @assert size(X, 2) == size(Y_matrix, 2) "X and Y must have the same number of observations" |
| 80 | + |
| 81 | + n_features, n_observations = size(X) |
| 82 | + n_targets = size(Y_matrix, 1) |
| 83 | + T = float(promote_type(eltype(X), eltype(Y_matrix), typeof(alg.λ))) |
| 84 | + |
| 85 | + coefficients = _initial_coefficients(alg, T, n_targets, n_features) |
| 86 | + covariance = _initial_covariance(alg, T, n_features) |
| 87 | + λ = T(alg.λ) |
| 88 | + |
| 89 | + for k in 1:n_observations |
| 90 | + basis_values = view(X, :, k) |
| 91 | + targets = view(Y_matrix, :, k) |
| 92 | + covariance_basis = covariance * basis_values |
| 93 | + denominator = λ + dot(basis_values, covariance_basis) |
| 94 | + gain = covariance_basis ./ denominator |
| 95 | + residual = targets .- coefficients * basis_values |
| 96 | + coefficients .+= residual * adjoint(gain) |
| 97 | + covariance .-= gain * adjoint(covariance_basis) |
| 98 | + covariance ./= λ |
| 99 | + end |
| 100 | + |
| 101 | + return coefficients, alg.λ, n_observations |
| 102 | +end |
0 commit comments