Skip to main content

vortex_array/compute/conformance/
take.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_buffer::buffer;
5use vortex_error::VortexExpect;
6
7use crate::ArrayRef;
8use crate::Canonical;
9use crate::ExecutionCtx;
10use crate::IntoArray as _;
11use crate::arrays::PrimitiveArray;
12use crate::dtype::Nullability;
13
14/// Test conformance of the take compute function for an array.
15///
16/// This function tests various scenarios including:
17/// - Taking all elements
18/// - Taking no elements
19/// - Taking selective elements
20/// - Taking with out-of-bounds indices (should panic)
21/// - Taking with nullable indices
22/// - Edge cases like empty arrays
23pub fn test_take_conformance(array: &ArrayRef, ctx: &mut ExecutionCtx) {
24    let len = array.len();
25
26    if len > 0 {
27        test_take_all(array, ctx);
28        test_take_none(array);
29        test_take_selective(array, ctx);
30        test_take_first_and_last(array, ctx);
31        test_take_with_nullable_indices(array, ctx);
32        test_take_repeated_indices(array, ctx);
33    }
34
35    test_empty_indices(array);
36
37    // Additional edge cases for non-empty arrays
38    if len > 0 {
39        test_take_reverse(array, ctx);
40        test_take_single_middle(array, ctx);
41    }
42
43    if len > 3 {
44        test_take_random_unsorted(array, ctx);
45        test_take_contiguous_range(array, ctx);
46        test_take_mixed_repeated(array, ctx);
47    }
48
49    // Test for larger arrays
50    if len >= 1024 {
51        test_take_large_indices(array, ctx);
52    }
53}
54
55fn test_take_all(array: &ArrayRef, ctx: &mut ExecutionCtx) {
56    let len = array.len();
57    let indices = PrimitiveArray::from_iter(0..len as u64);
58    let result = array
59        .take(indices.into_array())
60        .vortex_expect("take should succeed in conformance test");
61
62    assert_eq!(result.len(), len);
63    assert_eq!(result.dtype(), array.dtype());
64
65    // Verify elements match
66    let array_canonical = array
67        .clone()
68        .execute::<Canonical>(ctx)
69        .vortex_expect("to_canonical failed on array");
70    let result_canonical = result
71        .clone()
72        .execute::<Canonical>(ctx)
73        .vortex_expect("to_canonical failed on result");
74    match (array_canonical, result_canonical) {
75        (Canonical::Primitive(orig_prim), Canonical::Primitive(result_prim)) => {
76            assert_eq!(
77                orig_prim.buffer_handle().to_host_sync(),
78                result_prim.buffer_handle().to_host_sync()
79            );
80        }
81        _ => {
82            // For non-primitive types, check scalar values
83            for i in 0..len {
84                assert_eq!(
85                    array
86                        .execute_scalar(i, ctx)
87                        .vortex_expect("scalar_at should succeed in conformance test"),
88                    result
89                        .execute_scalar(i, ctx)
90                        .vortex_expect("scalar_at should succeed in conformance test")
91                );
92            }
93        }
94    }
95}
96
97fn test_take_none(array: &ArrayRef) {
98    let indices: PrimitiveArray = PrimitiveArray::from_iter::<[u64; 0]>([]);
99    let result = array
100        .take(indices.into_array())
101        .vortex_expect("take should succeed in conformance test");
102
103    assert_eq!(result.len(), 0);
104    assert_eq!(result.dtype(), array.dtype());
105}
106
107#[expect(clippy::cast_possible_truncation)]
108fn test_take_selective(array: &ArrayRef, ctx: &mut ExecutionCtx) {
109    let len = array.len();
110
111    // Take every other element
112    let indices: Vec<u64> = (0..len as u64).step_by(2).collect();
113    let expected_len = indices.len();
114    let indices_array = PrimitiveArray::from_iter(indices.clone());
115
116    let result = array
117        .take(indices_array.into_array())
118        .vortex_expect("take should succeed in conformance test");
119    assert_eq!(result.len(), expected_len);
120
121    // Verify the taken elements
122    for (result_idx, &original_idx) in indices.iter().enumerate() {
123        assert_eq!(
124            array
125                .execute_scalar(original_idx as usize, ctx)
126                .vortex_expect("scalar_at should succeed in conformance test"),
127            result
128                .execute_scalar(result_idx, ctx)
129                .vortex_expect("scalar_at should succeed in conformance test")
130        );
131    }
132}
133
134fn test_take_first_and_last(array: &ArrayRef, ctx: &mut ExecutionCtx) {
135    let len = array.len();
136    let indices = PrimitiveArray::from_iter([0u64, (len - 1) as u64]);
137    let result = array
138        .take(indices.into_array())
139        .vortex_expect("take should succeed in conformance test");
140
141    assert_eq!(result.len(), 2);
142    assert_eq!(
143        array
144            .execute_scalar(0, ctx)
145            .vortex_expect("scalar_at should succeed in conformance test"),
146        result
147            .execute_scalar(0, ctx)
148            .vortex_expect("scalar_at should succeed in conformance test")
149    );
150    assert_eq!(
151        array
152            .execute_scalar(len - 1, ctx)
153            .vortex_expect("scalar_at should succeed in conformance test"),
154        result
155            .execute_scalar(1, ctx)
156            .vortex_expect("scalar_at should succeed in conformance test")
157    );
158}
159
160#[expect(clippy::cast_possible_truncation)]
161fn test_take_with_nullable_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
162    let len = array.len();
163
164    // Create indices with some null values
165    let indices_vec: Vec<Option<u64>> = if len >= 3 {
166        vec![Some(0), None, Some((len - 1) as u64)]
167    } else if len >= 2 {
168        vec![Some(0), None]
169    } else {
170        vec![None]
171    };
172
173    let indices = PrimitiveArray::from_option_iter(indices_vec.clone());
174    let result = array
175        .take(indices.into_array())
176        .vortex_expect("take should succeed in conformance test");
177
178    assert_eq!(result.len(), indices_vec.len());
179    assert_eq!(
180        result.dtype(),
181        &array.dtype().with_nullability(Nullability::Nullable)
182    );
183
184    // Verify values
185    for (i, idx_opt) in indices_vec.iter().enumerate() {
186        match idx_opt {
187            Some(idx) => {
188                let expected = array
189                    .execute_scalar(*idx as usize, ctx)
190                    .vortex_expect("scalar_at should succeed in conformance test");
191                let actual = result
192                    .execute_scalar(i, ctx)
193                    .vortex_expect("scalar_at should succeed in conformance test");
194                assert_eq!(expected, actual);
195            }
196            None => {
197                assert!(
198                    result
199                        .execute_scalar(i, ctx)
200                        .vortex_expect("scalar_at should succeed in conformance test")
201                        .is_null()
202                );
203            }
204        }
205    }
206}
207
208fn test_take_repeated_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
209    if array.is_empty() {
210        return;
211    }
212
213    // Take the first element multiple times
214    let indices = buffer![0u64, 0, 0].into_array();
215    let result = array
216        .take(indices)
217        .vortex_expect("take should succeed in conformance test");
218
219    assert_eq!(result.len(), 3);
220    let first_elem = array
221        .execute_scalar(0, ctx)
222        .vortex_expect("scalar_at should succeed in conformance test");
223    for i in 0..3 {
224        assert_eq!(
225            result
226                .execute_scalar(i, ctx)
227                .vortex_expect("scalar_at should succeed in conformance test"),
228            first_elem
229        );
230    }
231}
232
233fn test_empty_indices(array: &ArrayRef) {
234    let indices = PrimitiveArray::empty::<u64>(Nullability::NonNullable);
235    let result = array
236        .take(indices.into_array())
237        .vortex_expect("take should succeed in conformance test");
238
239    assert_eq!(result.len(), 0);
240    assert_eq!(result.dtype(), array.dtype());
241}
242
243fn test_take_reverse(array: &ArrayRef, ctx: &mut ExecutionCtx) {
244    let len = array.len();
245    // Take elements in reverse order
246    let indices = PrimitiveArray::from_iter((0..len as u64).rev());
247    let result = array
248        .take(indices.into_array())
249        .vortex_expect("take should succeed in conformance test");
250
251    assert_eq!(result.len(), len);
252
253    // Verify elements are in reverse order
254    for i in 0..len {
255        assert_eq!(
256            array
257                .execute_scalar(len - 1 - i, ctx)
258                .vortex_expect("scalar_at should succeed in conformance test"),
259            result
260                .execute_scalar(i, ctx)
261                .vortex_expect("scalar_at should succeed in conformance test")
262        );
263    }
264}
265
266fn test_take_single_middle(array: &ArrayRef, ctx: &mut ExecutionCtx) {
267    let len = array.len();
268    let middle_idx = len / 2;
269
270    let indices = PrimitiveArray::from_iter([middle_idx as u64]);
271    let result = array
272        .take(indices.into_array())
273        .vortex_expect("take should succeed in conformance test");
274
275    assert_eq!(result.len(), 1);
276    assert_eq!(
277        array
278            .execute_scalar(middle_idx, ctx)
279            .vortex_expect("scalar_at should succeed in conformance test"),
280        result
281            .execute_scalar(0, ctx)
282            .vortex_expect("scalar_at should succeed in conformance test")
283    );
284}
285
286#[expect(clippy::cast_possible_truncation)]
287fn test_take_random_unsorted(array: &ArrayRef, ctx: &mut ExecutionCtx) {
288    let len = array.len();
289
290    // Create a pseudo-random but deterministic pattern
291    let mut indices = Vec::with_capacity(len.min(10));
292    let mut idx = 1u64;
293    for _ in 0..len.min(10) {
294        indices.push((idx * 7 + 3) % len as u64);
295        idx = (idx * 3 + 1) % len as u64;
296    }
297
298    let indices_array = PrimitiveArray::from_iter(indices.clone());
299    let result = array
300        .take(indices_array.into_array())
301        .vortex_expect("take should succeed in conformance test");
302
303    assert_eq!(result.len(), indices.len());
304
305    // Verify elements match
306    for (i, &idx) in indices.iter().enumerate() {
307        assert_eq!(
308            array
309                .execute_scalar(idx as usize, ctx)
310                .vortex_expect("scalar_at should succeed in conformance test"),
311            result
312                .execute_scalar(i, ctx)
313                .vortex_expect("scalar_at should succeed in conformance test")
314        );
315    }
316}
317
318fn test_take_contiguous_range(array: &ArrayRef, ctx: &mut ExecutionCtx) {
319    let len = array.len();
320    let start = len / 4;
321    let end = len / 2;
322
323    // Take a contiguous range from the middle
324    let indices = PrimitiveArray::from_iter(start as u64..end as u64);
325    let result = array
326        .take(indices.into_array())
327        .vortex_expect("take should succeed in conformance test");
328
329    assert_eq!(result.len(), end - start);
330
331    // Verify elements
332    for i in 0..(end - start) {
333        assert_eq!(
334            array
335                .execute_scalar(start + i, ctx)
336                .vortex_expect("scalar_at should succeed in conformance test"),
337            result
338                .execute_scalar(i, ctx)
339                .vortex_expect("scalar_at should succeed in conformance test")
340        );
341    }
342}
343
344#[expect(clippy::cast_possible_truncation)]
345fn test_take_mixed_repeated(array: &ArrayRef, ctx: &mut ExecutionCtx) {
346    let len = array.len();
347
348    // Create pattern with some repeated indices
349    let indices = vec![
350        0u64,
351        0,
352        1,
353        1,
354        len as u64 / 2,
355        len as u64 / 2,
356        len as u64 / 2,
357        (len - 1) as u64,
358    ];
359
360    let indices_array = PrimitiveArray::from_iter(indices.clone());
361    let result = array
362        .take(indices_array.into_array())
363        .vortex_expect("take should succeed in conformance test");
364
365    assert_eq!(result.len(), indices.len());
366
367    // Verify elements
368    for (i, &idx) in indices.iter().enumerate() {
369        assert_eq!(
370            array
371                .execute_scalar(idx as usize, ctx)
372                .vortex_expect("scalar_at should succeed in conformance test"),
373            result
374                .execute_scalar(i, ctx)
375                .vortex_expect("scalar_at should succeed in conformance test")
376        );
377    }
378}
379
380#[expect(clippy::cast_possible_truncation)]
381fn test_take_large_indices(array: &ArrayRef, ctx: &mut ExecutionCtx) {
382    // Test with a large number of indices to stress test performance
383    let len = array.len();
384    let num_indices = 10000.min(len * 3);
385
386    // Create many indices with a pattern
387    let indices: Vec<u64> = (0..num_indices)
388        .map(|i| ((i * 17 + 5) % len) as u64)
389        .collect();
390
391    let indices_array = PrimitiveArray::from_iter(indices.clone());
392    let result = array
393        .take(indices_array.into_array())
394        .vortex_expect("take should succeed in conformance test");
395
396    assert_eq!(result.len(), num_indices);
397
398    // Spot check a few elements
399    for i in (0..num_indices).step_by(1000) {
400        let expected_idx = indices[i] as usize;
401        assert_eq!(
402            array
403                .execute_scalar(expected_idx, ctx)
404                .vortex_expect("scalar_at should succeed in conformance test"),
405            result
406                .execute_scalar(i, ctx)
407                .vortex_expect("scalar_at should succeed in conformance test")
408        );
409    }
410}