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 Some(prev_val) = values[pi] else { continue };
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 Some(fv) = values[first]
115 {
116 for r in result.iter_mut().take(first) {
117 *r = fv;
118 }
119 }
120 if let Some(last) = values.iter().rposition(|v| v.is_some())
122 && let Some(lv) = values[last]
123 {
124 for r in result.iter_mut().skip(last + 1) {
125 *r = lv;
126 }
127 }
128
129 result
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 fn s(v: f64) -> Option<f64> {
137 Some(v)
138 }
139
140 #[test]
141 fn zero_fill() {
142 let r = ts_interpolate(&[s(1.0), None, s(3.0)], &[0, 1, 2], InterpolateMethod::Zero);
143 assert_eq!(r, vec![1.0, 0.0, 3.0]);
144 }
145
146 #[test]
147 fn previous_fill() {
148 let r = ts_interpolate(
149 &[s(10.0), None, None, s(40.0)],
150 &[0, 1, 2, 3],
151 InterpolateMethod::Previous,
152 );
153 assert_eq!(r, vec![10.0, 10.0, 10.0, 40.0]);
154 }
155
156 #[test]
157 fn next_fill() {
158 let r = ts_interpolate(
159 &[None, None, s(30.0), s(40.0)],
160 &[0, 1, 2, 3],
161 InterpolateMethod::Next,
162 );
163 assert_eq!(r, vec![30.0, 30.0, 30.0, 40.0]);
164 }
165
166 #[test]
167 fn linear_fill() {
168 let r = ts_interpolate(
169 &[s(0.0), None, None, s(30.0)],
170 &[0, 10, 20, 30],
171 InterpolateMethod::Linear,
172 );
173 assert!((r[0] - 0.0).abs() < 1e-12);
174 assert!((r[1] - 10.0).abs() < 1e-12);
175 assert!((r[2] - 20.0).abs() < 1e-12);
176 assert!((r[3] - 30.0).abs() < 1e-12);
177 }
178
179 #[test]
180 fn linear_edge_extrapolation() {
181 let r = ts_interpolate(
183 &[None, s(10.0), None, s(30.0), None],
184 &[0, 10, 20, 30, 40],
185 InterpolateMethod::Linear,
186 );
187 assert!((r[0] - 10.0).abs() < 1e-12); assert!((r[2] - 20.0).abs() < 1e-12); assert!((r[4] - 30.0).abs() < 1e-12); }
191
192 #[test]
193 fn all_present() {
194 let r = ts_interpolate(
195 &[s(1.0), s(2.0), s(3.0)],
196 &[0, 1, 2],
197 InterpolateMethod::Linear,
198 );
199 assert_eq!(r, vec![1.0, 2.0, 3.0]);
200 }
201
202 #[test]
203 fn empty() {
204 assert!(ts_interpolate(&[], &[], InterpolateMethod::Linear).is_empty());
205 }
206
207 #[test]
208 fn parse_method() {
209 assert_eq!(
210 InterpolateMethod::parse("linear"),
211 Some(InterpolateMethod::Linear)
212 );
213 assert_eq!(
214 InterpolateMethod::parse("PREV"),
215 Some(InterpolateMethod::Previous)
216 );
217 assert_eq!(
218 InterpolateMethod::parse("locf"),
219 Some(InterpolateMethod::Previous)
220 );
221 assert_eq!(
222 InterpolateMethod::parse("next"),
223 Some(InterpolateMethod::Next)
224 );
225 assert_eq!(
226 InterpolateMethod::parse("nocb"),
227 Some(InterpolateMethod::Next)
228 );
229 assert_eq!(
230 InterpolateMethod::parse("zero"),
231 Some(InterpolateMethod::Zero)
232 );
233 assert_eq!(InterpolateMethod::parse("unknown"), None);
234 }
235}