#![deny(clippy::indexing_slicing)]
use crate::error::PolynomialError;
use crate::polynomial::Polynomial;
use crate::scalar::Numeric;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RealRoots<const MAX_ROOTS: usize, T: Numeric = f64> {
values: [T; MAX_ROOTS],
length: usize,
}
impl<const MAX_ROOTS: usize, T: Numeric> Default for RealRoots<MAX_ROOTS, T> {
fn default() -> Self {
Self::new()
}
}
impl<const MAX_ROOTS: usize, T: Numeric> RealRoots<MAX_ROOTS, T> {
fn new() -> Self {
Self {
values: [T::ZERO; MAX_ROOTS],
length: 0,
}
}
fn push(&mut self, value: T) {
if let Some(slot) = self.values.get_mut(self.length) {
*slot = value;
self.length += 1;
}
}
fn sort_ascending(&mut self) {
for position in 1..self.length {
let mut index = position;
while index > 0 {
let previous = self.values.get(index - 1).copied().unwrap_or(T::ZERO);
let current = self.values.get(index).copied().unwrap_or(T::ZERO);
if previous <= current {
break;
}
self.values.swap(index - 1, index);
index -= 1;
}
}
}
#[inline]
#[must_use]
pub fn as_slice(&self) -> &[T] {
self.values.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
}
}
fn check<const COEFFICIENT_COUNT: usize, T: Numeric>(
polynomial: &Polynomial<COEFFICIENT_COUNT, T>,
highest: T,
) -> Result<(), PolynomialError> {
if !polynomial.is_finite() {
return Err(PolynomialError::NonFinite);
}
if highest == T::ZERO {
return Err(PolynomialError::LeadingCoefficientZero);
}
Ok(())
}
impl<T: Numeric> Polynomial<2, T> {
pub fn real_roots(&self) -> Result<RealRoots<1, T>, PolynomialError> {
let &[constant, linear] = self.coefficients();
check(self, linear)?;
let mut roots = RealRoots::new();
roots.push(-constant / linear);
Ok(roots)
}
}
impl<T: Numeric> Polynomial<3, T> {
pub fn real_roots(&self) -> Result<RealRoots<2, T>, PolynomialError> {
let &[constant, linear, quadratic] = self.coefficients();
check(self, quadratic)?;
let mut roots = RealRoots::new();
let discriminant = linear * linear - T::from_f64(4.0) * quadratic * constant;
if discriminant < T::ZERO {
return Ok(roots);
}
let combined = -T::HALF * (linear + discriminant.sqrt().copysign(linear));
if combined == T::ZERO {
roots.push(T::ZERO);
roots.push(T::ZERO);
} else {
roots.push(combined / quadratic);
roots.push(constant / combined);
}
roots.sort_ascending();
Ok(roots)
}
}
impl<T: Numeric> Polynomial<4, T> {
pub fn real_roots(&self) -> Result<RealRoots<3, T>, PolynomialError> {
let &[constant, linear, quadratic, cubic] = self.coefficients();
check(self, cubic)?;
let three = T::THREE;
let four = T::from_f64(4.0);
let twenty_seven = T::from_f64(27.0);
let slide = -quadratic / (three * cubic);
let reduced_linear =
(three * cubic * linear - quadratic * quadratic) / (three * cubic * cubic);
let reduced_constant = (T::TWO * quadratic * quadratic * quadratic
- T::from_f64(9.0) * cubic * quadratic * linear
+ twenty_seven * cubic * cubic * constant)
/ (twenty_seven * cubic * cubic * cubic);
let mut roots = RealRoots::new();
if reduced_linear == T::ZERO {
roots.push((-reduced_constant).cbrt() + slide);
} else if four * reduced_linear.powi(3) + twenty_seven * reduced_constant * reduced_constant
<= T::ZERO
{
let radius = T::TWO * (-reduced_linear / three).sqrt();
let cosine = (three * reduced_constant) / (T::TWO * reduced_linear)
* (-three / reduced_linear).sqrt();
let angle = cosine.max(-T::ONE).min(T::ONE).acos();
for step in 0..3 {
let turn = T::TWO * T::PI * T::from_usize(step) / three;
roots.push(radius * (angle / three - turn).cos() + slide);
}
} else {
let half_constant = -reduced_constant * T::HALF;
let spread = (reduced_constant * reduced_constant / four
+ reduced_linear.powi(3) / twenty_seven)
.sqrt();
roots.push((half_constant + spread).cbrt() + (half_constant - spread).cbrt() + slide);
}
roots.sort_ascending();
Ok(roots)
}
}
impl<T: Numeric> Polynomial<5, T> {
pub fn real_roots(&self) -> Result<RealRoots<4, T>, PolynomialError> {
let &[constant, linear, quadratic, cubic, quartic] = self.coefficients();
check(self, quartic)?;
let four = T::from_f64(4.0);
let eight = T::from_f64(8.0);
let two_hundred_fifty_six = T::from_f64(256.0);
let slide = -cubic / (four * quartic);
let reduced_quadratic =
(eight * quartic * quadratic - T::THREE * cubic * cubic) / (eight * quartic * quartic);
let reduced_linear = (cubic * cubic * cubic - four * quartic * cubic * quadratic
+ eight * quartic * quartic * linear)
/ (eight * quartic * quartic * quartic);
let reduced_constant = (-T::THREE * cubic.powi(4)
+ two_hundred_fifty_six * quartic.powi(3) * constant
- T::from_f64(64.0) * quartic * quartic * cubic * linear
+ T::from_f64(16.0) * quartic * cubic * cubic * quadratic)
/ (two_hundred_fifty_six * quartic.powi(4));
let mut roots = RealRoots::new();
if reduced_linear == T::ZERO {
let squares = Polynomial::<3, T>::new([reduced_constant, reduced_quadratic, T::ONE])
.real_roots()?;
for square in squares.as_slice() {
if *square >= T::ZERO {
let root = square.sqrt();
roots.push(root + slide);
roots.push(-root + slide);
}
}
} else {
let helper = Polynomial::<4, T>::new([
-reduced_linear * reduced_linear,
T::TWO * reduced_quadratic * reduced_quadratic - eight * reduced_constant,
eight * reduced_quadratic,
eight,
])
.real_roots()?;
let largest = helper.as_slice().last().copied().unwrap_or(T::ZERO);
let separation = (T::TWO * largest).sqrt();
let shared = reduced_quadratic * T::HALF + largest;
let apart = reduced_linear / (T::TWO * separation);
for factor in [
Polynomial::<3, T>::new([shared + apart, -separation, T::ONE]),
Polynomial::<3, T>::new([shared - apart, separation, T::ONE]),
] {
let factor_roots = factor.real_roots()?;
for root in factor_roots.as_slice() {
roots.push(*root + slide);
}
}
}
roots.sort_ascending();
Ok(roots)
}
}
fn remainder<const COEFFICIENT_COUNT: usize, T: Numeric>(
dividend: &Polynomial<COEFFICIENT_COUNT, T>,
divisor: &Polynomial<COEFFICIENT_COUNT, T>,
) -> Option<Polynomial<COEFFICIENT_COUNT, T>> {
let divisor_degree = divisor.degree()?;
let divisor_highest = divisor.coefficient(divisor_degree)?;
let mut working = *dividend.coefficients();
let mut power = COEFFICIENT_COUNT;
while power > divisor_degree {
power -= 1;
let highest = working.get(power).copied().unwrap_or(T::ZERO);
if highest == T::ZERO {
continue;
}
let share = highest / divisor_highest;
let offset = power - divisor_degree;
for (divisor_power, divisor_coefficient) in divisor.coefficients().iter().enumerate() {
if let Some(slot) = working.get_mut(offset + divisor_power) {
*slot -= share * *divisor_coefficient;
}
}
if let Some(slot) = working.get_mut(power) {
*slot = T::ZERO;
}
}
Some(Polynomial::new(working))
}
fn add_range<T: Numeric>(pending: &mut [(T, T)], length: &mut usize, range: (T, T)) {
if let Some(slot) = pending.get_mut(*length) {
*slot = range;
*length += 1;
}
}
fn sign_changes<const COEFFICIENT_COUNT: usize, T: Numeric>(
chain: &[Polynomial<COEFFICIENT_COUNT, T>; COEFFICIENT_COUNT],
length: usize,
at: T,
) -> usize {
let mut changes = 0;
let mut previous_positive = None;
for polynomial in chain.iter().take(length) {
let value = polynomial.evaluate(at);
if value == T::ZERO {
continue;
}
let positive = value > T::ZERO;
if previous_positive.is_some_and(|previous| previous != positive) {
changes += 1;
}
previous_positive = Some(positive);
}
changes
}
impl<const COEFFICIENT_COUNT: usize, T: Numeric> Polynomial<COEFFICIENT_COUNT, T> {
pub fn cauchy_root_bound(&self) -> Result<T, PolynomialError> {
if !self.is_finite() {
return Err(PolynomialError::NonFinite);
}
let degree = self
.degree()
.ok_or(PolynomialError::LeadingCoefficientZero)?;
let highest = self
.coefficient(degree)
.ok_or(PolynomialError::LeadingCoefficientZero)?;
let mut largest = T::ZERO;
for coefficient in self.coefficients().iter().take(degree) {
largest = largest.max((*coefficient / highest).abs());
}
Ok(T::ONE + largest)
}
fn root_counting_chain(
&self,
) -> Result<([Polynomial<COEFFICIENT_COUNT, T>; COEFFICIENT_COUNT], usize), PolynomialError>
{
if !self.is_finite() {
return Err(PolynomialError::NonFinite);
}
let degree = self
.degree()
.ok_or(PolynomialError::LeadingCoefficientZero)?;
let mut chain = [Polynomial::<COEFFICIENT_COUNT, T>::zeros(); COEFFICIENT_COUNT];
let mut length = 0;
if let Some(slot) = chain.get_mut(0) {
*slot = *self;
length = 1;
}
if degree >= 1 {
if let Some(slot) = chain.get_mut(1) {
*slot = self.derivative();
length = 2;
}
}
while length < COEFFICIENT_COUNT {
let previous = chain.get(length - 2).copied().unwrap_or_default();
let current = chain.get(length - 1).copied().unwrap_or_default();
let Some(next) = remainder(&previous, ¤t) else {
break;
};
if next.is_zero() {
break;
}
if let Some(slot) = chain.get_mut(length) {
*slot = -next;
}
length += 1;
}
Ok((chain, length))
}
pub fn count_real_roots(&self, lower: T, upper: T) -> Result<usize, PolynomialError> {
let (chain, length) = self.root_counting_chain()?;
Ok(sign_changes(&chain, length, lower).saturating_sub(sign_changes(&chain, length, upper)))
}
pub fn real_roots_in(
&self,
lower: T,
upper: T,
tolerance: T,
maximum_bisections: usize,
) -> Result<RealRoots<COEFFICIENT_COUNT, T>, PolynomialError> {
let (chain, length) = self.root_counting_chain()?;
let count_between = |from: T, to: T| {
sign_changes(&chain, length, from).saturating_sub(sign_changes(&chain, length, to))
};
let mut pending = [(T::ZERO, T::ZERO); COEFFICIENT_COUNT];
let mut pending_length = 0;
if count_between(lower, upper) > 0 {
add_range(&mut pending, &mut pending_length, (lower, upper));
}
let mut roots = RealRoots::new();
let mut steps = 0;
while pending_length > 0 {
pending_length -= 1;
let (range_lower, range_upper) = pending
.get(pending_length)
.copied()
.unwrap_or((T::ZERO, T::ZERO));
if count_between(range_lower, range_upper) == 1 {
let (mut low, mut high) = (range_lower, range_upper);
while high - low > tolerance {
if steps >= maximum_bisections {
return Err(PolynomialError::DidNotConverge { steps });
}
steps += 1;
let middle = (low + high) * T::HALF;
if count_between(low, middle) == 1 {
high = middle;
} else {
low = middle;
}
}
roots.push((low + high) * T::HALF);
continue;
}
if steps >= maximum_bisections {
return Err(PolynomialError::DidNotConverge { steps });
}
steps += 1;
let middle = (range_lower + range_upper) * T::HALF;
for (piece_lower, piece_upper) in [(range_lower, middle), (middle, range_upper)] {
if count_between(piece_lower, piece_upper) > 0 {
add_range(
&mut pending,
&mut pending_length,
(piece_lower, piece_upper),
);
}
}
}
roots.sort_ascending();
Ok(roots)
}
}