use std::ops::{AddAssign, MulAssign};
use nalgebra::{ComplexField, DMatrix, DVector, RealField};
use num_complex::Complex;
use num_traits::Float;
use thiserror::Error;
use crate::{Continuous, Polynomial, TransferFunction, dka_method, vieta_formula};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum InterSample {
ZeroOrderHold,
FirstOrderHold,
}
#[derive(Clone, Debug)]
pub enum Initialization<T> {
StateVariableFilter(T),
Model(TransferFunction<T, Continuous>),
}
#[derive(Clone, Debug)]
pub struct SrivcOptions<T> {
pub input_delay: usize,
pub input_intersample: InterSample,
pub max_iterations: usize,
pub tolerance: T,
}
impl<T: Float> Default for SrivcOptions<T> {
fn default() -> Self {
Self {
input_delay: 0,
input_intersample: InterSample::ZeroOrderHold,
max_iterations: 20,
tolerance: T::from(1e-8).unwrap(),
}
}
}
#[derive(Clone, Debug)]
pub struct SrivcResult<T> {
pub model: TransferFunction<T, Continuous>,
pub parameter: DVector<T>,
pub iterations: usize,
pub converged: bool,
}
#[derive(Clone, Debug, Error, PartialEq)]
pub enum SrivcError {
#[error("input and output lengths differ: {input} vs {output}")]
LengthMismatch { input: usize, output: usize },
#[error("invalid orders: numerator {numerator}, denominator {denominator} (need 1 <= n, m <= n)")]
InvalidOrder { numerator: usize, denominator: usize },
#[error("initial model does not match the orders (numerator <= {numerator}, denominator = {denominator})")]
InitialModel { numerator: usize, denominator: usize },
#[error("singular normal equations")]
Singular,
}
pub fn identify<T>(
u: &[T],
y: &[T],
ts: T,
numerator_order: usize,
denominator_order: usize,
initialization: &Initialization<T>,
options: &SrivcOptions<T>,
) -> Result<SrivcResult<T>, SrivcError>
where
T: Float + AddAssign + MulAssign + ComplexField + RealField,
{
let (m, n) = (numerator_order, denominator_order);
if u.len() != y.len() {
return Err(SrivcError::LengthMismatch { input: u.len(), output: y.len() });
}
if n == 0 || m > n {
return Err(SrivcError::InvalidOrder { numerator: m, denominator: n });
}
let nk = options.input_delay;
let u: Vec<T> = (0..u.len()).map(|k| if k >= nk { u[k - nk] } else { T::zero() }).collect();
let mut theta = match initialization {
Initialization::StateVariableFilter(lambda) => {
let mut a = vec![T::one()];
for _ in 0..n {
a.push(T::zero());
for i in (1..a.len()).rev() {
a[i] = a[i] + *lambda * a[i - 1];
}
}
Step::new(&a[1..], ts, &u, y, m, options.input_intersample).solve(None)?
}
Initialization::Model(tf) => initial_parameter(tf, m, n)?,
};
let mut iterations = 0;
let mut converged = false;
while iterations < options.max_iterations {
let a = stabilized(&theta.as_slice()[..n]);
let b = &theta.as_slice()[n..];
let next = Step::new(&a, ts, &u, y, m, options.input_intersample).solve(Some(b))?;
iterations += 1;
let change = (&next - &theta).norm() / next.norm();
theta = next;
if change <= options.tolerance {
converged = true;
break;
}
}
let mut denom = vec![T::one()];
denom.extend_from_slice(&theta.as_slice()[..n]);
let model = TransferFunction::from_polynomials(Polynomial(theta.as_slice()[n..].to_vec()), Polynomial(denom));
Ok(SrivcResult { model, parameter: theta, iterations, converged })
}
fn initial_parameter<T: Float + ComplexField>(tf: &TransferFunction<T, Continuous>, m: usize, n: usize) -> Result<DVector<T>, SrivcError> {
let trim = |p: &Polynomial<T>| p.iter().copied().skip_while(|c| c.is_zero()).collect::<Vec<T>>();
let (numer, denom) = (trim(&tf.numerator), trim(&tf.denominator));
if denom.len() != n + 1 || numer.len() > m + 1 {
return Err(SrivcError::InitialModel { numerator: m, denominator: n });
}
let lead = denom[0];
let mut theta = DVector::zeros(n + m + 1);
for i in 0..n {
theta[i] = denom[i + 1] / lead;
}
for (i, &c) in numer.iter().enumerate() {
theta[n + m + 1 - numer.len() + i] = c / lead;
}
Ok(theta)
}
fn stabilized<T: Float + ComplexField>(a: &[T]) -> Vec<T> {
let mut coefficients = vec![Complex::from(T::one())];
coefficients.extend(a.iter().map(|&c| Complex::from(c)));
let Some(roots) = dka_method(&Polynomial(coefficients)) else {
return a.to_vec();
};
if roots.iter().all(|r| r.re <= T::zero()) {
return a.to_vec();
}
let reflected: Vec<Complex<T>> = roots.iter().map(|r| Complex::new(-Float::abs(r.re), r.im)).collect();
vieta_formula(&reflected).0.iter().skip(1).map(|c| c.re).collect()
}
struct Prefilter<T> {
a: Vec<T>,
phi: DMatrix<T>,
gamma0: DVector<T>,
gamma1: DVector<T>,
}
impl<T: Float + AddAssign + ComplexField + RealField> Prefilter<T> {
fn new(a: &[T], ts: T) -> Self {
let n = a.len();
let mut aug = DMatrix::<T>::zeros(n + 2, n + 2);
for i in 0..n - 1 {
aug[(i, i + 1)] = ts;
}
for j in 0..n {
aug[(n - 1, j)] = -a[n - 1 - j] * ts;
}
aug[(n - 1, n)] = ts;
aug[(n, n + 1)] = T::one();
let e = aug.exp();
Self {
a: a.to_vec(),
phi: e.view((0, 0), (n, n)).into_owned(),
gamma0: e.view((0, n), (n, 1)).column(0).into_owned(),
gamma1: e.view((0, n + 1), (n, 1)).column(0).into_owned(),
}
}
fn apply(&self, v: &[T], hold: InterSample) -> DMatrix<T> {
let n = self.a.len();
let mut out = DMatrix::zeros(v.len(), n + 1);
let mut x = DVector::zeros(n);
for k in 0..v.len() {
let mut highest = v[k];
for i in 0..n {
out[(k, i)] = x[i];
highest -= self.a[n - 1 - i] * x[i];
}
out[(k, n)] = highest;
let slope = match hold {
InterSample::FirstOrderHold if k + 1 < v.len() => v[k + 1] - v[k],
_ => T::zero(),
};
x = &self.phi * &x + &self.gamma0 * v[k] + &self.gamma1 * slope;
}
out
}
}
struct Step<T> {
filter: Prefilter<T>,
uf: DMatrix<T>,
yf: DMatrix<T>,
m: usize,
}
impl<T: Float + AddAssign + MulAssign + ComplexField + RealField> Step<T> {
fn new(a: &[T], ts: T, u: &[T], y: &[T], m: usize, input_intersample: InterSample) -> Self {
let filter = Prefilter::new(a, ts);
let uf = filter.apply(u, input_intersample);
let yf = filter.apply(y, InterSample::FirstOrderHold);
Self { filter, uf, yf, m }
}
fn regressor(&self, vf: &DMatrix<T>, k: usize) -> DVector<T> {
let (n, m) = (self.filter.a.len(), self.m);
let mut phi = DVector::zeros(n + m + 1);
for i in 0..n {
phi[i] = -vf[(k, n - 1 - i)];
}
for j in 0..=m {
phi[n + j] = self.uf[(k, m - j)];
}
phi
}
fn solve(&self, b: Option<&[T]>) -> Result<DVector<T>, SrivcError> {
let n = self.filter.a.len();
let xf = b.map(|b| {
let x_hat: Vec<T> = (0..self.uf.nrows())
.map(|k| (0..=self.m).fold(T::zero(), |acc, j| acc + b[j] * self.uf[(k, self.m - j)]))
.collect();
self.filter.apply(&x_hat, InterSample::FirstOrderHold)
});
let instrument = xf.as_ref().unwrap_or(&self.yf);
let size = n + self.m + 1;
let mut zeta_phi_sum = DMatrix::zeros(size, size);
let mut zeta_y_sum = DVector::zeros(size);
for k in 0..self.yf.nrows() {
let phi = self.regressor(&self.yf, k);
let zeta = self.regressor(instrument, k);
zeta_y_sum += &zeta * self.yf[(k, n)];
zeta_phi_sum += &zeta * &phi.transpose();
}
zeta_phi_sum.lu().solve(&zeta_y_sum).ok_or(SrivcError::Singular)
}
}