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