use std::ops::{AddAssign, MulAssign};
use nalgebra::{ComplexField, DMatrix, DVector};
use num_traits::Float;
use crate::{Discrete, TransferFunction};
use crate::system_identification::arx::Arx;
pub struct DataBuffer<T> {
pub arx: Arx<T>,
psi_sum: DVector<T>,
phi_sum: DMatrix<T>,
y2_sum: T,
count: usize,
}
impl<T: Float + AddAssign + MulAssign + ComplexField> DataBuffer<T> {
pub fn new(input_order: usize, state_order: usize) -> Self {
Self::from_arx(Arx::new(input_order, state_order))
}
pub fn from_arx(arx: Arx<T>) -> Self {
let n = arx.parameter_len();
Self {
arx,
psi_sum: DVector::zeros(n),
phi_sum: DMatrix::zeros(n, n),
y2_sum: T::zero(),
count: 0,
}
}
pub fn with_input_delay(mut self, input_delay: usize) -> Self {
self.arx = self.arx.with_input_delay(input_delay);
self
}
pub fn add(&mut self, u: T, x: T, y: T) {
self.arx.push(u, x);
let phi = self.arx.regressor();
self.psi_sum += &phi * y;
self.phi_sum += &phi * &phi.transpose();
self.y2_sum += y * y;
self.count += 1;
}
fn solve(&self) -> Option<DVector<T>> {
self.phi_sum.clone().try_inverse().map(|inv| &inv * &self.psi_sum)
}
pub fn loss(&self) -> Option<T> {
let theta = self.solve()?;
let count = T::from(self.count.max(1)).unwrap();
Some(((self.y2_sum - theta.dot(&self.psi_sum)) / count).max(T::zero()))
}
pub fn identify(&mut self) -> Option<TransferFunction<T, Discrete>> {
self.arx.parameter = self.solve()?;
Some(self.arx.transfer_function())
}
}
#[derive(Clone, Debug)]
pub struct DelayEstimate<T> {
pub input_delay: usize,
pub model: TransferFunction<T, Discrete>,
pub losses: Vec<Option<T>>,
}
pub fn estimate_input_delay<T>(
u: &[T],
y: &[T],
input_order: usize,
state_order: usize,
max_delay: usize,
) -> Option<DelayEstimate<T>>
where
T: Float + AddAssign + MulAssign + ComplexField,
{
let mut buffers: Vec<DataBuffer<T>> = (0..=max_delay)
.map(|nk| {
let mut buffer = DataBuffer::new(input_order, state_order).with_input_delay(nk);
for (k, (&uk, &yk)) in u.iter().zip(y).enumerate() {
let y_prev = if k > 0 { y[k - 1] } else { T::zero() };
buffer.add(uk, y_prev, yk);
}
buffer
})
.collect();
let losses: Vec<Option<T>> = buffers.iter().map(|b| b.loss()).collect();
let min = losses.iter().flatten().copied().reduce(T::min)?;
let mean_y2 = y.iter().fold(T::zero(), |acc, &v| acc + v * v) / T::from(y.len().max(1)).unwrap();
let threshold = min + T::from(1e-9).unwrap() * mean_y2;
let input_delay = losses.iter().rposition(|l| l.is_some_and(|l| l <= threshold))?;
let model = buffers[input_delay].identify()?;
Some(DelayEstimate { input_delay, model, losses })
}