#![deny(clippy::indexing_slicing)]
use core::ops::{Add, Div, Mul, Neg, Sub};
use crate::error::PolynomialError;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Polynomial<const COEFFICIENT_COUNT: usize, T: Numeric = f64> {
coefficients: [T; COEFFICIENT_COUNT],
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Default for Polynomial<COEFFICIENT_COUNT, T> {
fn default() -> Self {
Self::zeros()
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Polynomial<COEFFICIENT_COUNT, T> {
#[inline]
#[must_use]
pub const fn new(coefficients: [T; COEFFICIENT_COUNT]) -> Self {
Self { coefficients }
}
#[inline]
#[must_use]
pub fn zeros() -> Self {
Self {
coefficients: [T::ZERO; COEFFICIENT_COUNT],
}
}
#[inline]
#[must_use]
pub fn coefficients(&self) -> &[T; COEFFICIENT_COUNT] {
&self.coefficients
}
#[inline]
#[must_use]
pub fn coefficient(&self, power: usize) -> Option<T> {
self.coefficients.get(power).copied()
}
#[inline]
#[must_use]
pub fn degree(&self) -> Option<usize> {
self.coefficients
.iter()
.rposition(|coefficient| *coefficient != T::ZERO)
}
#[inline]
#[must_use]
pub fn leading_coefficient(&self) -> Option<T> {
self.degree().and_then(|power| self.coefficient(power))
}
#[inline]
#[must_use]
pub fn is_zero(&self) -> bool {
self.coefficients
.iter()
.all(|coefficient| *coefficient == T::ZERO)
}
#[inline]
#[must_use]
pub fn is_finite(&self) -> bool {
self.coefficients
.iter()
.all(|coefficient| coefficient.is_finite())
}
#[must_use]
pub fn try_resize<const OTHER: usize>(&self) -> Option<Polynomial<OTHER, T>> {
if self
.coefficients
.get(OTHER..)
.is_some_and(|dropped| dropped.iter().any(|coefficient| *coefficient != T::ZERO))
{
return None;
}
let mut resized = Polynomial::<OTHER, T>::zeros();
for (slot, coefficient) in resized
.coefficients
.iter_mut()
.zip(self.coefficients.iter())
{
*slot = *coefficient;
}
Some(resized)
}
#[must_use]
pub fn evaluate(&self, x: T) -> T {
let mut accumulator = T::ZERO;
for coefficient in self.coefficients.iter().rev() {
accumulator = accumulator.mul_add(x, *coefficient);
}
accumulator
}
#[must_use]
pub fn evaluate_with_derivatives<const ORDER_COUNT: usize>(&self, x: T) -> [T; ORDER_COUNT] {
let mut result = [T::ZERO; ORDER_COUNT];
for coefficient in self.coefficients.iter().rev() {
for order in (1..ORDER_COUNT).rev() {
let lower = result.get(order - 1).copied().unwrap_or(T::ZERO);
if let Some(slot) = result.get_mut(order) {
*slot = slot.mul_add(x, lower);
}
}
if let Some(slot) = result.get_mut(0) {
*slot = slot.mul_add(x, *coefficient);
}
}
let mut scale = T::ONE;
for (order, slot) in result.iter_mut().enumerate() {
if order > 0 {
scale *= T::from_usize(order);
}
*slot *= scale;
}
result
}
#[must_use]
pub fn derivative(&self) -> Self {
let mut result = Self::zeros();
for (power, slot) in result.coefficients.iter_mut().enumerate() {
if let Some(above) = self.coefficient(power + 1) {
*slot = above * T::from_usize(power + 1);
}
}
result
}
#[must_use]
pub fn nth_derivative(&self, order: usize) -> Self {
if order >= COEFFICIENT_COUNT {
return Self::zeros();
}
let mut result = *self;
for _ in 0..order {
result = result.derivative();
}
result
}
#[must_use]
pub fn definite_integral(&self, lower: T, upper: T) -> T {
let mut total = T::ZERO;
let mut lower_power = lower;
let mut upper_power = upper;
for (power, coefficient) in self.coefficients.iter().enumerate() {
total += *coefficient * (upper_power - lower_power) / T::from_usize(power + 1);
lower_power *= lower;
upper_power *= upper;
}
total
}
#[inline]
#[must_use]
pub fn scale(mut self, factor: T) -> Self {
for slot in self.coefficients.iter_mut() {
*slot *= factor;
}
self
}
pub fn multiply_into<const OTHER: usize, const OUT: usize>(
&self,
other: &Polynomial<OTHER, T>,
) -> Result<Polynomial<OUT, T>, PolynomialError> {
let mut product = Polynomial::<OUT, T>::zeros();
for (left_power, left) in self.coefficients.iter().enumerate() {
for (right_power, right) in other.coefficients.iter().enumerate() {
let term = *left * *right;
match product.coefficients.get_mut(left_power + right_power) {
Some(slot) => *slot += term,
None if term != T::ZERO => return Err(PolynomialError::DegreeOverflow),
None => {}
}
}
}
Ok(product)
}
pub fn compose_into<const INNER: usize, const OUT: usize>(
&self,
inner: &Polynomial<INNER, T>,
) -> Result<Polynomial<OUT, T>, PolynomialError> {
let mut accumulator = Polynomial::<OUT, T>::zeros();
for coefficient in self.coefficients.iter().rev() {
accumulator = accumulator.multiply_into::<INNER, OUT>(inner)?;
match accumulator.coefficients.get_mut(0) {
Some(slot) => *slot += *coefficient,
None if *coefficient != T::ZERO => return Err(PolynomialError::DegreeOverflow),
None => {}
}
}
Ok(accumulator)
}
pub fn divide<const DIVISOR: usize, const QUOTIENT: usize, const REMAINDER: usize>(
&self,
divisor: &Polynomial<DIVISOR, T>,
) -> Result<(Polynomial<QUOTIENT, T>, Polynomial<REMAINDER, T>), PolynomialError> {
let divisor_degree = divisor
.degree()
.ok_or(PolynomialError::LeadingCoefficientZero)?;
let divisor_leading = divisor
.coefficient(divisor_degree)
.ok_or(PolynomialError::LeadingCoefficientZero)?;
let mut working = self.coefficients;
let mut quotient = Polynomial::<QUOTIENT, T>::zeros();
let mut power = COEFFICIENT_COUNT;
while power > divisor_degree {
power -= 1;
let leading = working.get(power).copied().unwrap_or(T::ZERO);
if leading == T::ZERO {
continue;
}
let share = leading / divisor_leading;
let quotient_power = power - divisor_degree;
match quotient.coefficients.get_mut(quotient_power) {
Some(slot) => *slot += share,
None => return Err(PolynomialError::DegreeOverflow),
}
for (divisor_power, divisor_coefficient) in divisor.coefficients.iter().enumerate() {
if let Some(slot) = working.get_mut(quotient_power + divisor_power) {
*slot -= share * *divisor_coefficient;
}
}
if let Some(slot) = working.get_mut(power) {
*slot = T::ZERO;
}
}
let mut remainder = Polynomial::<REMAINDER, T>::zeros();
for (power, coefficient) in working.iter().enumerate().take(divisor_degree) {
match remainder.coefficients.get_mut(power) {
Some(slot) => *slot = *coefficient,
None if *coefficient != T::ZERO => return Err(PolynomialError::DegreeOverflow),
None => {}
}
}
Ok((quotient, remainder))
}
#[must_use]
pub fn shift_argument(&self, offset: T) -> Self {
let mut shifted = Self::zeros();
let mut working = *self;
for slot in shifted.coefficients.iter_mut() {
let mut quotient = Self::zeros();
let mut carry = T::ZERO;
for power in (0..COEFFICIENT_COUNT).rev() {
let coefficient = working.coefficient(power).unwrap_or(T::ZERO);
carry = carry.mul_add(offset, coefficient);
if let Some(target) = power
.checked_sub(1)
.and_then(|lower| quotient.coefficients.get_mut(lower))
{
*target = carry;
}
}
*slot = carry;
working = quotient;
}
shifted
}
#[must_use]
pub fn scale_argument(&self, factor: T) -> Self {
let mut scaled = *self;
let mut power_of_factor = T::ONE;
for slot in scaled.coefficients.iter_mut() {
*slot *= power_of_factor;
power_of_factor *= factor;
}
scaled
}
#[must_use]
pub fn reverse(&self) -> Self {
let mut reversed = *self;
reversed.coefficients.reverse();
reversed
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Add for Polynomial<COEFFICIENT_COUNT, T> {
type Output = Self;
#[inline]
fn add(mut self, other: Self) -> Self {
for (slot, addend) in self.coefficients.iter_mut().zip(other.coefficients.iter()) {
*slot += *addend;
}
self
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Sub for Polynomial<COEFFICIENT_COUNT, T> {
type Output = Self;
#[inline]
fn sub(mut self, other: Self) -> Self {
for (slot, subtrahend) in self.coefficients.iter_mut().zip(other.coefficients.iter()) {
*slot -= *subtrahend;
}
self
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Neg for Polynomial<COEFFICIENT_COUNT, T> {
type Output = Self;
#[inline]
fn neg(mut self) -> Self {
for slot in self.coefficients.iter_mut() {
*slot = -*slot;
}
self
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Mul<T> for Polynomial<COEFFICIENT_COUNT, T> {
type Output = Self;
#[inline]
fn mul(self, factor: T) -> Self {
self.scale(factor)
}
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Div<T> for Polynomial<COEFFICIENT_COUNT, T> {
type Output = Self;
#[inline]
fn div(mut self, divisor: T) -> Self {
for slot in self.coefficients.iter_mut() {
*slot /= divisor;
}
self
}
}