ndhistogram/axis/
uniform.rs1use std::fmt::{Debug, Display};
2
3use num_traits::{Float, Num, NumCast, NumOps};
4
5use crate::error::AxisError;
6
7use super::{Axis, BinInterval};
8
9#[derive(Default, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
33pub struct Uniform<T = f64> {
34 num: usize,
35 low: T,
36 high: T,
37 step: T,
38}
39
40impl<T> Uniform<T>
41where
42 T: PartialOrd + Num + NumCast + NumOps + Copy,
43{
44 pub fn new(num: usize, low: T, high: T) -> Result<Self, AxisError>
49 where
50 T: Float,
51 {
52 if num == 0 {
53 return Err(AxisError::InvalidNumberOfBins);
54 }
55 if low == high {
56 return Err(AxisError::InvalidAxisRange);
57 }
58 let (low, high) = if low > high { (high, low) } else { (low, high) };
59 let step = (high - low) / T::from(num).ok_or(AxisError::InvalidNumberOfBins)?;
60 Ok(Self {
61 num,
62 low,
63 high,
64 step,
65 })
66 }
67
68 pub fn with_step_size(num: usize, low: T, step: T) -> Result<Self, AxisError> {
73 let high = T::from(num).ok_or(AxisError::InvalidNumberOfBins)? * step + low;
74 if num == 0 {
75 return Err(AxisError::InvalidNumberOfBins);
76 }
77 if step <= T::zero() {
78 return Err(AxisError::InvalidStepSize);
79 }
80 let (low, high) = if low > high { (high, low) } else { (low, high) };
81 Ok(Self {
82 num,
83 low,
84 high,
85 step,
86 })
87 }
88}
89
90impl<T> Uniform<T> {
91 pub fn low(&self) -> &T {
93 &self.low
94 }
95
96 pub fn high(&self) -> &T {
98 &self.high
99 }
100}
101
102impl<T: PartialOrd + NumCast + NumOps + Copy> Axis for Uniform<T> {
104 type Coordinate = T;
105 type BinInterval = BinInterval<T>;
106
107 #[inline]
108 fn index(&self, coordinate: &Self::Coordinate) -> Option<usize> {
109 if coordinate < &self.low {
110 return Some(0);
111 }
112 if coordinate >= &self.high {
113 return Some(self.num + 1);
114 }
115 let steps = (*coordinate - self.low) / (self.step);
116 Some(steps.to_usize().unwrap_or(self.num) + 1)
117 }
118
119 fn num_bins(&self) -> usize {
120 self.num + 2
121 }
122
123 fn bin(&self, index: usize) -> std::option::Option<<Self as Axis>::BinInterval> {
124 if index == 0 {
125 return Some(Self::BinInterval::underflow(self.low));
126 } else if index == (self.num + 1) {
127 return Some(Self::BinInterval::overflow(self.high));
128 } else if index > (self.num + 1) {
129 return None;
130 }
131 let start =
132 self.low + (T::from(index - 1)?) * (self.high - self.low) / (T::from(self.num)?);
133 let end = self.low + (T::from(index)?) * (self.high - self.low) / (T::from(self.num)?);
134 Some(Self::BinInterval::new(start, end))
135 }
136
137 fn indices(&self) -> Box<dyn Iterator<Item = usize>> {
138 Box::new(0..self.num_bins())
139 }
140}
141
142impl<T: Display> Display for Uniform<T> {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 write!(
145 f,
146 "Axis{{# bins={}, range=[{}, {}), class={}}}",
147 self.num,
148 self.low,
149 self.high,
150 stringify!(Uniform)
151 )
152 }
153}
154
155impl<'a, T> IntoIterator for &'a Uniform<T>
156where
157 Uniform<T>: Axis,
158{
159 type Item = (usize, <Uniform<T> as Axis>::BinInterval);
160 type IntoIter = Box<dyn Iterator<Item = Self::Item> + 'a>;
161
162 fn into_iter(self) -> Self::IntoIter {
163 self.iter()
164 }
165}