Skip to main content

oxihuman_core/
time_series_buffer.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Fixed-window time series ring buffer.
6
7#[derive(Debug, Clone)]
8pub struct TimeSeriesSample {
9    pub timestamp: f64,
10    pub value: f64,
11}
12
13#[derive(Debug, Clone)]
14pub struct TimeSeriesBuffer {
15    buf: Vec<TimeSeriesSample>,
16    capacity: usize,
17    head: usize,
18    len: usize,
19}
20
21impl TimeSeriesBuffer {
22    pub fn new(capacity: usize) -> Self {
23        TimeSeriesBuffer {
24            buf: Vec::with_capacity(capacity),
25            capacity,
26            head: 0,
27            len: 0,
28        }
29    }
30
31    pub fn push(&mut self, timestamp: f64, value: f64) {
32        let sample = TimeSeriesSample { timestamp, value };
33        if self.len < self.capacity {
34            self.buf.push(sample);
35            self.len += 1;
36        } else {
37            self.buf[self.head] = sample;
38            self.head = (self.head + 1) % self.capacity;
39        }
40    }
41
42    pub fn len(&self) -> usize {
43        self.len
44    }
45
46    pub fn is_empty(&self) -> bool {
47        self.len == 0
48    }
49
50    pub fn iter(&self) -> impl Iterator<Item = &TimeSeriesSample> {
51        let (a, b) = if self.len < self.capacity {
52            (&self.buf[..self.len], &self.buf[0..0])
53        } else {
54            let (lo, hi) = self.buf.split_at(self.head);
55            (hi, lo)
56        };
57        b.iter().chain(a.iter())
58    }
59
60    pub fn mean(&self) -> Option<f64> {
61        if self.is_empty() {
62            return None;
63        }
64        let sum: f64 = self.iter().map(|s| s.value).sum();
65        Some(sum / self.len as f64)
66    }
67
68    pub fn latest(&self) -> Option<&TimeSeriesSample> {
69        if self.is_empty() {
70            return None;
71        }
72        if self.len < self.capacity {
73            self.buf.last()
74        } else {
75            let prev = self.head.saturating_sub(1);
76            let idx = if self.head == 0 {
77                self.capacity - 1
78            } else {
79                prev
80            };
81            self.buf.get(idx)
82        }
83    }
84
85    pub fn capacity(&self) -> usize {
86        self.capacity
87    }
88}
89
90pub fn buffer_variance(buf: &TimeSeriesBuffer) -> Option<f64> {
91    let mean = buf.mean()?;
92    let var = buf.iter().map(|s| (s.value - mean).powi(2)).sum::<f64>() / buf.len() as f64;
93    Some(var)
94}
95
96pub fn buffer_min_max(buf: &TimeSeriesBuffer) -> Option<(f64, f64)> {
97    let mut it = buf.iter();
98    let first = it.next()?;
99    let (min, max) = it.fold((first.value, first.value), |(mn, mx), s| {
100        (mn.min(s.value), mx.max(s.value))
101    });
102    Some((min, max))
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn test_push_and_len() {
111        let mut buf = TimeSeriesBuffer::new(5);
112        buf.push(1.0, 10.0);
113        buf.push(2.0, 20.0);
114        assert_eq!(buf.len(), 2);
115    }
116
117    #[test]
118    fn test_ring_overwrite() {
119        let mut buf = TimeSeriesBuffer::new(3);
120        for i in 0..5 {
121            buf.push(i as f64, i as f64 * 10.0);
122        }
123        assert_eq!(buf.len(), 3 /* capacity capped at 3 */,);
124    }
125
126    #[test]
127    fn test_mean_simple() {
128        let mut buf = TimeSeriesBuffer::new(4);
129        buf.push(1.0, 2.0);
130        buf.push(2.0, 4.0);
131        buf.push(3.0, 6.0);
132        let m = buf.mean().expect("should succeed");
133        assert!((m - 4.0).abs() < 1e-10 /* mean of 2,4,6 = 4 */,);
134    }
135
136    #[test]
137    fn test_empty_mean_none() {
138        let buf = TimeSeriesBuffer::new(5);
139        assert!(buf.mean().is_none() /* empty buffer has no mean */,);
140    }
141
142    #[test]
143    fn test_variance() {
144        let mut buf = TimeSeriesBuffer::new(4);
145        buf.push(0.0, 2.0);
146        buf.push(1.0, 4.0);
147        buf.push(2.0, 6.0);
148        let var = buffer_variance(&buf).expect("should succeed");
149        assert!(var > 0.0 /* non-zero variance */,);
150    }
151
152    #[test]
153    fn test_min_max() {
154        let mut buf = TimeSeriesBuffer::new(10);
155        buf.push(0.0, 5.0);
156        buf.push(1.0, 1.0);
157        buf.push(2.0, 9.0);
158        let (mn, mx) = buffer_min_max(&buf).expect("should succeed");
159        assert_eq!(mn, 1.0);
160        assert_eq!(mx, 9.0);
161    }
162
163    #[test]
164    fn test_is_empty() {
165        let buf = TimeSeriesBuffer::new(5);
166        assert!(buf.is_empty() /* newly created buffer is empty */,);
167    }
168
169    #[test]
170    fn test_capacity() {
171        let buf = TimeSeriesBuffer::new(8);
172        assert_eq!(buf.capacity(), 8);
173    }
174
175    #[test]
176    fn test_latest_after_overwrite() {
177        let mut buf = TimeSeriesBuffer::new(3);
178        buf.push(1.0, 10.0);
179        buf.push(2.0, 20.0);
180        buf.push(3.0, 30.0);
181        buf.push(4.0, 40.0);
182        let latest = buf.latest().expect("should succeed");
183        assert!((latest.value - 40.0).abs() < 1e-10, /* latest should be 40 */);
184    }
185}