From 0861a7fd54eea4e4515ed1ab66e8c0a6352c30a5 Mon Sep 17 00:00:00 2001 From: David Hadley Date: Mon, 6 Jun 2022 11:55:51 +0200 Subject: [PATCH] Release 0.7.0 --- TASKS.md | 4 +- ndhistogram/CHANGELOG.md | 3 + ndhistogram/Cargo.toml | 3 +- ndhistogram/README.md | 2 +- ndhistogram/src/axis/mod.rs | 4 + ndhistogram/src/axis/uniformcyclic.rs | 141 ++++++++++++++++++ ndhistogram/src/axis/variable.rs | 2 +- ndhistogram/src/axis/variablecyclic.rs | 112 ++++++++++++++ ndhistogram/src/lib.rs | 4 +- ndhistogram/tests/mod.rs | 2 + ndhistogram/tests/test_uniformcyclic_axis.rs | 122 +++++++++++++++ ndhistogram/tests/test_variablecyclic_axis.rs | 128 ++++++++++++++++ 12 files changed, 520 insertions(+), 7 deletions(-) create mode 100644 ndhistogram/src/axis/uniformcyclic.rs create mode 100644 ndhistogram/src/axis/variablecyclic.rs create mode 100644 ndhistogram/tests/test_uniformcyclic_axis.rs create mode 100644 ndhistogram/tests/test_variablecyclic_axis.rs diff --git a/TASKS.md b/TASKS.md index cc89c31..c7c03b4 100644 --- a/TASKS.md +++ b/TASKS.md @@ -1,8 +1,8 @@ # Task List -- [ ] Periodic or cyclic axis +- [x] Periodic or cyclic axis - [ ] Uniform with transform binning - [ ] Dynamic histogram (i.e. axes only know at runtime) - [ ] Re-binnable axis. - [ ] Growable/Extendable axis. -- [ ] ndarray-like slicing? \ No newline at end of file +- [ ] ndarray-like slicing? diff --git a/ndhistogram/CHANGELOG.md b/ndhistogram/CHANGELOG.md index fc4ceab..b85ca84 100644 --- a/ndhistogram/CHANGELOG.md +++ b/ndhistogram/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.7.0 (2022-06-06) +- New [UniformCyclic](https://docs.rs/ndhistogram/0.7.0/ndhistogram/axis/struct.UniformCyclic.html) and [VariableCyclic](https://docs.rs/ndhistogram/0.7.0/ndhistogram/axis/struct.VariableCyclic.html) and `VariableCyclic` axis types (from [@jacg](https://github.com/jacg)). + # 0.6.3 (2022-04-07) - `Uniform::with_step_size` now panics when given a step size equal to zero (from [@jacg](https://github.com/jacg)). - Documentation and CI improvements from [@jacg](https://github.com/jacg). diff --git a/ndhistogram/Cargo.toml b/ndhistogram/Cargo.toml index b6d8403..9f185cd 100644 --- a/ndhistogram/Cargo.toml +++ b/ndhistogram/Cargo.toml @@ -15,6 +15,7 @@ rand_distr = "0.4.0" serde_json = "1.0.61" version-sync = "0.9.1" paste = "1.0.4" +rstest = "0.12" [dev-dependencies.criterion] version = "0.3.4" @@ -25,7 +26,7 @@ bench = false [package] name = "ndhistogram" -version = "0.6.3" +version = "0.7.0" authors = [ "David Hadley ",] edition = "2018" license = "MIT OR Apache-2.0" diff --git a/ndhistogram/README.md b/ndhistogram/README.md index 6724852..e496a4d 100644 --- a/ndhistogram/README.md +++ b/ndhistogram/README.md @@ -30,7 +30,7 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -ndhistogram = "0.6.3" +ndhistogram = "0.7.0" ``` See the [change log](https://github.com/davehadley/ndhistogram/blob/main/ndhistogram/CHANGELOG.md) diff --git a/ndhistogram/src/axis/mod.rs b/ndhistogram/src/axis/mod.rs index 59bd61c..116e8b1 100644 --- a/ndhistogram/src/axis/mod.rs +++ b/ndhistogram/src/axis/mod.rs @@ -6,6 +6,10 @@ mod bininterval; pub use bininterval::bininterval::BinInterval; pub use bininterval::singlevaluebininterval::SingleValueBinInterval; +mod uniformcyclic; +pub use uniformcyclic::UniformCyclic; +mod variablecyclic; +pub use variablecyclic::VariableCyclic; mod uniform; pub use uniform::Uniform; mod uniformnoflow; diff --git a/ndhistogram/src/axis/uniformcyclic.rs b/ndhistogram/src/axis/uniformcyclic.rs new file mode 100644 index 0000000..6f99ce7 --- /dev/null +++ b/ndhistogram/src/axis/uniformcyclic.rs @@ -0,0 +1,141 @@ +use super::{Axis, BinInterval, Uniform}; +use std::fmt::{Debug, Display}; + +use num_traits::{Float, Num, NumCast, NumOps}; +use serde::{Deserialize, Serialize}; + +/// A wrap-around axis with equal-sized bins. +/// +/// An axis with `N` equally-spaced, equal-sized bins, in `[low, high)`. +/// Entries outside this interval get wrapped around. +/// There are no overflow bins so this axis has exactly `N` bins. +/// +/// # Examples +/// 1D histogram with 4 bins distributed around a circle. +/// ``` +/// use ndhistogram::{ndhistogram, Histogram}; +/// use ndhistogram::axis::{Axis, BinInterval, UniformCyclic}; +/// let mut hist = ndhistogram!(UniformCyclic::new(4, 0.0, 360.0)); +/// hist.fill(& 45.0 ); // Add entry at 45 degrees +/// hist.fill(&(45.0 + 360.0)); // Add entry at 45 degrees + one whole turn +/// hist.fill(&(45.0 - 360.0)); // Add entry at 45 degrees + one whole turn backwards +/// // All 3 above entries end up in the same bin +/// assert_eq!(hist.value(&45.0), Some(&3.0)); +/// // Lookup also wraps around +/// assert_eq!(hist.value(&(45.0 + 360.0)), Some(&3.0)); +/// assert_eq!(hist.value(&(45.0 - 360.0)), Some(&3.0)); +/// ``` +/// Time of day +/// ``` +/// use ndhistogram::{ndhistogram, Histogram}; +/// use ndhistogram::axis::{Axis, BinInterval, UniformCyclic}; +/// let bins_per_day = 24; +/// let hours_per_bin = 1; +/// let start_at_zero = 0; +/// let four_pm = 16; +/// let mut hist = ndhistogram!(UniformCyclic::with_step_size( +/// bins_per_day, start_at_zero, hours_per_bin +/// )); +/// hist.fill(&40); // The 40th hour of the week ... +/// assert_eq!(hist.value(&four_pm), Some(&1.0)); // ... is at 4 pm. +/// ```` +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] +pub struct UniformCyclic { + axis: Uniform, +} + +impl UniformCyclic +where + T: PartialOrd + Num + NumCast + NumOps + Copy, +{ + /// Create a wrap-around axis with `nbins` uniformly-spaced bins in the range `[low, high)`. + /// + /// Only implemented for [Float]. Use [UniformCyclic::with_step_size] for integers. + /// + /// # Panics + /// Panics under the same conditions as [Uniform::new]. + pub fn new(nbins: usize, low: T, high: T) -> Self + where + T: Float, + { + Self { + axis: Uniform::new(nbins, low, high), + } + } + + /// Create a wrap-around axis with `nbins` uniformly-spaced bins in the range `[low, low+num*step)`. + /// # Panics + /// Panics under the same conditions as [Uniform::new]. + pub fn with_step_size(nbins: usize, low: T, step: T) -> Self { + Self { + axis: Uniform::with_step_size(nbins, low, step), + } + } +} + +impl UniformCyclic { + /// Low edge of axis (excluding wrap-around) + #[inline] + pub fn low(&self) -> &T { + self.axis.low() + } + /// High edge of axis (excluding wrap-around) + #[inline] + pub fn high(&self) -> &T { + self.axis.high() + } +} + +impl Axis for UniformCyclic { + type Coordinate = T; + type BinInterval = BinInterval; + + #[inline] + fn index(&self, coordinate: &Self::Coordinate) -> Option { + let (mut x, hi, lo) = (*coordinate, *self.axis.high(), *self.axis.low()); + let range = hi - lo; + x = (x - lo) % range; + if x < T::zero() { + x = range + x; + } + x = x + lo; + self.axis.index(&x).map(|n| n - 1) + } + + #[inline] + fn num_bins(&self) -> usize { + self.axis.num_bins() - 2 + } + + #[inline] + fn bin(&self, index: usize) -> Option<::BinInterval> { + self.axis.bin(index + 1) + } +} + +impl Display for UniformCyclic +where + T: PartialOrd + NumCast + NumOps + Copy + Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Axis{{# bins={}, range=[{}, {}), class={}}}", + self.axis.num_bins(), + self.axis.low(), + self.axis.high(), + stringify!(UniformCyclic) + ) + } +} + +impl<'a, T> IntoIterator for &'a UniformCyclic +where + UniformCyclic: Axis, +{ + type Item = (usize, as Axis>::BinInterval); + type IntoIter = Box + 'a>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} diff --git a/ndhistogram/src/axis/variable.rs b/ndhistogram/src/axis/variable.rs index e933f54..6bff324 100644 --- a/ndhistogram/src/axis/variable.rs +++ b/ndhistogram/src/axis/variable.rs @@ -39,7 +39,7 @@ where /// /// # Panics /// - /// Panics if errors less than 2 edges are provided or if it fails to sort the + /// Panics if less than 2 edges are provided or if it fails to sort the /// bin edges (for example if a NAN value is given). pub fn new>(bin_edges: I) -> Self { let mut bin_edges: Vec = bin_edges.into_iter().collect(); diff --git a/ndhistogram/src/axis/variablecyclic.rs b/ndhistogram/src/axis/variablecyclic.rs new file mode 100644 index 0000000..f287e24 --- /dev/null +++ b/ndhistogram/src/axis/variablecyclic.rs @@ -0,0 +1,112 @@ +use super::{Axis, BinInterval, Variable}; +use std::fmt::{Debug, Display}; + +use num_traits::Num; +use serde::{Deserialize, Serialize}; + +/// A wrap-around axis with variable-sized bins. +/// +/// A wrap-around axis with variable-sized bins, constructed from a list of bin +/// edges. +/// +/// # Examples +/// 1D histogram with cyclic variable sized azimuthal angle binning. +/// ``` +/// use ndhistogram::{ndhistogram, Histogram}; +/// use ndhistogram::axis::{Axis, BinInterval, VariableCyclic}; +/// use std::f64::consts::PI; +/// let mut hist = ndhistogram!(VariableCyclic::new(vec![0.0, PI/2.0, PI, 2.0*PI]); i32); +/// let angle = 0.1; +/// hist.fill(&angle); // fills the first bin +/// hist.fill(&(angle + 2.0*PI)); // wraps around and fills the same first bin +/// assert_eq!(hist.value(&angle), Some(&2)); +#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)] +pub struct VariableCyclic { + axis: Variable, +} + +impl VariableCyclic +where + T: PartialOrd + Copy, +{ + /// Create a wrap-around axis with [Variable] binning given a set of bin edges. + /// + /// # Panics + /// + /// Panics if fewer than 2 edges are provided, or if the edges cannot be + /// sorted (for example when given NAN). + pub fn new>(bin_edges: I) -> Self { + Self { + axis: Variable::new(bin_edges), + } + } + + /// Low edge of axis (excluding wrap-around) + #[inline] + pub fn low(&self) -> &T { + self.axis.low() + } + + /// High edge of axis (excluding wrap-around) + #[inline] + pub fn high(&self) -> &T { + self.axis.high() + } +} + +impl Axis for VariableCyclic +where + T: PartialOrd + Copy + Num, +{ + type Coordinate = T; + type BinInterval = BinInterval; + + #[inline] + fn index(&self, coordinate: &Self::Coordinate) -> Option { + let (mut x, hi, lo) = (*coordinate, *self.axis.high(), *self.axis.low()); + let range = hi - lo; + x = (x - lo) % range; + if x < T::zero() { + x = range + x; + } + x = x + lo; + self.axis.index(&x).map(|n| n - 1) + } + + #[inline] + fn num_bins(&self) -> usize { + self.axis.num_bins() - 2 + } + + #[inline] + fn bin(&self, index: usize) -> Option { + self.axis.bin(index + 1) + } +} + +impl Display for VariableCyclic +where + T: PartialOrd + Copy + Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "Axis{{# bins={}, range=[{}, {}), class={}}}", + self.axis.num_bins(), + self.axis.low(), + self.axis.high(), + stringify!(VariableCyclic) + ) + } +} + +impl<'a, T> IntoIterator for &'a VariableCyclic +where + VariableCyclic: Axis, +{ + type Item = (usize, as Axis>::BinInterval); + type IntoIter = Box + 'a>; + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} diff --git a/ndhistogram/src/lib.rs b/ndhistogram/src/lib.rs index fd5a4e5..e211d97 100644 --- a/ndhistogram/src/lib.rs +++ b/ndhistogram/src/lib.rs @@ -19,7 +19,7 @@ //! //! ```toml //! [dependencies] -//! ndhistogram = "0.6.3" +//! ndhistogram = "0.7.0" //! ``` //! //! See the [change log](https://github.com/davehadley/ndhistogram/blob/main/ndhistogram/CHANGELOG.md) @@ -128,7 +128,7 @@ //! User defined bin value types are possible by implementing the [Fill](Fill), [FillWith](FillWith) or [FillWithWeighted](FillWithWeighted) traits. #![doc(issue_tracker_base_url = "https://github.com/davehadley/rust-hist/issues")] -#![doc(html_root_url = "https://docs.rs/ndhistogram/0.6.3")] +#![doc(html_root_url = "https://docs.rs/ndhistogram/0.7.0")] #![cfg_attr( debug_assertions, warn( diff --git a/ndhistogram/tests/mod.rs b/ndhistogram/tests/mod.rs index 78da97f..37233f4 100644 --- a/ndhistogram/tests/mod.rs +++ b/ndhistogram/tests/mod.rs @@ -16,10 +16,12 @@ mod test_serialization; mod test_trait_object_safety; mod test_uniform_axis; mod test_uniform_axis_integer; +mod test_uniformcyclic_axis; mod test_value_mean; mod test_value_sum; mod test_value_weightedmean; mod test_value_weightedsum; mod test_variable_axis; +mod test_variablecyclic_axis; mod test_variablenoflow_axis; mod test_versions; diff --git a/ndhistogram/tests/test_uniformcyclic_axis.rs b/ndhistogram/tests/test_uniformcyclic_axis.rs new file mode 100644 index 0000000..7ae2736 --- /dev/null +++ b/ndhistogram/tests/test_uniformcyclic_axis.rs @@ -0,0 +1,122 @@ +use ndhistogram::axis::{Axis, BinInterval, UniformCyclic}; + +use rstest::rstest; + +#[rstest(/**/bin_no, expected_interval, + case(0 , Some(BinInterval::new(0.00, 0.25))), + case(1 , Some(BinInterval::new(0.25, 0.50))), + case(2 , Some(BinInterval::new(0.50, 0.75))), + case(3 , Some(BinInterval::new(0.75, 1.00))), +)] +fn bin(bin_no: usize, expected_interval: Option>) { + let axis = UniformCyclic::new(4, 0.0, 1.0); + assert_eq!(axis.bin(bin_no), expected_interval); +} + +#[rstest(coordinate, expected_index, + case( 0.0 , Some(0)), + case( 0.09, Some(0)), + case( 0.1 , Some(1)), + case( 0.19, Some(1)), + case( 0.2 , Some(2)), + case( 10.0 , Some(0)), + case( 20.33, Some(3)), + case( 50.99, Some(9)), + case( -0.1 , Some(9)), + case( -0.19, Some(8)), + case( -0.2 , Some(8)), + case( -0.9 , Some(1)), + case( -0.95, Some(0)), + case(-10.0 , Some(0)), + case(-10.05, Some(9)), + case(-10.1 , Some(8)), +)] +fn float_index(coordinate: f32, expected_index: Option) { + let axis = UniformCyclic::new(10, 0.0, 1.0); + assert_eq!(axis.index(&coordinate), expected_index); +} + +#[rstest(/**/ nbins, low, step, coordinate, expected_index, + case( 2 , 0 , 10 , 0 , Some(0) ), + case( 2 , 0 , 10 , 5 , Some(0) ), + case( 2 , 0 , 10 , 15 , Some(1) ), + case( 2 , 0 , 10 , 20 , Some(0) ), + case( 2 , 0 , 10 , 29 , Some(0) ), + case( 2 , 0 , 10 , 30 , Some(1) ), + case( 2 , 0 , 10 , -1 , Some(1) ), + case( 2 , 0 , 10 , -10 , Some(1) ), + case( 2 , 0 , 10 , -11 , Some(0) ), + case( 10 , 10 , 9 , 10 , Some(0) ), + case( 10 , 10 , 9 , 18 , Some(0) ), + case( 10 , 10 , 9 , 19 , Some(1) ), + case( 10 , 10 , 9 , 27 , Some(1) ), + case( 10 , 10 , 9 , 28 , Some(2) ), + case( 10 , 10 , 9 , 99 , Some(9) ), + case( 10 , 10 , 9 , 100 , Some(0) ), +)] +fn integer_index( + nbins: usize, + low: i32, + step: i32, + coordinate: i32, + expected_index: Option, +) { + let axis = UniformCyclic::with_step_size(nbins, low, step); + assert_eq!(axis.index(&coordinate), expected_index); +} + +#[test] +fn indices() { + let n = 7; + let axis = UniformCyclic::new(n, 23.4, 97.3); + let indices = axis.indices().collect::>(); + assert_eq!(indices, (0..n).collect::>()); +} + +use ndhistogram::{ndhistogram, Histogram}; + +#[test] +fn wrap_float_fill() { + let mut hist = ndhistogram!(UniformCyclic::new(4, 0.0, 360.0); u8); + hist.fill(&45.0); + hist.fill(&(45.0 + 360.0)); + hist.fill(&(45.0 - 360.0)); + assert_eq!(hist.value(&45.0), Some(&3)); + assert_eq!(hist.value_at_index(0), Some(&3)); +} + +#[test] +fn wrap_int_fill() { + let bins_per_day = 24; + let hours_per_bin = 1; + let start_at_zero = 0; + let mut hist = ndhistogram!(UniformCyclic::with_step_size( + bins_per_day, + start_at_zero, + hours_per_bin + )); + hist.fill(&40); // The 40th hour of the week ... + assert_eq!(hist.value(&16), Some(&1.0)); // ... is at 4 pm. +} + +#[test] +fn wrap_float_value() { + let mut hist = ndhistogram!(UniformCyclic::new(4, 0.0, 360.0); u8); + hist.fill(&45.0); + assert_eq!(hist.value(&45.0), Some(&1)); + assert_eq!(hist.value(&(45.0 + 360.0)), Some(&1)); + assert_eq!(hist.value(&(45.0 - 360.0)), Some(&1)); +} + +#[test] +fn into_iter() { + use ndhistogram::axis::BinInterval; + let axis = UniformCyclic::with_step_size(3, 15, 10); + let mut bins = vec![]; + for x in &axis { + bins.push(x) + } + assert_eq!(bins[0], (0, BinInterval::new(15, 25))); + assert_eq!(bins[1], (1, BinInterval::new(25, 35))); + assert_eq!(bins[2], (2, BinInterval::new(35, 45))); +} diff --git a/ndhistogram/tests/test_variablecyclic_axis.rs b/ndhistogram/tests/test_variablecyclic_axis.rs new file mode 100644 index 0000000..c7f71a4 --- /dev/null +++ b/ndhistogram/tests/test_variablecyclic_axis.rs @@ -0,0 +1,128 @@ +use ndhistogram::axis::{Axis, BinInterval, VariableCyclic}; + +use rstest::rstest; + +fn bint(lo: T, hi: T) -> Option> { + Some(BinInterval::new(lo, hi)) +} + +#[rstest(/**/ bin_no, expected_interval, edges, + case(0, bint(0.0, 0.5), &[0.0, 0.5, 2.0]), + case(0, bint(0.0, 0.5), &[0.5, 2.0, 0.0]), + case(1, bint(0.5, 2.0), &[0.0, 0.5, 2.0]), + case(1, bint(0.5, 2.0), &[0.5, 2.0, 0.0]), +)] +fn bin_float(bin_no: usize, expected_interval: Option>, edges: &[f32]) { + let axis = VariableCyclic::new(edges.iter().cloned()); + assert_eq!(axis.bin(bin_no), expected_interval); +} + +#[rstest(/**/ bin_no, expected_interval, edges, + case(0, bint(0, 5), &[ 0, 5, 20]), + case(0, bint(0, 5), &[ 5, 20, 0]), + case(1, bint(5, 20), &[ 0, 5, 20]), + case(1, bint(5, 20), &[ 5, 20, 0]), +)] +fn bin_int(bin_no: usize, expected_interval: Option>, edges: &[i32]) { + let axis = VariableCyclic::new(edges.iter().cloned()); + assert_eq!(axis.bin(bin_no), expected_interval); +} + +#[rstest(/**/coordinate, expected_index, edges, + case(0.5 , Some(0) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(1.5 , Some(1) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(3.0 , Some(2) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(6.0 , Some(3) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(0.5 + 8.0, Some(0) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(1.5 + 8.0, Some(1) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(3.0 + 8.0, Some(2) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(6.0 + 8.0, Some(3) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(0.5 - 8.0, Some(0) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(1.5 - 8.0, Some(1) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(3.0 - 8.0, Some(2) , &[0.0, 1.0, 2.0, 4.0, 8.0]), + case(6.0 - 8.0, Some(3) , &[0.0, 1.0, 2.0, 4.0, 8.0]), +)] +fn float_index(coordinate: f32, expected_index: Option, edges: &[f32]) { + let axis = VariableCyclic::new(edges.iter().cloned()); + assert_eq!(axis.index(&coordinate), expected_index); +} + +#[rstest(/**/coordinate, expected_index, edges, + case( 5 , Some(0) , &[ 0, 10, 20, 40, 80]), + case(15 , Some(1) , &[ 0, 10, 20, 40, 80]), + case(30 , Some(2) , &[ 0, 10, 20, 40, 80]), + case(60 , Some(3) , &[ 0, 10, 20, 40, 80]), + case( 5 + 80 , Some(0) , &[ 0, 10, 20, 40, 80]), + case(15 + 80 , Some(1) , &[ 0, 10, 20, 40, 80]), + case(30 + 80 , Some(2) , &[ 0, 10, 20, 40, 80]), + case(60 + 80 , Some(3) , &[ 0, 10, 20, 40, 80]), + case( 5 - 80 , Some(0) , &[ 0, 10, 20, 40, 80]), + case(15 - 80 , Some(1) , &[ 0, 10, 20, 40, 80]), + case(30 - 80 , Some(2) , &[ 0, 10, 20, 40, 80]), + case(60 - 80 , Some(3) , &[ 0, 10, 20, 40, 80]), +)] +fn int_index(coordinate: i32, expected_index: Option, edges: &[i32]) { + let axis = VariableCyclic::new(edges.iter().cloned()); + assert_eq!(axis.index(&coordinate), expected_index); +} + +#[rstest(/**/ edges, + case(&[1.0, 2.0]), + case(&[8.5, 2.3, 9.4, -23.4]), +)] +fn indices(edges: &[f32]) { + let n = edges.len() - 1; + let axis = VariableCyclic::new(edges.iter().cloned()); + let indices = axis.indices().collect::>(); + assert_eq!(indices, (0..n).collect::>()); +} + +use ndhistogram::{ndhistogram, Histogram}; + +#[test] +fn wrap_float_fill() { + let mut hist = ndhistogram!(VariableCyclic::new(vec![8.0, 0.0, 4.0, 2.0]); u8); + let v = &5.0; + let r = 8.0; + hist.fill(v); + hist.fill(&(v + r)); + hist.fill(&(v - r)); + assert_eq!(hist.value(v), Some(&3)); + assert_eq!(hist.value_at_index(2), Some(&3)); +} + +#[test] +fn wrap_int_fill() { + let mut hist = ndhistogram!(VariableCyclic::new(vec![8, 0, 4, 2]); u8); + let v = &5; + let r = 8; + hist.fill(v); + hist.fill(&(v + r)); + hist.fill(&(v - r)); + assert_eq!(hist.value(v), Some(&3)); + assert_eq!(hist.value_at_index(2), Some(&3)); +} + +#[test] +fn wrap_float_value() { + let mut hist = ndhistogram!(VariableCyclic::new(vec![4.0, 8.0, 2.0, 1.0]); u8); + let v = &2.3; + let r = 7.0; + hist.fill(v); + assert_eq!(hist.value(v), Some(&1)); + assert_eq!(hist.value(&(v - r)), Some(&1)); + assert_eq!(hist.value(&(v + r)), Some(&1)); +} + +#[test] +fn into_iter() { + use ndhistogram::axis::BinInterval; + let axis = VariableCyclic::new([0, 1, 10, 100]); + let mut bins = vec![]; + for x in &axis { + bins.push(x) + } + assert_eq!(bins[0], (0, BinInterval::new(0, 1))); + assert_eq!(bins[1], (1, BinInterval::new(1, 10))); + assert_eq!(bins[2], (2, BinInterval::new(10, 100))); +}