ndhistogram/histogram/
hashhistogram.rs

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