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::arrays::bool::BoolArrayExt;
11use crate::builtins::ArrayBuiltins;
12
13/// Test mask compute function with various array sizes and patterns.
14/// The mask operation sets elements to null where the mask is true.
15pub fn test_mask_conformance(array: &ArrayRef) {
16    let len = array.len();
17
18    if len > 0 {
19        test_heterogenous_mask(array);
20        test_empty_mask(array);
21        test_full_mask(array);
22        test_alternating_mask(array);
23        test_sparse_mask(array);
24        test_single_element_mask(array);
25    }
26
27    if len >= 5 {
28        test_double_mask(array);
29    }
30
31    if len > 0 {
32        test_nullable_mask_input(array);
33    }
34}
35
36/// Tests masking with a heterogeneous pattern
37fn test_heterogenous_mask(array: &ArrayRef) {
38    let len = array.len();
39
40    // Create a pattern where roughly half the values are masked
41    let mask_pattern: Vec<bool> = (0..len).map(|i| i % 3 != 1).collect();
42    let mask_array = Mask::from_iter(mask_pattern.clone());
43
44    let masked = array
45        .clone()
46        .mask((!&mask_array).into_array())
47        .vortex_expect("mask should succeed in conformance test");
48    assert_eq!(masked.len(), array.len());
49
50    // Verify masked elements are null and unmasked elements are preserved
51    for (i, &masked_out) in mask_pattern.iter().enumerate() {
52        if masked_out {
53            assert!(
54                !masked
55                    .is_valid(i)
56                    .vortex_expect("is_valid should succeed in conformance test")
57            );
58        } else {
59            assert_eq!(
60                masked
61                    .scalar_at(i)
62                    .vortex_expect("scalar_at should succeed in conformance test"),
63                array
64                    .scalar_at(i)
65                    .vortex_expect("scalar_at should succeed in conformance test")
66                    .into_nullable()
67            );
68        }
69    }
70}
71
72/// Tests that an empty mask (all false) preserves all elements
73fn test_empty_mask(array: &ArrayRef) {
74    let len = array.len();
75    let all_unmasked = vec![false; len];
76    let mask_array = Mask::from_iter(all_unmasked);
77
78    let masked = array
79        .clone()
80        .mask((!&mask_array).into_array())
81        .vortex_expect("mask should succeed in conformance test");
82    assert_eq!(masked.len(), array.len());
83
84    // All elements should be preserved
85    for i in 0..len {
86        assert_eq!(
87            masked
88                .scalar_at(i)
89                .vortex_expect("scalar_at should succeed in conformance test"),
90            array
91                .scalar_at(i)
92                .vortex_expect("scalar_at should succeed in conformance test")
93                .into_nullable()
94        );
95    }
96}
97
98/// Tests that a full mask (all true) makes all elements null
99fn test_full_mask(array: &ArrayRef) {
100    let len = array.len();
101    let all_masked = vec![true; len];
102    let mask_array = Mask::from_iter(all_masked);
103
104    let masked = array
105        .clone()
106        .mask((!&mask_array).into_array())
107        .vortex_expect("mask should succeed in conformance test");
108    assert_eq!(masked.len(), array.len());
109
110    // All elements should be null
111    for i in 0..len {
112        assert!(
113            !masked
114                .is_valid(i)
115                .vortex_expect("is_valid should succeed in conformance test")
116        );
117    }
118}
119
120/// Tests alternating mask pattern
121fn test_alternating_mask(array: &ArrayRef) {
122    let len = array.len();
123    let pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
124    let mask_array = Mask::from_iter(pattern);
125
126    let masked = array
127        .clone()
128        .mask((!&mask_array).into_array())
129        .vortex_expect("mask should succeed in conformance test");
130    assert_eq!(masked.len(), array.len());
131
132    for i in 0..len {
133        if i % 2 == 0 {
134            assert!(
135                !masked
136                    .is_valid(i)
137                    .vortex_expect("is_valid should succeed in conformance test")
138            );
139        } else {
140            assert_eq!(
141                masked
142                    .scalar_at(i)
143                    .vortex_expect("scalar_at should succeed in conformance test"),
144                array
145                    .scalar_at(i)
146                    .vortex_expect("scalar_at should succeed in conformance test")
147                    .into_nullable()
148            );
149        }
150    }
151}
152
153/// Tests sparse mask (only a few elements masked)
154fn test_sparse_mask(array: &ArrayRef) {
155    let len = array.len();
156    if len < 10 {
157        return; // Skip for small arrays
158    }
159
160    // Mask only about 10% of elements
161    let pattern: Vec<bool> = (0..len).map(|i| i % 10 == 0).collect();
162    let mask_array = Mask::from_iter(pattern.clone());
163
164    let masked = array
165        .clone()
166        .mask((!&mask_array).into_array())
167        .vortex_expect("mask should succeed in conformance test");
168    assert_eq!(masked.len(), array.len());
169
170    // Count how many elements are valid after masking
171    let valid_count = (0..len)
172        .filter(|&i| {
173            masked
174                .is_valid(i)
175                .vortex_expect("is_valid should succeed in conformance test")
176        })
177        .count();
178
179    // Count how many elements should be invalid:
180    // - Elements that were masked (pattern[i] == true)
181    // - Elements that were already invalid in the original array
182    let expected_invalid_count = (0..len)
183        .filter(|&i| {
184            pattern[i]
185                || !array
186                    .is_valid(i)
187                    .vortex_expect("is_valid should succeed in conformance test")
188        })
189        .count();
190
191    assert_eq!(valid_count, len - expected_invalid_count);
192}
193
194/// Tests masking a single element
195fn test_single_element_mask(array: &ArrayRef) {
196    let len = array.len();
197
198    // Mask only the first element
199    let mut pattern = vec![false; len];
200    pattern[0] = true;
201    let mask_array = Mask::from_iter(pattern);
202
203    let masked = array
204        .clone()
205        .mask((!&mask_array).into_array())
206        .vortex_expect("mask should succeed in conformance test");
207    assert!(
208        !masked
209            .is_valid(0)
210            .vortex_expect("is_valid should succeed in conformance test")
211    );
212
213    for i in 1..len {
214        assert_eq!(
215            masked
216                .scalar_at(i)
217                .vortex_expect("scalar_at should succeed in conformance test"),
218            array
219                .scalar_at(i)
220                .vortex_expect("scalar_at should succeed in conformance test")
221                .into_nullable()
222        );
223    }
224}
225
226/// Tests double masking operations
227fn test_double_mask(array: &ArrayRef) {
228    let len = array.len();
229
230    // Create two different mask patterns
231    let mask1_pattern: Vec<bool> = (0..len).map(|i| i % 3 == 0).collect();
232    let mask2_pattern: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
233
234    let mask1 = Mask::from_iter(mask1_pattern.clone());
235    let mask2 = Mask::from_iter(mask2_pattern.clone());
236
237    let first_masked = array
238        .clone()
239        .mask((!&mask1).into_array())
240        .vortex_expect("mask should succeed in conformance test");
241    let double_masked = first_masked
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}