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