use std::ops::{Deref, DerefMut};
use crate::types::buffers::NoiseWindowView;
use crate::types::{Float, WindowSize};
use crate::{Error, Result};
use crate::algorithms::Algorithm;
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rls<F: Float> {
forgetting_factor: F,
p_init_scale: F,
window_size: WindowSize,
inverse_corr_matrix: InverseCorrMatrix<F>,
kalman_gain: KalmanGain<F>,
}
impl<F: Float> Rls<F> {
pub fn new(forgetting_factor: F, p_init_scale: F, window_size: WindowSize) -> Result<Self> {
if forgetting_factor <= F::zero() || forgetting_factor > F::one() {
return Err(Error::InvalidForgettingFactorRange);
}
if p_init_scale <= F::zero() {
return Err(Error::NonPositivePInitScale);
}
Ok(Rls {
forgetting_factor,
p_init_scale,
window_size,
inverse_corr_matrix: InverseCorrMatrix::new(window_size, p_init_scale),
kalman_gain: KalmanGain::new(window_size),
})
}
}
#[allow(clippy::doc_paragraphs_missing_punctuation, reason = "maths")]
impl<F: Float> Algorithm<F> for Rls<F> {
fn update_step(&mut self, weights: &mut [F], error: F, noise_window: NoiseWindowView<F>) {
self.kalman_gain.update(
&self.inverse_corr_matrix,
&noise_window,
self.forgetting_factor,
);
for (w, k) in weights.iter_mut().zip(self.kalman_gain.iter().copied()) {
*w += k * error;
}
self.inverse_corr_matrix
.update(&self.kalman_gain, &noise_window, self.forgetting_factor);
}
fn window_size(&self) -> WindowSize {
self.window_size
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct KalmanGain<F: Float> {
inner: Box<[F]>,
}
impl<F: Float> KalmanGain<F> {
pub fn new(window_size: WindowSize) -> Self {
KalmanGain {
inner: vec![F::zero(); *window_size].into_boxed_slice(),
}
}
fn update(
&mut self,
inverse_corr_matrix: &InverseCorrMatrix<F>,
noise_ref: &NoiseWindowView<F>,
lambda: F,
) {
for (k_i, row) in self
.iter_mut()
.zip(inverse_corr_matrix.chunks_exact(noise_ref.len()))
{
*k_i = row
.iter()
.copied()
.zip(noise_ref.iter())
.map(|(px, x)| px * x)
.sum::<F>();
}
let denominator = lambda
+ noise_ref
.iter()
.zip(self.iter().copied())
.map(|(noise, num)| noise * num)
.sum::<F>();
for k_i in self.iter_mut() {
*k_i /= denominator;
}
}
}
impl<F: Float> Deref for KalmanGain<F> {
type Target = [F];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<F: Float> DerefMut for KalmanGain<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
struct InverseCorrMatrix<F: Float> {
inner: Box<[F]>,
}
impl<F: Float> InverseCorrMatrix<F> {
pub fn new(window_size: WindowSize, p_init_scale: F) -> Self {
let mut p = vec![F::zero(); (*window_size) * (*window_size)].into_boxed_slice();
for i in 0..(*window_size) {
if let Some(elem) = p.get_mut(i * (*window_size) + i) {
*elem = p_init_scale;
}
}
InverseCorrMatrix { inner: p }
}
fn update(&mut self, kalman_gain: &KalmanGain<F>, noise_ref: &NoiseWindowView<F>, lambda: F) {
for col in 0..noise_ref.len() {
let p_col = self.iter().copied().skip(col).step_by(noise_ref.len());
let xt_p_col = noise_ref.iter().zip(p_col).map(|(x, p)| x * p).sum::<F>();
for (row, k_i) in kalman_gain.iter().copied().enumerate() {
let index = row * noise_ref.len() + col;
let p_i = self
.get_mut(index)
.expect("Inverse corr matrix dimensions must match the reference window size");
*p_i = (*p_i - k_i * xt_p_col) / lambda;
}
}
}
}
impl<F: Float> Deref for InverseCorrMatrix<F> {
type Target = [F];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<F: Float> DerefMut for InverseCorrMatrix<F> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::indexing_slicing, reason = "Tests")]
mod tests {
use super::*;
use crate::{
test_utils::all_approx_equal,
types::buffers::NoiseBuffer,
types::{BlockSize, FilterWeights, WindowSize},
};
#[test]
fn init_p_matrix_works() {
let n = WindowSize::new(3).unwrap();
let p_matrix = InverseCorrMatrix::new(n, 10.0);
let expected = [10.0, 0.0, 0.0, 0.0, 10.0, 0.0, 0.0, 0.0, 10.0];
assert_eq!(*p_matrix, expected);
}
#[test]
fn update_kalman_gain_works() {
let mut k = KalmanGain {
inner: vec![0.0; 2].into_boxed_slice(),
};
let p = InverseCorrMatrix {
inner: vec![1.0, 0.0, 0.0, 1.0].into_boxed_slice(),
};
let x_n = NoiseBuffer::from_slice(
&[1.0, 2.0],
WindowSize::new(2).unwrap(),
BlockSize::new(1).unwrap(),
);
k.update(&p, &x_n.window(0).unwrap(), 1.0);
let expected = [1.0 / 6.0, 1.0 / 3.0];
assert!(all_approx_equal(k.iter(), expected.iter()));
}
#[test]
fn update_p_matrix_works() {
let mut p = InverseCorrMatrix {
inner: vec![1.0, 0.0, 0.0, 1.0].into_boxed_slice(),
};
let k = KalmanGain {
inner: vec![1.0 / 6.0, 1.0 / 3.0].into_boxed_slice(),
};
let x_n = NoiseBuffer::from_slice(
&[1.0, 2.0],
WindowSize::new(2).unwrap(),
BlockSize::new(1).unwrap(),
);
p.update(&k, &x_n.window(0).unwrap(), 1.0);
let expected = InverseCorrMatrix {
inner: vec![5.0 / 6.0, -1.0 / 3.0, -1.0 / 3.0, 1.0 / 3.0].into_boxed_slice(),
};
assert!(all_approx_equal(p.iter(), expected.iter()));
}
#[test]
fn update_rls_1() {
let window_size = WindowSize::new(2).unwrap();
let block_size = BlockSize::new(1).unwrap();
let mut rls = Rls::new(0.5, 1.0, window_size).unwrap();
let e_n = 2.0;
let x_n = NoiseBuffer::from_slice(&[1.0, -1.0], window_size, block_size);
let expected = [0.8, -0.8];
let mut weights = FilterWeights::new(window_size);
rls.update_step(&mut weights, e_n, x_n.window(0).unwrap());
assert!(all_approx_equal(weights.iter(), expected.iter()));
}
#[test]
fn update_rls_2() {
let window_size = WindowSize::new(2).unwrap();
let block_size = BlockSize::new(1).unwrap();
let mut rls = Rls::new(1.0, 1.0, window_size).unwrap();
let e_n = 1.0;
let x_n = NoiseBuffer::from_slice(&[5.0, 2.0], window_size, block_size);
let expected = [5.0 / 30.0, 2.0 / 30.0];
let mut weights = FilterWeights::new(WindowSize::new(2).unwrap());
rls.update_step(&mut weights, e_n, x_n.window(0).unwrap());
assert!(all_approx_equal(weights.iter(), expected.iter()));
}
#[test]
fn forgetting_factor_range() {
let p_init_scale = 1.0;
let window_size = WindowSize::new(8).unwrap();
Rls::new(0.01, p_init_scale, window_size).unwrap();
Rls::new(1.0, p_init_scale, window_size).unwrap();
assert!(matches!(
Rls::new(0.0, p_init_scale, window_size),
Err(Error::InvalidForgettingFactorRange)
));
assert!(matches!(
Rls::new(-1.0, p_init_scale, window_size),
Err(Error::InvalidForgettingFactorRange)
));
assert!(matches!(
Rls::new(2.0, p_init_scale, window_size),
Err(Error::InvalidForgettingFactorRange)
));
}
#[test]
fn p_init_scale_range() {
let good_init_scale = 100.0;
let window_size = WindowSize::new(8).unwrap();
Rls::new(0.01, good_init_scale, window_size).unwrap();
assert!(matches!(
Rls::new(0.5, 0.0, window_size),
Err(Error::NonPositivePInitScale)
));
assert!(matches!(
Rls::new(0.5, -1.0, window_size),
Err(Error::NonPositivePInitScale)
));
}
#[test]
fn window_size_works() {
let window_size = WindowSize::new(16).unwrap();
let rls = Rls::new(0.01, 1.0, window_size).unwrap();
assert_eq!(rls.window_size(), window_size);
}
}