use super::error::LtiError;
use super::transfer_function::TransferFunction;
use super::util::{
CompositionDomain, cast_real_scalar, identity_section, poly_mul, root_sections,
validate_sample_time,
};
use super::zpk::Zpk;
use super::{ContinuousStateSpace, ContinuousTime, DiscreteStateSpace, DiscreteTime};
use crate::scalar::complex_horner_step_real;
use alloc::vec::Vec;
use faer::complex::Complex;
use faer_traits::RealField;
use num_traits::{Float, NumCast};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct SecondOrderSection<R> {
numerator: [R; 3],
denominator: [R; 3],
}
impl<R> SecondOrderSection<R>
where
R: Float + RealField,
{
pub fn new(numerator: [R; 3], denominator: [R; 3]) -> Result<Self, LtiError> {
let leading = denominator
.into_iter()
.find(|&value| value != R::zero())
.ok_or(LtiError::ZeroLeadingCoefficient {
which: "sos.denominator",
})?;
if leading == R::zero() {
return Err(LtiError::ZeroLeadingCoefficient {
which: "sos.denominator",
});
}
let scale = leading.recip();
Ok(Self {
numerator: numerator.map(|value| value * scale),
denominator: denominator.map(|value| value * scale),
})
}
#[must_use]
pub fn numerator(&self) -> [R; 3] {
self.numerator
}
#[must_use]
pub fn denominator(&self) -> [R; 3] {
self.denominator
}
pub fn try_cast<S>(&self) -> Result<SecondOrderSection<S>, LtiError>
where
S: Float + RealField + NumCast,
{
SecondOrderSection::new(
[
cast_real_scalar(self.numerator[0], "sos.section.numerator")?,
cast_real_scalar(self.numerator[1], "sos.section.numerator")?,
cast_real_scalar(self.numerator[2], "sos.section.numerator")?,
],
[
cast_real_scalar(self.denominator[0], "sos.section.denominator")?,
cast_real_scalar(self.denominator[1], "sos.section.denominator")?,
cast_real_scalar(self.denominator[2], "sos.section.denominator")?,
],
)
}
#[must_use]
fn evaluate(&self, point: Complex<R>) -> Complex<R> {
let num = self
.numerator
.iter()
.fold(Complex::new(R::zero(), R::zero()), |acc, &coef| {
complex_horner_step_real(acc, point, coef)
});
let den = self
.denominator
.iter()
.fold(Complex::new(R::zero(), R::zero()), |acc, &coef| {
complex_horner_step_real(acc, point, coef)
});
num / den
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Sos<R, Domain> {
sections: Vec<SecondOrderSection<R>>,
gain: R,
domain: Domain,
}
pub type ContinuousSos<R> = Sos<R, ContinuousTime>;
pub type DiscreteSos<R> = Sos<R, DiscreteTime<R>>;
impl<R, Domain> Sos<R, Domain>
where
R: Float + RealField,
Domain: Clone,
{
pub fn new(
sections: impl Into<Vec<SecondOrderSection<R>>>,
gain: R,
domain: Domain,
) -> Result<Self, LtiError> {
let sections = sections.into();
if sections.is_empty() {
return Err(LtiError::EmptySos);
}
Ok(Self {
sections,
gain,
domain,
})
}
#[must_use]
pub fn sections(&self) -> &[SecondOrderSection<R>] {
&self.sections
}
#[must_use]
pub fn gain(&self) -> R {
self.gain
}
#[must_use]
pub fn domain(&self) -> &Domain {
&self.domain
}
#[must_use]
pub fn evaluate(&self, point: Complex<R>) -> Complex<R> {
self.sections.iter().fold(
Complex::new(self.gain, R::zero()),
|acc: Complex<R>, section: &SecondOrderSection<R>| acc * section.evaluate(point),
)
}
pub fn to_transfer_function(&self) -> Result<TransferFunction<R, Domain>, LtiError> {
let mut numerator = vec![self.gain];
let mut denominator = vec![R::one()];
for section in &self.sections {
numerator = poly_mul(&numerator, §ion.numerator);
denominator = poly_mul(&denominator, §ion.denominator);
}
TransferFunction::new(numerator, denominator, self.domain.clone())
}
pub fn to_zpk(&self) -> Result<Zpk<R, Domain>, LtiError> {
self.to_transfer_function()?.to_zpk()
}
pub fn from_zpk(zpk: &Zpk<R, Domain>) -> Result<Self, LtiError> {
let numerator_sections = root_sections(zpk.zeros(), "zeros")?;
let denominator_sections = root_sections(zpk.poles(), "poles")?;
let count = numerator_sections
.len()
.max(denominator_sections.len())
.max(1);
let mut sections = Vec::with_capacity(count);
for i in 0..count {
let numerator = numerator_sections
.get(i)
.copied()
.unwrap_or_else(identity_section);
let denominator = denominator_sections
.get(i)
.copied()
.unwrap_or_else(identity_section);
sections.push(SecondOrderSection::new(numerator, denominator)?);
}
Self::new(sections, zpk.gain(), zpk.domain().clone())
}
}
impl<R, Domain> Sos<R, Domain>
where
R: Float + RealField,
Domain: CompositionDomain<R>,
{
pub fn add(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.add(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn sub(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.sub(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn mul(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.mul(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn div(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.div(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn inv(&self) -> Result<Self, LtiError> {
self.to_transfer_function()?.inv()?.to_sos()
}
pub fn feedback(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.feedback(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn positive_feedback(&self, rhs: &Self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.positive_feedback(&rhs.to_transfer_function()?)?
.to_sos()
}
pub fn unity_feedback(&self) -> Result<Self, LtiError> {
self.to_transfer_function()?.unity_feedback()?.to_sos()
}
pub fn positive_unity_feedback(&self) -> Result<Self, LtiError> {
self.to_transfer_function()?
.positive_unity_feedback()?
.to_sos()
}
}
impl<R> ContinuousSos<R>
where
R: Float + RealField,
{
pub fn continuous(
sections: impl Into<Vec<SecondOrderSection<R>>>,
gain: R,
) -> Result<Self, LtiError> {
Self::new(sections, gain, ContinuousTime)
}
pub fn dc_gain(&self) -> Result<Complex<R>, LtiError> {
let gain = self.evaluate(Complex::new(R::zero(), R::zero()));
if gain.re.is_finite() && gain.im.is_finite() {
Ok(gain)
} else {
Err(LtiError::NonFiniteResult { which: "dc_gain" })
}
}
pub fn to_state_space(&self) -> Result<ContinuousStateSpace<R>, LtiError> {
self.to_transfer_function()?.to_state_space()
}
pub fn try_cast<S>(&self) -> Result<ContinuousSos<S>, LtiError>
where
S: Float + RealField + NumCast,
{
ContinuousSos::continuous(
self.sections()
.iter()
.map(|section| section.try_cast())
.collect::<Result<Vec<_>, _>>()?,
cast_real_scalar(self.gain(), "sos.gain")?,
)
}
}
impl<R> DiscreteSos<R>
where
R: Float + RealField,
{
pub fn discrete(
sections: impl Into<Vec<SecondOrderSection<R>>>,
gain: R,
sample_time: R,
) -> Result<Self, LtiError> {
validate_sample_time(sample_time)?;
Self::new(sections, gain, DiscreteTime::new(sample_time))
}
#[must_use]
pub fn sample_time(&self) -> R {
self.domain.sample_time()
}
pub fn delay(samples: usize, sample_time: R) -> Result<Self, LtiError> {
super::DiscreteZpk::delay(samples, sample_time)?.to_sos()
}
pub fn dc_gain(&self) -> Result<Complex<R>, LtiError> {
let gain = self.evaluate(Complex::new(R::one(), R::zero()));
if gain.re.is_finite() && gain.im.is_finite() {
Ok(gain)
} else {
Err(LtiError::NonFiniteResult { which: "dc_gain" })
}
}
pub fn to_state_space(&self) -> Result<DiscreteStateSpace<R>, LtiError> {
self.to_transfer_function()?.to_state_space()
}
pub fn try_cast<S>(&self) -> Result<DiscreteSos<S>, LtiError>
where
S: Float + RealField + NumCast,
{
DiscreteSos::discrete(
self.sections()
.iter()
.map(|section| section.try_cast())
.collect::<Result<Vec<_>, _>>()?,
cast_real_scalar(self.gain(), "sos.gain")?,
cast_real_scalar(self.sample_time(), "sos.sample_time")?,
)
}
}