• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

vortex-data / vortex / 16600365351

29 Jul 2025 03:21PM UTC coverage: 82.676% (-0.02%) from 82.699%
16600365351

Pull #4054

github

web-flow
Merge 906b77a3d into 5b0beed64
Pull Request #4054: varbinview zip kernel

10 of 29 new or added lines in 2 files covered. (34.48%)

1 existing line in 1 file now uncovered.

45232 of 54710 relevant lines covered (82.68%)

184659.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.38
/vortex-array/src/compute/zip.rs
1
// SPDX-License-Identifier: Apache-2.0
2
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3

4
use std::sync::LazyLock;
5

6
use arcref::ArcRef;
7
use vortex_dtype::DType;
8
use vortex_error::{VortexError, VortexResult, vortex_bail, vortex_err};
9
use vortex_mask::{AllOr, Mask};
10

11
use super::{ComputeFnVTable, InvocationArgs, Output, cast};
12
use crate::builders::{ArrayBuilder, builder_with_capacity};
13
use crate::compute::{ComputeFn, Kernel};
14
use crate::vtable::VTable;
15
use crate::{Array, ArrayRef};
16

17
/// Performs element-wise conditional selection between two arrays based on a mask.
18
///
19
/// Returns a new array where `result[i] = if_true[i]` when `mask[i]` is true,
20
/// otherwise `result[i] = if_false[i]`.
21
pub fn zip(mask: &Mask, if_true: &dyn Array, if_false: &dyn Array) -> VortexResult<ArrayRef> {
114✔
22
    ZIP_FN
114✔
23
        .invoke(&InvocationArgs {
114✔
24
            inputs: &[mask.into(), if_true.into(), if_false.into()],
114✔
25
            options: &(),
114✔
26
        })?
114✔
27
        .unwrap_array()
76✔
28
}
114✔
29

30
pub static ZIP_FN: LazyLock<ComputeFn> = LazyLock::new(|| {
114✔
31
    let compute = ComputeFn::new("zip".into(), ArcRef::new_ref(&Zip));
114✔
32
    for kernel in inventory::iter::<ZipKernelRef> {
228✔
33
        compute.register_kernel(kernel.0.clone());
114✔
34
    }
114✔
35
    compute
114✔
36
});
114✔
37

38
struct Zip;
39

40
impl ComputeFnVTable for Zip {
41
    fn invoke(
76✔
42
        &self,
76✔
43
        args: &InvocationArgs,
76✔
44
        kernels: &[ArcRef<dyn Kernel>],
76✔
45
    ) -> VortexResult<Output> {
76✔
46
        let ZipArgs {
47
            mask,
76✔
48
            if_true,
76✔
49
            if_false,
76✔
50
        } = ZipArgs::try_from(args)?;
76✔
51

52
        if mask.all_true() {
76✔
53
            return Ok(cast(if_true, &zip_return_dtype(if_true, if_false))?.into());
38✔
54
        }
38✔
55

56
        if mask.all_false() {
38✔
57
            return Ok(cast(if_false, &zip_return_dtype(if_true, if_false))?.into());
×
58
        }
38✔
59

60
        if if_true.is_canonical() && if_false.is_canonical() {
38✔
61
            // skip kernel lookup if both arrays are canonical
62
            return Ok(zip_impl(mask, if_true, if_false)?.into());
38✔
63
        }
×
64

65
        // check if if_true supports zip directly
66
        for kernel in kernels {
×
67
            if let Some(output) = kernel.invoke(args)? {
×
68
                return Ok(output);
×
69
            }
×
70
        }
71

72
        if let Some(output) = if_true.invoke(&ZIP_FN, args)? {
×
73
            return Ok(output);
×
74
        }
×
75

76
        // TODO(os): add invert_mask opt and check if if_false has a kernel like:
77
        //           kernel.invoke(Args(if_false, if_true, mask, invert_mask = true))
78

79
        Ok(zip_impl(
×
80
            mask,
×
81
            if_true.to_canonical()?.as_ref(),
×
82
            if_false.to_canonical()?.as_ref(),
×
83
        )?
×
84
        .into())
×
85
    }
76✔
86

87
    fn return_dtype(&self, args: &InvocationArgs) -> VortexResult<DType> {
76✔
88
        let ZipArgs {
89
            if_true, if_false, ..
76✔
90
        } = ZipArgs::try_from(args)?;
76✔
91

92
        if !if_true.dtype().eq_ignore_nullability(if_false.dtype()) {
76✔
93
            vortex_bail!("input arrays to zip must have the same dtype");
×
94
        }
76✔
95
        Ok(zip_return_dtype(if_true, if_false))
76✔
96
    }
76✔
97

98
    fn return_len(&self, args: &InvocationArgs) -> VortexResult<usize> {
76✔
99
        let ZipArgs { if_true, .. } = ZipArgs::try_from(args)?;
76✔
100
        // ComputeFn::invoke asserts if_true.len() == if_false.len(), because zip is elementwise
101
        Ok(if_true.len())
76✔
102
    }
76✔
103

104
    fn is_elementwise(&self) -> bool {
114✔
105
        true
114✔
106
    }
114✔
107
}
108

