Skip to main content

vortex_array/compute/conformance/
mask.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright the Vortex contributors
3
4use vortex_error::VortexExpect;
5use vortex_mask::Mask;
6
7use crate::ArrayRef;
8use crate::IntoArray;
9use crate::arrays::BoolArray;
10use crate::builtins::ArrayBuiltins;
11
12/// Test mask compute function with various array sizes and patterns.
13/// The mask operation sets elements to null where the mask is true.
14pub fn test_mask_conformance(array: &ArrayRef) {
15    let len = array.len();
16
17    if len > 0 {
18        test_heterogenous_mask(array);
19        test_empty_mask(array);
20        test_full_mask(array);
21        test_alternating_mask(array);
22        test_sparse_mask(array);
23        test_single_element_mask(array);
24    }
25
26    if len >= 5 {
27        test_double_mask(array);
28    }
29
30    if len > 0 {
31        test_nullable_mask_input(array);
32    }
33}
34
35/// Tests masking with a heterogeneous pattern
36fn test_heterogenous_mask(array: &ArrayRef) {
37    let len = array.len();
38
39    // Create a pattern where roughly half the values are masked
40    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 3 != 1).collect();
41    let mask_array = Mask::from_iter(mask_pattern.clone());
42
43    let masked = array
44        .clone()
45        .mask((!&mask_array).into_array())
46        .vortex_expect("mask should succeed in conformance test");
47    assert_eq!(masked.len(), array.len());
48
49    // Verify masked elements are null and unmasked elements are preserved
50    for (i, &masked_out) in mask_pattern.iter().enumerate() {
51        if masked_out {
52            assert!(
53                !masked
54                    .is_valid(i)
55                    .vortex_expect("is_valid should succeed in conformance test")
56            );
57        } else {
58            assert_eq!(
59                masked
60                    .scalar_at(i)
61                    .vortex_expect("scalar_at should succeed in conformance test"),
62                array
63                    .scalar_at(i)
64                    .vortex_expect("scalar_at should succeed in conformance test")
65                    .into_nullable()
66            );
67        }
68    }
69}
70
71/// Tests that an empty mask (all false) preserves all elements
72fn test_empty_mask(array: &ArrayRef) {
73    let len = array.len();
74    let all_unmasked = vec![false; len];
75    let mask_array = Mask::from_iter(all_unmasked);
76
77    let masked = array
78        .clone()
79        .mask((!&mask_array).into_array())
80        .vortex_expect("mask should succeed in conformance test");
81    assert_eq!(masked.len(), array.len());
82
83    // All elements should be preserved
84    for i in 0..len {
85        assert_eq!(
86            masked
87                .scalar_at(i)
88                .vortex_expect("scalar_at should succeed in conformance test"),
89            array
90                .scalar_at(i)
91                .vortex_expect("scalar_at should succeed in conformance test")
92                .into_nullable()
93        );
94    }
95}
96
97/// Tests that a full mask (all true) makes all elements null
98fn test_full_mask(array: &ArrayRef) {
99    let len = array.len();
100    let all_masked = vec![true; len];
101    let mask_array = Mask::from_iter(all_masked);
102
103    let masked = array
104        .clone()
105        .mask((!&mask_array).into_array())
106        .vortex_expect("mask should succeed in conformance test");
107    assert_eq!(masked.len(), array.len());
108
109    // All elements should be null
110    for i in 0..len {
111        assert!(
112            !masked
113                .is_valid(i)
114                .vortex_expect("is_valid should succeed in conformance test")
115        );
116    }
117}
118
119/// Tests alternating mask pattern
120fn test_alternating_mask(array: &ArrayRef) {
121    let len = array.len();
122    let pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
123    let mask_array = Mask::from_iter(pattern);
124
125    let masked = array
126        .clone()
127        .mask((!&mask_array).into_array())
128        .vortex_expect("mask should succeed in conformance test");
129    assert_eq!(masked.len(), array.len());
130
131    for i in 0..len {
132        if i % 2 == 0 {
133            assert!(
134                !masked
135                    .is_valid(i)
136                    .vortex_expect("is_valid should succeed in conformance test")
137            );
138        } else {
139            assert_eq!(
140                masked
141                    .scalar_at(i)
142                    .vortex_expect("scalar_at should succeed in conformance test"),
143                array
144                    .scalar_at(i)
145                    .vortex_expect("scalar_at should succeed in conformance test")
146                    .into_nullable()
147            );
148        }
149    }
150}
151
152/// Tests sparse mask (only a few elements masked)
153fn test_sparse_mask(array: &ArrayRef) {
154    let len = array.len();
155    if len < 10 {
156        return; // Skip for small arrays
157    }
158
159    // Mask only about 10% of elements
160    let pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
161    let mask_array = Mask::from_iter(pattern.clone());
162
163    let masked = array
164        .clone()
165        .mask((!&mask_array).into_array())
166        .vortex_expect("mask should succeed in conformance test");
167    assert_eq!(masked.len(), array.len());
168
169    // Count how many elements are valid after masking
170    let valid_count = (0..len)
171        .filter(|&i| {
172            masked
173                .is_valid(i)
174                .vortex_expect("is_valid should succeed in conformance test")
175        })
176        .count();
177
178    // Count how many elements should be invalid:
179    // - Elements that were masked (pattern[i] == true)
180    // - Elements that were already invalid in the original array
181    let expected_invalid_count = (0..len)
182        .filter(|&i| {
183            pattern[i]
184                || !array
185                    .is_valid(i)
186                    .vortex_expect("is_valid should succeed in conformance test")
187        })
188        .count();
189
190    assert_eq!(valid_count, len - expected_invalid_count);
191}
192
193/// Tests masking a single element
194fn test_single_element_mask(array: &ArrayRef) {
195    let len = array.len();
196
197    // Mask only the first element
198    let mut pattern = vec![false; len];
199    pattern[0] = true;
200    let mask_array = Mask::from_iter(pattern);
201
202    let masked = array
203        .clone()
204        .mask((!&mask_array).into_array())
205        .vortex_expect("mask should succeed in conformance test");
206    assert!(
207        !masked
208            .is_valid(0)
209            .vortex_expect("is_valid should succeed in conformance test")
210    );
211
212    for i in 1..len {
213        assert_eq!(
214            masked
215                .scalar_at(i)
216                .vortex_expect("scalar_at should succeed in conformance test"),
217            array
218                .scalar_at(i)
219                .vortex_expect("scalar_at should succeed in conformance test")
220                .into_nullable()
221        );
222    }
223}
224
225/// Tests double masking operations
226fn test_double_mask(array: &ArrayRef) {
227    let len = array.len();
228
229    // Create two different mask patterns
230    let mask1_pattern: Vec<bool> = (0..len).map(|i| i % 3 == 0).collect();
231    let mask2_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
232
233    let mask1 = Mask::from_iter(mask1_pattern.clone());
234    let mask2 = Mask::from_iter(mask2_pattern.clone());
235
236    let first_masked = array
237        .clone()
238        .mask((!&mask1).into_array())
239        .vortex_expect("mask should succeed in conformance test");
240    let double_masked = first_masked
241        .clone()
242        .mask((!&mask2).into_array())
243        .vortex_expect("mask should succeed in conformance test");
244
245    // Elements should be null if either mask is true
246    for i in 0..len {
247        if mask1_pattern[i] || mask2_pattern[i] {
248            assert!(
249                !double_masked
250                    .is_valid(i)
251                    .vortex_expect("is_valid should succeed in conformance test")
252            );
253        } else {
254            assert_eq!(
255                double_masked
256                    .scalar_at(i)
257                    .vortex_expect("scalar_at should succeed in conformance test"),
258                array
259                    .scalar_at(i)
260                    .vortex_expect("scalar_at should succeed in conformance test")
261                    .into_nullable()
262            );
263        }
264    }
265}
266
267/// Tests masking with nullable mask (nulls treated as false)
268fn test_nullable_mask_input(array: &ArrayRef) {
269    let len = array.len();
270    if len < 3 {
271        return; // Skip for very small arrays
272    }
273
274    // Create a nullable mask
275    let bool_values: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
276    let validity_values: Vec<bool> = (0..len).map(|i| i % 3 != 0).collect();
277
278    let bool_array = BoolArray::from_iter(bool_values.clone());
279    let validity = crate::validity::Validity::from_iter(validity_values.clone());
280    let nullable_mask = BoolArray::new(bool_array.to_bit_buffer(), validity);
281
282    let mask_array = nullable_mask.to_mask_fill_null_false();
283    let masked = array
284        .clone()
285        .mask((!&mask_array).into_array())
286        .vortex_expect("mask should succeed in conformance test");
287
288    // Elements are masked only if the mask is true AND valid
289    for i in 0..len {
290        if bool_values[i] && validity_values[i] {
291            assert!(
292                !masked
293                    .is_valid(i)
294                    .vortex_expect("is_valid should succeed in conformance test")
295            );
296        } else {
297            assert_eq!(
298                masked
299                    .scalar_at(i)
300                    .vortex_expect("scalar_at should succeed in conformance test"),
301                array
302                    .scalar_at(i)
303                    .vortex_expect("scalar_at should succeed in conformance test")
304                    .into_nullable()
305            );
306        }
307    }
308}