Skip to main content

libsurfer/
analog_signal_cache.rs

1//! Analog signal cache with fast min/max queries using Blocked RMQ + Binary Search
2//!
3//! # Complexity
4//!
5//! - Construction: O(N + N/B · log(N/B))
6//! - Time queries: O(log N) for binary search + O(1) for min/max
7//!
8//! - Memory: O(N + N/B · log(N/B)) where B is the block size
9//!
10//! Per-sample storage (N samples):
11//! - `timestamps: Vec<u64>` = 8N bytes
12//! - `values: Vec<f64>` = 8N bytes
13//!
14//! Sparse table storage (B = 64 default block size):
15//! - `MinMax` struct = 24 bytes (8 + 8 + 1 + padding)
16//! - `num_blocks` = ⌈N/B⌉
17//! - `num_levels` = 1 + ⌊`log₂(num_blocks)`⌋
18//! - Sparse table = `num_blocks` × `num_levels` × 24 bytes
19//!
20//! Total ≈ 16N + (N/64) × (1 + log₂(N/64)) × 24 bytes
21//!
22//! # Examples
23//!   1M Samples ~ 21.3 MB
24//!   100M Samples ~ 2.39 GB
25//!
26
27use crate::translation::DynTranslator;
28use crate::wave_container::{AnalogCacheKey, SignalAccessor, VariableMeta};
29use std::sync::OnceLock;
30
31pub use surfer_translation_types::{NAN_HIGHIMP, NAN_UNDEF, is_nan_highimp};
32
33#[derive(Clone, Copy, PartialEq)]
34struct MinMax {
35    min: f64,
36    max: f64,
37    has_non_finite: bool,
38}
39
40impl MinMax {
41    fn new(value: f64) -> Self {
42        if value.is_finite() {
43            Self {
44                min: value,
45                max: value,
46                has_non_finite: false,
47            }
48        } else {
49            // Use identity values for min/max operations:
50            // INFINITY.min(x) == x for any finite x
51            // NEG_INFINITY.max(x) == x for any finite x
52            Self {
53                min: f64::INFINITY,
54                max: f64::NEG_INFINITY,
55                has_non_finite: true,
56            }
57        }
58    }
59
60    fn combine(&self, other: &Self) -> Self {
61        Self {
62            min: self.min.min(other.min),
63            max: self.max.max(other.max),
64            has_non_finite: self.has_non_finite || other.has_non_finite,
65        }
66    }
67
68    fn from_slice(values: &[f64]) -> Self {
69        values
70            .iter()
71            .fold(Self::new(values[0]), |acc, &v| acc.combine(&Self::new(v)))
72    }
73}
74
75pub struct CacheQueryResult {
76    pub current: Option<(u64, f64)>,
77    pub next: Option<u64>,
78}
79
80struct SignalRMQ {
81    timestamps: Vec<u64>,
82    values: Vec<f64>,
83    block_size: usize,
84    /// `sparse_table`[level][block_idx] contains min/max for 2^level blocks starting at `block_idx`
85    /// Level 0 contains individual block summaries.
86    sparse_table: Vec<Vec<MinMax>>,
87}
88
89impl SignalRMQ {
90    fn new(signal: impl IntoIterator<Item = (u64, f64)>, block_size: usize) -> Self {
91        let data = signal.into_iter().collect::<Vec<_>>();
92
93        debug_assert!(
94            data.windows(2).all(|w| w[1].0 > w[0].0),
95            "Timestamps must be strictly increasing"
96        );
97
98        let (timestamps, values) = data.into_iter().unzip::<u64, f64, Vec<_>, Vec<_>>();
99
100        let num_blocks = values.len().div_ceil(block_size);
101
102        let block_summaries = (0..num_blocks)
103            .map(|block_idx| {
104                let start = block_idx * block_size;
105                let end = (start + block_size).min(values.len());
106                MinMax::from_slice(&values[start..end])
107            })
108            .collect::<Vec<_>>();
109
110        let sparse_table = Self::build_sparse_table(&block_summaries);
111
112        Self {
113            timestamps,
114            values,
115            block_size,
116            sparse_table,
117        }
118    }
119
120    /// Builds a sparse table for O(1) range min/max queries over blocks.
121    ///
122    /// `table[k][i]` stores min/max over up to 2^k consecutive blocks starting at block i.
123    /// Any range query can be answered by combining at most two overlapping entries.
124    fn build_sparse_table(block_summaries: &[MinMax]) -> Vec<Vec<MinMax>> {
125        let num_blocks = block_summaries.len();
126        let num_levels = num_blocks.ilog2() as usize;
127
128        // Level 0 contains individual block summaries
129        let mut table = vec![block_summaries.to_vec()];
130
131        for level in 0..num_levels {
132            let prev = &table[level];
133            let span = 1 << level; // Each entry covers 2^level blocks
134
135            // Level (level+1) combines pairs of entries from current level
136            let next_level = (0..num_blocks)
137                .map(|i| {
138                    let left = prev[i];
139                    // If (i + span) is out of bounds, just use 'left' (self-combine/no-op).
140                    prev.get(i + span).map_or(left, |right| left.combine(right))
141                })
142                .collect::<Vec<_>>();
143
144            table.push(next_level);
145        }
146
147        table
148    }
149
150    fn query_time_range(&self, t_start: u64, t_end: u64) -> Option<MinMax> {
151        if t_start > t_end {
152            return None;
153        }
154
155        let l = self
156            .timestamps
157            .binary_search(&t_start)
158            .unwrap_or_else(|x| x);
159        let r = match self.timestamps.binary_search(&t_end) {
160            Ok(idx) => idx,
161            Err(idx) => {
162                if idx == 0 {
163                    return None;
164                }
165                idx - 1
166            }
167        };
168
169        if l > r || l >= self.values.len() {
170            return None;
171        }
172
173        Some(self.query_index_range(l, r))
174    }
175
176    fn query_index_range(&self, l: usize, r: usize) -> MinMax {
177        debug_assert!(l <= r && r < self.values.len(), "Invalid index range");
178
179        let l_block = l / self.block_size;
180        let r_block = r / self.block_size;
181
182        if l_block == r_block {
183            return MinMax::from_slice(&self.values[l..=r]);
184        }
185
186        // Left partial block: from l to end of l_block (or r if smaller)
187        let l_block_end = (l_block + 1) * self.block_size - 1;
188        let mut result = MinMax::from_slice(&self.values[l..=l_block_end.min(r)]);
189
190        // Right partial block: from start of r_block to r
191        let r_block_start = r_block * self.block_size;
192        if r_block > l_block {
193            let partial = MinMax::from_slice(&self.values[r_block_start..=r]);
194            result = result.combine(&partial);
195        }
196
197        // Full blocks in the middle
198        let first_full_block = l_block + 1;
199        let last_full_block = r_block - 1;
200
201        if first_full_block <= last_full_block {
202            let middle = self.query_blocks(first_full_block, last_full_block);
203            result = result.combine(&middle);
204        }
205
206        result
207    }
208
209    fn query_blocks(&self, l_block: usize, r_block: usize) -> MinMax {
210        debug_assert!(
211            l_block <= r_block,
212            "query_blocks called with l_block > r_block"
213        );
214
215        if l_block == r_block {
216            return self.sparse_table[0][l_block];
217        }
218
219        let range_len = r_block - l_block + 1;
220        let level = range_len.ilog2() as usize;
221        let jump = 1 << level;
222
223        let left = self.sparse_table[level][l_block];
224        let right = self.sparse_table[level][r_block - jump + 1];
225
226        left.combine(&right)
227    }
228
229    fn time_range(&self) -> Option<(u64, u64)> {
230        if self.timestamps.is_empty() {
231            None
232        } else {
233            Some((self.timestamps[0], *self.timestamps.last().unwrap()))
234        }
235    }
236
237    /// Query the signal value at a specific time.
238    ///
239    /// Returns the value at or before the query time, along with the next transition time.
240    /// If the query time is before the first sample, `current` is `None` but `next` points
241    /// to the first sample.
242    fn query_at_time(&self, time: u64) -> CacheQueryResult {
243        match self.timestamps.binary_search(&time) {
244            Ok(idx) => CacheQueryResult {
245                current: Some((self.timestamps[idx], self.values[idx])),
246                next: self.timestamps.get(idx + 1).copied(),
247            },
248            Err(0) => CacheQueryResult {
249                current: None, // Before first sample
250                next: self.timestamps.first().copied(),
251            },
252            Err(idx) => CacheQueryResult {
253                current: Some((self.timestamps[idx - 1], self.values[idx - 1])),
254                next: self.timestamps.get(idx).copied(),
255            },
256        }
257    }
258}
259
260/// Cache entry for a single analog signal.
261pub struct AnalogSignalCache {
262    rmq: SignalRMQ,
263    pub global_min: f64,
264    pub global_max: f64,
265    /// Total number of time units (for cache invalidation on reload).
266    pub num_timestamps: u64,
267}
268
269impl AnalogSignalCache {
270    pub fn build(
271        accessor: SignalAccessor,
272        translator: &DynTranslator,
273        meta: &VariableMeta,
274        num_timestamps: u64,
275        block_size: Option<usize>,
276    ) -> Option<Self> {
277        let block_size = block_size.unwrap_or(64);
278
279        let mut signal_data = Vec::new();
280
281        for (time_u64, var_value) in accessor.iter_changes() {
282            let numeric = translator
283                .translate_numeric(meta, &var_value)
284                .unwrap_or(f64::NAN);
285            signal_data.push((time_u64, numeric));
286        }
287
288        if signal_data.is_empty() {
289            return None;
290        }
291
292        let rmq = SignalRMQ::new(signal_data, block_size);
293        let (first_time, last_time) = rmq.time_range()?;
294        let global = rmq.query_time_range(first_time, last_time)?;
295
296        Some(Self {
297            rmq,
298            global_min: global.min,
299            global_max: global.max,
300            num_timestamps,
301        })
302    }
303
304    #[must_use]
305    pub fn query_time_range(&self, start: u64, end: u64) -> Option<(f64, f64)> {
306        let result = self.rmq.query_time_range(start, end)?;
307        if result.has_non_finite {
308            // Propagate NaN so renderer draws undefined for ranges containing non-finite values
309            Some((result.min, NAN_UNDEF))
310        } else {
311            Some((result.min, result.max))
312        }
313    }
314
315    #[must_use]
316    pub fn query_at_time(&self, time: u64) -> CacheQueryResult {
317        self.rmq.query_at_time(time)
318    }
319}
320
321/// Wrapper for analog cache with reference counting and lazy initialization.
322///
323/// Used for cache sharing between variables with the same signal+translator combo.
324/// The cache is built asynchronously and set via `OnceLock` when ready.
325pub struct AnalogCacheEntry {
326    inner: OnceLock<AnalogSignalCache>,
327    pub cache_key: AnalogCacheKey,
328    pub generation: u64,
329}
330
331impl AnalogCacheEntry {
332    #[must_use]
333    pub fn new(cache_key: AnalogCacheKey, generation: u64) -> Self {
334        Self {
335            inner: OnceLock::new(),
336            cache_key,
337            generation,
338        }
339    }
340
341    pub fn is_ready(&self) -> bool {
342        self.inner.get().is_some()
343    }
344
345    pub fn get(&self) -> Option<&AnalogSignalCache> {
346        self.inner.get()
347    }
348
349    pub fn set(&self, cache: AnalogSignalCache) {
350        let _ = self.inner.set(cache);
351    }
352}
353
354#[cfg(test)]
355mod tests {
356    use super::*;
357
358    #[test]
359    fn test_single_sample() {
360        let signal = vec![(100, 5.0)];
361        let rmq = SignalRMQ::new(signal, 64);
362
363        assert_eq!(rmq.timestamps.len(), 1);
364        assert_eq!(rmq.time_range(), Some((100, 100)));
365
366        let result = rmq.query_time_range(100, 100).unwrap();
367        assert_eq!(result.min, 5.0);
368        assert_eq!(result.max, 5.0);
369
370        assert!(rmq.query_time_range(0, 99).is_none());
371        assert!(rmq.query_time_range(101, 200).is_none());
372    }
373
374    #[test]
375    fn test_two_samples() {
376        let signal = vec![(10, 3.0), (20, 7.0)];
377        let rmq = SignalRMQ::new(signal, 64);
378
379        let result = rmq.query_time_range(10, 20).unwrap();
380        assert_eq!(result.min, 3.0);
381        assert_eq!(result.max, 7.0);
382
383        let result = rmq.query_time_range(10, 10).unwrap();
384        assert_eq!(result.min, 3.0);
385        assert_eq!(result.max, 3.0);
386
387        let result = rmq.query_time_range(20, 20).unwrap();
388        assert_eq!(result.min, 7.0);
389        assert_eq!(result.max, 7.0);
390    }
391
392    #[cfg(debug_assertions)]
393    #[test]
394    #[should_panic(expected = "Timestamps must be strictly increasing")]
395    fn test_unsorted_input() {
396        let signal = vec![(20, 7.0), (10, 3.0), (15, 5.0)];
397        SignalRMQ::new(signal, 64);
398    }
399
400    #[test]
401    fn test_irregular_timestamps() {
402        let signal = vec![
403            (1, 1.0),
404            (37, 5.0),
405            (41, 2.0),
406            (512, 8.0),
407            (513, 3.0),
408            (2080, 6.0),
409        ];
410        let rmq = SignalRMQ::new(signal, 2);
411
412        // Query full range
413        let result = rmq.query_time_range(1, 2080).unwrap();
414        assert_eq!(result.min, 1.0);
415        assert_eq!(result.max, 8.0);
416
417        // Query subrange
418        let result = rmq.query_time_range(37, 513).unwrap();
419        assert_eq!(result.min, 2.0);
420        assert_eq!(result.max, 8.0);
421
422        // Query exact timestamp
423        let result = rmq.query_time_range(512, 512).unwrap();
424        assert_eq!(result.min, 8.0);
425        assert_eq!(result.max, 8.0);
426    }
427
428    #[test]
429    fn test_large_signal_multiple_blocks() {
430        let mut signal = Vec::new();
431        for i in 0..1000 {
432            signal.push((i as u64, f64::from(i % 100)));
433        }
434
435        let rmq = SignalRMQ::new(signal, 32);
436
437        // Query full range
438        let result = rmq.query_time_range(0, 999).unwrap();
439        assert_eq!(result.min, 0.0);
440        assert_eq!(result.max, 99.0);
441
442        // Query first block
443        let result = rmq.query_time_range(0, 31).unwrap();
444        assert_eq!(result.min, 0.0);
445        assert_eq!(result.max, 31.0);
446
447        // Query spanning blocks
448        let result = rmq.query_time_range(50, 150).unwrap();
449        assert_eq!(result.min, 0.0);
450        assert_eq!(result.max, 99.0);
451    }
452
453    #[test]
454    fn test_all_same_values() {
455        let signal: Vec<_> = (0..100).map(|i| (i as u64, 42.0)).collect();
456        let rmq = SignalRMQ::new(signal, 32);
457
458        let result = rmq.query_time_range(0, 99).unwrap();
459        assert_eq!(result.min, 42.0);
460        assert_eq!(result.max, 42.0);
461
462        let result = rmq.query_time_range(25, 75).unwrap();
463        assert_eq!(result.min, 42.0);
464        assert_eq!(result.max, 42.0);
465    }
466
467    #[test]
468    fn test_monotonic_increasing() {
469        let signal: Vec<_> = (0..100).map(|i| (i as u64, f64::from(i))).collect();
470        let rmq = SignalRMQ::new(signal, 32);
471
472        let result = rmq.query_time_range(0, 99).unwrap();
473        assert_eq!(result.min, 0.0);
474        assert_eq!(result.max, 99.0);
475
476        let result = rmq.query_time_range(20, 40).unwrap();
477        assert_eq!(result.min, 20.0);
478        assert_eq!(result.max, 40.0);
479    }
480
481    #[test]
482    fn test_monotonic_decreasing() {
483        let signal: Vec<_> = (0..100).map(|i| (i as u64, f64::from(99 - i))).collect();
484        let rmq = SignalRMQ::new(signal, 32);
485
486        let result = rmq.query_time_range(0, 99).unwrap();
487        assert_eq!(result.min, 0.0);
488        assert_eq!(result.max, 99.0);
489
490        let result = rmq.query_time_range(20, 40).unwrap();
491        assert_eq!(result.min, 59.0);
492        assert_eq!(result.max, 79.0);
493    }
494
495    #[test]
496    fn test_negative_values() {
497        let signal = vec![(0, -5.0), (1, 3.0), (2, -2.0), (3, 8.0), (4, -10.0)];
498        let rmq = SignalRMQ::new(signal, 2);
499
500        let result = rmq.query_time_range(0, 4).unwrap();
501        assert_eq!(result.min, -10.0);
502        assert_eq!(result.max, 8.0);
503
504        let result = rmq.query_time_range(0, 2).unwrap();
505        assert_eq!(result.min, -5.0);
506        assert_eq!(result.max, 3.0);
507    }
508
509    #[test]
510    fn test_very_large_timestamps() {
511        let signal = vec![
512            (1_000_000_000, 1.0),
513            (2_000_000_000, 5.0),
514            (3_000_000_000, 2.0),
515            (u64::MAX - 1, 10.0),
516        ];
517        let rmq = SignalRMQ::new(signal, 2);
518
519        let result = rmq.query_time_range(1_000_000_000, u64::MAX).unwrap();
520        assert_eq!(result.min, 1.0);
521        assert_eq!(result.max, 10.0);
522    }
523
524    #[test]
525    fn test_query_before_signal() {
526        let signal = vec![(100, 5.0), (200, 10.0)];
527        let rmq = SignalRMQ::new(signal, 64);
528
529        assert!(rmq.query_time_range(0, 50).is_none());
530        assert!(rmq.query_time_range(0, 99).is_none());
531    }
532
533    #[test]
534    fn test_query_after_signal() {
535        let signal = vec![(100, 5.0), (200, 10.0)];
536        let rmq = SignalRMQ::new(signal, 64);
537
538        assert!(rmq.query_time_range(300, 400).is_none());
539    }
540
541    #[test]
542    fn test_query_partial_overlap_start() {
543        let signal = vec![(100, 5.0), (200, 10.0), (300, 15.0)];
544        let rmq = SignalRMQ::new(signal, 64);
545
546        let result = rmq.query_time_range(50, 150).unwrap();
547        assert_eq!(result.min, 5.0);
548        assert_eq!(result.max, 5.0);
549    }
550
551    #[test]
552    fn test_query_partial_overlap_end() {
553        let signal = vec![(100, 5.0), (200, 10.0), (300, 15.0)];
554        let rmq = SignalRMQ::new(signal, 64);
555
556        let result = rmq.query_time_range(250, 400).unwrap();
557        assert_eq!(result.min, 15.0);
558        assert_eq!(result.max, 15.0);
559    }
560
561    #[test]
562    fn test_query_between_samples() {
563        let signal = vec![(100, 5.0), (200, 10.0), (300, 15.0)];
564        let rmq = SignalRMQ::new(signal, 64);
565
566        assert!(rmq.query_time_range(110, 190).is_none());
567    }
568
569    #[test]
570    fn test_small_block_size() {
571        let signal: Vec<_> = (0..20).map(|i| (i as u64, f64::from(i))).collect();
572        let rmq = SignalRMQ::new(signal, 2);
573
574        let result = rmq.query_time_range(0, 19).unwrap();
575        assert_eq!(result.min, 0.0);
576        assert_eq!(result.max, 19.0);
577
578        let result = rmq.query_time_range(5, 15).unwrap();
579        assert_eq!(result.min, 5.0);
580        assert_eq!(result.max, 15.0);
581    }
582
583    #[test]
584    fn test_large_block_size() {
585        let signal: Vec<_> = (0..20).map(|i| (i as u64, f64::from(i))).collect();
586        let rmq = SignalRMQ::new(signal, 100);
587
588        let result = rmq.query_time_range(0, 19).unwrap();
589        assert_eq!(result.min, 0.0);
590        assert_eq!(result.max, 19.0);
591    }
592
593    #[test]
594    fn test_floating_point_precision() {
595        let signal = vec![(0, 0.1), (1, 0.2), (2, 0.3)];
596        let rmq = SignalRMQ::new(signal, 64);
597
598        let result = rmq.query_time_range(0, 2).unwrap();
599        assert!((result.min - 0.1).abs() < 1e-10);
600        assert!((result.max - 0.3).abs() < 1e-10);
601    }
602
603    #[test]
604    fn test_special_float_values() {
605        let signal = vec![(0, 0.0), (1, -0.0), (2, 1.0)];
606        let rmq = SignalRMQ::new(signal, 64);
607
608        let result = rmq.query_time_range(0, 2).unwrap();
609        assert_eq!(result.min, 0.0);
610        assert_eq!(result.max, 1.0);
611    }
612
613    #[test]
614    fn test_query_exact_boundaries() {
615        let signal = vec![(10, 1.0), (20, 2.0), (30, 3.0), (40, 4.0)];
616        let rmq = SignalRMQ::new(signal, 2);
617
618        // Query exact sample points
619        let result = rmq.query_time_range(20, 30).unwrap();
620        assert_eq!(result.min, 2.0);
621        assert_eq!(result.max, 3.0);
622    }
623
624    #[test]
625    fn test_index_range_query() {
626        let signal: Vec<_> = (0..100).map(|i| (i as u64, f64::from(i))).collect();
627        let rmq = SignalRMQ::new(signal, 32);
628
629        let result = rmq.query_index_range(10, 20);
630        assert_eq!(result.min, 10.0);
631        assert_eq!(result.max, 20.0);
632
633        let result = rmq.query_index_range(0, 99);
634        assert_eq!(result.min, 0.0);
635        assert_eq!(result.max, 99.0);
636    }
637
638    #[test]
639    fn test_nans_in_signal() {
640        let mut signal: Vec<_> = (0..100).map(|i| (i + i, i as f64)).collect();
641        signal[10].1 = f64::NAN;
642        let rmq = SignalRMQ::new(signal, 32);
643
644        // Range containing NaN
645        let result = rmq.query_time_range(0, 30).unwrap();
646        assert!(result.has_non_finite);
647
648        // Range NOT containing NaN
649        let result = rmq.query_time_range(30, 50).unwrap();
650        assert!(!result.has_non_finite);
651        assert_eq!(result.min, 15.0);
652        assert_eq!(result.max, 25.0);
653    }
654
655    #[test]
656    fn test_all_nans() {
657        let signal: Vec<_> = (0..10).map(|i| (i as u64, f64::NAN)).collect();
658        let rmq = SignalRMQ::new(signal, 4);
659
660        let result = rmq.query_time_range(0, 9).unwrap();
661        assert!(result.has_non_finite);
662        // With identity values, min stays INFINITY and max stays NEG_INFINITY
663        assert_eq!(result.min, f64::INFINITY);
664        assert_eq!(result.max, f64::NEG_INFINITY);
665    }
666
667    #[test]
668    fn test_nan_propagation() {
669        let signal = vec![(0, 1.0), (1, f64::NAN), (2, 3.0)];
670        let rmq = SignalRMQ::new(signal, 64);
671
672        // Query including NaN
673        let result = rmq.query_time_range(0, 2).unwrap();
674        assert!(result.has_non_finite);
675
676        // Non-finite values are excluded from min/max computation.
677        // We get the min/max of finite values only.
678        assert_eq!(result.min, 1.0);
679        assert_eq!(result.max, 3.0);
680
681        // Query excluding NaN
682        let result = rmq.query_time_range(2, 2).unwrap();
683        assert!(!result.has_non_finite);
684        assert_eq!(result.min, 3.0);
685    }
686
687    #[test]
688    fn test_neg_infinity_values() {
689        let signal = vec![(0, 1.0), (1, f64::NEG_INFINITY), (2, 5.0)];
690        let rmq = SignalRMQ::new(signal, 64);
691
692        let result = rmq.query_time_range(0, 2).unwrap();
693        assert!(result.has_non_finite);
694        // NEG_INFINITY is excluded from min/max
695        assert_eq!(result.min, 1.0);
696        assert_eq!(result.max, 5.0);
697    }
698
699    #[test]
700    fn test_mixed_non_finite() {
701        let signal = vec![
702            (0, 1.0),
703            (1, f64::NAN),
704            (2, 3.0),
705            (3, f64::INFINITY),
706            (4, 5.0),
707            (5, f64::NEG_INFINITY),
708            (6, 2.0),
709        ];
710        let rmq = SignalRMQ::new(signal, 64);
711
712        let result = rmq.query_time_range(0, 6).unwrap();
713        assert!(result.has_non_finite);
714        // min/max are the extremes of finite values only
715        assert_eq!(result.min, 1.0);
716        assert_eq!(result.max, 5.0);
717    }
718
719    #[test]
720    fn test_all_infinity() {
721        let signal = vec![
722            (0, f64::INFINITY),
723            (1, f64::NEG_INFINITY),
724            (2, f64::INFINITY),
725        ];
726        let rmq = SignalRMQ::new(signal, 64);
727
728        let result = rmq.query_time_range(0, 2).unwrap();
729        assert!(result.has_non_finite);
730        // No finite values, so min/max remain at identity values
731        assert_eq!(result.min, f64::INFINITY);
732        assert_eq!(result.max, f64::NEG_INFINITY);
733    }
734
735    #[test]
736    fn test_single_block_query() {
737        let signal: Vec<_> = (0..10).map(|i| (i as u64, f64::from(i))).collect();
738        let rmq = SignalRMQ::new(signal, 64);
739
740        // All samples in one block
741        let result = rmq.query_time_range(0, 9).unwrap();
742        assert_eq!(result.min, 0.0);
743        assert_eq!(result.max, 9.0);
744    }
745
746    #[test]
747    fn test_cross_block_boundary() {
748        let signal: Vec<_> = (0..100).map(|i| (i as u64, f64::from(i))).collect();
749        let rmq = SignalRMQ::new(signal, 32);
750
751        // Query that crosses block boundary at 32
752        let result = rmq.query_time_range(30, 35).unwrap();
753        assert_eq!(result.min, 30.0);
754        assert_eq!(result.max, 35.0);
755    }
756
757    #[test]
758    fn test_zigzag_pattern() {
759        let mut signal = Vec::new();
760        for i in 0..100 {
761            let value = if i % 2 == 0 { 0.0 } else { 100.0 };
762            signal.push((i as u64, value));
763        }
764        let rmq = SignalRMQ::new(signal, 16);
765
766        let result = rmq.query_time_range(0, 99).unwrap();
767        assert_eq!(result.min, 0.0);
768        assert_eq!(result.max, 100.0);
769
770        // Any subrange should also contain both extremes
771        let result = rmq.query_time_range(10, 20).unwrap();
772        assert_eq!(result.min, 0.0);
773        assert_eq!(result.max, 100.0);
774    }
775
776    #[test]
777    fn test_query_at_time_exact_match() {
778        let signal = vec![(10, 1.0), (20, 2.0), (30, 3.0)];
779        let rmq = SignalRMQ::new(signal, 64);
780
781        let result = rmq.query_at_time(20);
782        assert_eq!(result.current, Some((20, 2.0)));
783        assert_eq!(result.next, Some(30));
784    }
785
786    #[test]
787    fn test_query_at_time_between_samples() {
788        let signal = vec![(10, 1.0), (20, 2.0), (30, 3.0)];
789        let rmq = SignalRMQ::new(signal, 64);
790
791        // Query at time 15 (between 10 and 20)
792        let result = rmq.query_at_time(15);
793        assert_eq!(result.current, Some((10, 1.0)));
794        assert_eq!(result.next, Some(20));
795
796        // Query at time 25 (between 20 and 30)
797        let result = rmq.query_at_time(25);
798        assert_eq!(result.current, Some((20, 2.0)));
799        assert_eq!(result.next, Some(30));
800    }
801
802    #[test]
803    fn test_query_at_time_before_first_sample() {
804        let signal = vec![(10, 1.0), (20, 2.0), (30, 3.0)];
805        let rmq = SignalRMQ::new(signal, 64);
806
807        let result = rmq.query_at_time(5);
808        assert_eq!(result.current, None);
809        assert_eq!(result.next, Some(10));
810    }
811
812    #[test]
813    fn test_query_at_time_after_last_sample() {
814        let signal = vec![(10, 1.0), (20, 2.0), (30, 3.0)];
815        let rmq = SignalRMQ::new(signal, 64);
816
817        let result = rmq.query_at_time(40);
818        assert_eq!(result.current, Some((30, 3.0)));
819        assert_eq!(result.next, None);
820    }
821
822    #[test]
823    fn test_query_at_time_single_sample() {
824        let signal = vec![(100, 5.0)];
825        let rmq = SignalRMQ::new(signal, 64);
826
827        // Before
828        let result = rmq.query_at_time(50);
829        assert_eq!(result.current, None);
830        assert_eq!(result.next, Some(100));
831
832        // Exact
833        let result = rmq.query_at_time(100);
834        assert_eq!(result.current, Some((100, 5.0)));
835        assert_eq!(result.next, None);
836
837        // After
838        let result = rmq.query_at_time(200);
839        assert_eq!(result.current, Some((100, 5.0)));
840        assert_eq!(result.next, None);
841    }
842
843    #[test]
844    fn test_query_at_time_at_boundaries() {
845        let signal = vec![(0, 1.0), (100, 2.0)];
846        let rmq = SignalRMQ::new(signal, 64);
847
848        // At first sample
849        let result = rmq.query_at_time(0);
850        assert_eq!(result.current, Some((0, 1.0)));
851        assert_eq!(result.next, Some(100));
852
853        // At last sample
854        let result = rmq.query_at_time(100);
855        assert_eq!(result.current, Some((100, 2.0)));
856        assert_eq!(result.next, None);
857    }
858}