#![allow(clippy::needless_range_loop, clippy::manual_memcpy)]
use nalgebra::{Const, DimName, OMatrix, OVector};
use super::types::{CovarianceGuards, ExitCode, UpdateStats};
#[derive(Clone)]
pub struct Rls<const N: usize, const D: usize> {
x: OVector<f32, Const<N>>,
p: OMatrix<f32, Const<N>, Const<N>>,
lambda: f32,
samples: u32,
guards: CovarianceGuards,
}
impl<const N: usize, const D: usize> Rls<N, D>
where
Const<N>: DimName,
Const<D>: DimName,
{
pub fn new(gamma: f32, lambda: f32, guards: CovarianceGuards) -> Self {
let mut p = OMatrix::<f32, Const<N>, Const<N>>::zeros();
for i in 0..N {
p[(i, i)] = gamma;
}
Self {
x: OVector::<f32, Const<N>>::zeros(),
p,
lambda,
samples: 0,
guards,
}
}
#[inline]
pub fn update(
&mut self,
a_t: &OMatrix<f32, Const<N>, Const<D>>,
y: &OVector<f32, Const<D>>,
) -> UpdateStats {
self.samples += 1;
let mut lam = self.lambda;
let mut trace_p: f32 = 0.0;
let mut exit_code = ExitCode::Success;
for i in 0..N {
trace_p += self.p[(i, i)];
if self.p[(i, i)] > self.guards.cov_max {
lam = 1.0 + 0.1 * (1.0 - self.lambda);
exit_code = ExitCode::CovarianceExplosion;
}
}
let mut pat = [[0.0f32; D]; N]; for j in 0..D {
for i in 0..N {
let mut sum = 0.0f32;
for k in 0..N {
sum += self.p[(k, i)] * a_t[(k, j)];
}
pat[i][j] = sum;
}
}
let mut m = [[0.0f32; D]; D];
for i in 0..D {
m[i][i] = lam;
}
for i in 0..D {
for j in 0..D {
let mut sum = 0.0f32;
for k in 0..N {
sum += pat[k][i] * a_t[(k, j)];
}
m[i][j] += sum;
}
}
let mut u = [[0.0f32; D]; D];
let mut i_diag = [0.0f32; D];
for i in 0..D {
for j in 0..=i {
let mut s = 0.0f32;
for k in 0..j {
s += u[i][k] * u[j][k];
}
if i == j {
u[i][j] = libm::sqrtf(m[i][i] - s);
i_diag[j] = 1.0 / u[i][j];
} else {
u[i][j] = i_diag[j] * (m[i][j] - s);
}
}
}
let mut kt = [[0.0f32; D]; N];
for col in 0..N {
let mut b = [0.0f32; D];
for row in 0..D {
b[row] = pat[col][row]; }
let mut x_sol = [0.0f32; D];
for j in 0..D {
let mut t = b[j];
for k in 0..j {
t -= u[j][k] * x_sol[k];
}
x_sol[j] = t * i_diag[j];
}
for j in (0..D).rev() {
let mut t = x_sol[j];
for k in (j + 1)..D {
t -= u[k][j] * x_sol[k];
}
x_sol[j] = t * i_diag[j];
}
for row in 0..D {
kt[col][row] = x_sol[row];
}
}
let mut trace_kap: f32 = 0.0;
for i in 0..N {
for j in 0..D {
trace_kap += pat[i][j] * kt[i][j];
}
}
let mut kap_mult: f32 = 1.0;
if trace_kap > self.guards.cov_min {
kap_mult = ((1.0 - self.guards.max_order_decrement) * (trace_p / trace_kap)).min(1.0);
}
let kap_ilam = kap_mult / lam;
let neg_ilam = -1.0 / lam;
for col in 0..N {
for row in 0..N {
let mut rank_d = 0.0f32;
for k in 0..D {
rank_d += pat[row][k] * kt[col][k];
}
self.p[(row, col)] = kap_ilam * self.p[(row, col)] + neg_ilam * rank_d;
}
}
let mut e = [0.0f32; D];
for j in 0..D {
let mut sum = 0.0f32;
for k in 0..N {
sum += self.x[k] * a_t[(k, j)];
}
e[j] = y[j] - sum;
}
for i in 0..N {
let mut dx = 0.0f32;
for j in 0..D {
dx += kt[i][j] * e[j];
}
self.x[i] += dx;
}
UpdateStats {
exit_code,
samples: self.samples,
}
}
#[inline]
pub fn params(&self) -> &OVector<f32, Const<N>> {
&self.x
}
#[inline]
pub fn params_mut(&mut self) -> &mut OVector<f32, Const<N>> {
&mut self.x
}
#[inline]
pub fn covariance(&self) -> &OMatrix<f32, Const<N>, Const<N>> {
&self.p
}
#[inline]
pub fn lambda(&self) -> f32 {
self.lambda
}
#[inline]
pub fn set_lambda(&mut self, lambda: f32) {
self.lambda = lambda;
}
#[inline]
pub fn samples(&self) -> u32 {
self.samples
}
}
#[derive(Clone)]
pub struct RlsParallel<const N: usize, const P: usize> {
x: OMatrix<f32, Const<N>, Const<P>>,
p: OMatrix<f32, Const<N>, Const<N>>,
lambda: f32,
samples: u32,
guards: CovarianceGuards,
}
impl<const N: usize, const P: usize> RlsParallel<N, P>
where
Const<N>: DimName,
Const<P>: DimName,
{
pub fn new(gamma: f32, lambda: f32, guards: CovarianceGuards) -> Self {
let mut p = OMatrix::<f32, Const<N>, Const<N>>::zeros();
for i in 0..N {
p[(i, i)] = gamma;
}
Self {
x: OMatrix::<f32, Const<N>, Const<P>>::zeros(),
p,
lambda,
samples: 0,
guards,
}
}
pub fn from_time_constant(gamma: f32, ts: f32, t_char: f32, guards: CovarianceGuards) -> Self {
let lambda = libm::powf(1.0 - core::f32::consts::LN_2, ts / t_char);
Self::new(gamma, lambda, guards)
}
#[inline]
pub fn update(
&mut self,
a: &OVector<f32, Const<N>>,
y: &OVector<f32, Const<P>>,
) -> UpdateStats {
self.samples += 1;
let mut lam = self.lambda;
let mut diag_p = [0.0f32; N];
let mut exit_code = ExitCode::Success;
for i in 0..N {
diag_p[i] = self.p[(i, i)];
if diag_p[i] > self.guards.cov_max {
lam = 1.0 + 0.1 * (1.0 - self.lambda);
exit_code = ExitCode::CovarianceExplosion;
}
}
let mut pa = [0.0f32; N];
for i in 0..N {
let mut sum = 0.0f32;
for k in 0..N {
sum += self.p[(k, i)] * a[k];
}
pa[i] = sum;
}
let mut a_pa: f32 = 0.0;
for i in 0..N {
a_pa += a[i] * pa[i];
}
let mut e = [0.0f32; P];
for j in 0..P {
let mut ax = 0.0f32;
for k in 0..N {
ax += a[k] * self.x[(k, j)];
}
e[j] = y[j] - ax;
}
let i_sig = 1.0 / (lam + a_pa);
let mut k = [0.0f32; N];
for i in 0..N {
k[i] = pa[i] * i_sig;
}
let mut max_diag_ratio: f32 = 0.0;
for i in 0..N {
let diag_kap = k[i] * pa[i];
if diag_kap > 1e-6 {
let ratio = diag_p[i] / diag_kap;
if ratio > max_diag_ratio {
max_diag_ratio = ratio;
}
}
}
let mut kap_mult: f32 = 1.0;
if max_diag_ratio > self.guards.cov_min {
kap_mult = ((1.0 - self.guards.max_order_decrement) * max_diag_ratio).min(1.0);
}
let kap_ilam = kap_mult / lam;
let neg_ilam = -1.0 / lam;
for col in 0..N {
let pa_col_scaled = pa[col] * neg_ilam;
for row in 0..N {
self.p[(row, col)] = kap_ilam * self.p[(row, col)] + k[row] * pa_col_scaled;
}
}
for j in 0..P {
for i in 0..N {
self.x[(i, j)] += k[i] * e[j];
}
}
UpdateStats {
exit_code,
samples: self.samples,
}
}
#[inline]
pub fn params(&self) -> &OMatrix<f32, Const<N>, Const<P>> {
&self.x
}
#[inline]
pub fn params_mut(&mut self) -> &mut OMatrix<f32, Const<N>, Const<P>> {
&mut self.x
}
#[inline]
pub fn covariance(&self) -> &OMatrix<f32, Const<N>, Const<N>> {
&self.p
}
#[inline]
pub fn lambda(&self) -> f32 {
self.lambda
}
#[inline]
pub fn set_lambda(&mut self, lambda: f32) {
self.lambda = lambda;
}
#[inline]
pub fn samples(&self) -> u32 {
self.samples
}
}