qualia_core_db/solvers/statistics/
histogram.rs1use super::descriptive::{max, min};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct HistRange {
11 pub min: f64,
12 pub max: f64,
13 pub bin_width: f64,
14}
15
16pub fn histogram_into(values: &[f64], counts: &mut [u32]) -> Option<HistRange> {
21 let bins = counts.len();
22 if values.is_empty() || bins == 0 {
23 return None;
24 }
25 let min_v = min(values)?;
26 let max_v = max(values)?;
27 let bin_width = (max_v - min_v) / bins as f64;
28
29 for c in counts.iter_mut() {
30 *c = 0;
31 }
32
33 for &v in values {
34 if v < min_v || v > max_v {
35 continue;
36 }
37 let bin_index = if !bin_width.is_finite() || bin_width <= 0.0 {
38 0
40 } else {
41 let idx = ((v - min_v) / bin_width) as usize;
42 if idx >= bins {
43 bins - 1
44 } else {
45 idx
46 }
47 };
48 counts[bin_index] += 1;
49 }
50
51 Some(HistRange {
52 min: min_v,
53 max: max_v,
54 bin_width,
55 })
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 const EPS: f64 = 1e-12;
62
63 #[test]
64 fn guards_empty() {
65 assert_eq!(histogram_into(&[], &mut [0u32; 4]), None);
66 assert_eq!(histogram_into(&[1.0, 2.0], &mut []), None);
67 }
68
69 #[test]
70 fn bins_uniform_data_and_conserves_count() {
71 let v = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0];
72 let mut counts = [0u32; 5];
73 let r = histogram_into(&v, &mut counts).unwrap();
74 assert!((r.min - 0.0).abs() < EPS);
75 assert!((r.max - 10.0).abs() < EPS);
76 assert!((r.bin_width - 2.0).abs() < EPS);
77 assert_eq!(counts.iter().sum::<u32>(), v.len() as u32); }
79
80 #[test]
81 fn degenerate_all_equal_goes_to_bin_zero() {
82 let v = [5.0, 5.0, 5.0];
83 let mut counts = [0u32; 3];
84 let r = histogram_into(&v, &mut counts).unwrap();
85 assert_eq!(r.bin_width, 0.0);
86 assert_eq!(counts[0], 3);
87 }
88
89 #[test]
90 fn buffer_is_zeroed_first() {
91 let v = [1.0, 2.0];
92 let mut counts = [99u32; 2]; histogram_into(&v, &mut counts).unwrap();
94 assert_eq!(counts.iter().sum::<u32>(), 2);
95 }
96}