1use crate::math::Transcendental;
2
3pub trait Interpolate {
5 type Output;
6
7 fn interpolate_linear(&self, index: f64) -> Self::Output;
10
11 fn interpolate_cubic(&self, index: f64) -> Self::Output;
14
15 fn interpolate_nearest(&self, index: f64) -> Self::Output;
17}
18
19impl<T: Transcendental + Copy> Interpolate for [T] {
20 type Output = T;
21
22 fn interpolate_linear(&self, index: f64) -> T {
23 let len = self.len();
24 if len == 0 {
25 return T::ZERO;
26 }
27 let idx = index.clamp(0.0, (len - 1) as f64);
28 let i0 = idx.floor() as usize;
29 let i1 = (i0 + 1).min(len - 1);
30 let frac = T::from_f64(idx.fract());
31 let a = self[i0];
32 let b = self[i1];
33 a + (b - a) * frac
34 }
35
36 fn interpolate_cubic(&self, index: f64) -> T {
37 let len = self.len();
38 if len < 4 {
39 return self.interpolate_linear(index);
40 }
41 let idx = index.clamp(1.0, (len - 3) as f64);
42 let i = idx.floor() as usize;
43 let i0 = i - 1;
44 let i1 = i;
45 let i2 = i + 1;
46 let i3 = i + 2;
47 let frac = T::from_f64(idx.fract());
48
49 let c0 = self[i1];
50 let c1 = (self[i2] - self[i0]) * T::from_f32(0.5);
51 let c2 = self[i0] * T::from_f32(-1.5)
52 + self[i1] * T::from_f32(2.0)
53 + self[i2] * T::from_f32(-0.5);
54 let c3 = self[i0] * T::from_f32(-0.5)
55 + self[i1] * T::from_f32(1.5)
56 + self[i2] * T::from_f32(-1.5)
57 + self[i3] * T::from_f32(0.5);
58
59 let f2 = frac * frac;
60 let f3 = f2 * frac;
61 c0 + c1 * frac + c2 * f2 + c3 * f3
62 }
63
64 fn interpolate_nearest(&self, index: f64) -> T {
65 let len = self.len();
66 if len == 0 {
67 return T::ZERO;
68 }
69 let idx = (index.round() as usize).min(len - 1);
70 self[idx]
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_linear_simple() {
80 let buf: [f64; 5] = [0.0, 1.0, 2.0, 3.0, 4.0];
81 assert_eq!(buf.interpolate_linear(0.0), 0.0);
82 assert_eq!(buf.interpolate_linear(4.0), 4.0);
83 assert!((buf.interpolate_linear(0.5) - 0.5).abs() < 1e-10);
84 assert!((buf.interpolate_linear(2.5) - 2.5).abs() < 1e-10);
85 }
86
87 #[test]
88 fn test_linear_clamp() {
89 let buf: [f64; 3] = [10.0, 20.0, 30.0];
90 assert_eq!(buf.interpolate_linear(-1.0), 10.0);
91 assert_eq!(buf.interpolate_linear(100.0), 30.0);
92 }
93
94 #[test]
95 fn test_linear_empty() {
96 let buf: [f64; 0] = [];
97 assert_eq!(buf.interpolate_linear(0.0), 0.0);
98 }
99
100 #[test]
101 fn test_cubic_exact_at_knots() {
102 let buf: [f64; 6] = [0.0, 0.5, 1.0, 0.8, 0.3, 0.0];
103 for i in 1..=3 {
104 let v = buf.interpolate_cubic(i as f64);
105 assert!((v - buf[i]).abs() < 1e-10,
106 "cubic should pass through knot {}: got {}, expected {}", i, v, buf[i]);
107 }
108 }
109
110 #[test]
111 fn test_cubic_interior() {
112 let buf: [f64; 4] = [0.0, 0.3, 0.7, 1.0];
113 for i in 0..=10 {
114 let t = 1.0 + i as f64 / 10.0;
115 let v = buf.interpolate_cubic(t);
116 assert!(v >= -0.1 && v <= 1.1,
117 "cubic range violated at t={}: got {}", t, v);
118 }
119 }
120
121 #[test]
122 fn test_cubic_short_fallback() {
123 let buf: [f64; 2] = [0.0, 1.0];
124 assert_eq!(buf.interpolate_cubic(0.5), buf.interpolate_linear(0.5));
125 }
126
127 #[test]
128 fn test_nearest() {
129 let buf: [f64; 3] = [10.0, 20.0, 30.0];
130 assert_eq!(buf.interpolate_nearest(0.0), 10.0);
131 assert_eq!(buf.interpolate_nearest(0.4), 10.0);
132 assert_eq!(buf.interpolate_nearest(0.6), 20.0);
133 assert_eq!(buf.interpolate_nearest(2.0), 30.0);
134 }
135
136 #[test]
137 fn test_on_vec() {
138 let buf: Vec<f64> = vec![0.0, 1.0, 2.0];
139 assert_eq!(buf.interpolate_linear(1.5), 1.5);
140 }
141
142 #[test]
143 fn test_on_boxed_slice() {
144 let buf: Box<[f64]> = vec![0.0, 1.0, 2.0].into_boxed_slice();
145 assert_eq!(buf.interpolate_linear(1.5), 1.5);
146 }
147}