1use std::collections::VecDeque;
11
12use crate::column::ColumnData;
13use crate::duration::parse_duration;
14use crate::elements::Elements;
15use crate::error::{PondError, Result};
16use crate::reducer::{
17 Agg, InputKind, Inputs, Reducer, check_source_kind, clamp_at_zero, input_kind, output_column,
18 percentile_of_sorted,
19};
20use crate::reshape::At;
21use crate::sequence::Sequence;
22use crate::series::{Column, IntervalKeys, Key, TimeSeries};
23
24const CALL: &str = "rolling";
25
26pub(crate) const SUM_SPLIT: f64 = pow2(960);
31pub(crate) const STDEV_SPLIT: f64 = pow2(480);
33
34pub(crate) const fn pow2(e: u64) -> f64 {
36 f64::from_bits((e + 1023) << 52)
37}
38
39pub(crate) trait WindowState {
43 fn add(&mut self, row: usize, v: f64);
44 fn remove(&mut self, row: usize, v: f64);
45 fn snapshot(&mut self) -> Option<f64>;
47}
48
49#[derive(Default)]
50struct CountState(i64);
51
52impl WindowState for CountState {
53 fn add(&mut self, _: usize, _: f64) {
54 self.0 += 1;
55 }
56 fn remove(&mut self, _: usize, _: f64) {
57 self.0 -= 1;
58 }
59 fn snapshot(&mut self) -> Option<f64> {
60 Some(self.0 as f64)
61 }
62}
63
64const SPLIT: f64 = 134_217_729.0;
66
67#[derive(Default, Clone)]
71pub(crate) struct WindowSum {
72 pub(crate) hi: f64,
73 lo: f64,
74 pub(crate) n: i64,
75}
76
77impl WindowSum {
78 pub(crate) fn add(&mut self, v: f64) {
79 self.n += 1;
80 self.acc(v);
81 }
82 pub(crate) fn remove(&mut self, v: f64) {
83 self.n -= 1;
84 if self.n == 0 {
85 self.hi = 0.0;
86 self.lo = 0.0;
87 return;
88 }
89 self.acc(-v);
90 }
91 fn acc(&mut self, v: f64) {
92 let h = self.hi;
93 let s = h + v;
94 let z = s - h;
95 let e = h - (s - z) + (v - z) + self.lo;
96 let t = s + e;
97 self.lo = e - (t - s);
98 self.hi = t;
99 }
100}
101
102#[derive(Default)]
104struct SumState(WindowSum);
105
106impl WindowState for SumState {
107 fn add(&mut self, _: usize, v: f64) {
108 self.0.add(v);
109 }
110 fn remove(&mut self, _: usize, v: f64) {
111 self.0.remove(v);
112 }
113 fn snapshot(&mut self) -> Option<f64> {
114 Some(self.0.hi)
115 }
116}
117
118#[derive(Default)]
120struct AvgState(WindowSum);
121
122impl WindowState for AvgState {
123 fn add(&mut self, _: usize, v: f64) {
124 self.0.add(v);
125 }
126 fn remove(&mut self, _: usize, v: f64) {
127 self.0.remove(v);
128 }
129 fn snapshot(&mut self) -> Option<f64> {
130 (self.0.n != 0).then(|| self.0.hi / self.0.n as f64)
131 }
132}
133
134#[derive(Default)]
137pub(crate) struct ExtremeState<const MAX: bool>(pub(crate) VecDeque<(usize, f64)>);
138
139impl<const MAX: bool> WindowState for ExtremeState<MAX> {
140 fn add(&mut self, row: usize, v: f64) {
141 while let Some(&(_, back)) = self.0.back() {
142 let keep = if MAX { back >= v } else { back <= v };
143 if keep {
144 break;
145 }
146 self.0.pop_back();
147 }
148 self.0.push_back((row, v));
149 }
150 fn remove(&mut self, row: usize, _: f64) {
151 if self.0.front().is_some_and(|&(r, _)| r == row) {
152 self.0.pop_front();
153 }
154 }
155 fn snapshot(&mut self) -> Option<f64> {
156 self.0.front().map(|&(_, v)| v)
157 }
158}
159
160struct OrderedState<const LAST: bool>(VecDeque<(usize, f64)>);
162
163impl<const LAST: bool> WindowState for OrderedState<LAST> {
164 fn add(&mut self, row: usize, v: f64) {
165 self.0.push_back((row, v));
166 }
167 fn remove(&mut self, row: usize, _: f64) {
168 if self.0.front().is_some_and(|&(r, _)| r == row) {
169 self.0.pop_front();
170 }
171 }
172 fn snapshot(&mut self) -> Option<f64> {
173 if LAST { self.0.back() } else { self.0.front() }.map(|&(_, v)| v)
174 }
175}
176
177pub(crate) struct SortedState {
179 pub(crate) sorted: Vec<f64>,
180 pub(crate) q: f64,
181}
182
183impl WindowState for SortedState {
184 fn add(&mut self, _: usize, v: f64) {
185 let at = lower_bound(&self.sorted, v);
186 self.sorted.insert(at, v);
187 }
188 fn remove(&mut self, _: usize, v: f64) {
189 let at = lower_bound(&self.sorted, v);
190 if at < self.sorted.len() && self.sorted[at] == v {
191 self.sorted.remove(at);
192 }
193 }
194 fn snapshot(&mut self) -> Option<f64> {
195 (!self.sorted.is_empty()).then(|| percentile_of_sorted(&self.sorted, self.q))
196 }
197}
198
199#[derive(Default)]
210pub(crate) struct DiffState {
211 max: ExtremeState<true>,
212 min: ExtremeState<false>,
213 zeros: VecDeque<f64>,
214}
215
216impl DiffState {
217 #[cfg(feature = "live")]
219 pub(crate) fn peek(&self) -> Option<f64> {
220 let (hi, lo) = (self.max.0.front()?.1, self.min.0.front()?.1);
221 if hi == 0.0 && lo == 0.0 {
222 return Some(self.zeros.back()? - self.zeros.front()?);
223 }
224 Some(hi - lo)
225 }
226}
227
228impl WindowState for DiffState {
229 fn add(&mut self, row: usize, v: f64) {
230 self.max.add(row, v);
231 self.min.add(row, v);
232 if v == 0.0 {
233 self.zeros.push_front(v);
234 }
235 }
236 fn remove(&mut self, row: usize, v: f64) {
237 self.max.remove(row, v);
238 self.min.remove(row, v);
239 if v == 0.0 {
240 self.zeros.pop_front();
241 }
242 }
243 fn snapshot(&mut self) -> Option<f64> {
244 let (hi, lo) = (self.max.snapshot()?, self.min.snapshot()?);
245 if hi == 0.0 && lo == 0.0 {
246 return Some(self.zeros.back()? - self.zeros.front()?);
247 }
248 Some(hi - lo)
249 }
250}
251
252#[derive(Default)]
258struct KeepState {
259 max: ExtremeState<true>,
260 min: ExtremeState<false>,
261}
262
263impl WindowState for KeepState {
264 fn add(&mut self, row: usize, v: f64) {
265 self.max.add(row, v);
266 self.min.add(row, v);
267 }
268 fn remove(&mut self, row: usize, v: f64) {
269 self.max.remove(row, v);
270 self.min.remove(row, v);
271 }
272 fn snapshot(&mut self) -> Option<f64> {
273 let (hi, lo) = (self.max.snapshot()?, self.min.snapshot()?);
274 (hi == lo).then_some(hi + 0.0)
275 }
276}
277
278#[derive(Default, Clone)]
284pub(crate) struct StdevState {
285 pub(crate) n: f64,
286 k: f64,
287 s1h: f64,
288 s1l: f64,
289 s2h: f64,
290 s2l: f64,
291}
292
293impl StdevState {
294 fn acc(&mut self, v: f64, sign: f64) {
295 let k = self.k;
296 let dh = v - k;
297 let bb = dh - v;
298 let dl = v - (dh - bb) + (-k - bb);
299 let c = SPLIT * dh;
300 let ah = c - (c - dh);
301 let al = dh - ah;
302 let p = dh * dh;
303 let e = ah * ah - p + 2.0 * ah * al + al * al + 2.0 * dh * dl;
304 let mut h = self.s1h;
305 let mut x = sign * dh;
306 let mut s = h + x;
307 let mut z = s - h;
308 let mut er = h - (s - z) + (x - z) + self.s1l + sign * dl;
309 h = s + er;
310 self.s1l = er - (h - s);
311 self.s1h = h;
312 h = self.s2h;
313 x = sign * p;
314 s = h + x;
315 z = s - h;
316 er = h - (s - z) + (x - z) + self.s2l + sign * e;
317 h = s + er;
318 self.s2l = er - (h - s);
319 self.s2h = h;
320 }
321
322 #[cfg(feature = "live")]
325 pub(crate) fn mean(&self) -> f64 {
326 if self.n == 0.0 {
327 return 0.0;
328 }
329 self.k + (self.s1h + self.s1l) / self.n
330 }
331
332 pub(crate) fn m2(&mut self) -> f64 {
333 let n = self.n;
334 if n <= 1.0 {
335 return 0.0;
336 }
337 let mut mh = self.s1h / n;
338 let mut q = self.s1h * mh;
339 if q > 0.5 * self.s2h {
340 self.reshift(mh);
341 mh = self.s1h / n;
342 q = self.s1h * mh;
343 }
344 self.s2h - q + self.s2l - 2.0 * mh * self.s1l
345 }
346
347 fn reshift(&mut self, delta: f64) {
348 let k = self.k;
349 let n = self.n;
350 let k_new = k + delta;
351 let gh = k_new - k;
352 let mut bb = gh - k_new;
353 let gl = k_new - (gh - bb) + (-k - bb);
354 let mut c = SPLIT * gh;
355 let ah = c - (c - gh);
356 let al = gh - ah;
357 c = SPLIT * n;
358 let bh = c - (c - n);
359 let bl = n - bh;
360 let nh = n * gh;
361 let nl = ah * bh - nh + ah * bl + al * bh + al * bl + n * gl;
362 let (s1h, s1l) = (self.s1h, self.s1l);
363 let mut s = s1h - nh;
364 bb = s - s1h;
365 let mut e = s1h - (s - bb) + (-nh - bb) + s1l - nl;
366 let u1h = s + e;
367 let u1l = e - (u1h - s);
368 s = s1h + u1h;
369 bb = s - s1h;
370 e = s1h - (s - bb) + (u1h - bb) + s1l + u1l;
371 let th = s + e;
372 let tl = e - (th - s);
373 c = SPLIT * th;
374 let ch = c - (c - th);
375 let cl = th - ch;
376 let ph = gh * th;
377 let pl = ah * ch - ph + ah * cl + al * ch + al * cl + gh * tl + gl * th;
378 let s2h = self.s2h;
379 s = s2h - ph;
380 bb = s - s2h;
381 e = s2h - (s - bb) + (-ph - bb) + self.s2l - pl;
382 let u2h = s + e;
383 self.s2l = e - (u2h - s);
384 self.s2h = u2h;
385 self.s1h = u1h;
386 self.s1l = u1l;
387 self.k = k_new;
388 }
389}
390
391impl WindowState for StdevState {
392 fn add(&mut self, _: usize, v: f64) {
393 if self.n == 0.0 {
394 *self = Self {
395 n: 1.0,
396 k: v,
397 ..Self::default()
398 };
399 return;
400 }
401 self.n += 1.0;
402 self.acc(v, 1.0);
403 }
404 fn remove(&mut self, _: usize, v: f64) {
405 if self.n <= 1.0 {
406 *self = Self::default();
407 return;
408 }
409 self.n -= 1.0;
410 self.acc(v, -1.0);
411 }
412 fn snapshot(&mut self) -> Option<f64> {
413 if self.n == 0.0 {
414 return None;
415 }
416 if self.n == 1.0 {
417 return Some(0.0);
418 }
419 let m2 = self.m2();
420 Some(clamp_at_zero(m2 / self.n).sqrt())
421 }
422}
423
424pub(crate) fn lower_bound(sorted: &[f64], v: f64) -> usize {
426 sorted.partition_point(|&x| x < v)
427}
428
429pub(crate) trait Window {
432 fn enter(&mut self, row: usize);
433 fn leave(&mut self, row: usize);
434 fn emit(&mut self, rows: std::ops::Range<usize>);
435}
436
437#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
439pub enum Alignment {
440 #[default]
443 Trailing,
444 Leading,
447 Centered,
451}
452
453#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
457pub struct Rolling {
458 pub alignment: Alignment,
460 pub min_samples: usize,
464 pub append: bool,
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
476pub struct RollingOver {
477 pub alignment: Alignment,
479 pub min_samples: usize,
481 pub sample: At,
485 pub range: Option<(i64, i64)>,
487}
488
489#[derive(Clone, Copy)]
493pub(crate) enum Span<'a> {
494 Time(i64),
495 Rows(usize),
496 Windows(&'a [std::ops::Range<usize>]),
497}
498
499pub(crate) trait Anchor: Copy + PartialEq {
503 fn ms(self) -> f64;
504}
505
506impl Anchor for i64 {
507 fn ms(self) -> f64 {
508 self as f64
510 }
511}
512
513impl Anchor for f64 {
514 fn ms(self) -> f64 {
515 self
516 }
517}
518
519#[derive(Clone, Copy)]
521pub(crate) struct Frame<'a, T: Anchor = i64> {
522 pub times: &'a [T],
523 pub span: Span<'a>,
524 pub alignment: Alignment,
525 pub min_samples: usize,
526}
527
528impl<T: Anchor> Frame<'_, T> {
529 pub fn len(&self) -> usize {
531 match self.span {
532 Span::Windows(w) => w.len(),
533 _ => self.times.len(),
534 }
535 }
536}
537
538pub(crate) fn walk<T: Anchor>(frame: &Frame<T>, w: &mut impl Window) {
547 let times = frame.times;
548 let rows = times.len();
549 let (mut start, mut end) = (0usize, 0usize);
550 let emit = |w: &mut _, out: std::ops::Range<usize>, start: usize, end: usize| {
551 if end - start >= frame.min_samples {
552 Window::emit(w, out);
553 }
554 };
555 if let Span::Windows(windows) = frame.span {
556 for (i, r) in windows.iter().enumerate() {
559 while end < r.end {
560 w.enter(end);
561 end += 1;
562 }
563 while start < r.start {
564 w.leave(start);
565 start += 1;
566 }
567 emit(w, i..i + 1, start, end);
568 }
569 return;
570 }
571 if let Span::Rows(n) = frame.span {
572 let left = (n - 1) / 2;
573 let right = n - 1 - left;
574 for i in 0..rows {
575 let (lo, hi) = match frame.alignment {
576 Alignment::Trailing => (i.saturating_sub(n - 1), i),
577 Alignment::Leading => (i, i.saturating_add(n - 1).min(rows - 1)),
578 Alignment::Centered => (
579 i.saturating_sub(left),
580 i.saturating_add(right).min(rows - 1),
581 ),
582 };
583 while end <= hi {
584 w.enter(end);
585 end += 1;
586 }
587 while start < lo {
588 w.leave(start);
589 start += 1;
590 }
591 emit(w, i..i + 1, start, end);
592 }
593 return;
594 }
595 let Span::Time(window_ms) = frame.span else {
596 unreachable!()
597 };
598 let window = window_ms as f64;
599 let mut group_start = 0;
600 while group_start < rows {
601 let anchor = times[group_start];
602 let mut group_end = group_start + 1;
603 while group_end < rows && times[group_end] == anchor {
604 group_end += 1;
605 }
606 let a = anchor.ms();
611 let t = |row: usize| times[row].ms();
612 match frame.alignment {
613 Alignment::Trailing => {
614 while end < rows && t(end) <= a {
615 w.enter(end);
616 end += 1;
617 }
618 let lower = a - window;
619 while start < end && t(start) <= lower {
620 w.leave(start);
621 start += 1;
622 }
623 }
624 Alignment::Leading | Alignment::Centered => {
625 let (lower, upper) = if frame.alignment == Alignment::Leading {
626 (a, a + window)
627 } else {
628 let half = window / 2.0;
629 (a - half, a + half)
630 };
631 while start < end && t(start) < lower {
632 w.leave(start);
633 start += 1;
634 }
635 while end < rows && t(end) < upper {
636 w.enter(end);
637 end += 1;
638 }
639 }
640 }
641 emit(w, group_start..group_end, start, end);
642 group_start = group_end;
643 }
644}
645
646struct Rolled<'a, S> {
648 state: S,
649 values: &'a [f64],
650 out: Vec<f64>,
651}
652
653impl<S: WindowState> Window for Rolled<'_, S> {
654 fn enter(&mut self, row: usize) {
655 let v = self.values[row];
656 if v.is_finite() {
657 self.state.add(row, v);
658 }
659 }
660 fn leave(&mut self, row: usize) {
661 let v = self.values[row];
662 if v.is_finite() {
663 self.state.remove(row, v);
664 }
665 }
666 fn emit(&mut self, rows: std::ops::Range<usize>) {
667 let snap = self
669 .state
670 .snapshot()
671 .filter(|v| v.is_finite())
672 .unwrap_or(f64::NAN);
673 self.out[rows].fill(snap);
674 }
675}
676
677fn roll<S: WindowState, T: Anchor>(state: S, frame: &Frame<T>, values: &[f64]) -> Vec<f64> {
679 let mut w = Rolled {
680 state,
681 values,
682 out: vec![f64::NAN; frame.len()],
683 };
684 walk(frame, &mut w);
685 w.out
686}
687
688pub(crate) fn roll_column<T: Anchor>(
689 frame: &Frame<T>,
690 values: &[f64],
691 reducer: Reducer,
692) -> Vec<f64> {
693 match reducer {
694 Reducer::Count => roll(CountState::default(), frame, values),
695 Reducer::Sum => roll(SumState::default(), frame, values),
696 Reducer::Avg => roll(AvgState::default(), frame, values),
697 Reducer::Min => roll(ExtremeState::<false>(VecDeque::new()), frame, values),
698 Reducer::Max => roll(ExtremeState::<true>(VecDeque::new()), frame, values),
699 Reducer::First => roll(OrderedState::<false>(VecDeque::new()), frame, values),
700 Reducer::Last => roll(OrderedState::<true>(VecDeque::new()), frame, values),
701 Reducer::Median => roll(
702 SortedState {
703 sorted: Vec::new(),
704 q: 50.0,
705 },
706 frame,
707 values,
708 ),
709 Reducer::Percentile(q) => roll(
710 SortedState {
711 sorted: Vec::new(),
712 q,
713 },
714 frame,
715 values,
716 ),
717 Reducer::Stdev => roll(StdevState::default(), frame, values),
718 Reducer::Difference => roll(DiffState::default(), frame, values),
719 Reducer::Keep => roll(KeepState::default(), frame, values),
720 Reducer::Unique | Reducer::Top(_) | Reducer::Samples => {
721 unreachable!("list reducers run in elements.rs")
722 }
723 }
724}
725
726impl TimeSeries {
727 pub fn rolling(&self, window: &str, aggs: &[Agg]) -> Result<TimeSeries> {
770 self.rolling_with(window, aggs, Rolling::default())
771 }
772
773 pub fn rolling_with(&self, window: &str, aggs: &[Agg], options: Rolling) -> Result<TimeSeries> {
790 let window_ms = parse_duration(window)?;
791 self.roll(Span::Time(window_ms), aggs, options)
792 }
793
794 pub fn rolling_rows(&self, rows: usize, aggs: &[Agg], options: Rolling) -> Result<TimeSeries> {
815 if rows == 0 {
816 return Err(PondError::InvalidData {
817 call: CALL,
818 message: "a row window must hold at least 1 row".into(),
819 });
820 }
821 self.roll(Span::Rows(rows), aggs, options)
822 }
823
824 fn roll(&self, span: Span, aggs: &[Agg], options: Rolling) -> Result<TimeSeries> {
825 let frame = Frame {
828 times: self.key().begin(),
829 span,
830 alignment: options.alignment,
831 min_samples: options.min_samples,
832 };
833 let plan = self.rolling_plan(aggs, self.key_name())?;
834 if options.append {
835 for agg in aggs {
836 let taken = agg.output == self.key_name()
837 || self.columns().iter().any(|c| c.name() == agg.output);
838 if taken {
839 return Err(PondError::InvalidData {
840 call: CALL,
841 message: format!(
842 "output column '{}' already exists; with append, give each output its own name (Agg::new)",
843 agg.output
844 ),
845 });
846 }
847 }
848 }
849 let mut columns = self.roll_columns(CALL, &frame, &plan)?;
850 if options.append {
851 let mut all = self.columns().to_vec();
852 all.append(&mut columns);
853 columns = all;
854 }
855 Ok(TimeSeries::from_parts(
856 self.name().to_string(),
857 self.key_name().to_string(),
858 self.shared_key(),
859 columns,
860 ))
861 }
862
863 pub(crate) fn roll_columns(
866 &self,
867 call: &'static str,
868 frame: &Frame,
869 plan: &[(usize, &Agg)],
870 ) -> Result<Vec<Column>> {
871 for &(source, agg) in plan {
872 check_magnitude(call, &self.columns()[source], agg.reducer)?;
873 }
874 let frame = *frame;
875 let used: Vec<(usize, Reducer)> = plan
876 .iter()
877 .map(|&(source, agg)| (source, agg.reducer))
878 .collect();
879 let inputs = Inputs::build(self.columns(), &used);
880 crate::par::map(plan, |&(source, agg)| {
881 let data = self.columns()[source].data();
882 if agg.reducer.gives_list() {
883 let lists = Elements::new(data).roll(agg.reducer, &frame);
884 return Ok(Column::from_data(agg.output.clone(), lists, false));
885 }
886 let kind = input_kind(data, agg.reducer);
887 if let (ColumnData::Decimal(d), InputKind::ExactSum) = (data, kind) {
888 return crate::decimal::rolling_sum(call, &agg.output, d, &frame);
889 }
890 let input = inputs.get(source, kind);
891 if let (ColumnData::Decimal(d), Reducer::Difference) = (data, agg.reducer) {
892 let lo = roll_column(&frame, &input.values, Reducer::Min);
893 let hi = roll_column(&frame, &input.values, Reducer::Max);
894 return crate::decimal::difference(call, &agg.output, d, input, &lo, &hi);
895 }
896 let out = roll_column(&frame, &input.values, agg.reducer);
897 Ok(output_column(
898 agg.output.clone(),
899 data,
900 agg.reducer,
901 input,
902 out,
903 ))
904 })
905 .into_iter()
906 .collect()
907 }
908
909 pub fn rolling_over(
940 &self,
941 sequence: &Sequence,
942 window: &str,
943 aggs: &[Agg],
944 options: RollingOver,
945 ) -> Result<TimeSeries> {
946 let window_ms = parse_duration(window)?;
947 let plan = self.rolling_plan(aggs, "interval")?;
948 let buckets = match self.grid_range(CALL, options.range)? {
949 None => crate::grid::no_buckets(),
950 Some((begin, end)) => sequence.buckets_sampled(begin, end, options.sample)?,
951 };
952 let ranges = grid_windows(
953 self.key().begin(),
954 &buckets,
955 options.sample,
956 window_ms,
957 options.alignment,
958 );
959 let plan: Vec<(&Agg, usize)> = plan
960 .into_iter()
961 .map(|(source, agg)| (agg, source))
962 .collect();
963 let mut columns = self.reduce_columns(CALL, &plan, &ranges)?;
964 if ranges.iter().any(|r| r.len() < options.min_samples) {
965 let keep: Vec<Option<usize>> = ranges
966 .iter()
967 .enumerate()
968 .map(|(i, r)| (r.len() >= options.min_samples).then_some(i))
969 .collect();
970 for c in &mut columns {
971 *c = Column::from_data(c.name().to_string(), c.data().take(&keep), false);
972 }
973 }
974 Ok(TimeSeries::from_parts(
975 self.name().to_string(),
976 "interval".to_string(),
977 Key::Interval(buckets),
978 columns,
979 ))
980 }
981
982 fn rolling_plan<'a>(&self, aggs: &'a [Agg], key: &str) -> Result<Vec<(usize, &'a Agg)>> {
987 let mut plan = Vec::with_capacity(aggs.len());
988 for (i, agg) in aggs.iter().enumerate() {
989 let source = self.column_index(CALL, &agg.source)?;
990 agg.reducer.check(CALL)?;
991 if agg.output == key || aggs[..i].iter().any(|a| a.output == agg.output) {
992 return Err(PondError::DuplicateOutput {
993 call: CALL,
994 column: agg.output.clone(),
995 });
996 }
997 check_source_kind(CALL, &self.columns()[source], agg.reducer)?;
998 plan.push((source, agg));
999 }
1000 if aggs.iter().all(|a| a.shorthand) {
1001 plan.sort_by_key(|&(source, _)| source);
1003 }
1004 Ok(plan)
1005 }
1006}
1007
1008fn grid_windows(
1013 starts: &[i64],
1014 buckets: &IntervalKeys,
1015 sample: At,
1016 window_ms: i64,
1017 alignment: Alignment,
1018) -> Vec<std::ops::Range<usize>> {
1019 let w = window_ms as f64;
1020 let t = |row: usize| starts[row] as f64;
1021 let n = starts.len();
1022 let (mut lo, mut hi) = (0usize, 0usize);
1023 buckets
1024 .begin
1025 .iter()
1026 .zip(&buckets.end)
1027 .map(|(&b, &e)| {
1028 let (b, e) = (b as f64, e as f64);
1029 let p = match sample {
1030 At::Begin => b,
1031 At::Center => b + (e - b) / 2.0,
1032 At::End => e,
1033 };
1034 let (from, to) = match alignment {
1036 Alignment::Trailing => (p - w, p),
1037 Alignment::Leading => (p, p + w),
1038 Alignment::Centered => (p - w / 2.0, p + w / 2.0),
1039 };
1040 let trailing = alignment == Alignment::Trailing;
1041 while lo < n && (t(lo) < from || (trailing && t(lo) == from)) {
1042 lo += 1;
1043 }
1044 hi = hi.max(lo);
1045 while hi < n && (t(hi) < to || (trailing && t(hi) == to)) {
1046 hi += 1;
1047 }
1048 lo..hi
1049 })
1050 .collect()
1051}
1052
1053pub(crate) fn check_magnitude(call: &'static str, column: &Column, reducer: Reducer) -> Result<()> {
1055 check_magnitude_in(call, column, reducer, None)
1056}
1057
1058pub(crate) fn check_magnitude_in(
1061 call: &'static str,
1062 column: &Column,
1063 reducer: Reducer,
1064 rows: Option<&[u32]>,
1065) -> Result<()> {
1066 let split = match reducer {
1067 Reducer::Sum | Reducer::Avg => SUM_SPLIT,
1068 Reducer::Stdev => STDEV_SPLIT,
1069 _ => return Ok(()),
1070 };
1071 let Ok(values) = column.numbers().map(|c| c.values()) else {
1074 return Ok(());
1075 };
1076 let too_big = |&row: &usize| values[row].is_finite() && values[row].abs() >= split;
1077 let found = match rows {
1078 None => (0..values.len()).find(too_big),
1079 Some(rows) => rows.iter().map(|&r| r as usize).find(too_big),
1080 };
1081 match found {
1082 None => Ok(()),
1083 Some(row) => Err(PondError::Unsupported {
1084 call,
1085 message: format!(
1086 "column '{}' row {row} holds {:e}, too large for a rolling {:?} (limit {split:e})",
1087 column.name(),
1088 values[row],
1089 reducer
1090 ),
1091 }),
1092 }
1093}
1094
1095#[cfg(test)]
1096mod tests {
1097 use super::*;
1098
1099 #[test]
1100 fn extreme_times_keep_their_own_row() {
1101 use crate::MAX_TIME_MS;
1102 for t in [-MAX_TIME_MS, MAX_TIME_MS] {
1103 let s = TimeSeries::new("s", vec![t], vec![Column::required("v", vec![7.0])]).unwrap();
1104 for window in ["1ms", "100000000d"] {
1105 let r = s
1106 .rolling(window, &[Agg::new("n", "v", Reducer::Count)])
1107 .unwrap();
1108 assert_eq!(
1109 r.column("n").unwrap().numbers().unwrap().get(0),
1110 Some(1.0),
1111 "{t} {window}"
1112 );
1113 }
1114 }
1115 let too_early =
1116 TimeSeries::new("s", vec![i64::MIN], vec![Column::required("v", vec![7.0])]);
1117 assert!(too_early.is_err());
1118 }
1119
1120 #[test]
1123 fn difference_matches_the_sorted_window_bit_for_bit() {
1124 struct Sorted(Vec<f64>);
1125 impl WindowState for Sorted {
1126 fn add(&mut self, _: usize, v: f64) {
1127 let at = lower_bound(&self.0, v);
1128 self.0.insert(at, v);
1129 }
1130 fn remove(&mut self, _: usize, v: f64) {
1131 let at = lower_bound(&self.0, v);
1132 if at < self.0.len() && self.0[at] == v {
1133 self.0.remove(at);
1134 }
1135 }
1136 fn snapshot(&mut self) -> Option<f64> {
1137 Some(self.0.last()? - self.0.first()?)
1138 }
1139 }
1140 let mut seed = 11u64;
1141 let mut next = || {
1142 seed = seed
1143 .wrapping_mul(6364136223846793005)
1144 .wrapping_add(1442695040888963407);
1145 seed >> 33
1146 };
1147 for _ in 0..300 {
1148 let n = 1 + next() as usize % 60;
1149 let mut t = 0;
1150 let times: Vec<i64> = (0..n)
1151 .map(|_| {
1152 t += next() as i64 % 3;
1153 t
1154 })
1155 .collect();
1156 let values: Vec<f64> = (0..n)
1157 .map(|_| [0.0, -0.0, 0.0, -0.0, 1.0, -2.0, f64::NAN][next() as usize % 7])
1158 .collect();
1159 let window = 1 + next() as i64 % 8;
1160 let frame = Frame {
1161 times: ×,
1162 span: Span::Time(window),
1163 alignment: Alignment::Trailing,
1164 min_samples: 0,
1165 };
1166 let want = roll(Sorted(Vec::new()), &frame, &values);
1167 let got = roll(DiffState::default(), &frame, &values);
1168 let bits = |xs: &[f64]| xs.iter().map(|x| x.to_bits()).collect::<Vec<_>>();
1169 assert_eq!(bits(&got), bits(&want), "{times:?} {values:?} {window}");
1170 }
1171 }
1172
1173 #[test]
1174 fn splits_are_the_pond_ts_powers_of_two() {
1175 assert_eq!(SUM_SPLIT, 2f64.powi(960));
1176 assert_eq!(STDEV_SPLIT, 2f64.powi(480));
1177 }
1178}