use std::ops::AddAssign;
use nalgebra::{DVector, Scalar};
use num_complex::Complex;
use num_traits::Float;
use crate::{Discrete, Polynomial, TransferFunction, dka_method, vieta_formula};
#[derive(Clone, Debug)]
pub struct Arx<T> {
pub u: DVector<T>,
pub x: DVector<T>,
pub parameter: DVector<T>,
input_order: usize,
state_order: usize,
input_delay: usize,
}
impl<T: Float + AddAssign + Scalar> Arx<T> {
pub fn new(state_order: usize, input_order: usize) -> Self {
Self {
u: DVector::zeros(input_order + 1),
x: DVector::zeros(state_order),
parameter: DVector::zeros(input_order + state_order + 1),
input_order,
state_order,
input_delay: 0,
}
}
pub fn with_input_delay(mut self, input_delay: usize) -> Self {
self.input_delay = input_delay;
self.u = DVector::zeros(input_delay + self.input_order + 1);
self
}
pub fn input_order(&self) -> usize {
self.input_order
}
pub fn state_order(&self) -> usize {
self.state_order
}
pub fn input_delay(&self) -> usize {
self.input_delay
}
pub fn parameter_len(&self) -> usize {
self.state_order + self.input_order + 1
}
pub fn clear_history(&mut self) {
self.u.fill(T::zero());
self.x.fill(T::zero());
}
pub fn push(&mut self, u: T, x: T) {
for i in (1..self.u.len()).rev() {
self.u[i] = self.u[i - 1]
}
self.u[0] = u;
for i in (1..self.state_order).rev() {
self.x[i] = self.x[i - 1]
}
if self.state_order > 0 {
self.x[0] = x;
}
}
pub fn regressor(&self) -> DVector<T> {
let mut phi = DVector::zeros(self.parameter_len());
for i in 0..self.state_order {
phi[i] = self.x[i]
}
for i in 0..(self.input_order + 1) {
phi[i + self.state_order] = self.u[i + self.input_delay]
}
phi
}
pub fn predict(&self) -> T {
self.regressor().iter().zip(self.parameter.iter()).fold(T::zero(), |acc, (&p, &t)| acc + p * t)
}
pub fn is_stable(&self) -> bool {
let na = self.state_order;
let mut c: Vec<T> = std::iter::once(T::one()).chain(self.parameter.iter().take(na).map(|&a| -a)).collect();
if c.iter().any(|v| !v.is_finite()) {
return false;
}
while c.len() > 1 {
let n = c.len() - 1;
let k = c[n] / c[0];
if k.abs() >= T::one() {
return false;
}
c = (0..n).map(|i| (c[i] - k * c[n - i]) / (T::one() - k * k)).collect();
}
true
}
pub fn stabilized(&self) -> Self {
let na = self.state_order;
if self.is_stable() {
return self.clone();
}
let c: Vec<Complex<T>> =
std::iter::once(T::one()).chain(self.parameter.iter().take(na).map(|&a| -a)).map(|v| Complex::new(v, T::zero())).collect();
let Some(roots) = dka_method(&Polynomial(c)) else {
return self.clone();
};
let reflected: Vec<Complex<T>> = roots.iter().map(|r| if r.norm() > T::one() { r.conj().inv() } else { *r }).collect();
let mut model = self.clone();
for (a, c) in model.parameter.iter_mut().zip(vieta_formula(&reflected).0.iter().skip(1)) {
*a = -c.re;
}
model
}
pub fn transfer_function(&self) -> TransferFunction<T, Discrete> {
let (a, b) = self.parameter.as_slice().split_at(self.state_order);
let order = a.len().max(self.input_delay + b.len().saturating_sub(1));
let mut denom = Vec::with_capacity(order + 1);
denom.push(T::one());
denom.extend(a.iter().map(|&ai| -ai));
denom.resize(order + 1, T::zero());
let mut numer = vec![T::zero(); self.input_delay];
numer.extend_from_slice(b);
numer.resize(order + 1, T::zero());
TransferFunction::discrete(&numer, &denom)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn model(a: &[f64]) -> Arx<f64> {
let mut m = Arx::<f64>::new(a.len(), 0);
m.parameter.rows_mut(0, a.len()).copy_from_slice(a);
m.parameter[a.len()] = 1.0;
m
}
#[test]
fn schur_cohn_stability() {
assert!(model(&[1.7, -0.72]).is_stable());
assert!(model(&[1.5, -0.7]).is_stable());
assert!(!model(&[1.1]).is_stable());
assert!(!model(&[-1.0]).is_stable());
assert!(!model(&[1.9, -0.88]).is_stable());
assert!(model(&[]).is_stable());
}
#[test]
fn stabilized_reflects_unstable_roots() {
let m = model(&[2.05, -1.0]).stabilized();
assert!(m.is_stable());
assert!((m.parameter[0] - 1.6).abs() < 1e-12 && (m.parameter[1] + 0.64).abs() < 1e-12, "{:?}", m.parameter);
assert_eq!(m.parameter[2], 1.0);
}
}