use crate::laplace_transform::DelayedRationalSum;
use crate::{Continuous, Polynomial, TransferFunction};
use num_traits::Float;
const JUMP_RELATIVE_TOLERANCE: f64 = 1e-9;
#[derive(Clone, Debug)]
pub struct PiecewisePolynomial<T> {
pub start: T,
pub pieces: Vec<(T, Polynomial<T>)>,
pub tail: Polynomial<T>,
}
#[derive(Clone, Debug)]
pub(crate) struct Jump<T> {
pub time: T,
pub coefficients: Vec<T>,
}
impl<T: Float> Jump<T> {
pub fn order(&self) -> usize {
self.coefficients.iter().position(|c| !c.is_zero()).unwrap_or(self.coefficients.len())
}
}
impl<T: Float> PiecewisePolynomial<T> {
pub fn new(start: T, pieces: Vec<(T, Polynomial<T>)>, tail: Polynomial<T>) -> Self {
Self { start, pieces, tail }
}
pub fn degree(&self) -> usize {
self.pieces
.iter()
.map(|(_, p)| p)
.chain(std::iter::once(&self.tail))
.map(|p| p.len().saturating_sub(1))
.max()
.unwrap_or(0)
}
fn local(&self, t: T) -> Option<(&Polynomial<T>, T)> {
if t < self.start {
return None;
}
let mut begin = self.start;
for (duration, p) in &self.pieces {
if t < begin + *duration {
return Some((p, t - begin));
}
begin = begin + *duration;
}
Some((&self.tail, t - begin))
}
pub fn value(&self, t: T) -> T {
self.derivatives(t, 1)[0]
}
pub fn derivatives(&self, t: T, count: usize) -> Vec<T> {
let Some((p, tau)) = self.local(t) else {
return vec![T::zero(); count];
};
let mut coeffs = p.0.clone();
let mut out = Vec::with_capacity(count);
for _ in 0..count {
out.push(coeffs.iter().fold(T::zero(), |acc, &c| acc * tau + c));
coeffs = derivative(&coeffs);
}
out
}
pub(crate) fn jumps(&self) -> Vec<Jump<T>> {
let mut jumps = Vec::with_capacity(self.pieces.len() + 1);
let mut time = self.start;
let mut previous: Option<(Vec<T>, T)> = None;
for next in self.pieces.iter().map(|(d, p)| (*d, p)).chain(std::iter::once((T::zero(), &self.tail))) {
let (duration, p) = next;
let current = ascending(p);
let coefficients = match &previous {
None => current.clone(),
Some((prev, length)) => {
let shifted = shift(prev, *length);
let len = current.len().max(shifted.len());
let at = |v: &[T], i: usize| v.get(i).copied().unwrap_or_else(T::zero);
let natural = |v: &[T]| {
v.iter().enumerate().fold(T::zero(), |acc, (i, &c)| acc.max(c.abs() * length.powi(i as i32)))
};
let scale = natural(¤t).max(natural(prev));
let tol = T::from(JUMP_RELATIVE_TOLERANCE).unwrap();
(0..len)
.map(|m| {
let c = at(¤t, m) - at(&shifted, m);
let threshold = tol * scale / length.powi(m as i32);
if c.abs() <= threshold { T::zero() } else { c }
})
.collect()
}
};
let mut coefficients: Vec<T> = coefficients;
while coefficients.last().is_some_and(|c| c.is_zero()) {
coefficients.pop();
}
if !coefficients.is_empty() {
jumps.push(Jump { time, coefficients });
}
previous = Some((current, duration));
time = time + duration;
}
jumps
}
}
impl<T: Float + std::ops::AddAssign> PiecewisePolynomial<T> {
pub fn laplace_transform(&self) -> DelayedRationalSum<T> {
let mut signal = DelayedRationalSum::new();
for jump in self.jumps() {
signal.push(jump.time, jump_rational(&jump));
}
signal
}
}
pub(crate) fn jump_rational<T: Float>(jump: &Jump<T>) -> TransferFunction<T, Continuous> {
let len = jump.coefficients.len();
let mut factorial = T::one();
let mut numerator = vec![T::zero(); len];
for (m, &c) in jump.coefficients.iter().enumerate() {
if m > 0 {
factorial = factorial * T::from(m).unwrap();
}
numerator[m] = c * factorial;
}
let mut denominator = vec![T::zero(); len + 1];
denominator[0] = T::one();
TransferFunction::from_polynomials(Polynomial(numerator), Polynomial(denominator))
}
fn ascending<T: Float>(p: &Polynomial<T>) -> Vec<T> {
p.iter().rev().copied().collect()
}
fn derivative<T: Float>(coeffs: &[T]) -> Vec<T> {
let degree = coeffs.len().saturating_sub(1);
coeffs
.iter()
.take(degree)
.enumerate()
.map(|(i, &c)| c * T::from(degree - i).unwrap())
.collect()
}
fn shift<T: Float>(p: &[T], a: T) -> Vec<T> {
let n = p.len();
(0..n)
.map(|m| {
let mut binomial = T::one(); let mut power = T::one(); let mut sum = T::zero();
for (i, &c) in p.iter().enumerate().skip(m) {
if i > m {
binomial = binomial * T::from(i).unwrap() / T::from(i - m).unwrap();
power = power * a;
}
sum = sum + binomial * power * c;
}
sum
})
.collect()
}