nodedb_query/ts_functions/
interpolate.rs1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum InterpolateMethod {
6 Linear,
8 Previous,
10 Next,
12 Zero,
14}
15
16impl InterpolateMethod {
17 pub fn parse(s: &str) -> Option<Self> {
19 match s.to_ascii_lowercase().as_str() {
20 "linear" => Some(Self::Linear),
21 "prev" | "previous" | "locf" => Some(Self::Previous),
22 "next" | "nocb" => Some(Self::Next),
23 "zero" => Some(Self::Zero),
24 _ => None,
25 }
26 }
27}
28
29pub fn ts_interpolate(
38 values: &[Option<f64>],
39 timestamps_ns: &[i64],
40 method: InterpolateMethod,
41) -> Vec<f64> {
42 debug_assert_eq!(values.len(), timestamps_ns.len());
43 let n = values.len();
44 if n == 0 {
45 return vec![];
46 }
47
48 match method {
49 InterpolateMethod::Zero => values.iter().map(|v| v.unwrap_or(0.0)).collect(),
50 InterpolateMethod::Previous => fill_previous(values),
51 InterpolateMethod::Next => fill_next(values),
52 InterpolateMethod::Linear => fill_linear(values, timestamps_ns),
53 }
54}
55
56fn fill_previous(values: &[Option<f64>]) -> Vec<f64> {
57 let mut result = Vec::with_capacity(values.len());
58 let mut last = 0.0;
59 for v in values {
60 match v {
61 Some(val) => {
62 last = *val;
63 result.push(*val);
64 }
65 None => result.push(last),
66 }
67 }
68 result
69}
70
71fn fill_next(values: &[Option<f64>]) -> Vec<f64> {
72 let n = values.len();
73 let mut result = vec![0.0; n];
74 let mut next_val = 0.0;
75 for i in (0..n).rev() {
76 match values[i] {
77 Some(val) => {
78 next_val = val;
79 result[i] = val;
80 }
81 None => result[i] = next_val,
82 }
83 }
84 result
85}
86
87fn fill_linear(values: &[Option<f64>], timestamps_ns: &[i64]) -> Vec<f64> {
88 let n = values.len();
89 let mut result = vec![0.0; n];
90
91 let mut prev_idx: Option<usize> = None;
93
94 for i in 0..n {
95 if let Some(val) = values[i] {
96 if let Some(pi) = prev_idx {
98 let prev_val = values[pi].unwrap();
99 let dt = (timestamps_ns[i] - timestamps_ns[pi]) as f64;
100 if dt > 0.0 && pi + 1 < i {
101 for j in (pi + 1)..i {
102 let frac = (timestamps_ns[j] - timestamps_ns[pi]) as f64 / dt;
103 result[j] = prev_val + frac * (val - prev_val);
104 }
105 }
106 }
107 result[i] = val;
108 prev_idx = Some(i);
109 }
110 }
111
112 if let Some(first) = values.iter().position(|v| v.is_some()) {
114 let fv = values[first].unwrap();
115 for r in result.iter_mut().take(first) {
116 *r = fv;
117 }
118 }
119 if let Some(last) = values.iter().rposition(|v| v.is_some()) {
121 let lv = values[last].unwrap();
122 for r in result.iter_mut().skip(last + 1) {
123 *r = lv;
124 }
125 }
126
127 result
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133
134 fn s(v: f64) -> Option<f64> {
135 Some(v)
136 }
137
138 #[test]
139 fn zero_fill() {
140 let r = ts_interpolate(&[s(1.0), None, s(3.0)], &[0, 1, 2], InterpolateMethod::Zero);
141 assert_eq!(r, vec![1.0, 0.0, 3.0]);
142 }
143
144 #[test]
145 fn previous_fill() {
146 let r = ts_interpolate(
147 &[s(10.0), None, None, s(40.0)],
148 &[0, 1, 2, 3],
149 InterpolateMethod::Previous,
150 );
151 assert_eq!(r, vec![10.0, 10.0, 10.0, 40.0]);
152 }
153
154 #[test]
155 fn next_fill() {
156 let r = ts_interpolate(
157 &[None, None, s(30.0), s(40.0)],
158 &[0, 1, 2, 3],
159 InterpolateMethod::Next,
160 );
161 assert_eq!(r, vec![30.0, 30.0, 30.0, 40.0]);
162 }
163
164 #[test]
165 fn linear_fill() {
166 let r = ts_interpolate(
167 &[s(0.0), None, None, s(30.0)],
168 &[0, 10, 20, 30],
169 InterpolateMethod::Linear,
170 );
171 assert!((r[0] - 0.0).abs() < 1e-12);
172 assert!((r[1] - 10.0).abs() < 1e-12);
173 assert!((r[2] - 20.0).abs() < 1e-12);
174 assert!((r[3] - 30.0).abs() < 1e-12);
175 }
176
177 #[test]
178 fn linear_edge_extrapolation() {
179 let r = ts_interpolate(
181 &[None, s(10.0), None, s(30.0), None],
182 &[0, 10, 20, 30, 40],
183 InterpolateMethod::Linear,
184 );
185 assert!((r[0] - 10.0).abs() < 1e-12); assert!((r[2] - 20.0).abs() < 1e-12); assert!((r[4] - 30.0).abs() < 1e-12); }
189
190 #[test]
191 fn all_present() {
192 let r = ts_interpolate(
193 &[s(1.0), s(2.0), s(3.0)],
194 &[0, 1, 2],
195 InterpolateMethod::Linear,
196 );
197 assert_eq!(r, vec![1.0, 2.0, 3.0]);
198 }
199
200 #[test]
201 fn empty() {
202 assert!(ts_interpolate(&[], &[], InterpolateMethod::Linear).is_empty());
203 }
204
205 #[test]
206 fn parse_method() {
207 assert_eq!(
208 InterpolateMethod::parse("linear"),
209 Some(InterpolateMethod::Linear)
210 );
211 assert_eq!(
212 InterpolateMethod::parse("PREV"),
213 Some(InterpolateMethod::Previous)
214 );
215 assert_eq!(
216 InterpolateMethod::parse("locf"),
217 Some(InterpolateMethod::Previous)
218 );
219 assert_eq!(
220 InterpolateMethod::parse("next"),
221 Some(InterpolateMethod::Next)
222 );
223 assert_eq!(
224 InterpolateMethod::parse("nocb"),
225 Some(InterpolateMethod::Next)
226 );
227 assert_eq!(
228 InterpolateMethod::parse("zero"),
229 Some(InterpolateMethod::Zero)
230 );
231 assert_eq!(InterpolateMethod::parse("unknown"), None);
232 }
233}