use std::borrow::Borrow;
use nalgebra::{ComplexField, DMatrix, DVector, RealField};
use num_traits::Float;
use thiserror::Error;
use super::ReferenceSignal;
use crate::discretize::Zoh;
use crate::laplace_transform::StableInverseError;
use crate::{Continuous, Discrete, StateSpace, StateSpaceError};
pub struct LiftedDiscretizedSystem<T> {
pub continuous: StateSpace<T, Continuous>,
pub ssr: StateSpace<T, Discrete>,
pub ts: T,
pub order: u32,
inv_b: DMatrix<T>,
}
impl<T: Float + ComplexField + RealField> LiftedDiscretizedSystem<T> {
pub fn new<S: Borrow<StateSpace<T, Continuous>>>(continuous: S, ts: T) -> Result<Self, StateSpaceError> {
let continuous = continuous.borrow().clone();
let discrete = continuous.discretize(Zoh, ts)?;
let n = discrete.order.system;
let m = discrete.order.input;
let order = n as u32;
let a = &discrete.a;
let b = &discrete.b;
let a_lifted = a.pow(order);
let mut b_lifted = DMatrix::<T>::zeros(n, m * order as usize);
let mut a_power = DMatrix::<T>::identity(n, n);
for k in 0..order as usize {
let col = (order as usize - 1 - k) * m;
b_lifted.columns_mut(col, m).copy_from(&(&a_power * b));
a_power = a * &a_power;
}
let d = DMatrix::<T>::zeros(discrete.order.output, order as usize);
let inv_b = b_lifted.clone()
.try_inverse()
.ok_or(StateSpaceError::SingularMatrix)?;
let ssr = StateSpace::new(a_lifted, b_lifted, discrete.c.clone(), d)?;
Ok(Self { continuous, ssr, ts, order, inv_b })
}
}
#[derive(Clone, Debug, Error, PartialEq)]
pub enum PtcError {
#[error("Perfect tracking from an output reference supports SISO systems only, got {inputs} inputs and {outputs} outputs")]
NotSiso {
inputs: usize,
outputs: usize,
},
#[error("The system has a direct feedthrough term (D != 0)")]
Feedthrough,
#[error("(A, B) is not controllable")]
Uncontrollable,
#[error(transparent)]
StableInverse(#[from] StableInverseError),
}
impl<T: Float + ComplexField + RealField> LiftedDiscretizedSystem<T> {
fn inputs_from_frame_states(&self, frames: &[DVector<T>]) -> Vec<T> {
let mut u = Vec::<T>::with_capacity(frames.len() * self.order as usize);
for points in frames.windows(2) {
let u_effort = &points[1] - &self.ssr.a * &points[0];
let u_local = &self.inv_b * u_effort;
u.extend(u_local.iter());
}
u
}
pub fn calculate_ptc_input_for_reference_output<S: ReferenceSignal<T>>(
&self,
y_d: &S,
samples: usize,
) -> Result<Vec<T>, PtcError> {
let reference = self.state_reference_from_output(y_d)?;
let n = self.order as usize;
let frame_time = self.ts * T::from(n).unwrap();
let frames: Vec<DVector<T>> = (0..=samples.div_ceil(n))
.map(|k| reference(frame_time * T::from(k).unwrap()))
.collect();
Ok(self.inputs_from_frame_states(&frames))
}
pub fn state_reference_from_output<S: ReferenceSignal<T>>(&self, y_d: &S) -> Result<impl Fn(T) -> DVector<T> + use<T, S>, PtcError> {
let sys = &self.continuous;
if sys.order.input != 1 || sys.order.output != 1 {
return Err(PtcError::NotSiso { inputs: sys.order.input, outputs: sys.order.output });
}
if sys.d.iter().any(|d| !d.is_zero()) {
return Err(PtcError::Feedthrough);
}
let plant = sys.transfer_function().map_err(|_| PtcError::NotSiso { inputs: sys.order.input, outputs: sys.order.output })?;
let reference = y_d.to_state_reference(&plant)?;
let canonical = StateSpace::normalized_controllable_canonical(&plant).map_err(|_| PtcError::Uncontrollable)?;
let w = controllability_matrix(&sys.a, &sys.b);
let w_c = controllability_matrix(&canonical.a, &canonical.b);
let w_c_inv = w_c.try_inverse().ok_or(PtcError::Uncontrollable)?;
let transform = w * w_c_inv;
if transform.clone().try_inverse().is_none() {
return Err(PtcError::Uncontrollable);
}
Ok(move |t: T| &transform * DVector::from(reference.state(t)))
}
}
fn controllability_matrix<T: Float + ComplexField + RealField>(a: &DMatrix<T>, b: &DMatrix<T>) -> DMatrix<T> {
let n = a.nrows();
let mut w = DMatrix::<T>::zeros(n, n);
let mut column = b.column(0).into_owned();
for k in 0..n {
w.set_column(k, &column);
column = a * column;
}
w
}