Skip to main content

cgrustplot/helper/
math.rs

1const D: f64 = 1e-6;
2const HALF_INV_D: f64 = 0.5 / D;  // Statically precomputed for der function
3
4/// Finds the minimum value of a vector (or default if length is zero).
5/// 
6/// # Examples
7/// ```
8/// use cgrustplot::helper::math::min_always;
9/// let result = min_always(&vec![1., 2., 3., -4., f64::NAN], 0.);
10/// assert_eq!(result, -4.);
11/// ```
12/// 
13/// # Notes
14/// 
15/// Nan-valued elements are ignored.
16pub fn min_always<T: PartialOrd + Copy>(v: &Vec<T>, default: T) -> T {
17    match v.iter()
18    .filter(|i| i == i) // Filter out NaN Values
19    .min_by(|x, y| {
20        // None case unused for floats and ints 
21        match x.partial_cmp(y) {Some(ord) => ord, None => std::cmp::Ordering::Equal}
22    }) {Some(val) => *val, None => default} // for empty iterator
23}
24
25/// Finds the maximum value of a vector (or default if length is zero).
26/// 
27/// # Examples
28/// ```
29/// use cgrustplot::helper::math::max_always;
30/// let result = max_always(&vec![1., 2., 3., -4., f64::NAN], 0.);
31/// assert_eq!(result, 3.);
32/// ```
33/// 
34/// # Notes
35/// 
36/// Nan-valued elements are ignored.
37pub fn max_always<T: PartialOrd + Copy>(v: &Vec<T>, default: T) -> T {
38    match v.iter()
39    .filter(|i| i == i) // Filter out NaN Values
40    .max_by(|x, y| {
41        // None case unused for floats and ints 
42        match x.partial_cmp(y) {Some(ord) => ord, None => std::cmp::Ordering::Equal}
43    }) {Some(val) => *val, None => default} // for empty iterator
44}
45
46/// Subdivides an inclusive interval into n equally-spaced points.
47/// 
48/// # Arguments
49/// 
50/// * `low` - Minimum value of the interval.
51/// * `high` - Maximum value of the interval.
52/// * `n` - Number of output points
53/// 
54/// # Examples
55/// ```
56/// use cgrustplot::helper::math::subdivide;
57/// let result = subdivide(0., 5., 6);
58/// assert_eq!(result, vec![0., 1., 2., 3., 4., 5.]);
59/// ```
60/// 
61/// # Notes
62/// 
63/// Returns an empty vector if n is zero.
64/// 
65/// Returns a constant vector if low == high.
66/// 
67pub fn subdivide(low: f64, high: f64, n: u32) -> Vec<f64> {
68    if n == 0 {return Vec::new()}
69
70    let diff = (high - low) / (n - 1) as f64;
71
72    (0..n).map(|i| low + (i as f64) * diff).collect()
73}
74
75/// Subdivides an inclusive interval into n equally-spaced integers.
76/// 
77/// Just a rounded version of subdivide().
78/// 
79/// # Arguments
80/// 
81/// * `low` - Minimum value of the interval.
82/// * `high` - Maximum value of the interval.
83/// * `n` - Number of output points
84/// 
85/// # Examples
86/// ```
87/// use cgrustplot::helper::math::subdivide_round;
88/// let result = subdivide_round(0, 100, 6);
89/// assert_eq!(result, vec![0, 20, 40, 60, 80, 100]);
90/// ```
91/// 
92/// # Notes
93/// 
94/// Returns an empty vector if n is zero.
95/// Returns a constant vector if low == high
96/// 
97pub fn subdivide_round(low: i32, high: i32, n: u32) -> Vec<i32> {
98    subdivide(low as f64, high as f64, n).into_iter().map(|i| i.round() as i32).collect()
99}
100
101/// Pads an interval by a proportion of it's width.
102/// 
103/// # Examples
104/// ```
105/// use cgrustplot::helper::math::pad_range;
106/// let result = pad_range((0., 1.), 0.1);
107/// assert_eq!(result, (-0.1, 1.1));
108/// ```
109/// 
110/// ```
111/// use cgrustplot::helper::math::pad_range;
112/// let result = pad_range((-1., 1.), 0.1);
113/// assert_eq!(result, (-1.2, 1.2));
114/// ```
115pub fn pad_range(bounds: (f64, f64), padding: f64) -> (f64, f64) {
116    let dif = bounds.1 - bounds.0;
117
118    (bounds.0 - padding * dif, bounds.1 + padding * dif)
119}
120
121/// Converts a vector of bits into a u8.
122/// Vector's length must not exceed 8.
123pub(crate) fn bin_to_u8(bin: Vec<bool>) -> u8 {
124    assert!(bin.len() <= 8);
125    bin.iter().enumerate().fold(0u8, |acc, (i, &b)| acc | ((b as u8) << i))
126}
127
128/// Optimized integer cieling division
129/// 
130/// Equivalent to `(a as f64 / b as f64).ciel() as T` but
131/// by only using integer arithemtic.
132pub(crate) fn ciel_div<T: num::Integer + Copy>(a: T, b: T) -> T {
133    (a + b.clone() - T::one()) / b
134}
135
136/// Generates the derivative of a function with the centered finite difference method.
137/// 
138/// der(f)(x) = (f(x + D) - f(x - D)) / (2 * D)
139/// 
140/// # Example
141/// ```
142/// use cgrustplot::helper::math::der;
143/// let f = |x: f64| x * x;  // f(x) = x^2
144/// let d = der(f);          // f'(x) = 2 * x
145/// let result = d(2.);      // f'(2) = 4
146/// assert!((result - 4.).abs() < 1e-6);
147/// ```
148pub fn der<F: Fn(f64) -> f64>(f: F) -> impl Fn(f64) -> f64 {
149    move |x: f64| (f(x + D) - f(x - D)) * HALF_INV_D
150}
151
152/// Generates the derivative of a function with the centered finite difference method.
153/// 
154/// der(f, x) = (f(x + D) - f(x - D)) / (2 * D)
155/// 
156/// # Example
157/// ```
158/// use cgrustplot::helper::math::der_p;
159/// let f = |x: f64| x * x; // f(x) = x^2
160/// let result = der_p(f, 2.); // f'(2) = 4
161/// assert!((result - 4.).abs() < 1e-6);
162/// ```
163pub fn der_p<F: Fn(f64) -> f64>(f: F, x: f64) -> f64 {
164    (f(x + D) - f(x - D)) * HALF_INV_D
165}
166
167/// Non-NAN numerical wrapper type
168pub(crate) mod non_nan_type {
169    use std::hash::{Hash, Hasher};
170
171    #[derive(Debug)]
172    pub(crate) struct NonNanWrapper<T: PartialOrd + Copy> {
173        num: T,
174    }
175
176    impl<T: PartialOrd + Copy> NonNanWrapper<T> {
177        #[allow(dead_code)] // It is used within unit tests
178        pub(crate) fn value(&self) -> T {self.num}
179    }
180
181    impl<T: PartialOrd + Copy> From<T> for NonNanWrapper<T> {
182        fn from(value: T) -> Self {
183            assert!(value == value, "Value for NonNanWrapper is NaN");
184            NonNanWrapper {num: value}
185        }
186    }
187
188    impl<T: PartialOrd  + Copy> Clone for NonNanWrapper<T> {
189        fn clone(&self) -> Self {*self}
190        fn clone_from(&mut self, source: &Self) {*self = *source;}
191    }
192    impl<T: PartialOrd + Copy> Copy for NonNanWrapper<T> {}
193
194    impl<T: PartialOrd + Copy> PartialEq for NonNanWrapper<T> {
195        fn eq(&self, other: &Self) -> bool {
196            self.num == other.num
197        }
198    }
199    impl<T: PartialOrd + Copy> Eq for NonNanWrapper<T> {}
200
201    impl<T: PartialOrd + Copy> PartialOrd for NonNanWrapper<T> {
202        fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
203            Some(self.cmp(other))
204        }
205    }
206    impl<T: PartialOrd + Copy> Ord for NonNanWrapper<T> {
207        fn cmp(&self, other: &Self) -> std::cmp::Ordering {
208            self.num.partial_cmp(&other.num).unwrap()
209        }
210    }
211    impl<T: PartialOrd + Copy> Hash for NonNanWrapper<T> {
212        fn hash<H: Hasher>(&self, state: &mut H) {
213            // Get the underlying bytes of self and use those for hashing.
214            // Because T: Copy, a hash can be generated from the byte value,
215            // so it doesn't cause issues with references.
216            unsafe { 
217                std::slice::from_raw_parts(
218                    &self.num as *const T as *const u8, 
219                    std::mem::size_of::<T>()
220                )
221            }.hash(state);
222        }
223    }
224}
225