Skip to content

Commit aeeb8ad

Browse files
authored
🕳️ handle gaps in x (#20)
* 🕳️ handle gaps with minmax downsampler * 🎀 use Option for searchsorted to handle gaps (#25) * 🎀 parallel searchsorted Option return * 🧹 * 🧹 match -> if let * 🧹 * ♻️ cleanup code * 🐛 x searchsorted bugfix * 🔥 add test for gaps in x * 🧹 cleanup code
1 parent b6291cb commit aeeb8ad

File tree

9 files changed

+434
-99
lines changed

9 files changed

+434
-99
lines changed

downsample_rs/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ num-traits = { version = "0.2.15", default-features = false }
1616
rayon = { version = "1.6.0", default-features = false }
1717

1818
[dev-dependencies]
19-
criterion = "0.3.0"
19+
criterion = "0.4.0"
2020
dev_utils = { path = "dev_utils" }
2121

2222
[[bench]]

downsample_rs/src/m4/generic.rs

Lines changed: 65 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@ use ndarray::{s, Array1, ArrayView1};
44
use rayon::iter::IndexedParallelIterator;
55
use rayon::prelude::*;
66

7+
// TODO: check for duplicate data in the output array
8+
// -> In the current implementation we always add 4 datapoints per bin (if of
9+
// course the bin has >= 4 datapoints). However, the argmin and argmax might
10+
// be the start and end of the bin, which would result in duplicate data in
11+
// the output array. (this is for example the case for monotonic data).
12+
713
// --------------------- WITHOUT X
814

915
#[inline(always)]
@@ -26,7 +32,6 @@ pub(crate) fn m4_generic<T: Copy + PartialOrd>(
2632
.exact_chunks(block_size)
2733
.into_iter()
2834
.enumerate()
29-
// .take(n_out / 4)
3035
.for_each(|(i, step)| {
3136
let (min_index, max_index) = f_argminmax(step);
3237

@@ -95,7 +100,7 @@ pub(crate) fn m4_generic_parallel<T: Copy + PartialOrd + Send + Sync>(
95100
#[inline(always)]
96101
pub(crate) fn m4_generic_with_x<T: Copy>(
97102
arr: ArrayView1<T>,
98-
bin_idx_iterator: impl Iterator<Item = (usize, usize)>,
103+
bin_idx_iterator: impl Iterator<Item = Option<(usize, usize)>>,
99104
n_out: usize,
100105
f_argminmax: fn(ArrayView1<T>) -> (usize, usize),
101106
) -> Array1<usize> {
@@ -105,35 +110,43 @@ pub(crate) fn m4_generic_with_x<T: Copy>(
105110
}
106111

107112
let arr_ptr = arr.as_ptr();
108-
let mut sampled_indices: Array1<usize> = Array1::<usize>::default(n_out);
109-
110-
bin_idx_iterator
111-
.enumerate()
112-
.for_each(|(i, (start_idx, end_idx))| {
113-
let step =
114-
unsafe { ArrayView1::from_shape_ptr(end_idx - start_idx, arr_ptr.add(start_idx)) };
115-
let (min_index, max_index) = f_argminmax(step);
116-
117-
sampled_indices[4 * i] = start_idx;
118-
119-
// Add the indexes in sorted order
120-
if min_index < max_index {
121-
sampled_indices[4 * i + 1] = min_index + start_idx;
122-
sampled_indices[4 * i + 2] = max_index + start_idx;
113+
let mut sampled_indices: Vec<usize> = Vec::with_capacity(n_out);
114+
115+
bin_idx_iterator.for_each(|bin| {
116+
if let Some((start, end)) = bin {
117+
if end <= start + 4 {
118+
// If the bin has <= 4 elements, just add them all
119+
for i in start..end {
120+
sampled_indices.push(i);
121+
}
123122
} else {
124-
sampled_indices[4 * i + 1] = max_index + start_idx;
125-
sampled_indices[4 * i + 2] = min_index + start_idx;
123+
// If the bin has > 4 elements, add the first and last + argmin and argmax
124+
let step = unsafe { ArrayView1::from_shape_ptr(end - start, arr_ptr.add(start)) };
125+
let (min_index, max_index) = f_argminmax(step);
126+
127+
sampled_indices.push(start);
128+
129+
// Add the indexes in sorted order
130+
if min_index < max_index {
131+
sampled_indices.push(min_index + start);
132+
sampled_indices.push(max_index + start);
133+
} else {
134+
sampled_indices.push(max_index + start);
135+
sampled_indices.push(min_index + start);
136+
}
137+
138+
sampled_indices.push(end - 1);
126139
}
127-
sampled_indices[4 * i + 3] = end_idx - 1;
128-
});
140+
}
141+
});
129142

130-
sampled_indices
143+
Array1::from_vec(sampled_indices)
131144
}
132145

133146
#[inline(always)]
134147
pub(crate) fn m4_generic_with_x_parallel<T: Copy + PartialOrd + Send + Sync>(
135148
arr: ArrayView1<T>,
136-
bin_idx_iterator: impl IndexedParallelIterator<Item = impl Iterator<Item = (usize, usize)>>,
149+
bin_idx_iterator: impl IndexedParallelIterator<Item = impl Iterator<Item = Option<(usize, usize)>>>,
137150
n_out: usize,
138151
f_argminmax: fn(ArrayView1<T>) -> (usize, usize),
139152
) -> Array1<usize> {
@@ -146,24 +159,37 @@ pub(crate) fn m4_generic_with_x_parallel<T: Copy + PartialOrd + Send + Sync>(
146159
bin_idx_iterator
147160
.flat_map(|bin_idx_iterator| {
148161
bin_idx_iterator
149-
.map(|(start, end)| {
150-
let step = unsafe {
151-
ArrayView1::from_shape_ptr(end - start, arr.as_ptr().add(start))
152-
};
153-
let (min_index, max_index) = f_argminmax(step);
154-
155-
// Add the indexes in sorted order
156-
let mut sampled_index = [start, 0, 0, end - 1];
157-
if min_index < max_index {
158-
sampled_index[1] = min_index + start;
159-
sampled_index[2] = max_index + start;
160-
} else {
161-
sampled_index[1] = max_index + start;
162-
sampled_index[2] = min_index + start;
162+
.map(|bin| {
163+
match bin {
164+
Some((start, end)) => {
165+
if end <= start + 4 {
166+
// If the bin has <= 4 elements, just return them all
167+
return (start..end).collect::<Vec<usize>>();
168+
}
169+
170+
// If the bin has > 4 elements, return the first and last + argmin and argmax
171+
let step = unsafe {
172+
ArrayView1::from_shape_ptr(end - start, arr.as_ptr().add(start))
173+
};
174+
let (min_index, max_index) = f_argminmax(step);
175+
176+
// Return the indexes in sorted order
177+
let mut sampled_index = vec![start, 0, 0, end - 1];
178+
if min_index < max_index {
179+
sampled_index[1] = min_index + start;
180+
sampled_index[2] = max_index + start;
181+
} else {
182+
sampled_index[1] = max_index + start;
183+
sampled_index[2] = min_index + start;
184+
}
185+
sampled_index
186+
} // If the bin is empty, return empty Vec
187+
None => {
188+
vec![]
189+
}
163190
}
164-
sampled_index
165191
})
166-
.collect::<Vec<[usize; 4]>>()
192+
.collect::<Vec<Vec<usize>>>()
167193
})
168194
.flatten()
169195
.collect::<Vec<usize>>(),

downsample_rs/src/m4/scalar.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,72 @@ mod tests {
166166
assert_eq!(sampled_values, Array1::from(expected_values));
167167
}
168168

169+
#[test]
170+
fn test_m4_scalar_with_x_gap() {
171+
// We will create a gap in the middle of the array
172+
let x = (0..101).collect::<Vec<i32>>();
173+
174+
// Increment the second half of the array by 50
175+
let x = x
176+
.iter()
177+
.map(|x| if *x > 50 { *x + 50 } else { *x })
178+
.collect::<Vec<i32>>();
179+
let x = Array1::from(x);
180+
let arr = (0..101).map(|x| x as f32).collect::<Vec<f32>>();
181+
let arr = Array1::from(arr);
182+
183+
let sampled_indices = m4_scalar_with_x(x.view(), arr.view(), 20);
184+
assert_eq!(sampled_indices.len(), 16); // One full gap
185+
let expected_indices = vec![0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99];
186+
assert_eq!(sampled_indices, Array1::from(expected_indices));
187+
188+
// Increment the second half of the array by 50 again
189+
let x = x
190+
.iter()
191+
.map(|x| if *x > 101 { *x + 50 } else { *x })
192+
.collect::<Vec<i32>>();
193+
let x = Array1::from(x);
194+
195+
let sampled_indices = m4_scalar_with_x(x.view(), arr.view(), 20);
196+
assert_eq!(sampled_indices.len(), 17); // Gap with 1 value
197+
let expected_indices = vec![
198+
0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99, 100,
199+
];
200+
}
201+
202+
#[test]
203+
fn test_m4_scalar_with_x_gap_parallel() {
204+
// We will create a gap in the middle of the array
205+
let x = (0..101).collect::<Vec<i32>>();
206+
207+
// Increment the second half of the array by 50
208+
let x = x
209+
.iter()
210+
.map(|x| if *x > 50 { *x + 50 } else { *x })
211+
.collect::<Vec<i32>>();
212+
let x = Array1::from(x);
213+
let arr = (0..101).map(|x| x as f32).collect::<Vec<f32>>();
214+
let arr = Array1::from(arr);
215+
216+
let sampled_indices = m4_scalar_with_x_parallel(x.view(), arr.view(), 20);
217+
assert_eq!(sampled_indices.len(), 16); // One full gap
218+
let expected_indices = vec![0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99];
219+
assert_eq!(sampled_indices, Array1::from(expected_indices));
220+
221+
// Increment the second half of the array by 50 again
222+
let x = x
223+
.iter()
224+
.map(|x| if *x > 101 { *x + 50 } else { *x })
225+
.collect::<Vec<i32>>();
226+
let x = Array1::from(x);
227+
228+
let sampled_indices = m4_scalar_with_x_parallel(x.view(), arr.view(), 20);
229+
assert_eq!(sampled_indices.len(), 17); // Gap with 1 value
230+
let expected_indices = vec![
231+
0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99, 100,
232+
];
233+
}
234+
169235
#[test]
170236
fn test_many_random_runs_correct() {
171237
let n: usize = 20_001; // not 20_000 because then the last bin is not "full"

downsample_rs/src/m4/simd.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,72 @@ mod tests {
161161
assert_eq!(sampled_values, Array1::from(expected_values));
162162
}
163163

164+
#[test]
165+
fn test_m4_simd_with_x_gap() {
166+
// We will create a gap in the middle of the array
167+
let x = (0..101).collect::<Vec<i32>>();
168+
169+
// Increment the second half of the array by 50
170+
let x = x
171+
.iter()
172+
.map(|x| if *x > 50 { *x + 50 } else { *x })
173+
.collect::<Vec<i32>>();
174+
let x = Array1::from(x);
175+
let arr = (0..101).map(|x| x as f32).collect::<Vec<f32>>();
176+
let arr = Array1::from(arr);
177+
178+
let sampled_indices = m4_simd_with_x(x.view(), arr.view(), 20);
179+
assert_eq!(sampled_indices.len(), 16); // One full gap
180+
let expected_indices = vec![0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99];
181+
assert_eq!(sampled_indices, Array1::from(expected_indices));
182+
183+
// Increment the second half of the array by 50 again
184+
let x = x
185+
.iter()
186+
.map(|x| if *x > 101 { *x + 50 } else { *x })
187+
.collect::<Vec<i32>>();
188+
let x = Array1::from(x);
189+
190+
let sampled_indices = m4_simd_with_x(x.view(), arr.view(), 20);
191+
assert_eq!(sampled_indices.len(), 17); // Gap with 1 value
192+
let expected_indices = vec![
193+
0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99, 100,
194+
];
195+
}
196+
197+
#[test]
198+
fn test_m4_simd_with_x_gap_parallel() {
199+
// We will create a gap in the middle of the array
200+
let x = (0..101).collect::<Vec<i32>>();
201+
202+
// Increment the second half of the array by 50
203+
let x = x
204+
.iter()
205+
.map(|x| if *x > 50 { *x + 50 } else { *x })
206+
.collect::<Vec<i32>>();
207+
let x = Array1::from(x);
208+
let arr = (0..101).map(|x| x as f32).collect::<Vec<f32>>();
209+
let arr = Array1::from(arr);
210+
211+
let sampled_indices = m4_simd_with_x_parallel(x.view(), arr.view(), 20);
212+
assert_eq!(sampled_indices.len(), 16); // One full gap
213+
let expected_indices = vec![0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99];
214+
assert_eq!(sampled_indices, Array1::from(expected_indices));
215+
216+
// Increment the second half of the array by 50 again
217+
let x = x
218+
.iter()
219+
.map(|x| if *x > 101 { *x + 50 } else { *x })
220+
.collect::<Vec<i32>>();
221+
let x = Array1::from(x);
222+
223+
let sampled_indices = m4_simd_with_x_parallel(x.view(), arr.view(), 20);
224+
assert_eq!(sampled_indices.len(), 17); // Gap with 1 value
225+
let expected_indices = vec![
226+
0, 0, 29, 29, 30, 30, 50, 50, 51, 51, 69, 69, 70, 70, 99, 99, 100,
227+
];
228+
}
229+
164230
#[test]
165231
fn test_many_random_runs_correct() {
166232
let n = 20_001; // not 20_000 because then the last bin is not "full"

0 commit comments

Comments
 (0)