use super::error::LtiError;
use super::util::{cast_real_scalar, trim_leading_zeros, validate_sample_time};
use super::{DiscreteSos, DiscreteTime};
use alloc::vec::Vec;
use faer::complex::Complex;
use faer_traits::RealField;
use num_traits::{Float, NumCast};
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum DeltaSection<R> {
Direct { d: R },
First { alpha0: R, c0: R, d: R },
Second {
alpha0: R,
alpha1: R,
c1: R,
c2: R,
d: R,
},
}
#[derive(Clone, Debug, PartialEq)]
pub struct DeltaSos<R> {
sections: Vec<DeltaSection<R>>,
gain: R,
domain: DiscreteTime<R>,
}
impl<R> DeltaSos<R>
where
R: Float + RealField,
{
pub fn new(
sections: impl Into<Vec<DeltaSection<R>>>,
gain: R,
sample_time: R,
) -> Result<Self, LtiError> {
validate_sample_time(sample_time)?;
let sections = sections.into();
if sections.is_empty() {
return Err(LtiError::EmptySos);
}
Ok(Self {
sections,
gain,
domain: DiscreteTime::new(sample_time),
})
}
#[must_use]
pub fn sections(&self) -> &[DeltaSection<R>] {
&self.sections
}
#[must_use]
pub fn gain(&self) -> R {
self.gain
}
#[must_use]
pub fn sample_time(&self) -> R {
self.domain.sample_time()
}
pub fn dc_gain(&self) -> Result<Complex<R>, LtiError> {
let mut gain = self.gain;
for section in &self.sections {
gain *= delta_section_dc_gain(*section);
}
let gain = Complex::new(gain, R::zero());
if gain.re.is_finite() && gain.im.is_finite() {
Ok(gain)
} else {
Err(LtiError::NonFiniteResult { which: "dc_gain" })
}
}
pub fn try_cast<S>(&self) -> Result<DeltaSos<S>, LtiError>
where
S: Float + RealField + NumCast,
{
DeltaSos::new(
self.sections
.iter()
.map(|section| section.try_cast())
.collect::<Result<Vec<_>, _>>()?,
cast_real_scalar(self.gain, "delta_sos.gain")?,
cast_real_scalar(self.sample_time(), "delta_sos.sample_time")?,
)
}
}
impl<R> DeltaSection<R>
where
R: Float + RealField,
{
pub fn try_cast<S>(&self) -> Result<DeltaSection<S>, LtiError>
where
S: Float + RealField + NumCast,
{
match *self {
DeltaSection::Direct { d } => Ok(DeltaSection::Direct {
d: cast_real_scalar(d, "delta_sos.section.d")?,
}),
DeltaSection::First { alpha0, c0, d } => Ok(DeltaSection::First {
alpha0: cast_real_scalar(alpha0, "delta_sos.section.alpha0")?,
c0: cast_real_scalar(c0, "delta_sos.section.c0")?,
d: cast_real_scalar(d, "delta_sos.section.d")?,
}),
DeltaSection::Second {
alpha0,
alpha1,
c1,
c2,
d,
} => Ok(DeltaSection::Second {
alpha0: cast_real_scalar(alpha0, "delta_sos.section.alpha0")?,
alpha1: cast_real_scalar(alpha1, "delta_sos.section.alpha1")?,
c1: cast_real_scalar(c1, "delta_sos.section.c1")?,
c2: cast_real_scalar(c2, "delta_sos.section.c2")?,
d: cast_real_scalar(d, "delta_sos.section.d")?,
}),
}
}
}
impl<R> DiscreteSos<R>
where
R: Float + RealField,
{
pub fn to_delta_sos(&self) -> Result<DeltaSos<R>, LtiError> {
let dt = self.sample_time();
let sections = self
.sections()
.iter()
.map(|section| delta_section_from_sos(section.numerator(), section.denominator(), dt))
.collect::<Result<Vec<_>, _>>()?;
DeltaSos::new(sections, self.gain(), self.sample_time())
}
}
fn delta_section_dc_gain<R>(section: DeltaSection<R>) -> R
where
R: Float + RealField,
{
match section {
DeltaSection::Direct { d } => d,
DeltaSection::First { alpha0, c0, d } => d + c0 / alpha0,
DeltaSection::Second { alpha0, c1, d, .. } => d + c1 / alpha0,
}
}
fn delta_section_from_sos<R>(
numerator: [R; 3],
denominator: [R; 3],
sample_time: R,
) -> Result<DeltaSection<R>, LtiError>
where
R: Float + RealField,
{
let (numerator, denominator) = reduced_section_polynomials(numerator, denominator)?;
let dt = sample_time;
let dt2 = dt * dt;
match denominator.len() - 1 {
0 => {
if numerator.len() != 1 {
return Err(LtiError::ImproperTransferFunction {
numerator_degree: numerator.len() - 1,
denominator_degree: 0,
});
}
Ok(DeltaSection::Direct { d: numerator[0] })
}
1 => {
let d0 = denominator[1];
let [n1, n0] = match numerator.as_slice() {
[n0] => [R::zero(), *n0],
[n1, n0] => [*n1, *n0],
_ => {
return Err(LtiError::ImproperTransferFunction {
numerator_degree: numerator.len() - 1,
denominator_degree: 1,
});
}
};
let alpha0 = (R::one() + d0) / dt;
let d = n1;
let g0 = (n1 + n0) / dt;
let c0 = g0 - alpha0 * d;
Ok(DeltaSection::First { alpha0, c0, d })
}
2 => {
let d1 = denominator[1];
let d0 = denominator[2];
let [n2, n1, n0] = match numerator.as_slice() {
[n0] => [R::zero(), R::zero(), *n0],
[n1, n0] => [R::zero(), *n1, *n0],
[n2, n1, n0] => [*n2, *n1, *n0],
_ => unreachable!("section order is at most two"),
};
let alpha1 = (R::one() + R::one() + d1) / dt;
let alpha0 = (R::one() + d1 + d0) / dt2;
let d = n2;
let g1 = (n2 + n2 + n1) / dt;
let g0 = (n2 + n1 + n0) / dt2;
let c2 = g1 - alpha1 * d;
let c1 = g0 - alpha0 * d;
Ok(DeltaSection::Second {
alpha0,
alpha1,
c1,
c2,
d,
})
}
_ => unreachable!("SOS sections have order at most two"),
}
}
fn reduced_section_polynomials<R>(
numerator: [R; 3],
denominator: [R; 3],
) -> Result<(Vec<R>, Vec<R>), LtiError>
where
R: Float + RealField,
{
let mut numerator = trim_leading_zeros(&numerator);
let mut denominator = trim_leading_zeros(&denominator);
while numerator.len() > 1
&& denominator.len() > 1
&& numerator.last() == Some(&R::zero())
&& denominator.last() == Some(&R::zero())
{
numerator.pop();
denominator.pop();
}
if denominator.is_empty() || denominator[0] == R::zero() {
return Err(LtiError::ZeroLeadingCoefficient {
which: "sos.denominator",
});
}
let scale = denominator[0].recip();
for value in &mut numerator {
*value *= scale;
}
for value in &mut denominator {
*value *= scale;
}
if numerator.len() > denominator.len() {
return Err(LtiError::ImproperTransferFunction {
numerator_degree: numerator.len() - 1,
denominator_degree: denominator.len() - 1,
});
}
Ok((numerator, denominator))
}