sampling/histogram/
histogram_fast.rs

1use{
2    crate::histogram::*,
3    num_traits::{
4        Bounded,
5        int::*,
6        cast::*,
7        identities::*,
8        ops::{
9            checked::*, 
10            wrapping::*
11        }
12    },
13    std::{
14        borrow::*,
15        ops::*,
16        num::*
17    }
18};
19use super::binning::BinDisplay;
20
21#[cfg(feature = "serde_support")]
22use serde::{Serialize, Deserialize};
23
24/// You have done something that can succeed or fail
25#[derive(Debug, PartialEq, Eq, Clone, Copy)]
26pub enum Outcome{
27    /// It worked :)
28    Success,
29    /// It did not work…
30    Failure
31}
32
33impl Outcome{
34    /// Check if Outcome is success variant
35    pub fn is_success(self) -> bool
36    {
37        self == Outcome::Success
38    }
39
40    /// Check if outcome is failure variant
41    pub fn is_failure(self) -> bool
42    {
43        self == Outcome::Failure
44    }
45}
46
47/// # Faster version of HistogramInt for Integers
48/// provided the bins should be: (left, left +1, ..., right - 1)
49/// then you should use this version!
50#[derive(Debug, Clone)]
51#[cfg_attr(feature = "serde_support", derive(Serialize, Deserialize))]
52pub struct HistogramFast<T> {
53    left: T, 
54    right: T,
55    hist: Vec<usize>,
56}
57
58impl<T> BinDisplay for HistogramFast<T>
59where 
60T: PrimInt + HasUnsignedVersion + Copy + std::fmt::Display + WrappingAdd,
61T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes> 
62    + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned>
63{
64    type BinEntry = T;
65
66    fn display_bin_iter(&'_ self) -> Box<dyn Iterator<Item=Self::BinEntry> + '_>{
67        Box::new(
68            self.bin_iter()
69        )
70    }
71
72    fn write_bin<W: std::io::Write>(entry: &Self::BinEntry, mut writer: W) -> std::io::Result<()> {
73        write!(writer, "{entry}")
74    }
75
76    fn write_header<W: std::io::Write>(&self, mut writer: W) -> std::io::Result<()> {
77        write!(writer, "Bin")
78    }
79}
80
81impl<T> HistogramFast<T>
82where T: Copy
83{
84    /// Get left border, inclusive
85    pub fn left(&self) -> T
86    {
87        self.left
88    }
89
90    /// Get right border, inclusive
91    pub fn right(&self) -> T
92    {
93        self.right
94    }
95
96    /// # Returns the range covered by the bins as a `RangeInclusive<T>`
97    pub fn range_inclusive(&self) -> RangeInclusive<T>
98    {
99        self.left..=self.right
100    }
101}
102
103impl<T> HistogramFast<T> 
104    where 
105    T: PrimInt + HasUnsignedVersion + WrappingAdd,
106    T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes> 
107        + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned>
108{
109    /// # Create a new interval
110    /// * same as `Self::new_inclusive(left, right - 1)` though with checks
111    /// * That makes `left` an inclusive and `right` an exclusive border
112    pub fn new(left: T, right: T) -> Result<Self, HistErrors>
113    {
114        let right = match right.checked_sub(&T::one()){
115            Some(res) => res,
116            None => return Err(HistErrors::Underflow),
117        };
118        Self::new_inclusive(left, right)
119    }
120
121    /// # Create new histogram with inclusive borders
122    /// * `Err` if `left > right`
123    /// * `left` is inclusive border
124    /// * `right` is inclusive border
125    pub fn new_inclusive(left: T, right: T) -> Result<Self, HistErrors>
126    {
127        if left > right {
128            Err(HistErrors::OutsideHist)
129        } else {
130            let left_u = to_u(left);
131            let right_u = to_u(right);
132            let size = match (right_u - left_u).to_usize(){
133                Some(val) => match val.checked_add(1){
134                    Some(val) => val,
135                    None => return Err(HistErrors::Overflow),
136                },
137                None => return Err(HistErrors::UsizeCastError),
138            };
139
140            Ok(
141                Self{
142                    left,
143                    right,
144                    hist: vec![0; size],
145                }
146            )
147        }
148    }
149
150    /// # Iterator over all the bins
151    /// In HistogramFast is hit only when a value matches the corresponding bin exactly,
152    /// which means there exists a map between bins and corresponding values that would hit them.
153    /// So a bin is perfectly defined by one value. That is what we are iterating over here
154    /// 
155    /// This iterates over these values
156    /// # Example
157    /// ```
158    /// use sampling::histogram::HistogramFast;
159    /// 
160    /// let hist = HistogramFast::<u8>::new_inclusive(2, 5).unwrap();
161    /// let vec: Vec<u8> =  hist.bin_iter().collect();
162    /// assert_eq!(&vec, &[2, 3, 4, 5]);
163    /// ```
164    pub fn bin_iter(&self) -> impl Iterator<Item=T>
165    {
166        HistFastIterHelper{
167            current: self.left,
168            right: self.right,
169            invalid: false
170        }
171
172    }
173
174    /// # Iterator over all the bins
175    /// In HistogramFast is hit only when a value matches the corresponding bin exactly,
176    /// which means there exists a map between bins and corresponding values that would hit them.
177    /// 
178    /// This iterates over these values as well as the corresponding hit count (i.e., how often the corresponding bin was hit)
179    /// # Item of Iterator
180    /// `(value_corresponding_to_bin, number_of_hits)`
181    /// # Example
182    /// ```
183    /// use sampling::histogram::HistogramFast;
184    /// 
185    /// let mut hist = HistogramFast::<u8>::new_inclusive(2, 5).unwrap();
186    /// hist.increment(4).unwrap();
187    /// hist.increment(5).unwrap();
188    /// hist.increment(5).unwrap();
189    /// let vec: Vec<(u8, usize)> =  hist.bin_hits_iter().collect();
190    /// assert_eq!(&vec, &[(2, 0), (3, 0), (4, 1), (5, 2)]);
191    /// ```
192    pub fn bin_hits_iter(&'_ self) -> impl Iterator<Item=(T, usize)> + '_
193    {
194        self.bin_iter()
195            .zip(
196                self.hist
197                    .iter()
198                    .copied()
199            )
200    }
201
202    /// checks if the range of two Histograms is equal, i.e.,
203    /// if they have the same bin borders
204    pub fn equal_range(&self, other: &Self) -> bool
205    where T: Eq
206    {
207        self.left.eq(&other.left) && self.right.eq(&other.right)
208    }
209
210    /// # Add other histogram to self
211    /// * will fail if the ranges are not equal, i.e., if [equal_range](Self::equal_range)
212    ///   returns false
213    /// * Otherwise the hit count of the bins of self will be increased 
214    ///   by the corresponding hit count of other. 
215    /// * other will be unchanged
216    pub fn try_add(&mut self, other: &Self) -> Outcome
217    where T: Eq
218    {
219        if self.equal_range(other) {
220            self.hist
221                .iter_mut()
222                .zip(other.hist().iter())
223                .for_each(|(s, o)| *s += o);
224            Outcome::Success
225        } else {
226            Outcome::Failure
227        }
228    }
229
230    #[inline]
231    /// # Increment hit count 
232    /// If `val` is inside the histogram, the corresponding bin count will be increased
233    /// by 1 and the index corresponding to the bin in returned: `Ok(index)`.
234    /// Otherwise an Error is returned
235    /// ## Note
236    /// This is the same as [HistogramVal::count_val]
237    pub fn increment<V: Borrow<T>>(&mut self, val: V) -> Result<usize, HistErrors> {
238        self.count_val(val)
239    }
240
241    #[inline]
242    /// # Increment hit count
243    /// Increments the hit count of the bin corresponding to `val`.
244    /// If no bin corresponding to `val` exists, nothing happens
245    pub fn increment_quiet<V: Borrow<T>>(&mut self, val: V)
246    {
247        let _ = self.increment(val);
248    }
249}
250
251pub(crate) struct HistFastIterHelper<T>
252{
253    pub(crate) current: T,
254    pub(crate) right: T,
255    pub(crate) invalid: bool,
256}
257
258impl<T> Iterator for HistFastIterHelper<T>
259where 
260    T: PrimInt + WrappingAdd,
261{
262    type Item = T;
263
264    #[inline]
265    fn next(&mut self) -> Option<T>
266    {
267        if self.invalid {
268            return None;
269        }
270
271        let next = self.current.wrapping_add(&T::one());
272        let current = std::mem::replace(&mut self.current, next);
273        self.invalid = current == self.right;
274        Some(
275            current
276        )
277
278    }
279}
280
281pub(crate) struct BinModIterHelper<T>
282{
283    pub(crate) current: T,
284    pub(crate) right: T,
285    pub(crate) step_by: T,
286    pub(crate) invalid: bool,
287}
288
289impl<T> BinModIterHelper<T>
290{
291    pub(crate) fn new_unchecked(left: T, right: T, step_by: T) -> Self
292    {
293        Self{
294            current: left,
295            right,
296            step_by,
297            invalid: false
298        }
299    }
300}
301
302impl<T> Iterator for BinModIterHelper<T>
303where 
304    T: Add::<T, Output=T> 
305        + Ord + Copy + WrappingAdd
306        + WrappingSub
307        + One,
308{
309    type Item = RangeInclusive<T>;
310
311    #[inline]
312    fn next(&mut self) -> Option<RangeInclusive<T>>
313    {
314        if self.invalid {
315            return None;
316        }
317
318        let next = self.current.wrapping_add(&self.step_by);
319        let right = next.wrapping_sub(&T::one());
320        self.invalid = right == self.right;
321        let left = std::mem::replace(&mut self.current, next);
322        Some(
323            left..=right
324        )
325
326    }
327}
328
329impl<T> HistogramPartition for HistogramFast<T> 
330where T: PrimInt + CheckedSub + ToPrimitive + CheckedAdd + One + FromPrimitive
331    + HasUnsignedVersion + Bounded + WrappingAdd,
332T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes, Unsigned=T::Unsigned> 
333    + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned> + FromPrimitive + WrappingSub,
334{
335    fn overlapping_partition(&self, n: NonZeroUsize, overlap: usize) -> Result<Vec<Self>, HistErrors>
336    {
337        let mut result = Vec::with_capacity(n.get());
338        let size = self.bin_count() - 1;
339        let denominator = n.get() + overlap;
340        for c in 0..n.get() {
341            let left_distance = c.checked_mul(size)
342                .ok_or(HistErrors::Overflow)?
343                / denominator;
344                    
345            let left = to_u(self.left) + T::Unsigned::from_usize(left_distance)
346                .ok_or(HistErrors::CastError)?;
347            
348            let right_distance = (c + overlap + 1).checked_mul(size)
349                .ok_or(HistErrors::Overflow)?
350                / denominator;
351            
352            let right = to_u(self.left) + T::Unsigned::from_usize(right_distance)
353                .ok_or(HistErrors::CastError)?;
354            
355            let left = from_u(left);
356            let right = from_u(right);
357
358            result.push(Self::new_inclusive(left, right)?);
359            if result.last()
360                .unwrap()
361                .hist
362                .is_empty()
363            {
364                return Err(HistErrors::IntervalWidthZero);
365            }
366
367        }
368        Ok(result)
369    }
370}
371
372/// Histogram for binning `usize`- alias for `HistogramFast<usize>`
373pub type HistUsizeFast = HistogramFast<usize>;
374/// Histogram for binning `u128` - alias for `HistogramFast<u128>`
375pub type HistU128Fast = HistogramFast<u128>;
376/// Histogram for binning `u64` - alias for `HistogramFast<u64>`
377pub type HistU64Fast = HistogramFast<u64>;
378/// Histogram for binning `u32` - alias for `HistogramFast<u32>`
379pub type HistU32Fast = HistogramFast<u32>;
380/// Histogram for binning `u16` - alias for `HistogramFast<u16>`
381pub type HistU16Fast = HistogramFast<u16>;
382/// Histogram for binning `u8` - alias for `HistogramFast<u8>`
383pub type HistU8Fast = HistogramFast<u8>;
384
385/// Histogram for binning `isize` - alias for `HistogramFast<isize>`
386pub type HistIsizeFast = HistogramFast<isize>;
387/// Histogram for binning `i128` - alias for `HistogramFast<i128>`
388pub type HistI128Fast = HistogramFast<i128>;
389/// Histogram for binning `i64` - alias for `HistogramFast<i64>`
390pub type HistI64Fast = HistogramFast<i64>;
391/// Histogram for binning `i32` - alias for `HistogramFast<i32>`
392pub type HistI32Fast = HistogramFast<i32>;
393/// Histogram for binning `i16` - alias for `HistogramFast<i16>`
394pub type HistI16Fast = HistogramFast<i16>;
395/// Histogram for binning `i8` - alias for `HistogramFastiu8>`
396pub type HistI8Fast = HistogramFast<i8>;
397
398
399impl<T> Histogram for HistogramFast<T> 
400{
401
402    #[inline]
403    fn increment_index_by(&mut self, index: usize, count: usize) -> Result<(), HistErrors> {
404        match self.hist.get_mut(index) {
405            None => Err(HistErrors::OutsideHist),
406            Some(val) => {
407                *val += count;
408                Ok(())
409            },
410        }
411    }
412
413    #[inline]
414    fn hist(&self) -> &Vec<usize> {
415        &self.hist
416    }
417
418    #[inline]
419    fn bin_count(&self) -> usize {
420        self.hist.len()
421    }
422
423    #[inline]
424    fn reset(&mut self) {
425        // compiles to memset =)
426        self.hist
427            .iter_mut()
428            .for_each(|h| *h = 0);
429    }
430} 
431
432impl<T> HistogramVal<T> for HistogramFast<T>
433where T: PrimInt + HasUnsignedVersion + WrappingAdd,
434    T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes> 
435        + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned>
436{
437    #[inline]
438    fn first_border(&self) -> T {
439        self.left
440    }
441
442    fn last_border(&self) -> T {
443        self.right
444    }
445
446    #[inline(always)]
447    fn last_border_is_inclusive(&self) -> bool {
448        true
449    }
450
451    fn distance<V: Borrow<T>>(&self, val: V) -> f64 {
452        let val = val.borrow();
453        if self.not_inside(val) {
454            let dist = if *val < self.first_border() {
455                self.first_border() - *val
456            } else {
457                val.saturating_sub(self.right)
458            };
459            dist.to_f64()
460                .unwrap_or(f64::INFINITY)
461        } else {
462            0.0
463        }
464    }
465
466    #[inline(always)]
467    fn get_bin_index<V: Borrow<T>>(&self, val: V) -> Result<usize, HistErrors> {
468        let val = *val.borrow();
469        if val <= self.right{
470            match val.checked_sub(&self.left) {
471                None => {
472                    let left = self.left.to_isize()
473                        .ok_or(HistErrors::CastError)?;
474                    let val = val.to_isize()
475                        .ok_or(HistErrors::CastError)?;
476                    match val.checked_sub(left){
477                        None => Err(HistErrors::OutsideHist),
478                        Some(index) => {
479                            index.to_usize()
480                                .ok_or(HistErrors::OutsideHist)
481                        }
482                    }
483                },
484                Some(index) => index.to_usize()
485                    .ok_or(HistErrors::OutsideHist)
486            }
487        } else {
488            Err(HistErrors::OutsideHist)
489        }
490    }
491
492    /// # Iterator over the bins
493    /// * This iterator will always return SingleValued bins
494    /// * Consider using `self.bin_iter()` instead, its more efficient
495    fn bin_enum_iter(&self) -> Box<dyn Iterator<Item=Bin<T>> + '_>{
496        
497        let iter = self
498            .bin_iter()
499            .map(|bin| Bin::SingleValued(bin));
500        Box::new(iter)
501    }
502
503    #[inline]
504    fn is_inside<V: Borrow<T>>(&self, val: V) -> bool {
505        let val = *val.borrow();
506        val >= self.left && val <= self.right
507    }
508
509    #[inline]
510    fn not_inside<V: Borrow<T>>(&self, val: V) -> bool {
511        let val = *val.borrow();
512        val > self.right || val < self.left
513    }
514
515    #[inline]
516    fn count_val<V: Borrow<T>>(&mut self, val: V) -> Result<usize, HistErrors> {
517        let index = self.get_bin_index(val)?;
518        self.hist[index] += 1;
519        Ok(index)
520    }
521}
522
523impl<T> HistogramIntervalDistance<T> for HistogramFast<T> 
524where Self: HistogramVal<T>,
525    T: PartialOrd + std::ops::Sub<Output=T> + NumCast + Copy
526{
527    fn interval_distance_overlap<V: Borrow<T>>(&self, val: V, overlap: NonZeroUsize) -> usize {
528        let val = val.borrow();
529        if self.not_inside(val) {
530            let num_bins_overlap = 1usize.max(self.bin_count() / overlap.get());
531            let dist = 
532            if *val < self.left { 
533                self.left - *val
534            } else {
535                *val - self.right
536            };
537            1 + dist.to_usize().unwrap() / num_bins_overlap
538        } else {
539            0
540        }
541    }
542}
543
544impl<T> HistogramCombine for HistogramFast<T>
545    where   Self: HistogramVal<T>,
546    T: PrimInt + HasUnsignedVersion + WrappingAdd,
547    T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes> 
548    + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned>
549{
550    fn encapsulating_hist<S>(hists: &[S]) -> Result<Self, HistErrors>
551    where S: Borrow<Self> {
552        if hists.is_empty() {
553            Err(HistErrors::EmptySlice)
554        } else if hists.len() == 1 {
555            let h = hists[0].borrow();
556            Ok(Self{
557                left: h.left,
558                right: h.right,
559                hist: vec![0; h.hist.len()]
560            })
561        } else {
562            let mut min = hists[0].borrow().left;
563            let mut max = hists[0].borrow().right;
564            hists[1..].iter()
565                .for_each(
566                    |h|
567                    {
568                        let h = h.borrow();
569                        if h.left < min {
570                            min = h.left;
571                        }
572                        if h.right > max {
573                            max = h.right;
574                        }
575                    }
576                );
577            Self::new_inclusive(min, max)
578        }
579    }
580
581    fn align<S>(&self, right: S) -> Result<usize, HistErrors>
582    where S: Borrow<Self> {
583        let right = right.borrow();
584
585        if self.is_inside(right.left) {
586            (to_u(right.left) - to_u(self.left))
587                .to_usize()
588                .ok_or(HistErrors::UsizeCastError)
589        } else { 
590            Err(HistErrors::OutsideHist)
591        }
592    }
593}
594
595impl<T> IntervalOrder for HistogramFast<T>
596where T: PrimInt
597{
598    fn left_compare(&self, other: &Self) -> std::cmp::Ordering {
599        let order =  self.left.cmp(&other.left);
600        if order.is_eq() {
601            return self.right.cmp(&other.right)
602        }
603        order
604    }
605}
606
607
608
609#[cfg(test)]
610mod tests{
611    use super::*;
612    use rand::{distr::*, SeedableRng};
613    use rand_pcg::Pcg64Mcg;
614
615    fn hist_test_fast<T>(left: T, right: T)
616    where T: PrimInt + num_traits::Bounded + PartialOrd + CheckedSub + One 
617        + NumCast + Copy + FromPrimitive + HasUnsignedVersion + WrappingAdd,
618    std::ops::RangeInclusive<T>: Iterator<Item=T>,
619    T::Unsigned: Bounded + HasUnsignedVersion<LeBytes=T::LeBytes> 
620        + WrappingAdd + ToPrimitive + Sub<Output=T::Unsigned>
621    {
622        let mut hist = HistogramFast::<T>::new_inclusive(left, right).unwrap();
623        assert!(hist.not_inside(T::max_value()));
624        assert!(hist.not_inside(T::min_value()));
625        let two = unsafe{NonZeroUsize::new_unchecked(2)};
626        for (id, i) in (left..=right).enumerate() {
627            assert!(hist.is_inside(i));
628            assert_eq!(hist.is_inside(i), !hist.not_inside(i));
629            assert!(hist.get_bin_index(i).unwrap() == id);
630            assert_eq!(hist.distance(i), 0.0);
631            assert_eq!(hist.interval_distance_overlap(i, two), 0);
632            hist.count_val(i).unwrap();
633        }
634        let lm1 = left - T::one();
635        let rp1 = right + T::one();
636        assert!(hist.not_inside(lm1));
637        assert!(hist.not_inside(rp1));
638        assert_eq!(hist.is_inside(lm1), !hist.not_inside(lm1));
639        assert_eq!(hist.is_inside(rp1), !hist.not_inside(rp1));
640        assert_eq!(hist.distance(lm1), 1.0);
641        assert_eq!(hist.distance(rp1), 1.0);
642        let one = unsafe{NonZeroUsize::new_unchecked(1)};
643        assert_eq!(hist.interval_distance_overlap(rp1, one), 1);
644        assert_eq!(hist.interval_distance_overlap(lm1, one), 1);
645        assert_eq!(hist.bin_enum_iter().count(), hist.bin_count());
646    }
647
648    #[test]
649    fn hist_fast()
650    {
651        hist_test_fast(20usize, 31usize);
652        hist_test_fast(-23isize, 31isize);
653        hist_test_fast(-23i16, 31);
654        hist_test_fast(1u8, 3u8);
655        hist_test_fast(123u128, 300u128);
656        hist_test_fast(-123i128, 300i128);
657
658        hist_test_fast(-100i8, 100i8);
659    }
660
661    
662
663    #[test]
664    fn hist_creation()
665    {
666        let _ = HistU8Fast::new_inclusive(0, u8::MAX).unwrap();
667        let _ = HistI8Fast::new_inclusive(i8::MIN, i8::MAX).unwrap();
668    }
669
670    #[test]
671    fn partion_test()
672    {
673        let n = NonZeroUsize::new(2).unwrap();
674        let h = HistU8Fast::new_inclusive(0, u8::MAX).unwrap();
675        let h_part = h.overlapping_partition(n, 0).unwrap();
676        assert_eq!(h.left, h_part[0].left);
677        assert_eq!(h.right, h_part.last().unwrap().right);
678
679
680        let h = HistI8Fast::new_inclusive(i8::MIN, i8::MAX).unwrap();
681        let h_part = h.overlapping_partition(n, 0).unwrap();
682        assert_eq!(h.left, h_part[0].left);
683        assert_eq!(h.right, h_part.last().unwrap().right);
684
685        let h = HistI16Fast::new_inclusive(i16::MIN, i16::MAX).unwrap();
686        let h_part = h.overlapping_partition(n, 2).unwrap();
687        assert_eq!(h.left, h_part[0].left);
688        assert_eq!(h.right, h_part.last().unwrap().right);
689
690
691        let _ = h.overlapping_partition(NonZeroUsize::new(2000).unwrap(), 0).unwrap();
692    }
693
694    #[test]
695    fn overlapping_partition_test2()
696    {
697        let mut rng = Pcg64Mcg::seed_from_u64(2314668);
698        let uni = Uniform::new_inclusive(-100, 100)
699            .unwrap();
700        for overlap in 0..=3 {
701            for _ in 0..100 {
702                let (left, right) = loop {
703                    let mut num_1 = uni.sample(&mut rng);
704                    let mut num_2 = uni.sample(&mut rng);
705
706                    if num_1 != num_2 {
707                        if num_2 < num_1 {
708                            std::mem::swap(&mut num_1, &mut num_2);
709                        }
710                        if (num_2 as isize - num_1 as isize) < (overlap as isize + 1) {
711                            continue;
712                        }
713                        break (num_1, num_2)
714                    }
715                };
716                let hist_fast = HistI8Fast::new_inclusive(left, right).unwrap();
717                let overlapping = hist_fast.overlapping_partition(NonZeroUsize::new(3).unwrap(), overlap).unwrap();
718
719                assert_eq!(
720                    overlapping.last().unwrap().last_border(),
721                    hist_fast.last_border()
722                );
723
724                assert_eq!(
725                    overlapping.first().unwrap().first_border(),
726                    hist_fast.first_border()
727                );
728            }
729        }
730    }
731
732    #[test]
733    fn hist_combine()
734    {
735        let left = HistI8Fast::new_inclusive(-5,0).unwrap();
736        let right = HistI8Fast::new_inclusive(-1, 2).unwrap();
737
738        let en = HistI8Fast::encapsulating_hist(&[&left, &right]).unwrap();
739
740        assert_eq!(en.left, left.left);
741        assert_eq!(en.right, right.right);
742        assert_eq!(en.bin_count(), 8);
743
744        let align = left.align(right).unwrap();
745
746        assert_eq!(align, 4);
747
748        let left = HistI8Fast::new_inclusive(i8::MIN, 0).unwrap();
749        let right = HistI8Fast::new_inclusive(0, i8::MAX).unwrap();
750
751        let en = HistI8Fast::encapsulating_hist(&[&left, &right]).unwrap();
752
753        assert_eq!(en.bin_count(), 256);
754
755        let align = left.align(right).unwrap();
756
757        assert_eq!(128, align);
758
759        let left = HistI8Fast::new_inclusive(i8::MIN, i8::MAX).unwrap();
760        let small = HistI8Fast::new_inclusive(127, 127).unwrap();
761
762        let align = left.align(&small).unwrap();
763
764        assert_eq!(255, align);
765
766        let en = HistI8Fast::encapsulating_hist(&[&left]).unwrap();
767        assert_eq!(en.bin_count(), 256);
768        let slice = [&left];
769        let en = HistI8Fast::encapsulating_hist(&slice[1..]);
770        assert_eq!(en.err(), Some(HistErrors::EmptySlice));
771        let en = HistI8Fast::encapsulating_hist(&[small, left]).unwrap();
772
773        assert_eq!(en.bin_count(), 256);
774    }
775
776    #[test]
777    fn hist_try_add()
778    {
779        let mut first = HistU8Fast::new_inclusive(0, 23)
780            .unwrap();
781        let mut second = HistU8Fast::new_inclusive(0, 23)
782            .unwrap();
783        
784        for i in 0..=23{
785            first.increment(i)
786                .unwrap();
787        }
788        for i in 0..=11{
789            second.increment(i)
790                .unwrap();
791        }
792
793        let outcome = first.try_add(&second);
794        assert!(outcome.is_success());
795
796        let hist = first.hist();
797
798        #[allow(clippy::needless_range_loop)]
799        for i in 0..=11{
800            assert_eq!(hist[i], 2);
801        }
802        #[allow(clippy::needless_range_loop)]
803        for i in 12..=23{
804            assert_eq!(hist[i], 1);
805        }
806
807        let third = HistU8Fast::new(0,23)
808            .unwrap();
809            
810        let outcome = first.try_add(&third);
811        assert!(
812            outcome.is_failure(),
813            "Needs to be Err because ranges do not match"
814        )
815    }
816
817}