#![deny(clippy::indexing_slicing)]
use crate::error::PolynomialError;
use crate::linear_algebra::Vector;
use crate::polynomial::Polynomial;
use crate::scalar::Numeric;
fn raise<T: Numeric>(value: T, exponent: u32) -> T {
value.powi(i32::try_from(exponent).unwrap_or(i32::MAX))
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MultivariateTerm<const VARIABLES: usize, T: Numeric = f64> {
coefficient: T,
exponents: [u32; VARIABLES],
}
impl<const VARIABLES: usize, T: Numeric> MultivariateTerm<VARIABLES, T> {
#[inline]
#[must_use]
pub const fn new(coefficient: T, exponents: [u32; VARIABLES]) -> Self {
Self {
coefficient,
exponents,
}
}
#[inline]
#[must_use]
pub fn coefficient(&self) -> T {
self.coefficient
}
#[inline]
#[must_use]
pub fn exponents(&self) -> &[u32; VARIABLES] {
&self.exponents
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MultivariatePolynomial<const VARIABLES: usize, const MAX_TERMS: usize, T: Numeric = f64>
{
terms: [MultivariateTerm<VARIABLES, T>; MAX_TERMS],
length: usize,
}
impl<const VARIABLES: usize, const MAX_TERMS: usize, T: Numeric> Default
for MultivariatePolynomial<VARIABLES, MAX_TERMS, T>
{
fn default() -> Self {
Self::new()
}
}
impl<const VARIABLES: usize, const MAX_TERMS: usize, T: Numeric>
MultivariatePolynomial<VARIABLES, MAX_TERMS, T>
{
#[inline]
#[must_use]
pub fn new() -> Self {
Self {
terms: [MultivariateTerm::new(T::ZERO, [0; VARIABLES]); MAX_TERMS],
length: 0,
}
}
pub fn try_from_terms(
terms: &[MultivariateTerm<VARIABLES, T>],
) -> Result<Self, PolynomialError> {
let mut result = Self::new();
for term in terms {
result.push(*term)?;
}
Ok(result)
}
pub fn try_from_array(
terms: [MultivariateTerm<VARIABLES, T>; MAX_TERMS],
) -> Result<Self, PolynomialError> {
if terms.iter().any(|term| !term.coefficient.is_finite()) {
return Err(PolynomialError::NonFinite);
}
Ok(Self {
terms,
length: MAX_TERMS,
})
}
pub fn push(&mut self, term: MultivariateTerm<VARIABLES, T>) -> Result<(), PolynomialError> {
if !term.coefficient.is_finite() {
return Err(PolynomialError::NonFinite);
}
match self.terms.get_mut(self.length) {
Some(slot) => {
*slot = term;
self.length += 1;
Ok(())
}
None => Err(PolynomialError::CapacityExceeded),
}
}
#[inline]
#[must_use]
pub fn terms(&self) -> &[MultivariateTerm<VARIABLES, T>] {
self.terms.get(..self.length).unwrap_or(&[])
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.length
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.length == 0
}
#[inline]
#[must_use]
pub fn is_finite(&self) -> bool {
self.terms().iter().all(|term| term.coefficient.is_finite())
}
#[must_use]
pub fn evaluate(&self, variables: &[T; VARIABLES]) -> T {
let mut total = T::ZERO;
for term in self.terms() {
let mut product = term.coefficient;
for (value, exponent) in variables.iter().zip(term.exponents.iter()) {
product *= raise(*value, *exponent);
}
total += product;
}
total
}
pub fn partial_derivative(&self, variable: usize) -> Result<Self, PolynomialError> {
if variable >= VARIABLES {
return Err(PolynomialError::VariableOutOfRange);
}
let mut result = Self::new();
for term in self.terms() {
let exponent = term.exponents.get(variable).copied().unwrap_or(0);
if exponent == 0 {
continue;
}
let mut exponents = term.exponents;
if let Some(slot) = exponents.get_mut(variable) {
*slot = exponent - 1;
}
let coefficient = term.coefficient * T::from_usize(exponent as usize);
result.push(MultivariateTerm::new(coefficient, exponents))?;
}
Ok(result)
}
pub fn gradient_at(&self, variables: &[T; VARIABLES]) -> Vector<VARIABLES, T> {
let mut totals = [T::ZERO; VARIABLES];
for term in self.terms() {
for (variable, exponent) in term.exponents.iter().enumerate() {
if *exponent == 0 {
continue;
}
let mut product = term.coefficient * T::from_usize(*exponent as usize);
for (other, (value, power)) in
variables.iter().zip(term.exponents.iter()).enumerate()
{
let power = if other == variable {
*power - 1
} else {
*power
};
product *= raise(*value, power);
}
if let Some(slot) = totals.get_mut(variable) {
*slot += product;
}
}
}
Vector::new(totals)
}
pub fn partial_antiderivative(&self, variable: usize) -> Result<Self, PolynomialError> {
if variable >= VARIABLES {
return Err(PolynomialError::VariableOutOfRange);
}
let mut result = Self::new();
for term in self.terms() {
let exponent = term.exponents.get(variable).copied().unwrap_or(0);
let raised = exponent
.checked_add(1)
.ok_or(PolynomialError::DegreeOverflow)?;
let mut exponents = term.exponents;
if let Some(slot) = exponents.get_mut(variable) {
*slot = raised;
}
let coefficient = term.coefficient / T::from_usize(raised as usize);
result.push(MultivariateTerm::new(coefficient, exponents))?;
}
Ok(result)
}
pub fn substitute(&self, variable: usize, value: T) -> Result<Self, PolynomialError> {
if variable >= VARIABLES {
return Err(PolynomialError::VariableOutOfRange);
}
let mut result = Self::new();
for term in self.terms() {
let exponent = term.exponents.get(variable).copied().unwrap_or(0);
let mut exponents = term.exponents;
if let Some(slot) = exponents.get_mut(variable) {
*slot = 0;
}
let coefficient = term.coefficient * raise(value, exponent);
result.push(MultivariateTerm::new(coefficient, exponents))?;
}
result.collect_like_terms();
Ok(result)
}
pub fn add_into<const OTHER: usize, const OUT: usize>(
&self,
other: &MultivariatePolynomial<VARIABLES, OTHER, T>,
) -> Result<MultivariatePolynomial<VARIABLES, OUT, T>, PolynomialError> {
let mut result = MultivariatePolynomial::<VARIABLES, OUT, T>::new();
for term in self.terms().iter().chain(other.terms().iter()) {
result.push(*term)?;
}
result.collect_like_terms();
Ok(result)
}
pub fn multiply_into<const OTHER: usize, const OUT: usize>(
&self,
other: &MultivariatePolynomial<VARIABLES, OTHER, T>,
) -> Result<MultivariatePolynomial<VARIABLES, OUT, T>, PolynomialError> {
let mut result = MultivariatePolynomial::<VARIABLES, OUT, T>::new();
for left in self.terms() {
for right in other.terms() {
let mut exponents = left.exponents;
for (slot, added) in exponents.iter_mut().zip(right.exponents.iter()) {
*slot = slot
.checked_add(*added)
.ok_or(PolynomialError::DegreeOverflow)?;
}
let coefficient = left.coefficient * right.coefficient;
result.push(MultivariateTerm::new(coefficient, exponents))?;
}
}
result.collect_like_terms();
Ok(result)
}
pub fn collect_like_terms(&mut self) {
let mut kept = 0;
for index in 0..self.length {
let Some(term) = self.terms.get(index).copied() else {
continue;
};
let match_found = self.terms.get_mut(..kept).and_then(|kept_terms| {
kept_terms
.iter_mut()
.find(|existing| existing.exponents == term.exponents)
});
if let Some(existing) = match_found {
existing.coefficient += term.coefficient;
} else if let Some(slot) = self.terms.get_mut(kept) {
*slot = term;
kept += 1;
}
}
let mut written = 0;
for index in 0..kept {
let Some(term) = self.terms.get(index).copied() else {
continue;
};
if term.coefficient == T::ZERO {
continue;
}
if let Some(slot) = self.terms.get_mut(written) {
*slot = term;
written += 1;
}
}
self.length = written;
}
#[must_use]
pub fn total_degree(&self) -> Option<u32> {
let mut largest: Option<u32> = None;
for term in self.terms() {
let total = term
.exponents
.iter()
.fold(0_u32, |running, power| running.saturating_add(*power));
largest = Some(match largest {
Some(previous) => previous.max(total),
None => total,
});
}
largest
}
#[must_use]
pub fn degree_in(&self, variable: usize) -> Option<u32> {
if variable >= VARIABLES || self.length == 0 {
return None;
}
let mut largest = 0;
for term in self.terms() {
largest = largest.max(term.exponents.get(variable).copied().unwrap_or(0));
}
Some(largest)
}
}
impl<const MAX_TERMS: usize, T: Numeric> MultivariatePolynomial<1, MAX_TERMS, T> {
pub fn to_univariate<const COEFFICIENT_COUNT: usize>(
&self,
) -> Result<Polynomial<COEFFICIENT_COUNT, T>, PolynomialError> {
let mut coefficients = [T::ZERO; COEFFICIENT_COUNT];
for term in self.terms() {
let power = term.exponents.first().copied().unwrap_or(0) as usize;
match coefficients.get_mut(power) {
Some(slot) => *slot += term.coefficient,
None => return Err(PolynomialError::DegreeOverflow),
}
}
Ok(Polynomial::new(coefficients))
}
pub fn from_univariate<const COEFFICIENT_COUNT: usize>(
polynomial: &Polynomial<COEFFICIENT_COUNT, T>,
) -> Result<Self, PolynomialError> {
let mut result = Self::new();
for (power, coefficient) in polynomial.coefficients().iter().enumerate() {
if *coefficient == T::ZERO {
continue;
}
let exponent = u32::try_from(power).map_err(|_| PolynomialError::DegreeOverflow)?;
result.push(MultivariateTerm::new(*coefficient, [exponent]))?;
}
Ok(result)
}
}