[go: up one dir, main page]

average/
histogram.rs

1/// Invalid ranges were specified for constructing the histogram.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum InvalidRangeError {
4    /// The number of ranges is less than the number of bins + 1.
5    NotEnoughRanges,
6    /// The ranges are not sorted or `(low, high)` where `low` > `high` is
7    /// encountered.
8    NotSorted,
9    /// A range contains `nan`.
10    NaN,
11}
12
13/// A sample is out of range of the histogram.
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub struct SampleOutOfRangeError;
16
17#[doc(hidden)]
18#[macro_export]
19macro_rules! define_histogram_common {
20    ($LEN:expr) => {
21        use $crate::Histogram as Trait;
22
23        /// The number of bins of the histogram.
24        const LEN: usize = $LEN;
25
26        impl ::core::fmt::Debug for Histogram {
27            fn fmt(&self, formatter: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
28                formatter.write_str("Histogram {{ range: ")?;
29                self.range[..].fmt(formatter)?;
30                formatter.write_str(", bins: ")?;
31                self.bin[..].fmt(formatter)?;
32                formatter.write_str(" }}")
33            }
34        }
35
36        impl Histogram {
37            /// Construct a histogram with constant bin width.
38            #[inline]
39            pub fn with_const_width(start: f64, end: f64) -> Self {
40                let step = (end - start) / (LEN as f64);
41                let mut range = [0.; LEN + 1];
42                for (i, r) in range.iter_mut().enumerate() {
43                    *r = start + step * (i as f64);
44                }
45
46                Self {
47                    range,
48                    bin: [0; LEN],
49                }
50            }
51
52            /// Construct a histogram from given ranges.
53            ///
54            /// The ranges are given by an iterator of floats where neighboring
55            /// pairs `(a, b)` define a bin for all `x` where `a <= x < b`.
56            ///
57            /// Fails if the iterator is too short (less than `n + 1` where `n`
58            /// is the number of bins), is not sorted or contains `nan`. `inf`
59            /// and empty ranges are allowed.
60            #[inline]
61            pub fn from_ranges<T>(ranges: T) -> Result<Self, $crate::InvalidRangeError>
62            where
63                T: IntoIterator<Item = f64>,
64            {
65                let mut range = [0.; LEN + 1];
66                let mut last_i = 0;
67                for (i, r) in ranges.into_iter().enumerate() {
68                    if i > LEN {
69                        break;
70                    }
71                    if r.is_nan() {
72                        return Err($crate::InvalidRangeError::NaN);
73                    }
74                    if i > 0 && range[i - 1] > r {
75                        return Err($crate::InvalidRangeError::NotSorted);
76                    }
77                    range[i] = r;
78                    last_i = i;
79                }
80                if last_i != LEN {
81                    return Err($crate::InvalidRangeError::NotEnoughRanges);
82                }
83                Ok(Self {
84                    range,
85                    bin: [0; LEN],
86                })
87            }
88
89            /// Find the index of the bin corresponding to the given sample.
90            ///
91            /// Fails if the sample is out of range of the histogram.
92            #[inline]
93            pub fn find(&self, x: f64) -> Result<usize, $crate::SampleOutOfRangeError> {
94                // We made sure our ranges are valid at construction, so we can
95                // safely unwrap.
96                match self.range.binary_search_by(|p| p.partial_cmp(&x).unwrap()) {
97                    Ok(i) if i < LEN => Ok(i),
98                    Err(i) if i > 0 && i < LEN + 1 => Ok(i - 1),
99                    _ => Err($crate::SampleOutOfRangeError),
100                }
101            }
102
103            /// Add a sample to the histogram.
104            ///
105            /// Fails if the sample is out of range of the histogram.
106            #[inline]
107            pub fn add(&mut self, x: f64) -> Result<(), $crate::SampleOutOfRangeError> {
108                if let Ok(i) = self.find(x) {
109                    self.bin[i] += 1;
110                    Ok(())
111                } else {
112                    Err($crate::SampleOutOfRangeError)
113                }
114            }
115
116            /// Return the ranges of the histogram.
117            #[inline]
118            pub fn ranges(&self) -> &[f64] {
119                &self.range[..]
120            }
121
122            /// Return an iterator over the bins and corresponding ranges:
123            /// `((lower, upper), count)`
124            #[inline]
125            pub fn iter(&self) -> IterHistogram<'_> {
126                self.into_iter()
127            }
128
129            /// Reset all bins to zero.
130            #[inline]
131            pub fn reset(&mut self) {
132                self.bin = [0; LEN];
133            }
134
135            /// Return the lower range limit.
136            ///
137            /// (The corresponding bin might be empty.)
138            #[inline]
139            pub fn range_min(&self) -> f64 {
140                self.range[0]
141            }
142
143            /// Return the upper range limit.
144            ///
145            /// (The corresponding bin might be empty.)
146            #[inline]
147            pub fn range_max(&self) -> f64 {
148                self.range[LEN]
149            }
150        }
151
152        /// Iterate over all `(range, count)` pairs in the histogram.
153        #[derive(Debug, Clone)]
154        pub struct IterHistogram<'a> {
155            remaining_bin: &'a [u64],
156            remaining_range: &'a [f64],
157        }
158
159        impl<'a> ::core::iter::Iterator for IterHistogram<'a> {
160            type Item = ((f64, f64), u64);
161            fn next(&mut self) -> Option<((f64, f64), u64)> {
162                if let Some((&bin, rest)) = self.remaining_bin.split_first() {
163                    let left = self.remaining_range[0];
164                    let right = self.remaining_range[1];
165                    self.remaining_bin = rest;
166                    self.remaining_range = &self.remaining_range[1..];
167                    return Some(((left, right), bin));
168                }
169                None
170            }
171        }
172
173        impl<'a> ::core::iter::IntoIterator for &'a Histogram {
174            type Item = ((f64, f64), u64);
175            type IntoIter = IterHistogram<'a>;
176            fn into_iter(self) -> IterHistogram<'a> {
177                IterHistogram {
178                    remaining_bin: self.bins(),
179                    remaining_range: self.ranges(),
180                }
181            }
182        }
183
184        impl $crate::Histogram for Histogram {
185            #[inline]
186            fn bins(&self) -> &[u64] {
187                &self.bin[..]
188            }
189        }
190
191        impl<'a> ::core::ops::AddAssign<&'a Self> for Histogram {
192            #[inline]
193            fn add_assign(&mut self, other: &Self) {
194                for (a, b) in self.range.iter().zip(other.range.iter()) {
195                    assert_eq!(a, b, "Both histograms must have the same ranges");
196                }
197                for (x, y) in self.bin.iter_mut().zip(other.bin.iter()) {
198                    *x += y;
199                }
200            }
201        }
202
203        impl ::core::ops::MulAssign<u64> for Histogram {
204            #[inline]
205            fn mul_assign(&mut self, other: u64) {
206                for x in &mut self.bin[..] {
207                    *x *= other;
208                }
209            }
210        }
211
212        impl $crate::Merge for Histogram {
213            fn merge(&mut self, other: &Self) {
214                assert_eq!(self.bin.len(), other.bin.len());
215                for (a, b) in self.range.iter().zip(other.range.iter()) {
216                    assert_eq!(a, b, "Both histograms must have the same ranges");
217                }
218                for (a, b) in self.bin.iter_mut().zip(other.bin.iter()) {
219                    *a += *b;
220                }
221            }
222        }
223    };
224}
225
226#[cfg(feature = "serde")]
227#[doc(hidden)]
228#[macro_export]
229macro_rules! define_histogram_inner {
230    ($name:ident, $LEN:expr) => {
231        mod $name {
232            $crate::define_histogram_common!($LEN);
233
234            use ::serde::{Deserialize, Serialize};
235            use serde_big_array::BigArray;
236
237            /// A histogram with a number of bins known at compile time.
238            #[derive(Clone, Serialize, Deserialize)]
239            pub struct Histogram {
240                /// The ranges defining the bins of the histogram.
241                #[serde(with = "BigArray")]
242                range: [f64; LEN + 1],
243                /// The bins of the histogram.
244                #[serde(with = "BigArray")]
245                bin: [u64; LEN],
246            }
247        }
248    };
249}
250
251#[cfg(not(feature = "serde"))]
252#[doc(hidden)]
253#[macro_export]
254macro_rules! define_histogram_inner {
255    ($name:ident, $LEN:expr) => {
256        mod $name {
257            $crate::define_histogram_common!($LEN);
258
259            /// A histogram with a number of bins known at compile time.
260            #[derive(Clone)]
261            pub struct Histogram {
262                /// The ranges defining the bins of the histogram.
263                range: [f64; LEN + 1],
264                /// The bins of the histogram.
265                bin: [u64; LEN],
266            }
267        }
268    };
269}
270
271/// Define a histogram with a number of bins known at compile time.
272///
273/// Because macros are not hygienic for items, everything is defined in a private
274/// module with the given name. This includes the `Histogram` struct, the number
275/// of bins `LEN` and the histogram iterator `HistogramIter`.
276///
277/// Note that you need to make sure that `core` is accessible to the macro.
278///
279///
280/// # Example
281///
282/// ```
283/// use average::{Histogram, define_histogram};
284///
285/// define_histogram!(hist, 10);
286/// let mut h = hist::Histogram::with_const_width(0., 100.);
287/// for i in 0..100 {
288///     h.add(i as f64).unwrap();
289/// }
290/// assert_eq!(h.bins(), &[10, 10, 10, 10, 10, 10, 10, 10, 10, 10]);
291/// ```
292#[macro_export]
293macro_rules! define_histogram {
294    ($name:ident, $LEN:expr) => {
295        $crate::define_histogram_inner!($name, $LEN);
296    };
297}