use crate::polynomial::{Polynomial, Term, Var};
#[allow(unused_imports)]
use crate::prelude::*;
use num_rational::BigRational;
use num_traits::{One, Zero};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Point {
pub x: BigRational,
pub y: BigRational,
}
impl Point {
pub fn new(x: BigRational, y: BigRational) -> Self {
Self { x, y }
}
pub fn from_ints(x: i64, y: i64) -> Self {
Self {
x: BigRational::from_integer(x.into()),
y: BigRational::from_integer(y.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HermitePoint {
pub x: BigRational,
pub y: BigRational,
pub dy: BigRational,
}
impl HermitePoint {
pub fn new(x: BigRational, y: BigRational, dy: BigRational) -> Self {
Self { x, y, dy }
}
}
#[derive(Debug, Clone)]
pub struct InterpolationConfig {
pub variable: Var,
pub check_stability: bool,
pub max_degree: usize,
}
impl Default for InterpolationConfig {
fn default() -> Self {
Self {
variable: 0,
check_stability: true,
max_degree: 100,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct InterpolationStats {
pub interpolations: u64,
pub avg_degree: f64,
pub instability_warnings: u64,
}
pub struct PolynomialInterpolator {
config: InterpolationConfig,
stats: InterpolationStats,
}
impl PolynomialInterpolator {
pub fn new(config: InterpolationConfig) -> Self {
Self {
config,
stats: InterpolationStats::default(),
}
}
pub fn default_config() -> Self {
Self::new(InterpolationConfig::default())
}
pub fn lagrange(&mut self, points: &[Point]) -> Result<Polynomial, InterpolationError> {
if points.is_empty() {
return Err(InterpolationError::NoPoints);
}
if points.len() > self.config.max_degree + 1 {
return Err(InterpolationError::DegreeTooHigh);
}
self.stats.interpolations += 1;
for i in 0..points.len() {
for j in (i + 1)..points.len() {
if points[i].x == points[j].x {
return Err(InterpolationError::DuplicateXValue);
}
}
}
let var = self.config.variable;
let mut result = Polynomial::zero();
for i in 0..points.len() {
let mut basis = Polynomial::one();
for j in 0..points.len() {
if i == j {
continue;
}
let numerator =
Polynomial::from_var(var) - Polynomial::constant(points[j].x.clone());
let denominator = &points[i].x - &points[j].x;
if denominator.is_zero() {
return Err(InterpolationError::DuplicateXValue);
}
basis = &basis * &numerator;
let inv = BigRational::from_integer(1.into()) / denominator;
basis = Self::multiply_by_constant(&basis, &inv);
}
basis = Self::multiply_by_constant(&basis, &points[i].y);
result = &result + &basis;
}
self.update_degree_stats(result.total_degree() as usize);
Ok(result)
}
pub fn newton(&mut self, points: &[Point]) -> Result<Polynomial, InterpolationError> {
if points.is_empty() {
return Err(InterpolationError::NoPoints);
}
if points.len() > self.config.max_degree + 1 {
return Err(InterpolationError::DegreeTooHigh);
}
self.stats.interpolations += 1;
let n = points.len();
let mut dd = vec![vec![BigRational::zero(); n]; n];
for i in 0..n {
dd[i][0] = points[i].y.clone();
}
for j in 1..n {
for i in 0..(n - j) {
let numerator = &dd[i + 1][j - 1] - &dd[i][j - 1];
let denominator = &points[i + j].x - &points[i].x;
if denominator.is_zero() {
return Err(InterpolationError::DuplicateXValue);
}
dd[i][j] = numerator / denominator;
}
}
let var = self.config.variable;
let mut result = Polynomial::constant(dd[0][0].clone());
let mut product = Polynomial::one();
for i in 1..n {
let factor = Polynomial::from_var(var) - Polynomial::constant(points[i - 1].x.clone());
product = &product * &factor;
let term = Self::multiply_by_constant(&product, &dd[0][i]);
result = &result + &term;
}
self.update_degree_stats(result.total_degree() as usize);
Ok(result)
}
pub fn hermite(&mut self, points: &[HermitePoint]) -> Result<Polynomial, InterpolationError> {
if points.is_empty() {
return Err(InterpolationError::NoPoints);
}
let n = points.len();
let total_conditions = 2 * n;
if total_conditions > self.config.max_degree + 1 {
return Err(InterpolationError::DegreeTooHigh);
}
self.stats.interpolations += 1;
let mut extended_points = Vec::new();
for point in points {
extended_points.push(Point::new(point.x.clone(), point.y.clone()));
extended_points.push(Point::new(point.x.clone(), point.y.clone()));
}
let m = extended_points.len();
let mut dd = vec![vec![BigRational::zero(); m]; m];
for i in 0..m {
dd[i][0] = extended_points[i].y.clone();
}
for i in 0..(m - 1) {
if extended_points[i].x == extended_points[i + 1].x {
let point_idx = i / 2;
dd[i][1] = points[point_idx].dy.clone();
} else {
let numerator = &dd[i + 1][0] - &dd[i][0];
let denominator = &extended_points[i + 1].x - &extended_points[i].x;
if !denominator.is_zero() {
dd[i][1] = numerator / denominator;
}
}
}
for j in 2..m {
for i in 0..(m - j) {
let denominator = &extended_points[i + j].x - &extended_points[i].x;
if !denominator.is_zero() {
let numerator = &dd[i + 1][j - 1] - &dd[i][j - 1];
dd[i][j] = numerator / denominator;
}
}
}
let var = self.config.variable;
let mut result = Polynomial::constant(dd[0][0].clone());
let mut product = Polynomial::one();
for i in 1..m {
let factor =
Polynomial::from_var(var) - Polynomial::constant(extended_points[i - 1].x.clone());
product = &product * &factor;
let term = Self::multiply_by_constant(&product, &dd[0][i]);
result = &result + &term;
}
self.update_degree_stats(result.total_degree() as usize);
Ok(result)
}
pub fn evaluate(&self, poly: &Polynomial, x: &BigRational) -> BigRational {
let mut result = BigRational::zero();
for term in poly.terms() {
let mut term_value = term.coeff.clone();
for var_power in term.monomial.vars() {
if var_power.var == self.config.variable {
let power_value = Self::power(x, var_power.power);
term_value *= power_value;
}
}
result += term_value;
}
result
}
fn power(x: &BigRational, n: u32) -> BigRational {
if n == 0 {
BigRational::one()
} else if n == 1 {
x.clone()
} else {
let mut result = x.clone();
for _ in 1..n {
result *= x;
}
result
}
}
fn update_degree_stats(&mut self, degree: usize) {
let count = self.stats.interpolations;
let old_avg = self.stats.avg_degree;
self.stats.avg_degree = (old_avg * (count - 1) as f64 + degree as f64) / count as f64;
}
pub fn stats(&self) -> &InterpolationStats {
&self.stats
}
pub fn reset_stats(&mut self) {
self.stats = InterpolationStats::default();
}
fn multiply_by_constant(poly: &Polynomial, scalar: &BigRational) -> Polynomial {
if scalar.is_zero() {
return Polynomial::zero();
}
if scalar.is_one() {
return poly.clone();
}
let new_terms: Vec<Term> = poly
.terms()
.iter()
.map(|term| Term {
coeff: &term.coeff * scalar,
monomial: term.monomial.clone(),
})
.collect();
Polynomial::from_terms(new_terms, poly.order)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InterpolationError {
NoPoints,
DuplicateXValue,
DegreeTooHigh,
NumericalInstability,
}
impl core::fmt::Display for InterpolationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
InterpolationError::NoPoints => write!(f, "no interpolation points provided"),
InterpolationError::DuplicateXValue => write!(f, "duplicate x-values in points"),
InterpolationError::DegreeTooHigh => write!(f, "degree too high"),
InterpolationError::NumericalInstability => write!(f, "numerical instability"),
}
}
}
impl core::error::Error for InterpolationError {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point_creation() {
let p = Point::from_ints(1, 2);
assert_eq!(p.x, BigRational::from_integer(1.into()));
assert_eq!(p.y, BigRational::from_integer(2.into()));
}
#[test]
fn test_lagrange_linear() {
let mut interpolator = PolynomialInterpolator::default_config();
let points = vec![Point::from_ints(0, 1), Point::from_ints(1, 3)];
let poly = interpolator
.lagrange(&points)
.expect("interpolation failed");
let y0 = interpolator.evaluate(&poly, &BigRational::zero());
let y1 = interpolator.evaluate(&poly, &BigRational::one());
assert_eq!(y0, BigRational::from_integer(1.into()));
assert_eq!(y1, BigRational::from_integer(3.into()));
}
#[test]
fn test_lagrange_quadratic() {
let mut interpolator = PolynomialInterpolator::default_config();
let points = vec![
Point::from_ints(0, 0),
Point::from_ints(1, 1),
Point::from_ints(2, 4),
];
let poly = interpolator
.lagrange(&points)
.expect("interpolation failed");
for point in &points {
let y = interpolator.evaluate(&poly, &point.x);
assert_eq!(y, point.y);
}
}
#[test]
fn test_newton_linear() {
let mut interpolator = PolynomialInterpolator::default_config();
let points = vec![Point::from_ints(0, 1), Point::from_ints(1, 3)];
let poly = interpolator.newton(&points).expect("interpolation failed");
let y0 = interpolator.evaluate(&poly, &BigRational::zero());
let y1 = interpolator.evaluate(&poly, &BigRational::one());
assert_eq!(y0, BigRational::from_integer(1.into()));
assert_eq!(y1, BigRational::from_integer(3.into()));
}
#[test]
fn test_duplicate_x_error() {
let mut interpolator = PolynomialInterpolator::default_config();
let points = vec![Point::from_ints(0, 1), Point::from_ints(0, 2)];
let result = interpolator.lagrange(&points);
assert!(matches!(result, Err(InterpolationError::DuplicateXValue)));
}
#[test]
fn test_stats() {
let mut interpolator = PolynomialInterpolator::default_config();
assert_eq!(interpolator.stats().interpolations, 0);
let points = vec![Point::from_ints(0, 0), Point::from_ints(1, 1)];
let _ = interpolator.lagrange(&points);
assert_eq!(interpolator.stats().interpolations, 1);
}
}