Skip to content

Commit

Permalink
Release 0.7.0
Browse files Browse the repository at this point in the history
  • Loading branch information
davehadley committed Jun 6, 2022
1 parent 43a8110 commit 0861a7f
Show file tree
Hide file tree
Showing 12 changed files with 520 additions and 7 deletions.
4 changes: 2 additions & 2 deletions TASKS.md
Original file line number Diff line number Diff line change
@@ -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?
- [ ] ndarray-like slicing?
3 changes: 3 additions & 0 deletions ndhistogram/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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).
Expand Down
3 changes: 2 additions & 1 deletion ndhistogram/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -25,7 +26,7 @@ bench = false

[package]
name = "ndhistogram"
version = "0.6.3"
version = "0.7.0"
authors = [ "David Hadley <[email protected]>",]
edition = "2018"
license = "MIT OR Apache-2.0"
Expand Down
2 changes: 1 addition & 1 deletion ndhistogram/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions ndhistogram/src/axis/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
141 changes: 141 additions & 0 deletions ndhistogram/src/axis/uniformcyclic.rs
Original file line number Diff line number Diff line change
@@ -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<T = f64> {
axis: Uniform<T>,
}

impl<T> UniformCyclic<T>
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<T> UniformCyclic<T> {
/// 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<T: PartialOrd + Num + NumCast + NumOps + Copy> Axis for UniformCyclic<T> {
type Coordinate = T;
type BinInterval = BinInterval<T>;

#[inline]
fn index(&self, coordinate: &Self::Coordinate) -> Option<usize> {
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 as Axis>::BinInterval> {
self.axis.bin(index + 1)
}
}

impl<T> Display for UniformCyclic<T>
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<T>
where
UniformCyclic<T>: Axis,
{
type Item = (usize, <UniformCyclic<T> as Axis>::BinInterval);
type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
2 changes: 1 addition & 1 deletion ndhistogram/src/axis/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<I: IntoIterator<Item = T>>(bin_edges: I) -> Self {
let mut bin_edges: Vec<T> = bin_edges.into_iter().collect();
Expand Down
112 changes: 112 additions & 0 deletions ndhistogram/src/axis/variablecyclic.rs
Original file line number Diff line number Diff line change
@@ -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<T = f64> {
axis: Variable<T>,
}

impl<T> VariableCyclic<T>
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<I: IntoIterator<Item = T>>(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<T> Axis for VariableCyclic<T>
where
T: PartialOrd + Copy + Num,
{
type Coordinate = T;
type BinInterval = BinInterval<T>;

#[inline]
fn index(&self, coordinate: &Self::Coordinate) -> Option<usize> {
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::BinInterval> {
self.axis.bin(index + 1)
}
}

impl<T> Display for VariableCyclic<T>
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<T>
where
VariableCyclic<T>: Axis,
{
type Item = (usize, <VariableCyclic<T> as Axis>::BinInterval);
type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
4 changes: 2 additions & 2 deletions ndhistogram/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 2 additions & 0 deletions ndhistogram/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Loading

0 comments on commit 0861a7f

Please sign in to comment.