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, TransferFunctionWithDelay, dka_method, vieta_formula};
use crate::discretize::state_variable_filter::{StateVariableFilter, root_radius};
pub use crate::discretize::InterSample;
pub mod search;
pub use search::{search, Candidate, Outcome, SearchOptions, Structure, StructureSearch};
#[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,
pub evaluated_from: usize,
}
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(),
evaluated_from: 0,
}
}
}
#[derive(Clone, Debug)]
pub struct SrivcResult<T> {
pub model: TransferFunctionWithDelay<T>,
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: denominator {denominator}, numerator {numerator} (need m <= n, and n >= 1 unless there are integrators)")]
InvalidOrder { denominator: usize, numerator: usize },
#[error("initial model does not match the orders (denominator = {denominator}, numerator <= {numerator})")]
InitialModel { denominator: usize, numerator: usize },
#[error("singular normal equations")]
Singular,
#[error("invalid prefilter (need a positive, finite cutoff)")]
InvalidPrefilter,
#[error("{integrators} integrators need a prefilter")]
NoPrefilter { integrators: usize },
#[error("too few samples: {samples}, evaluated from {evaluated_from}, for {parameters} parameters")]
TooFewSamples { samples: usize, evaluated_from: usize, parameters: usize },
}
pub fn identify<T>(
u: &[T],
y: &[T],
ts: T,
denominator_order: usize,
numerator_order: usize,
initialization: &Initialization<T>,
options: &SrivcOptions<T>,
) -> Result<SrivcResult<T>, SrivcError>
where
T: Float + AddAssign + MulAssign + ComplexField + RealField,
{
identify_with_prefilter(u, y, ts, denominator_order, numerator_order, &Prefilter::none(), initialization, options)
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Prefilter<T> {
order: usize,
cutoff: T,
integrators: usize,
}
impl<T: Float> Prefilter<T> {
pub fn new(integrators: usize, cutoff: T) -> Result<Self, SrivcError> {
if !(cutoff > T::zero() && cutoff.is_finite()) {
return Err(SrivcError::InvalidPrefilter);
}
Ok(Self { order: integrators + 1, cutoff, integrators })
}
fn none() -> Self {
Self { order: 0, cutoff: T::zero(), integrators: 0 }
}
pub fn order(&self) -> usize {
self.order
}
pub fn cutoff(&self) -> T {
self.cutoff
}
pub fn integrators(&self) -> usize {
self.integrators
}
}
#[allow(clippy::too_many_arguments)]
pub fn identify_with_prefilter<T>(
u: &[T],
y: &[T],
ts: T,
denominator_order: usize,
numerator_order: usize,
prefilter: &Prefilter<T>,
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 && prefilter.integrators == 0) || m > n {
return Err(SrivcError::InvalidOrder { denominator: n, numerator: m });
}
if u.len() < options.evaluated_from + n + m + 1 {
return Err(SrivcError::TooFewSamples { samples: u.len(), evaluated_from: options.evaluated_from, parameters: n + m + 1 });
}
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) => {
Step::new(×_power(&[], *lambda, n), prefilter, ts, &u, y, m, options).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, prefilter, ts, &u, y, m, options).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]);
denom.resize(n + 1 + prefilter.integrators, T::zero());
let tf = TransferFunction::from_polynomials(Polynomial(theta.as_slice()[n..].to_vec()), Polynomial(denom));
let model = TransferFunctionWithDelay::new(tf, ts * T::from(nk).unwrap());
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 { denominator: n, numerator: m });
}
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 scaled_roots<T: Float + ComplexField>(a: &[T]) -> Option<(T, Vec<Complex<T>>)> {
let rho = root_radius(a);
if a.is_empty() {
return Some((rho, Vec::new()));
}
let mut coefficients = vec![Complex::from(T::one())];
coefficients.extend(a.iter().enumerate().map(|(i, &c)| Complex::from(c / Float::powi(rho, i as i32 + 1))));
dka_method(&Polynomial(coefficients)).map(|roots| (rho, roots))
}
pub(super) fn has_unstable_root<T: Float + ComplexField>(a: &[T], rate: T) -> bool {
scaled_roots(a).is_some_and(|(rho, roots)| {
let threshold = Float::max(rate / rho, Float::sqrt(T::epsilon()));
roots.iter().any(|r| r.re > threshold)
})
}
fn stabilized<T: Float + ComplexField>(a: &[T]) -> Vec<T> {
let Some((rho, roots)) = scaled_roots(a) 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).enumerate().map(|(i, c)| c.re * Float::powi(rho, i as i32 + 1)).collect()
}
fn times_power<T: Float>(a: &[T], root: T, count: usize) -> Vec<T> {
let mut c = vec![T::one()];
c.extend_from_slice(a);
for _ in 0..count {
c.push(T::zero());
for i in (1..c.len()).rev() {
c[i] = c[i] + root * c[i - 1];
}
}
c.split_off(1)
}
struct Step<T> {
filter: StateVariableFilter<T>,
instrument_filter: Option<StateVariableFilter<T>>,
uf: DMatrix<T>,
yf: DMatrix<T>,
n: usize,
m: usize,
u_shift: usize,
y_shift: usize,
start: usize,
}
impl<T: Float + AddAssign + MulAssign + ComplexField + RealField> Step<T> {
#[allow(clippy::too_many_arguments)]
fn new(a: &[T], prefilter: &Prefilter<T>, ts: T, u: &[T], y: &[T], m: usize, options: &SrivcOptions<T>) -> Self {
let filter = StateVariableFilter::new(×_power(a, prefilter.cutoff, prefilter.order), ts);
let instrument_filter = (prefilter.order > 0 && !a.is_empty()).then(|| StateVariableFilter::new(a, ts));
let uf = filter.apply(u, options.input_intersample);
let yf = filter.apply(y, InterSample::FirstOrderHold);
let (u_shift, y_shift) = (prefilter.order - prefilter.integrators, prefilter.order);
Self { filter, instrument_filter, uf, yf, n: a.len(), m, u_shift, y_shift, start: options.evaluated_from }
}
fn regressors(&self, vf: &DMatrix<T>, shift: usize) -> DMatrix<T> {
let (n, m) = (self.n, self.m);
let rows = vf.nrows() - self.start;
let mut regressors = DMatrix::zeros(rows, n + m + 1);
for i in 0..n {
let mut column = regressors.column_mut(i);
column.copy_from(&vf.column(shift + n - 1 - i).rows(self.start, rows));
column.neg_mut();
}
for j in 0..=m {
regressors.column_mut(n + j).copy_from(&self.uf.column(self.u_shift + m - j).rows(self.start, rows));
}
regressors
}
fn solve(&self, b: Option<&[T]>) -> Result<DVector<T>, SrivcError> {
let n = self.n;
let xf = b.filter(|_| n > 0).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.u_shift + self.m - j)]))
.collect();
self.instrument_filter.as_ref().unwrap_or(&self.filter).apply(&x_hat, InterSample::FirstOrderHold)
});
let instrument = xf.as_ref().unwrap_or(&self.yf);
let size = n + self.m + 1;
let phi = self.regressors(&self.yf, self.y_shift);
let zeta_instrument = xf.as_ref().map(|_| self.regressors(instrument, 0));
let zeta = zeta_instrument.as_ref().unwrap_or(&phi); let zeta_phi_sum = zeta.tr_mul(&phi);
let zeta_y_sum: DVector<T> = zeta.tr_mul(&self.yf.column(self.y_shift + n).rows(self.start, self.yf.nrows() - self.start));
let col: Vec<T> = (0..size).map(|j| zeta_phi_sum.column(j).amax()).collect();
let row: Vec<T> = (0..size).map(|i| zeta_phi_sum.row(i).amax()).collect();
let mut scaled = zeta_phi_sum.clone();
let mut rhs = zeta_y_sum.clone();
for i in 0..size {
for j in 0..size {
scaled[(i, j)] /= row[i] * col[j];
}
rhs[i] /= row[i];
}
let x = scaled.lu().solve(&rhs).ok_or(SrivcError::Singular)?;
Ok(DVector::from_iterator(size, (0..size).map(|j| x[j] / col[j])))
}
}