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