qualia_core_db/solvers/interpolation/
lagrange.rs1use super::InterpolationError;
5
6fn validate(xs: &[f64], ys: &[f64]) -> Result<(), InterpolationError> {
7 if xs.is_empty() || xs.len() != ys.len() {
8 return Err(InterpolationError::InsufficientData);
9 }
10 for i in 0..xs.len() {
11 for j in (i + 1)..xs.len() {
12 if xs[i] == xs[j] {
13 return Err(InterpolationError::DuplicateNodes);
14 }
15 }
16 }
17 Ok(())
18}
19
20pub fn lagrange_eval(xs: &[f64], ys: &[f64], x: f64) -> Result<f64, InterpolationError> {
22 validate(xs, ys)?;
23 let n = xs.len();
24 let mut sum = 0.0;
25 for i in 0..n {
26 let mut li = 1.0;
27 for j in 0..n {
28 if i != j {
29 li *= (x - xs[j]) / (xs[i] - xs[j]);
30 }
31 }
32 sum += ys[i] * li;
33 }
34 Ok(sum)
35}
36
37pub fn newton_coefficients(xs: &[f64], ys: &[f64]) -> Result<Vec<f64>, InterpolationError> {
40 validate(xs, ys)?;
41 let n = xs.len();
42 let mut coef = ys.to_vec();
43 for j in 1..n {
44 for i in (j..n).rev() {
45 coef[i] = (coef[i] - coef[i - 1]) / (xs[i] - xs[i - j]);
46 }
47 }
48 Ok(coef)
49}
50
51pub fn newton_eval(xs: &[f64], coef: &[f64], x: f64) -> f64 {
53 let n = coef.len();
54 let mut acc = coef[n - 1];
55 for i in (0..n - 1).rev() {
56 acc = acc * (x - xs[i]) + coef[i];
57 }
58 acc
59}
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 const EPS: f64 = 1e-9;
65
66 #[test]
67 fn interpolant_passes_through_nodes() {
68 let xs = [0.0, 1.0, 2.0, 3.0];
69 let ys = [1.0, 3.0, 2.0, 5.0];
70 for i in 0..xs.len() {
71 assert!((lagrange_eval(&xs, &ys, xs[i]).unwrap() - ys[i]).abs() < EPS);
72 }
73 }
74
75 #[test]
76 fn reproduces_a_quadratic_exactly() {
77 let f = |x: f64| 2.0 * x * x - 3.0 * x + 1.0;
79 let xs = [-1.0, 0.0, 2.0];
80 let ys = xs.map(f);
81 for &q in &[0.5, 1.7, -0.3, 5.0] {
82 assert!((lagrange_eval(&xs, &ys, q).unwrap() - f(q)).abs() < 1e-8);
83 }
84 }
85
86 #[test]
87 fn newton_matches_lagrange() {
88 let xs = [0.0, 1.0, 2.0, 4.0];
89 let ys = [1.0, 2.0, 0.0, 8.0];
90 let coef = newton_coefficients(&xs, &ys).unwrap();
91 for &q in &[0.3, 1.5, 3.0] {
92 assert!(
93 (newton_eval(&xs, &coef, q) - lagrange_eval(&xs, &ys, q).unwrap()).abs() < 1e-9
94 );
95 }
96 }
97
98 #[test]
99 fn fails_closed() {
100 assert_eq!(
101 lagrange_eval(&[], &[], 0.0).unwrap_err(),
102 InterpolationError::InsufficientData
103 );
104 assert_eq!(
105 lagrange_eval(&[1.0, 1.0], &[2.0, 3.0], 0.0).unwrap_err(),
106 InterpolationError::DuplicateNodes
107 );
108 }
109}