oxihuman_core/
bucket_histogram.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
8pub struct BucketHistogram {
9 pub min: f64,
10 pub max: f64,
11 buckets: Vec<u64>,
12 pub underflow: u64,
13 pub overflow: u64,
14 total: u64,
15}
16
17impl BucketHistogram {
18 pub fn new(min: f64, max: f64, num_buckets: usize) -> Self {
19 assert!(num_buckets > 0, "need at least one bucket");
20 assert!(max > min, "max must be greater than min");
21 BucketHistogram {
22 min,
23 max,
24 buckets: vec![0u64; num_buckets],
25 underflow: 0,
26 overflow: 0,
27 total: 0,
28 }
29 }
30
31 pub fn num_buckets(&self) -> usize {
32 self.buckets.len()
33 }
34
35 pub fn bucket_width(&self) -> f64 {
36 (self.max - self.min) / self.buckets.len() as f64
37 }
38
39 pub fn add(&mut self, value: f64) {
40 self.total += 1;
41 if value < self.min {
42 self.underflow += 1;
43 return;
44 }
45 if value >= self.max {
46 self.overflow += 1;
47 return;
48 }
49 let idx = ((value - self.min) / self.bucket_width()) as usize;
50 let idx = idx.min(self.buckets.len() - 1);
51 self.buckets[idx] += 1;
52 }
53
54 pub fn count(&self, bucket: usize) -> u64 {
55 self.buckets.get(bucket).copied().unwrap_or(0)
56 }
57
58 pub fn total(&self) -> u64 {
59 self.total
60 }
61
62 pub fn mode_bucket(&self) -> usize {
63 self.buckets
64 .iter()
65 .enumerate()
66 .max_by_key(|(_, &c)| c)
67 .map(|(i, _)| i)
68 .unwrap_or(0)
69 }
70
71 pub fn bucket_lower(&self, idx: usize) -> f64 {
72 self.min + idx as f64 * self.bucket_width()
73 }
74
75 pub fn bucket_upper(&self, idx: usize) -> f64 {
76 self.bucket_lower(idx) + self.bucket_width()
77 }
78
79 pub fn clear(&mut self) {
80 self.buckets.iter_mut().for_each(|c| *c = 0);
81 self.underflow = 0;
82 self.overflow = 0;
83 self.total = 0;
84 }
85}
86
87pub fn histogram_mean(hist: &BucketHistogram) -> Option<f64> {
88 let in_range = hist.total.saturating_sub(hist.underflow + hist.overflow);
89 if in_range == 0 {
90 return None;
91 }
92 let sum: f64 = (0..hist.num_buckets())
93 .map(|i| {
94 let mid = (hist.bucket_lower(i) + hist.bucket_upper(i)) / 2.0;
95 mid * hist.count(i) as f64
96 })
97 .sum();
98 Some(sum / in_range as f64)
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_basic_add() {
107 let mut h = BucketHistogram::new(0.0, 10.0, 10);
108 h.add(5.0);
109 assert_eq!(h.total(), 1);
110 }
111
112 #[test]
113 fn test_underflow() {
114 let mut h = BucketHistogram::new(0.0, 10.0, 10);
115 h.add(-1.0);
116 assert_eq!(h.underflow, 1);
117 }
118
119 #[test]
120 fn test_overflow() {
121 let mut h = BucketHistogram::new(0.0, 10.0, 10);
122 h.add(10.0);
123 assert_eq!(h.overflow, 1);
124 }
125
126 #[test]
127 fn test_correct_bucket() {
128 let mut h = BucketHistogram::new(0.0, 10.0, 10);
129 h.add(2.5);
130 assert_eq!(h.count(2), 1 ,);
131 }
132
133 #[test]
134 fn test_mode_bucket() {
135 let mut h = BucketHistogram::new(0.0, 10.0, 10);
136 h.add(5.0);
137 h.add(5.1);
138 h.add(5.2);
139 h.add(1.0);
140 assert_eq!(h.mode_bucket(), 5 ,);
141 }
142
143 #[test]
144 fn test_clear() {
145 let mut h = BucketHistogram::new(0.0, 10.0, 5);
146 h.add(1.0);
147 h.add(2.0);
148 h.clear();
149 assert_eq!(h.total(), 0);
150 assert_eq!(h.count(0), 0);
151 }
152
153 #[test]
154 fn test_bucket_width() {
155 let h = BucketHistogram::new(0.0, 10.0, 5);
156 assert!((h.bucket_width() - 2.0).abs() < 1e-10 ,);
157 }
158
159 #[test]
160 fn test_histogram_mean() {
161 let mut h = BucketHistogram::new(0.0, 10.0, 10);
162 for _ in 0..10 {
163 h.add(5.0);
164 }
165 let m = histogram_mean(&h).expect("should succeed");
166 assert!((m - 5.5).abs() < 1.0 ,);
167 }
168
169 #[test]
170 fn test_bucket_bounds() {
171 let h = BucketHistogram::new(0.0, 10.0, 10);
172 assert!((h.bucket_lower(0) - 0.0).abs() < 1e-10);
173 assert!((h.bucket_upper(0) - 1.0).abs() < 1e-10);
174 }
175}