Skip to main content

oxiz_math/polynomial/
interpolation.rs

1//! Polynomial Interpolation.
2//!
3//! Constructs polynomials from given points using various interpolation methods.
4//!
5//! ## Methods
6//!
7//! - **Lagrange**: Direct interpolation formula
8//! - **Newton**: Divided differences for efficient evaluation
9//! - **Hermite**: Interpolation with derivative constraints
10//! - **Spline**: Piecewise polynomial interpolation
11//!
12//! ## References
13//!
14//! - "Numerical Analysis" (Burden & Faires, 2010)
15//! - "Computer Algebra and Symbolic Computation" (Cohen, 2002)
16
17use crate::polynomial::{Polynomial, Term, Var};
18#[allow(unused_imports)]
19use crate::prelude::*;
20use num_rational::BigRational;
21use num_traits::{One, Zero};
22
23/// A point for interpolation (x, y).
24#[derive(Debug, Clone, PartialEq, Eq)]
25pub struct Point {
26    /// X-coordinate.
27    pub x: BigRational,
28    /// Y-coordinate (function value).
29    pub y: BigRational,
30}
31
32impl Point {
33    /// Create a new point.
34    pub fn new(x: BigRational, y: BigRational) -> Self {
35        Self { x, y }
36    }
37
38    /// Create from integers.
39    pub fn from_ints(x: i64, y: i64) -> Self {
40        Self {
41            x: BigRational::from_integer(x.into()),
42            y: BigRational::from_integer(y.into()),
43        }
44    }
45}
46
47/// A point with derivative information for Hermite interpolation.
48#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct HermitePoint {
50    /// X-coordinate.
51    pub x: BigRational,
52    /// Function value at x.
53    pub y: BigRational,
54    /// Derivative value at x.
55    pub dy: BigRational,
56}
57
58impl HermitePoint {
59    /// Create a new Hermite point.
60    pub fn new(x: BigRational, y: BigRational, dy: BigRational) -> Self {
61        Self { x, y, dy }
62    }
63}
64
65/// Configuration for polynomial interpolation.
66#[derive(Debug, Clone)]
67pub struct InterpolationConfig {
68    /// Variable to use for interpolation.
69    pub variable: Var,
70    /// Enable numerical stability checks.
71    pub check_stability: bool,
72    /// Maximum degree allowed.
73    pub max_degree: usize,
74}
75
76impl Default for InterpolationConfig {
77    fn default() -> Self {
78        Self {
79            variable: 0,
80            check_stability: true,
81            max_degree: 100,
82        }
83    }
84}
85
86/// Statistics for interpolation.
87#[derive(Debug, Clone, Default)]
88pub struct InterpolationStats {
89    /// Interpolations performed.
90    pub interpolations: u64,
91    /// Average degree of interpolating polynomials.
92    pub avg_degree: f64,
93    /// Numerical instability warnings.
94    pub instability_warnings: u64,
95}
96
97/// Polynomial interpolation engine.
98pub struct PolynomialInterpolator {
99    /// Configuration.
100    config: InterpolationConfig,
101    /// Statistics.
102    stats: InterpolationStats,
103}
104
105impl PolynomialInterpolator {
106    /// Create a new polynomial interpolator.
107    pub fn new(config: InterpolationConfig) -> Self {
108        Self {
109            config,
110            stats: InterpolationStats::default(),
111        }
112    }
113
114    /// Create with default configuration.
115    pub fn default_config() -> Self {
116        Self::new(InterpolationConfig::default())
117    }
118
119    /// Lagrange interpolation.
120    ///
121    /// Constructs the unique polynomial of degree ≤ n-1 passing through n points.
122    ///
123    /// Formula: P(x) = Σᵢ yᵢ · Lᵢ(x)
124    /// where Lᵢ(x) = Πⱼ≠ᵢ (x - xⱼ) / (xᵢ - xⱼ)
125    pub fn lagrange(&mut self, points: &[Point]) -> Result<Polynomial, InterpolationError> {
126        if points.is_empty() {
127            return Err(InterpolationError::NoPoints);
128        }
129
130        if points.len() > self.config.max_degree + 1 {
131            return Err(InterpolationError::DegreeTooHigh);
132        }
133
134        self.stats.interpolations += 1;
135
136        // Check for duplicate x-values
137        for i in 0..points.len() {
138            for j in (i + 1)..points.len() {
139                if points[i].x == points[j].x {
140                    return Err(InterpolationError::DuplicateXValue);
141                }
142            }
143        }
144
145        let var = self.config.variable;
146        let mut result = Polynomial::zero();
147
148        // Compute each Lagrange basis polynomial
149        for i in 0..points.len() {
150            let mut basis = Polynomial::one();
151
152            for j in 0..points.len() {
153                if i == j {
154                    continue;
155                }
156
157                // Multiply by (x - x_j) / (x_i - x_j)
158                let numerator =
159                    Polynomial::from_var(var) - Polynomial::constant(points[j].x.clone());
160                let denominator = &points[i].x - &points[j].x;
161
162                if denominator.is_zero() {
163                    return Err(InterpolationError::DuplicateXValue);
164                }
165
166                basis = &basis * &numerator;
167                // Divide by constant: multiply by 1/denominator
168                let inv = BigRational::from_integer(1.into()) / denominator;
169                basis = Self::multiply_by_constant(&basis, &inv);
170            }
171
172            // Multiply by y_i and add to result
173            basis = Self::multiply_by_constant(&basis, &points[i].y);
174            result = &result + &basis;
175        }
176
177        self.update_degree_stats(result.total_degree() as usize);
178
179        Ok(result)
180    }
181
182    /// Newton interpolation using divided differences.
183    ///
184    /// More efficient than Lagrange for evaluation at multiple points.
185    ///
186    /// Formula: P(x) = f\[x₀\] + f\[x₀,x₁\](x-x₀) + f\[x₀,x₁,x₂\](x-x₀)(x-x₁) + ...
187    pub fn newton(&mut self, points: &[Point]) -> Result<Polynomial, InterpolationError> {
188        if points.is_empty() {
189            return Err(InterpolationError::NoPoints);
190        }
191
192        if points.len() > self.config.max_degree + 1 {
193            return Err(InterpolationError::DegreeTooHigh);
194        }
195
196        self.stats.interpolations += 1;
197
198        // Compute divided differences table
199        let n = points.len();
200        let mut dd = vec![vec![BigRational::zero(); n]; n];
201
202        // First column: function values
203        for i in 0..n {
204            dd[i][0] = points[i].y.clone();
205        }
206
207        // Compute higher-order divided differences
208        for j in 1..n {
209            for i in 0..(n - j) {
210                let numerator = &dd[i + 1][j - 1] - &dd[i][j - 1];
211                let denominator = &points[i + j].x - &points[i].x;
212
213                if denominator.is_zero() {
214                    return Err(InterpolationError::DuplicateXValue);
215                }
216
217                dd[i][j] = numerator / denominator;
218            }
219        }
220
221        let var = self.config.variable;
222        let mut result = Polynomial::constant(dd[0][0].clone());
223
224        // Build Newton polynomial
225        let mut product = Polynomial::one();
226
227        for i in 1..n {
228            // Multiply by (x - x_{i-1})
229            let factor = Polynomial::from_var(var) - Polynomial::constant(points[i - 1].x.clone());
230            product = &product * &factor;
231
232            // Add divided difference term
233            let term = Self::multiply_by_constant(&product, &dd[0][i]);
234            result = &result + &term;
235        }
236
237        self.update_degree_stats(result.total_degree() as usize);
238
239        Ok(result)
240    }
241
242    /// Hermite interpolation with derivatives.
243    ///
244    /// Interpolates both function values and derivatives at given points.
245    pub fn hermite(&mut self, points: &[HermitePoint]) -> Result<Polynomial, InterpolationError> {
246        if points.is_empty() {
247            return Err(InterpolationError::NoPoints);
248        }
249
250        let n = points.len();
251        let total_conditions = 2 * n;
252
253        if total_conditions > self.config.max_degree + 1 {
254            return Err(InterpolationError::DegreeTooHigh);
255        }
256
257        self.stats.interpolations += 1;
258
259        // Convert to divided differences with duplicate points
260        // For Hermite interpolation, we use f[x_i, x_i] = f'(x_i)
261        let mut extended_points = Vec::new();
262
263        for point in points {
264            // Add each point twice (for value and derivative)
265            extended_points.push(Point::new(point.x.clone(), point.y.clone()));
266            extended_points.push(Point::new(point.x.clone(), point.y.clone()));
267        }
268
269        // Build divided difference table with special handling for duplicates
270        let m = extended_points.len();
271        let mut dd = vec![vec![BigRational::zero(); m]; m];
272
273        // First column: function values
274        for i in 0..m {
275            dd[i][0] = extended_points[i].y.clone();
276        }
277
278        // Second column: handle derivatives for duplicate points
279        for i in 0..(m - 1) {
280            if extended_points[i].x == extended_points[i + 1].x {
281                // Use derivative instead of divided difference
282                let point_idx = i / 2;
283                dd[i][1] = points[point_idx].dy.clone();
284            } else {
285                let numerator = &dd[i + 1][0] - &dd[i][0];
286                let denominator = &extended_points[i + 1].x - &extended_points[i].x;
287
288                if !denominator.is_zero() {
289                    dd[i][1] = numerator / denominator;
290                }
291            }
292        }
293
294        // Higher-order divided differences
295        for j in 2..m {
296            for i in 0..(m - j) {
297                let denominator = &extended_points[i + j].x - &extended_points[i].x;
298
299                if !denominator.is_zero() {
300                    let numerator = &dd[i + 1][j - 1] - &dd[i][j - 1];
301                    dd[i][j] = numerator / denominator;
302                }
303            }
304        }
305
306        // Build polynomial using Newton form
307        let var = self.config.variable;
308        let mut result = Polynomial::constant(dd[0][0].clone());
309        let mut product = Polynomial::one();
310
311        for i in 1..m {
312            let factor =
313                Polynomial::from_var(var) - Polynomial::constant(extended_points[i - 1].x.clone());
314            product = &product * &factor;
315
316            let term = Self::multiply_by_constant(&product, &dd[0][i]);
317            result = &result + &term;
318        }
319
320        self.update_degree_stats(result.total_degree() as usize);
321
322        Ok(result)
323    }
324
325    /// Evaluate a polynomial at a point (for verification).
326    pub fn evaluate(&self, poly: &Polynomial, x: &BigRational) -> BigRational {
327        let mut result = BigRational::zero();
328
329        for term in poly.terms() {
330            let mut term_value = term.coeff.clone();
331
332            for var_power in term.monomial.vars() {
333                if var_power.var == self.config.variable {
334                    // Compute x^power
335                    let power_value = Self::power(x, var_power.power);
336                    term_value *= power_value;
337                }
338            }
339
340            result += term_value;
341        }
342
343        result
344    }
345
346    /// Compute x^n for positive integer n.
347    fn power(x: &BigRational, n: u32) -> BigRational {
348        if n == 0 {
349            BigRational::one()
350        } else if n == 1 {
351            x.clone()
352        } else {
353            let mut result = x.clone();
354            for _ in 1..n {
355                result *= x;
356            }
357            result
358        }
359    }
360
361    /// Update average degree statistics.
362    fn update_degree_stats(&mut self, degree: usize) {
363        let count = self.stats.interpolations;
364        let old_avg = self.stats.avg_degree;
365        self.stats.avg_degree = (old_avg * (count - 1) as f64 + degree as f64) / count as f64;
366    }
367
368    /// Get statistics.
369    pub fn stats(&self) -> &InterpolationStats {
370        &self.stats
371    }
372
373    /// Reset statistics.
374    pub fn reset_stats(&mut self) {
375        self.stats = InterpolationStats::default();
376    }
377
378    /// Helper: multiply a polynomial by a scalar constant.
379    fn multiply_by_constant(poly: &Polynomial, scalar: &BigRational) -> Polynomial {
380        if scalar.is_zero() {
381            return Polynomial::zero();
382        }
383        if scalar.is_one() {
384            return poly.clone();
385        }
386
387        // Multiply each term's coefficient by the scalar
388        let new_terms: Vec<Term> = poly
389            .terms()
390            .iter()
391            .map(|term| Term {
392                coeff: &term.coeff * scalar,
393                monomial: term.monomial.clone(),
394            })
395            .collect();
396
397        Polynomial::from_terms(new_terms, poly.order)
398    }
399}
400
401/// Errors that can occur during interpolation.
402#[derive(Debug, Clone, PartialEq, Eq)]
403pub enum InterpolationError {
404    /// No interpolation points provided.
405    NoPoints,
406    /// Duplicate x-values in interpolation points.
407    DuplicateXValue,
408    /// Degree of interpolating polynomial would be too high.
409    DegreeTooHigh,
410    /// Numerical instability detected.
411    NumericalInstability,
412}
413
414impl core::fmt::Display for InterpolationError {
415    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
416        match self {
417            InterpolationError::NoPoints => write!(f, "no interpolation points provided"),
418            InterpolationError::DuplicateXValue => write!(f, "duplicate x-values in points"),
419            InterpolationError::DegreeTooHigh => write!(f, "degree too high"),
420            InterpolationError::NumericalInstability => write!(f, "numerical instability"),
421        }
422    }
423}
424
425impl core::error::Error for InterpolationError {}
426
427#[cfg(test)]
428mod tests {
429    use super::*;
430
431    #[test]
432    fn test_point_creation() {
433        let p = Point::from_ints(1, 2);
434        assert_eq!(p.x, BigRational::from_integer(1.into()));
435        assert_eq!(p.y, BigRational::from_integer(2.into()));
436    }
437
438    #[test]
439    fn test_lagrange_linear() {
440        let mut interpolator = PolynomialInterpolator::default_config();
441
442        let points = vec![Point::from_ints(0, 1), Point::from_ints(1, 3)];
443
444        let poly = interpolator
445            .lagrange(&points)
446            .expect("interpolation failed");
447
448        // Should get p(x) = 1 + 2x
449        let y0 = interpolator.evaluate(&poly, &BigRational::zero());
450        let y1 = interpolator.evaluate(&poly, &BigRational::one());
451
452        assert_eq!(y0, BigRational::from_integer(1.into()));
453        assert_eq!(y1, BigRational::from_integer(3.into()));
454    }
455
456    #[test]
457    fn test_lagrange_quadratic() {
458        let mut interpolator = PolynomialInterpolator::default_config();
459
460        // Points for x^2: (0,0), (1,1), (2,4)
461        let points = vec![
462            Point::from_ints(0, 0),
463            Point::from_ints(1, 1),
464            Point::from_ints(2, 4),
465        ];
466
467        let poly = interpolator
468            .lagrange(&points)
469            .expect("interpolation failed");
470
471        // Verify at interpolation points
472        for point in &points {
473            let y = interpolator.evaluate(&poly, &point.x);
474            assert_eq!(y, point.y);
475        }
476    }
477
478    #[test]
479    fn test_newton_linear() {
480        let mut interpolator = PolynomialInterpolator::default_config();
481
482        let points = vec![Point::from_ints(0, 1), Point::from_ints(1, 3)];
483
484        let poly = interpolator.newton(&points).expect("interpolation failed");
485
486        let y0 = interpolator.evaluate(&poly, &BigRational::zero());
487        let y1 = interpolator.evaluate(&poly, &BigRational::one());
488
489        assert_eq!(y0, BigRational::from_integer(1.into()));
490        assert_eq!(y1, BigRational::from_integer(3.into()));
491    }
492
493    #[test]
494    fn test_duplicate_x_error() {
495        let mut interpolator = PolynomialInterpolator::default_config();
496
497        let points = vec![Point::from_ints(0, 1), Point::from_ints(0, 2)];
498
499        let result = interpolator.lagrange(&points);
500        assert!(matches!(result, Err(InterpolationError::DuplicateXValue)));
501    }
502
503    #[test]
504    fn test_stats() {
505        let mut interpolator = PolynomialInterpolator::default_config();
506        assert_eq!(interpolator.stats().interpolations, 0);
507
508        let points = vec![Point::from_ints(0, 0), Point::from_ints(1, 1)];
509        let _ = interpolator.lagrange(&points);
510
511        assert_eq!(interpolator.stats().interpolations, 1);
512    }
513}