use crate::core::matrix::{matvec, outer, Matrix};
use crate::core::scalar::ControlScalar;
pub struct Rls<S: ControlScalar, const N: usize> {
pub theta: [S; N],
pub p: Matrix<S, N, N>,
pub lambda: S,
}
impl<S: ControlScalar, const N: usize> Rls<S, N> {
pub fn new(lambda: S, p0: S) -> Self {
Self {
theta: [S::ZERO; N],
p: Matrix::<S, N, N>::identity().scale(p0),
lambda,
}
}
pub fn update(&mut self, phi: &[S; N], y: S) -> &[S; N] {
let y_hat: S = phi
.iter()
.zip(self.theta.iter())
.map(|(&p, &t)| p * t)
.fold(S::ZERO, |a, b| a + b);
let error = y - y_hat;
let p_phi = matvec(&self.p, phi);
let phi_p_phi: S = phi
.iter()
.zip(p_phi.iter())
.map(|(&a, &b)| a * b)
.fold(S::ZERO, |acc, x| acc + x);
let denom = self.lambda + phi_p_phi;
if denom.abs() < S::EPSILON {
return &self.theta;
}
let k: [S; N] = core::array::from_fn(|i| p_phi[i] / denom);
for (i, &ki) in k.iter().enumerate().take(N) {
self.theta[i] += ki * error;
}
let k_phi_t: Matrix<S, N, N> = outer(&k, phi); let k_phi_t_p = {
let mut kp = Matrix::<S, N, N>::zeros();
for r in 0..N {
for c in 0..N {
kp.data[r][c] = k_phi_t.data[r]
.iter()
.zip(self.p.data.iter())
.map(|(&kf, pr)| kf * pr[c])
.fold(S::ZERO, |acc, x| acc + x);
}
}
kp
};
self.p = self.p.sub_mat(&k_phi_t_p).scale(S::ONE / self.lambda);
&self.theta
}
pub fn prediction_error(&self, phi: &[S; N], y: S) -> S {
let y_hat: S = phi
.iter()
.zip(self.theta.iter())
.map(|(&p, &t)| p * t)
.fold(S::ZERO, |acc, x| acc + x);
y - y_hat
}
pub fn reset(&mut self, p0: S) {
self.theta = [S::ZERO; N];
self.p = Matrix::<S, N, N>::identity().scale(p0);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn identifies_single_parameter() {
let mut rls = Rls::<f64, 1>::new(1.0, 1e4);
let true_gain = 3.0_f64;
for k in 1..=200 {
let phi = [k as f64];
let y = true_gain * phi[0];
rls.update(&phi, y);
}
assert!(
(rls.theta[0] - true_gain).abs() < 0.01,
"θ={}",
rls.theta[0]
);
}
#[test]
fn identifies_two_parameters() {
let mut rls = Rls::<f64, 2>::new(1.0, 1e4);
let params = [2.0_f64, 3.0];
for k in 1..=500 {
let phi = [k as f64, (k + 17) as f64];
let y = params[0] * phi[0] + params[1] * phi[1];
rls.update(&phi, y);
}
assert!(
(rls.theta[0] - params[0]).abs() < 0.1,
"θ0={}",
rls.theta[0]
);
assert!(
(rls.theta[1] - params[1]).abs() < 0.1,
"θ1={}",
rls.theta[1]
);
}
#[test]
fn forgetting_factor_tracks_change() {
let mut rls = Rls::<f64, 1>::new(0.95, 1e4);
for _k in 1..=500 {
let phi = [1.0_f64];
rls.update(&phi, 1.0);
}
for _k in 1..=500 {
let phi = [1.0_f64];
rls.update(&phi, 5.0);
}
assert!((rls.theta[0] - 5.0).abs() < 0.5, "θ={}", rls.theta[0]);
}
#[test]
fn prediction_error_zero_on_perfect_fit() {
let mut rls = Rls::<f64, 1>::new(1.0, 1e4);
for k in 1..=200 {
let phi = [k as f64];
rls.update(&phi, 2.0 * phi[0]);
}
let e = rls.prediction_error(&[5.0], 10.0);
assert!(e.abs() < 0.01);
}
}