109
struct ZipArgs<'a> {
110
    mask: &'a Mask,
111
    if_true: &'a dyn Array,
112
    if_false: &'a dyn Array,
113
}
114

115
impl<'a> TryFrom<&InvocationArgs<'a>> for ZipArgs<'a> {
116
    type Error = VortexError;
117

118
    fn try_from(value: &InvocationArgs<'a>) -> Result<Self, Self::Error> {
228✔
119
        if value.inputs.len() != 3 {
228✔
120
            vortex_bail!("Expected 3 inputs for zip, found {}", value.inputs.len());
×
121
        }
228✔
122
        let mask = value.inputs[0]
228✔
123
            .mask()
228✔
124
            .ok_or_else(|| vortex_err!("Expected input 0 to be a mask"))?;
228✔
125

126
        let if_true = value.inputs[1]
228✔
127
            .array()
228✔
128
            .ok_or_else(|| vortex_err!("Expected input 1 to be an array"))?;
228✔
129

130
        let if_false = value.inputs[2]
228✔
131
            .array()
228✔
132
            .ok_or_else(|| vortex_err!("Expected input 2 to be an array"))?;
228✔
133
        Ok(Self {
228✔
134
            mask,
228✔
135
            if_true,
228✔
136
            if_false,
228✔
137
        })
228✔
138
    }
228✔
139
}
140

141
pub trait ZipKernel: VTable {
142
    fn zip(
143
        &self,
144
        mask: &Mask,
145
        if_true: &Self::Array,
146
        if_false: &dyn Array,
147
    ) -> VortexResult<Option<ArrayRef>>;
148
}
149

150
pub struct ZipKernelRef(pub ArcRef<dyn Kernel>);
151
inventory::collect!(ZipKernelRef);
152

153
#[derive(Debug)]
154
pub struct ZipKernelAdapter<V: VTable>(pub V);
155

156
impl<V: VTable + ZipKernel> ZipKernelAdapter<V> {
157
    pub const fn lift(&'static self) -> ZipKernelRef {
×
158
        ZipKernelRef(ArcRef::new_ref(self))
×
159
    }
×
160
}
161

162
impl<V: VTable + ZipKernel> Kernel for ZipKernelAdapter<V> {
163
    fn invoke(&self, args: &InvocationArgs) -> VortexResult<Option<Output>> {
×
164
        let ZipArgs {
165
            mask,
×
166
            if_true,
×
167
            if_false,
×
168
        } = ZipArgs::try_from(args)?;
×
169
        let Some(if_true) = if_true.as_opt::<V>() else {
×
170
            return Ok(None);
×
171
        };
NEW
172
        Ok(V::zip(&self.0, mask, if_true, if_false)?.map(Into::into))
×
173
    }
×
174
}
175

176
pub(crate) fn zip_return_dtype(if_true: &dyn Array, if_false: &dyn Array) -> DType {
152✔
177
    if_true
152✔
178
        .dtype()
152✔
179
        .union_nullability(if_false.dtype().nullability())
152✔
180
}
152✔
181

