1use ndarray::{ArrayBase, Data, Ix1};
2use std::fmt::Debug;
3
4pub trait VectorExtensions<T> {
6 fn monotonic_prop(&self) -> Monotonic;
8
9 fn get_lower_index(&self, x: T) -> usize;
21}
22
23#[derive(Debug)]
25pub enum Monotonic {
26 Rising { strict: bool },
27 Falling { strict: bool },
28 NotMonotonic,
29}
30use num_traits::{cast, Num, NumCast};
31use Monotonic::*;
32
33use crate::interp1d::Linear;
34
35impl<S> VectorExtensions<S::Elem> for ArrayBase<S, Ix1>
36where
37 S: Data,
38 S::Elem: Debug + PartialOrd + Num + NumCast + Copy,
39{
40 fn monotonic_prop(&self) -> Monotonic {
41 if self.len() <= 1 {
42 return NotMonotonic;
43 };
44
45 self.windows(2)
46 .into_iter()
47 .try_fold(MonotonicState::start(), |state, items| {
48 let a = items[0];
49 let b = items[1];
50 state.update(a, b).short_circuit()
51 })
52 .map_or_else(|mon| mon, |state| state.finish())
53 }
54
55 fn get_lower_index(&self, x: S::Elem) -> usize {
56 if x <= self[0] {
62 return 0;
63 }
64 if x >= self[self.len() - 1] {
65 return self.len() - 2;
66 }
67
68 let mut range = (0usize, self.len() - 1);
71 let p1 = (
72 self[range.0],
73 cast(range.0)
74 .unwrap_or_else(|| unimplemented!("casting from usize should always work!")),
75 );
76 let p2 = (
77 self[range.1],
78 cast(range.1)
79 .unwrap_or_else(|| unimplemented!("casting from usize should always work!")),
80 );
81
82 let mid = Linear::calc_frac(p1, p2, x);
83 let mid_idx: usize =
84 cast(mid).unwrap_or_else(|| unimplemented!("failed to convert {mid:?} to usize"));
85
86 let mid_x = self[mid_idx];
87
88 if mid_x <= x && x < self[mid_idx + 1] {
89 return mid_idx;
90 }
91 if mid_x <= x {
92 range.0 = mid_idx;
94 } else {
95 range.1 = mid_idx;
96 }
97
98 while range.0 + 1 < range.1 {
101 let mid_idx = (range.1 - range.0) / 2 + range.0;
102 let mid_x = self[mid_idx];
103
104 if mid_x <= x {
105 range.0 = mid_idx;
106 } else {
107 range.1 = mid_idx;
108 }
109 }
110 range.0
111 }
112}
113
114#[derive(Debug)]
116enum MonotonicState {
117 Init,
118 NotStrict,
119 Likely(Monotonic),
120}
121
122impl MonotonicState {
123 fn start() -> Self {
125 Self::Init
126 }
127
128 fn update<T>(self, a: T, b: T) -> Self
130 where
131 T: PartialOrd,
132 {
133 use MonotonicState::*;
134 match self {
135 Init => {
136 if a < b {
137 Likely(Rising { strict: true })
138 } else if a == b {
139 NotStrict
140 } else {
141 Likely(Falling { strict: true })
142 }
143 }
144 NotStrict => {
145 if a < b {
146 Likely(Rising { strict: false })
147 } else if a == b {
148 NotStrict
149 } else {
150 Likely(Falling { strict: false })
151 }
152 }
153 Likely(Rising { strict }) => {
154 if a == b {
155 Likely(Rising { strict: false })
156 } else if a < b {
157 Likely(Rising { strict })
158 } else {
159 Likely(NotMonotonic)
160 }
161 }
162 Likely(Falling { strict }) => {
163 if a == b {
164 Likely(Falling { strict: false })
165 } else if a > b {
166 Likely(Falling { strict })
167 } else {
168 Likely(NotMonotonic)
169 }
170 }
171 Likely(NotMonotonic) => Likely(NotMonotonic),
172 }
173 }
174
175 fn short_circuit(self) -> Result<Self, Monotonic> {
181 match self {
182 MonotonicState::Likely(NotMonotonic) => Err(NotMonotonic),
183 _ => Ok(self),
184 }
185 }
186
187 fn finish(self) -> Monotonic {
192 match self {
193 MonotonicState::Init => panic!("`MonotonicState::update` was never called"),
194 MonotonicState::NotStrict => NotMonotonic,
195 MonotonicState::Likely(mon) => mon,
196 }
197 }
198}
199
200#[cfg(test)]
201mod test {
202 use ndarray::{array, s, Array, Array1};
203
204 use super::{Monotonic, VectorExtensions};
205
206 macro_rules! test_index {
207 ($i:expr, $q:expr) => {
208 let data = Array::linspace(0.0, 10.0, 11);
209 assert_eq!($i, data.get_lower_index($q));
210 };
211 ($i:expr, $q:expr, exp) => {
212 let data = Array::from_iter((0..11).map(|x| 2f64.powi(x)));
213 assert_eq!($i, data.get_lower_index($q));
214 };
215 ($i:expr, $q:expr, ln) => {
216 let data = Array::from_iter((0..11).map(|x| (x as f64).ln_1p()));
217 assert_eq!($i, data.get_lower_index($q));
218 };
219 }
220
221 #[test]
222 fn test_outside_left() {
223 test_index!(0, -1.0);
224 }
225
226 #[test]
227 fn test_outside_right() {
228 test_index!(9, 25.0);
229 }
230
231 #[test]
232 fn test_left_border() {
233 test_index!(0, 0.0);
234 }
235
236 #[test]
237 fn test_right_border() {
238 test_index!(9, 10.0);
239 }
240
241 #[test]
242 fn test_exact_index() {
243 for i in 0..10 {
244 test_index!(i, i as f64);
245 }
246 }
247
248 #[test]
249 fn test_index() {
250 for mut i in 0..100usize {
251 let q = i as f64 / 10.0;
252 i /= 10;
253 test_index!(i, q);
254 }
255 }
256
257 #[test]
258 fn test_pos_inf_index() {
259 test_index!(9, f64::INFINITY);
260 }
261
262 #[test]
263 fn test_neg_inf_index() {
264 test_index!(0, f64::NEG_INFINITY);
265 }
266
267 #[test]
268 #[should_panic(expected = "not implemented: failed to convert NaN to usize")]
269 fn test_nan() {
270 test_index!(0, f64::NAN);
271 }
272
273 #[test]
274 fn test_exponential_exact_index() {
275 for (i, q) in (0..10).map(|x| (x as usize, 2f64.powi(x))) {
276 test_index!(i, q, exp);
277 }
278 }
279
280 #[test]
281 fn test_exponential_index() {
282 for (i, q) in (0..100usize).map(|x| (x / 10, 2f64.powf(x as f64 / 10.0))) {
283 test_index!(i, q, exp);
284 }
285 }
286
287 #[test]
288 fn test_exponential_right_border() {
289 test_index!(9, 1024.0, exp);
290 }
291
292 #[test]
293 fn test_exponential_left_border() {
294 test_index!(0, 1.0, exp);
295 }
296
297 #[test]
298 fn test_log() {
299 for (i, q) in (0..100usize).map(|x| (x / 10, (x as f64 / 10.0).ln_1p())) {
300 test_index!(i, q, ln);
301 }
302 }
303
304 macro_rules! test_monotonic {
305 ($d:ident, $expected:pat) => {
306 match $d.monotonic_prop() {
307 $expected => (),
308 value => panic!("{}", format!("got {value:?}")),
309 };
310 match $d.slice(s![..;1]).monotonic_prop() {
311 $expected => (),
312 _ => panic!(),
313 };
314 };
315 }
316
317 #[test]
319 fn test_strict_monotonic_rising_f64() {
320 let data: Array1<f64> = array![1.1, 2.0, 3.123, 4.5];
321 test_monotonic!(data, Monotonic::Rising { strict: true });
322 }
323
324 #[test]
325 fn test_monotonic_rising_f64() {
326 let data: Array1<f64> = array![1.1, 2.0, 3.123, 3.123, 4.5];
327 test_monotonic!(data, Monotonic::Rising { strict: false });
328 }
329
330 #[test]
331 fn test_strict_monotonic_falling_f64() {
332 let data: Array1<f64> = array![5.8, 4.123, 3.1, 2.0, 1.0];
333 test_monotonic!(data, Monotonic::Falling { strict: true });
334 }
335
336 #[test]
337 fn test_monotonic_falling_f64() {
338 let data: Array1<f64> = array![5.8, 4.123, 3.1, 3.1, 2.0, 1.0];
339 test_monotonic!(data, Monotonic::Falling { strict: false });
340 }
341
342 #[test]
343 fn test_not_monotonic_f64() {
344 let data: Array1<f64> = array![1.1, 2.0, 3.123, 3.120, 4.5];
345 test_monotonic!(data, Monotonic::NotMonotonic);
346 }
347
348 #[test]
350 fn test_strict_monotonic_rising_i32() {
351 let data: Array1<i32> = array![1, 2, 3, 4, 5];
352 test_monotonic!(data, Monotonic::Rising { strict: true });
353 }
354
355 #[test]
356 fn test_monotonic_rising_i32() {
357 let data: Array1<i32> = array![1, 2, 3, 3, 4, 5];
358 test_monotonic!(data, Monotonic::Rising { strict: false });
359 }
360
361 #[test]
362 fn test_strict_monotonic_falling_i32() {
363 let data: Array1<i32> = array![5, 4, 3, 2, 1];
364 test_monotonic!(data, Monotonic::Falling { strict: true });
365 }
366
367 #[test]
368 fn test_monotonic_falling_i32() {
369 let data: Array1<i32> = array![5, 4, 3, 3, 2, 1];
370 test_monotonic!(data, Monotonic::Falling { strict: false });
371 }
372
373 #[test]
374 fn test_not_monotonic_i32() {
375 let data: Array1<i32> = array![1, 2, 3, 2, 4, 5];
376 test_monotonic!(data, Monotonic::NotMonotonic);
377 }
378
379 #[test]
380 fn test_ordered_view_on_unordred_array() {
381 let data: Array1<i32> = array![5, 4, 3, 2, 1];
382 let ordered = data.slice(s![..;-1]);
383 test_monotonic!(ordered, Monotonic::Rising { strict: true });
384 }
385
386 #[test]
387 fn test_starting_flat() {
388 let data: Array1<i32> = array![1, 1, 2, 3, 4, 5];
389 test_monotonic!(data, Monotonic::Rising { strict: false });
390 }
391
392 #[test]
393 fn test_flat() {
394 let data: Array1<i32> = array![1, 1, 1];
395 test_monotonic!(data, Monotonic::NotMonotonic);
396 }
397
398 #[test]
399 fn test_one_element_array() {
400 let data: Array1<i32> = array![1];
401 test_monotonic!(data, Monotonic::NotMonotonic);
402 }
403}