use crate::error::{all_finite, finite, NumericError, NumericResult};
use crate::root::find_root;
#[derive(Debug, Clone, PartialEq)]
pub struct Polynomial {
coefficients: Vec<f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RootKind {
Simple,
Unresolved {
max_count: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PolynomialRoot {
pub x: f64,
pub lower: f64,
pub upper: f64,
pub kind: RootKind,
}
impl PolynomialRoot {
pub fn error_bound(&self) -> f64 {
self.upper - self.lower
}
}
impl Polynomial {
pub fn new(coefficients: &[f64]) -> NumericResult<Self> {
all_finite(coefficients, "polynomial coefficient")?;
let mut coefficients = coefficients.to_vec();
while coefficients.last() == Some(&0.0) {
coefficients.pop();
}
Ok(Self { coefficients })
}
pub fn from_roots(roots: &[f64]) -> NumericResult<Self> {
all_finite(roots, "root")?;
let mut c = vec![1.0];
for &r in roots {
let mut next = vec![0.0; c.len() + 1];
for (i, &ci) in c.iter().enumerate() {
next[i + 1] += ci;
next[i] -= r * ci;
}
c = next;
}
if c.iter().any(|v| !v.is_finite()) {
return Err(NumericError::InvalidArgument {
name: "roots",
reason: "expand to coefficients that overflow",
});
}
Self::new(&c)
}
pub fn coefficients(&self) -> &[f64] {
&self.coefficients
}
pub fn degree(&self) -> Option<usize> {
self.coefficients.len().checked_sub(1)
}
pub fn evaluate(&self, x: f64) -> f64 {
self.coefficients
.iter()
.rev()
.fold(0.0, |acc, &a| acc * x + a)
}
pub fn evaluate_with_bound(&self, x: f64) -> (f64, f64) {
let n = self.coefficients.len();
let mut value = 0.0f64;
let mut magnitude = 0.0f64;
let ax = x.abs();
for &a in self.coefficients.iter().rev() {
value = value * x + a;
magnitude = magnitude * ax + a.abs();
}
let u = f64::EPSILON / 2.0;
let k = (2 * n + 1) as f64;
let gamma = k * u / (1.0 - k * u);
let bound = 2.0 * gamma * magnitude + (2 * n) as f64 * f64::MIN_POSITIVE;
(value, bound)
}
pub fn derivative(&self) -> Polynomial {
let coefficients: Vec<f64> = self
.coefficients
.iter()
.enumerate()
.skip(1)
.map(|(i, &a)| a * i as f64)
.collect();
Polynomial { coefficients }
}
pub fn real_roots(&self, lower: f64, upper: f64) -> NumericResult<Vec<PolynomialRoot>> {
finite(lower, "lower bound")?;
finite(upper, "upper bound")?;
if lower > upper {
return Err(NumericError::InvalidArgument {
name: "interval",
reason: "must have lower <= upper",
});
}
if self.coefficients.is_empty() {
return Err(NumericError::ZeroPolynomial);
}
self.isolate(lower, upper)
}
fn sign(&self, x: f64) -> i8 {
let (v, bound) = self.evaluate_with_bound(x);
if v.abs() <= bound {
0
} else if v > 0.0 {
1
} else {
-1
}
}
fn isolate(&self, lower: f64, upper: f64) -> NumericResult<Vec<PolynomialRoot>> {
if self.coefficients.len() <= 1 {
return Ok(Vec::new());
}
let critical = self.derivative().isolate(lower, upper)?;
let mut points: Vec<(f64, usize)> = vec![(lower, 0)];
for c in &critical {
let count = match c.kind {
RootKind::Simple => 1,
RootKind::Unresolved { max_count } => max_count,
};
let x = c.x.clamp(lower, upper);
match points.last_mut() {
Some(last) if last.0 == x => last.1 += count,
_ => points.push((x, count)),
}
}
match points.last_mut() {
Some(last) if last.0 == upper => {}
_ => points.push((upper, 0)),
}
let signs: Vec<i8> = points.iter().map(|&(x, _)| self.sign(x)).collect();
let mut roots = Vec::new();
let mut i = 0;
while i < points.len() {
if signs[i] == 0 {
let start = i;
let mut crit_count = 0;
while i < points.len() && signs[i] == 0 {
crit_count += points[i].1;
i += 1;
}
let end = i - 1;
let left_limit = if start > 0 {
points[start - 1].0
} else {
lower
};
let right_limit = if i < points.len() { points[i].0 } else { upper };
let x = if start == end {
points[start].0
} else {
0.5 * points[start].0 + 0.5 * points[end].0
};
roots.push(PolynomialRoot {
x,
lower: self.boundary(points[start].0, left_limit),
upper: self.boundary(points[end].0, right_limit),
kind: RootKind::Unresolved {
max_count: crit_count + 1,
},
});
continue;
}
if i + 1 < points.len() && signs[i + 1] != 0 && signs[i] != signs[i + 1] {
let (a, b) = (points[i].0, points[i + 1].0);
let root = find_root(|x| self.evaluate(x), a, b, 0.0)?;
let lo = if self.sign(root.lower) != 0 {
root.lower
} else {
self.boundary(root.lower, a)
};
let hi = if self.sign(root.upper) != 0 {
root.upper
} else {
self.boundary(root.upper, b)
};
roots.push(PolynomialRoot {
x: root.x,
lower: lo,
upper: hi,
kind: RootKind::Simple,
});
}
i += 1;
}
Ok(roots)
}
fn boundary(&self, inside: f64, limit: f64) -> f64 {
if inside == limit || self.sign(limit) == 0 {
return limit;
}
let (mut uncertain, mut certain) = (inside, limit);
for _ in 0..200 {
let mid = 0.5 * uncertain + 0.5 * certain;
if mid == uncertain || mid == certain {
break;
}
if self.sign(mid) == 0 {
uncertain = mid;
} else {
certain = mid;
}
}
certain
}
}