ndarray_histogram/quantile/
interpolate.rs

1//! Interpolation strategies.
2use num_traits::{Float, FromPrimitive, NumOps, ToPrimitive};
3
4fn float_quantile_index<F: Float>(q: F, len: usize) -> F {
5	q * (F::from(len - 1).unwrap())
6}
7
8/// Returns the fraction that the quantile is between the lower and higher indices.
9///
10/// This ranges from 0, where the quantile exactly corresponds the lower index,
11/// to 1, where the quantile exactly corresponds to the higher index.
12fn float_quantile_index_fraction<F: Float>(q: F, len: usize) -> F {
13	float_quantile_index(q, len).fract()
14}
15
16/// Returns the index of the value on the lower side of the quantile.
17pub(crate) fn lower_index<F: Float>(q: F, len: usize) -> usize {
18	float_quantile_index(q, len).floor().to_usize().unwrap()
19}
20
21/// Returns the index of the value on the higher side of the quantile.
22pub(crate) fn higher_index<F: Float>(q: F, len: usize) -> usize {
23	float_quantile_index(q, len).ceil().to_usize().unwrap()
24}
25
26/// Used to provide an interpolation strategy to [`quantile_axis_mut`].
27///
28/// [`quantile_axis_mut`]: ../trait.QuantileExt.html#tymethod.quantile_axis_mut
29pub trait Interpolate<T> {
30	/// Returns `true` iff the lower value is needed to compute the
31	/// interpolated value.
32	#[doc(hidden)]
33	fn needs_lower<F: Float>(q: F, len: usize) -> bool;
34
35	/// Returns `true` iff the higher value is needed to compute the
36	/// interpolated value.
37	#[doc(hidden)]
38	fn needs_higher<F: Float>(q: F, len: usize) -> bool;
39
40	/// Computes the interpolated value.
41	///
42	/// **Panics** if `None` is provided for the lower value when it's needed
43	/// or if `None` is provided for the higher value when it's needed.
44	#[doc(hidden)]
45	fn interpolate<F: Float>(lower: Option<T>, higher: Option<T>, q: F, len: usize) -> T;
46
47	private_decl! {}
48}
49
50/// Select the higher value.
51pub struct Higher;
52/// Select the lower value.
53pub struct Lower;
54/// Select the nearest value.
55pub struct Nearest;
56/// Select the midpoint of the two values (`(lower + higher) / 2`).
57pub struct Midpoint;
58/// Linearly interpolate between the two values
59/// (`lower + (higher - lower) * fraction`, where `fraction` is the
60/// fractional part of the index surrounded by `lower` and `higher`).
61pub struct Linear;
62
63impl<T> Interpolate<T> for Higher {
64	fn needs_lower<F: Float>(_q: F, _len: usize) -> bool {
65		false
66	}
67	fn needs_higher<F: Float>(_q: F, _len: usize) -> bool {
68		true
69	}
70	fn interpolate<F: Float>(_lower: Option<T>, higher: Option<T>, _q: F, _len: usize) -> T {
71		higher.unwrap()
72	}
73	private_impl! {}
74}
75
76impl<T> Interpolate<T> for Lower {
77	fn needs_lower<F: Float>(_q: F, _len: usize) -> bool {
78		true
79	}
80	fn needs_higher<F: Float>(_q: F, _len: usize) -> bool {
81		false
82	}
83	fn interpolate<F: Float>(lower: Option<T>, _higher: Option<T>, _q: F, _len: usize) -> T {
84		lower.unwrap()
85	}
86	private_impl! {}
87}
88
89impl<T> Interpolate<T> for Nearest {
90	fn needs_lower<F: Float>(q: F, len: usize) -> bool {
91		float_quantile_index_fraction(q, len) < F::from(0.5).unwrap()
92	}
93	fn needs_higher<F: Float>(q: F, len: usize) -> bool {
94		!<Self as Interpolate<T>>::needs_lower(q, len)
95	}
96	fn interpolate<F: Float>(lower: Option<T>, higher: Option<T>, q: F, len: usize) -> T {
97		if <Self as Interpolate<T>>::needs_lower(q, len) {
98			lower.unwrap()
99		} else {
100			higher.unwrap()
101		}
102	}
103	private_impl! {}
104}
105
106impl<T> Interpolate<T> for Midpoint
107where
108	T: NumOps + Clone + FromPrimitive,
109{
110	fn needs_lower<F: Float>(_q: F, _len: usize) -> bool {
111		true
112	}
113	fn needs_higher<F: Float>(_q: F, _len: usize) -> bool {
114		true
115	}
116	fn interpolate<F: Float>(lower: Option<T>, higher: Option<T>, _q: F, _len: usize) -> T {
117		let denom = T::from_u8(2).unwrap();
118		let lower = lower.unwrap();
119		let higher = higher.unwrap();
120		lower.clone() + (higher - lower) / denom
121	}
122	private_impl! {}
123}
124
125impl<T> Interpolate<T> for Linear
126where
127	T: NumOps + Clone + FromPrimitive + ToPrimitive,
128{
129	fn needs_lower<F: Float>(_q: F, _len: usize) -> bool {
130		true
131	}
132	fn needs_higher<F: Float>(_q: F, _len: usize) -> bool {
133		true
134	}
135	fn interpolate<F: Float>(lower: Option<T>, higher: Option<T>, q: F, len: usize) -> T {
136		let fraction = float_quantile_index_fraction(q, len).to_f64().unwrap();
137		let lower = lower.unwrap();
138		let higher = higher.unwrap();
139		let lower_f64 = lower.to_f64().unwrap();
140		let higher_f64 = higher.to_f64().unwrap();
141		lower + T::from_f64(fraction * (higher_f64 - lower_f64)).unwrap()
142	}
143	private_impl! {}
144}