182
fn zip_impl(mask: &Mask, if_true: &dyn Array, if_false: &dyn Array) -> VortexResult<ArrayRef> {
38✔
183
    // if_true.len() == if_false.len() from ComputeFn::invoke
184
    let builder = builder_with_capacity(&zip_return_dtype(if_true, if_false), if_true.len());
38✔
185
    zip_impl_with_builder(mask, if_true, if_false, builder)
38✔
186
}
38✔
187

188
pub(crate) fn zip_impl_with_builder(
38✔
189
    mask: &Mask,
38✔
190
    if_true: &dyn Array,
38✔
191
    if_false: &dyn Array,
38✔
192
    mut builder: Box<dyn ArrayBuilder>,
38✔
193
) -> VortexResult<ArrayRef> {
38✔
194
    match mask.slices() {
38✔
195
        AllOr::All => Ok(if_true.to_array()),
×
196
        AllOr::None => Ok(if_false.to_array()),
×
197
        AllOr::Some(slices) => {
38✔
198
            for (start, end) in slices {
114✔
199
                builder.extend_from_array(&if_false.slice(builder.len(), *start)?)?;
76✔
200
                builder.extend_from_array(&if_true.slice(*start, *end)?)?;
76✔
201
            }
202
            if builder.len() < if_false.len() {
38✔
203
                builder.extend_from_array(&if_false.slice(builder.len(), if_false.len())?)?;
38✔
204
            }
×
205
            Ok(builder.finish())
38✔
206
        }
207
    }
208
}
38✔
209

210
#[cfg(test)]
211
mod tests {
212
    use vortex_array::arrays::{BoolArray, PrimitiveArray};
213
    use vortex_array::compute::zip;
214
    use vortex_array::{IntoArray, ToCanonical};
215
    use vortex_mask::Mask;
216

217
    #[test]
218
    fn test_zip_basic() {
1✔
219
        let mask =
1✔
220
            Mask::try_from(&BoolArray::from_iter([true, false, false, true, false])).unwrap();
1✔
221
        let if_true = PrimitiveArray::from_iter([10, 20, 30, 40, 50]).into_array();
1✔
222
        let if_false = PrimitiveArray::from_iter([1, 2, 3, 4, 5]).into_array();
1✔
223

224
        let result = zip(&mask, &if_true, &if_false).unwrap();
1✔
225
        let expected = PrimitiveArray::from_iter([10, 2, 3, 40, 5]);
1✔
226

227
        assert_eq!(
1✔
228
            result.to_primitive().unwrap().as_slice::<i32>(),
1✔
229
            expected.as_slice::<i32>()
1✔
230
        );
231
    }
1✔
232

233
    #[test]
234
    fn test_zip_all_true() {
1✔
235
        let mask = Mask::new_true(4);
1✔
236
        let if_true = PrimitiveArray::from_iter([10, 20, 30, 40]).into_array();
1✔
237
        let if_false =
1✔
238
            PrimitiveArray::from_option_iter([Some(1), Some(2), Some(3), None]).into_array();
1✔
239

240
        let result = zip(&mask, &if_true, &if_false).unwrap();
1✔
241

242
        assert_eq!(
1✔
243
            result.to_primitive().unwrap().as_slice::<i32>(),
1✔
244
            if_true.to_primitive().unwrap().as_slice::<i32>()
1✔
245
        );
246

247
        // result must be nullable even if_true was not
248
        assert_eq!(result.dtype(), if_false.dtype())
1✔
249
    }
1✔
250

251
    #[test]
252
    #[should_panic]
253
    fn test_invalid_lengths() {
1✔
254
        let mask = Mask::new_false(4);
1✔
255
        let if_true = PrimitiveArray::from_iter([10, 20, 30]).into_array();
1✔
256
        let if_false = PrimitiveArray::from_iter([1, 2, 3, 4]).into_array();
1✔
257

258
        zip(&mask, &if_true, &if_false).unwrap();
1✔
259
    }
1✔
260
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc