#![deny(clippy::indexing_slicing)]
use crate::error::PolynomialError;
use crate::linear_algebra::Vector;
use crate::polynomial::Polynomial;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PiecewisePolynomial<
const MAX_PIECES: usize,
const COEFFICIENTS_PER_PIECE: usize,
const DIMENSION: usize,
T: Numeric = f64,
> {
pieces: [[Polynomial<COEFFICIENTS_PER_PIECE, T>; DIMENSION]; MAX_PIECES],
spans: [T; MAX_PIECES],
length: usize,
}
impl<
const MAX_PIECES: usize,
const COEFFICIENTS_PER_PIECE: usize,
const DIMENSION: usize,
T: Numeric,
> PiecewisePolynomial<MAX_PIECES, COEFFICIENTS_PER_PIECE, DIMENSION, T>
{
pub fn try_from_pieces(
pieces: &[[Polynomial<COEFFICIENTS_PER_PIECE, T>; DIMENSION]],
spans: &[T],
) -> Result<Self, PolynomialError> {
if pieces.is_empty() || spans.is_empty() {
return Err(PolynomialError::Empty);
}
if pieces.len() != spans.len() || pieces.len() > MAX_PIECES {
return Err(PolynomialError::CapacityExceeded);
}
for span in spans {
if !span.is_finite() || *span <= T::ZERO {
return Err(PolynomialError::SpanNotPositive);
}
}
let mut curve = Self {
pieces: [[Polynomial::zeros(); DIMENSION]; MAX_PIECES],
spans: [T::ZERO; MAX_PIECES],
length: pieces.len(),
};
for (slot, piece) in curve.pieces.iter_mut().zip(pieces.iter()) {
*slot = *piece;
}
for (slot, span) in curve.spans.iter_mut().zip(spans.iter()) {
*slot = *span;
}
Ok(curve)
}
#[inline]
#[must_use]
pub fn piece_count(&self) -> usize {
self.length
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
#[inline]
#[must_use]
pub fn total_span(&self) -> T {
let mut total = T::ZERO;
for span in self.spans.iter().take(self.length) {
total += *span;
}
total
}
#[inline]
#[must_use]
pub fn span(&self, piece: usize) -> Option<T> {
if piece >= self.length {
return None;
}
self.spans.get(piece).copied()
}
#[inline]
#[must_use]
pub fn piece_polynomial(
&self,
piece: usize,
axis: usize,
) -> Option<&Polynomial<COEFFICIENTS_PER_PIECE, T>> {
if piece >= self.length {
return None;
}
self.pieces.get(piece)?.get(axis)
}
fn locate(&self, parameter: T) -> Option<(usize, T)> {
if self.length == 0 {
return None;
}
if parameter <= T::ZERO {
return Some((0, T::ZERO));
}
let mut covered = T::ZERO;
for (index, span) in self.spans.iter().take(self.length).enumerate() {
if parameter < covered + *span {
return Some((index, (parameter - covered) / *span));
}
covered += *span;
}
Some((self.length - 1, T::ONE))
}
pub fn evaluate(&self, parameter: T) -> Result<Vector<DIMENSION, T>, PolynomialError> {
let (piece, along) = self.locate(parameter).ok_or(PolynomialError::Empty)?;
let polynomials = self.pieces.get(piece).ok_or(PolynomialError::Empty)?;
let mut values = [T::ZERO; DIMENSION];
for (slot, polynomial) in values.iter_mut().zip(polynomials.iter()) {
*slot = polynomial.evaluate(along);
}
Ok(Vector::new(values))
}
pub fn evaluate_with_derivatives<const ORDER_COUNT: usize>(
&self,
parameter: T,
) -> Result<[Vector<DIMENSION, T>; ORDER_COUNT], PolynomialError> {
let (piece, along) = self.locate(parameter).ok_or(PolynomialError::Empty)?;
let polynomials = self.pieces.get(piece).ok_or(PolynomialError::Empty)?;
let span = self
.spans
.get(piece)
.copied()
.ok_or(PolynomialError::Empty)?;
let mut per_axis = [[T::ZERO; ORDER_COUNT]; DIMENSION];
for (slot, polynomial) in per_axis.iter_mut().zip(polynomials.iter()) {
*slot = polynomial.evaluate_with_derivatives(along);
}
let mut result = [Vector::<DIMENSION, T>::zeros(); ORDER_COUNT];
let mut span_raised = T::ONE;
for (order, vector) in result.iter_mut().enumerate() {
if order > 0 {
span_raised *= span;
}
let converted = T::ONE / span_raised;
let mut values = [T::ZERO; DIMENSION];
for (slot, orders) in values.iter_mut().zip(per_axis.iter()) {
*slot = orders.get(order).copied().unwrap_or(T::ZERO) * converted;
}
*vector = Vector::new(values);
}
Ok(result)
}
pub fn definite_integral(
&self,
lower: T,
upper: T,
) -> Result<Vector<DIMENSION, T>, PolynomialError> {
if self.length == 0 {
return Err(PolynomialError::Empty);
}
let flipped = upper < lower;
let (wanted_start, wanted_end) = if flipped {
(upper, lower)
} else {
(lower, upper)
};
let total = self.total_span();
let wanted_start = wanted_start.max(T::ZERO).min(total);
let wanted_end = wanted_end.max(T::ZERO).min(total);
let mut areas = [T::ZERO; DIMENSION];
let mut covered = T::ZERO;
for (polynomials, span) in self.pieces.iter().zip(self.spans.iter()).take(self.length) {
let piece_start = covered;
covered += *span;
let start = wanted_start.max(piece_start);
let end = wanted_end.min(covered);
if end <= start {
continue;
}
let along_start = (start - piece_start) / *span;
let along_end = (end - piece_start) / *span;
for (slot, polynomial) in areas.iter_mut().zip(polynomials.iter()) {
*slot += polynomial.definite_integral(along_start, along_end) * *span;
}
}
if flipped {
for slot in areas.iter_mut() {
*slot = -*slot;
}
}
Ok(Vector::new(areas))
}
#[must_use]
pub fn derivative(&self) -> Self {
self.nth_derivative(1)
}
#[must_use]
pub fn nth_derivative(&self, order: usize) -> Self {
let mut result = *self;
for (piece, span) in result
.pieces
.iter_mut()
.zip(self.spans.iter())
.take(self.length)
{
let mut span_raised = T::ONE;
for _ in 0..order.min(COEFFICIENTS_PER_PIECE) {
span_raised *= *span;
}
let converted = T::ONE / span_raised;
for polynomial in piece.iter_mut() {
*polynomial = polynomial.nth_derivative(order).scale(converted);
}
}
result
}
}