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::Array;
8use crate::ArrayRef;
9use crate::IntoArray;
10use crate::arrays::BoolArray;
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        .clone()
243        .mask((!&mask2).into_array())
244        .vortex_expect("mask should succeed in conformance test");
245
246    // Elements should be null if either mask is true
247    for i in 0..len {
248        if mask1_pattern[i] || mask2_pattern[i] {
249            assert!(
250                !double_masked
251                    .is_valid(i)
252                    .vortex_expect("is_valid should succeed in conformance test")
253            );
254        } else {
255            assert_eq!(
256                double_masked
257                    .scalar_at(i)
258                    .vortex_expect("scalar_at should succeed in conformance test"),
259                array
260                    .scalar_at(i)
261                    .vortex_expect("scalar_at should succeed in conformance test")
262                    .into_nullable()
263            );
264        }
265    }
266}
267
268/// Tests masking with nullable mask (nulls treated as false)
269fn test_nullable_mask_input(array: &ArrayRef) {
270    let len = array.len();
271    if len < 3 {
272        return; // Skip for very small arrays
273    }
274
275    // Create a nullable mask
276    let bool_values: Vec<bool> = (0..len).map(|i| i % 2 == 0).collect();
277    let validity_values: Vec<bool> = (0..len).map(|i| i % 3 != 0).collect();
278
279    let bool_array = BoolArray::from_iter(bool_values.clone());
280    let validity = crate::validity::Validity::from_iter(validity_values.clone());
281    let nullable_mask = BoolArray::new(bool_array.to_bit_buffer(), validity);
282
283    let mask_array = nullable_mask.to_mask_fill_null_false();
284    let masked = array
285        .clone()
286        .mask((!&mask_array).into_array())
287        .vortex_expect("mask should succeed in conformance test");
288
289    // Elements are masked only if the mask is true AND valid
290    for i in 0..len {
291        if bool_values[i] && validity_values[i] {
292            assert!(
293                !masked
294                    .is_valid(i)
295                    .vortex_expect("is_valid should succeed in conformance test")
296            );
297        } else {
298            assert_eq!(
299                masked
300                    .scalar_at(i)
301                    .vortex_expect("scalar_at should succeed in conformance test"),
302                array
303                    .scalar_at(i)
304                    .vortex_expect("scalar_at should succeed in conformance test")
305                    .into_nullable()
306            );
307        }
308    }
309}