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