ndhistogram/histogram/
vechistogram.rs

1use std::{
2    cmp::Ordering,
3    fmt::Display,
4    ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign},
5};
6
7use crate::axis::Axis;
8
9use super::histogram::{Histogram, Item, Iter, IterMut, ValuesMut};
10
11/// A [Histogram] that stores its values in a [Vec].
12///
13/// See [crate::ndhistogram] for examples of its use.
14#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct VecHistogram<A, V> {
17    axes: A,
18    values: Vec<V>,
19}
20
21impl<A: Axis, V: Default + Clone> VecHistogram<A, V> {
22    /// Factory method for VecHistogram. It is recommended to use the
23    /// [ndhistogram](crate::ndhistogram) macro instead.
24    pub fn new(axes: A) -> Self {
25        let size = axes.num_bins();
26        Self {
27            axes,
28            values: vec![V::default(); size],
29        }
30    }
31}
32
33impl<A: Axis, V> Histogram<A, V> for VecHistogram<A, V> {
34    fn value(&self, coordinate: &A::Coordinate) -> Option<&V> {
35        let index = self.axes.index(coordinate)?;
36        self.values.get(index)
37    }
38
39    #[inline]
40    fn axes(&self) -> &A {
41        &self.axes
42    }
43
44    fn value_at_index(&self, index: usize) -> Option<&V> {
45        self.values.get(index)
46    }
47
48    fn values<'a>(&'a self) -> Box<dyn Iterator<Item = &'a V> + 'a> {
49        Box::new(self.values.iter())
50    }
51
52    fn iter<'a>(&'a self) -> Box<dyn Iterator<Item = Item<A::BinInterval, &'a V>> + 'a> {
53        Box::new(self.axes().iter().map(move |(index, binrange)| {
54            Item {
55                index,
56                bin: binrange,
57                value: self
58                    .value_at_index(index)
59                    .expect("iter() indices are always in range"),
60            }
61        }))
62    }
63
64    fn value_at_index_mut(&mut self, index: usize) -> Option<&mut V> {
65        self.values.get_mut(index)
66    }
67
68    fn values_mut(&mut self) -> ValuesMut<'_, V> {
69        Box::new(self.values.iter_mut())
70    }
71
72    fn iter_mut(&mut self) -> IterMut<'_, A, V> {
73        Box::new(
74            self.axes
75                .iter()
76                .zip(self.values.iter_mut())
77                .map(|((index, bin), value)| Item { index, bin, value }),
78        )
79    }
80}
81
82impl<'a, A: Axis, V> IntoIterator for &'a VecHistogram<A, V> {
83    type Item = Item<A::BinInterval, &'a V>;
84
85    type IntoIter = Iter<'a, A, V>;
86
87    fn into_iter(self) -> Self::IntoIter {
88        self.iter()
89    }
90}
91
92impl<'a, A: Axis, V: 'a> IntoIterator for &'a mut VecHistogram<A, V> {
93    type Item = Item<A::BinInterval, &'a mut V>;
94
95    type IntoIter = IterMut<'a, A, V>;
96
97    fn into_iter(self) -> Self::IntoIter {
98        self.iter_mut()
99    }
100}
101
102impl<A: Axis, V> Display for VecHistogram<A, V>
103where
104    V: Clone + Into<f64>,
105    A::BinInterval: Display,
106{
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        let precision = f.precision().unwrap_or(2);
109
110        let sum = self
111            .values()
112            .map(|it| {
113                let x: f64 = it.clone().into();
114                x
115            })
116            .fold(0.0, |it, value| it + value);
117        write!(
118            f,
119            "VecHistogram{}D({} bins, sum={})",
120            self.axes().num_dim(),
121            self.axes().num_bins(),
122            sum
123        )?;
124        let values: Vec<_> = self
125            .iter()
126            .take(50)
127            .map(|item| {
128                (item.bin, {
129                    let x: f64 = item.value.clone().into();
130                    x
131                })
132            })
133            .collect();
134        let scale = values
135            .iter()
136            .max_by(|l, r| l.1.partial_cmp(&r.1).unwrap_or(Ordering::Less))
137            .map(|it| it.1)
138            .unwrap_or(f64::INFINITY);
139        values
140            .into_iter()
141            .map(|(bin, value)| (bin, 50.0 * (value / scale)))
142            .map(|(bin, value)| {
143                (
144                    format!("{:.precision$}", bin, precision = precision),
145                    "#".repeat(value as usize),
146                )
147            })
148            .map(|(bin, value)| write!(f, "\n{:>16} | {}", bin, value))
149            .filter_map(Result::ok)
150            .count();
151        Ok(())
152    }
153}
154
155macro_rules! impl_binary_op_with_immutable_borrow {
156    ($Trait:tt, $method:tt, $mathsymbol:tt, $testresult:tt) => {
157        impl<A: Axis + PartialEq + Clone, V> $Trait<&VecHistogram<A, V>> for &VecHistogram<A, V>
158where
159    for<'a> &'a V: $Trait<Output = V>,
160{
161    type Output = Result<VecHistogram<A, V>, crate::error::BinaryOperationError>;
162
163    /// Combine the right-hand histogram with the left-hand histogram,
164    /// returning a copy, and leaving the original histograms intact.
165    ///
166    /// If the input histograms have incompatible axes, this operation
167    /// will return a [crate::error::BinaryOperationError].
168    ///
169    /// # Examples
170    ///
171    /// ```rust
172    /// use ndhistogram::{Histogram, ndhistogram, axis::Uniform};
173    /// # fn main() -> Result<(), ndhistogram::Error> {
174    /// let mut hist1 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
175    /// let mut hist2 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
176    /// hist1.fill_with(&0.0, 2.0);
177    /// hist2.fill(&0.0);
178    #[doc=concat!("let combined_hist = (&hist1 ", stringify!($mathsymbol), " &hist2).expect(\"Axes are compatible\");")]
179    #[doc=concat!("assert_eq!(combined_hist.value(&0.0).unwrap(), &", stringify!($testresult), ");")]
180    /// # Ok(()) }
181    /// ```
182    fn $method(self, rhs: &VecHistogram<A, V>) -> Self::Output {
183        if self.axes() != rhs.axes() {
184            return Err(crate::error::BinaryOperationError);
185        }
186        let values = self
187            .values
188            .iter()
189            .zip(rhs.values.iter())
190            .map(|(l, r)| l $mathsymbol r)
191            .collect();
192        Ok(VecHistogram {
193            axes: self.axes().clone(),
194            values,
195        })
196    }
197}
198    };
199}
200
201impl_binary_op_with_immutable_borrow! {Add, add, +, 3.0}
202impl_binary_op_with_immutable_borrow! {Sub, sub, -, 1.0}
203impl_binary_op_with_immutable_borrow! {Mul, mul, *, 2.0}
204impl_binary_op_with_immutable_borrow! {Div, div, /, 2.0}
205
206macro_rules! impl_binary_op_with_scalar {
207    ($Trait:tt, $method:tt, $mathsymbol:tt) => {
208        impl<A: Axis + PartialEq + Clone, V> $Trait<&V> for &VecHistogram<A, V>
209where
210    for<'a> &'a V: $Trait<Output = V>,
211{
212    type Output = VecHistogram<A, V>;
213
214    fn $method(self, rhs: &V) -> Self::Output {
215        let values = self
216            .values
217            .iter()
218            .map(|l| l $mathsymbol rhs)
219            .collect();
220        VecHistogram {
221            axes: self.axes().clone(),
222            values,
223        }
224    }
225}
226    };
227}
228
229impl_binary_op_with_scalar! {Add, add, +}
230impl_binary_op_with_scalar! {Sub, sub, -}
231impl_binary_op_with_scalar! {Mul, mul, *}
232impl_binary_op_with_scalar! {Div, div, /}
233
234macro_rules! impl_binary_op_with_owned {
235    ($Trait:tt, $method:tt, $ValueAssignTrait:tt, $mathsymbol:tt, $assignmathsymbol:tt, $testresult:tt) => {
236        impl<A: Axis + PartialEq, V> $Trait<&VecHistogram<A, V>> for VecHistogram<A, V>
237        where
238            for<'a> V: $ValueAssignTrait<&'a V>,
239        {
240            type Output = Result<VecHistogram<A, V>, crate::error::BinaryOperationError>;
241
242            /// Combine the right-hand histogram with the left-hand histogram,
243            /// consuming the left-hand histogram and returning a new value.
244            /// As this avoids making copies of the histograms, this is the
245            /// recommended method to merge histograms.
246            ///
247            /// If the input histograms have incompatible axes, this operation
248            /// will return a [crate::error::BinaryOperationError].
249            ///
250            /// # Examples
251            ///
252            /// ```rust
253            /// use ndhistogram::{Histogram, ndhistogram, axis::Uniform};
254            /// # fn main() -> Result<(), ndhistogram::Error> {
255            /// let mut hist1 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
256            /// let mut hist2 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
257            /// hist1.fill_with(&0.0, 2.0);
258            /// hist2.fill(&0.0);
259            #[doc=concat!("let combined_hist = (hist1 ", stringify!($mathsymbol), " &hist2).expect(\"Axes are compatible\");")]
260            #[doc=concat!("assert_eq!(combined_hist.value(&0.0).unwrap(), &", stringify!($testresult), ");")]
261            /// # Ok(()) }
262            /// ```
263            fn $method(mut self, rhs: &VecHistogram<A, V>) -> Self::Output {
264                if self.axes() != rhs.axes() {
265                    return Err(crate::error::BinaryOperationError);
266                }
267                self.values
268                    .iter_mut()
269                    .zip(rhs.values.iter())
270                    .for_each(|(l, r)| *l $assignmathsymbol &r);
271                Ok(self)
272            }
273        }
274    };
275}
276
277impl_binary_op_with_owned! {Add, add, AddAssign, +, +=, 3.0}
278impl_binary_op_with_owned! {Sub, sub, SubAssign, -, -=, 1.0}
279impl_binary_op_with_owned! {Mul, mul, MulAssign, *, *=, 2.0}
280impl_binary_op_with_owned! {Div, div, DivAssign, /, /=, 2.0}
281
282macro_rules! impl_binary_op_assign {
283    ($Trait:tt, $method:tt, $ValueAssignTrait:tt, $mathsymbol:tt, $testresult:tt) => {
284        impl<A: Axis + PartialEq, V> $Trait<&VecHistogram<A, V>> for VecHistogram<A, V>
285        where
286            for<'a> V: $ValueAssignTrait<&'a V>,
287        {
288            /// Combine the right-hand histogram with the left-hand histogram,
289            /// mutating the left-hand histogram.
290            ///
291            /// # Panics
292            ///
293            /// Panics if the histograms have incompatible axes.
294            /// To handle this failure mode at runtime, use the non-assign
295            /// version of this operation, which returns an Result.
296            ///
297            /// # Examples
298            ///
299            /// ```rust
300            /// use ndhistogram::{Histogram, ndhistogram, axis::Uniform};
301            /// # fn main() -> Result<(), ndhistogram::Error> {
302            /// let mut hist1 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
303            /// let mut hist2 = ndhistogram!(Uniform::<f64>::new(10, -5.0, 5.0)?);
304            /// hist1.fill_with(&0.0, 2.0);
305            /// hist2.fill(&0.0);
306            #[doc=concat!("hist1 ", stringify!($mathsymbol), " &hist2;")]
307            #[doc=concat!("assert_eq!(hist1.value(&0.0).unwrap(), &", stringify!($testresult), ");")]
308            /// # Ok(()) }
309            /// ```
310            fn $method(&mut self, rhs: &VecHistogram<A, V>) {
311                if self.axes() != rhs.axes() {
312                    panic!("Cannot combine VecHistograms with incompatible axes.");
313                }
314                self.values
315                    .iter_mut()
316                    .zip(rhs.values.iter())
317                    .for_each(|(l, r)| *l $mathsymbol &r);
318            }
319        }
320    };
321}
322
323impl_binary_op_assign! {AddAssign, add_assign, AddAssign, +=, 3.0}
324impl_binary_op_assign! {SubAssign, sub_assign, SubAssign, -=, 1.0}
325impl_binary_op_assign! {MulAssign, mul_assign, MulAssign, *=, 2.0}
326impl_binary_op_assign! {DivAssign, div_assign, DivAssign, /=, 2.0}
327
328#[cfg(feature = "rayon")]
329use rayon::prelude::*;
330
331// TODO: It would be better to implement rayon::iter::IntoParallelIterator
332// but this isn't possible with the current closure based implementation
333// as rayon traits are not object safe, we can't return a Box<dyn ParallelIterator>
334// With nightly feature type_alias_impl_trait
335// https://rust-lang.github.io/rfcs/2515-type_alias_impl_trait.html
336// we could implement this as:
337//
338// #[cfg(feature = "rayon")]
339// impl <'a, A, V> rayon::iter::IntoParallelIterator for &'a VecHistogram<A, V>
340// where V: Sync, A: Axis + Sync, <A as Axis>::BinInterval: Send
341// {
342//     type Iter = impl ParallelIterator<Item=Self::Item>;
343//
344//     type Item = Item<<A as Axis>::BinInterval, &'a V>;
345//
346//     fn into_par_iter(self) -> Self::Iter {
347//         self.par_iter()
348//     }
349// }
350//
351// However we want to crate to build on stable.
352
353impl<A, V> VecHistogram<A, V> {
354    /// An [immutable rayon parallel iterator](rayon::iter::IndexedParallelIterator) over the histogram values.
355    ///
356    /// This requires the "rayon" [crate feature](index.html#crate-feature-flags) to be enabled.
357    #[cfg(feature = "rayon")]
358    pub fn par_values(&self) -> impl IndexedParallelIterator<Item = &V>
359    where
360        V: Sync,
361    {
362        self.values.par_iter()
363    }
364
365    /// A [mutable rayon parallel iterator](rayon::iter::IndexedParallelIterator) over the histogram values.
366    ///
367    /// This requires the "rayon" [crate feature](index.html#crate-feature-flags) to be enabled.
368    #[cfg(feature = "rayon")]
369    pub fn par_values_mut(&mut self) -> impl IndexedParallelIterator<Item = &mut V>
370    where
371        V: Send,
372    {
373        self.values.par_iter_mut()
374    }
375
376    /// An [immutable rayon parallel iterator](rayon::iter::IndexedParallelIterator) over bin indices, bin interval and bin values.
377    ///
378    /// This requires the "rayon" [crate feature](index.html#crate-feature-flags) to be enabled.
379    #[cfg(feature = "rayon")]
380    pub fn par_iter(
381        &self,
382    ) -> impl IndexedParallelIterator<Item = Item<<A as Axis>::BinInterval, &V>>
383    where
384        A: Axis + Sync,
385        V: Sync,
386        <A as Axis>::BinInterval: Send,
387    {
388        self.values
389            .par_iter()
390            .enumerate()
391            .map(move |(index, value)| Item {
392                index,
393                bin: self
394                    .axes
395                    .bin(index)
396                    .expect("We only iterate over valid indices."),
397                value,
398            })
399    }
400
401    /// An [mutable rayon parallel iterator](rayon::iter::IndexedParallelIterator) over bin indices, bin interval and bin values.
402    ///
403    /// This requires the "rayon" [crate feature](index.html#crate-feature-flags) to be enabled.
404    #[cfg(feature = "rayon")]
405    pub fn par_iter_mut(
406        &mut self,
407    ) -> impl IndexedParallelIterator<Item = Item<<A as Axis>::BinInterval, &mut V>>
408    where
409        A: Axis + Sync + Send,
410        V: Send + Sync,
411        <A as Axis>::BinInterval: Send,
412    {
413        let axes = &self.axes;
414        self.values.par_iter_mut().enumerate().map(move |it| Item {
415            index: it.0,
416            bin: axes.bin(it.0).expect("We only iterate over valid indices."),
417            value: it.1,
418        })
419    }
420}