vortex_fastlanes/bitpacking/compute/
is_constant.rs1use std::ops::Range;
5
6use itertools::Itertools;
7use lending_iterator::LendingIterator;
8use vortex_array::ToCanonical;
9use vortex_array::arrays::IS_CONST_LANE_WIDTH;
10use vortex_array::arrays::PrimitiveArray;
11use vortex_array::arrays::compute_is_constant;
12use vortex_array::compute::IsConstantKernel;
13use vortex_array::compute::IsConstantKernelAdapter;
14use vortex_array::compute::IsConstantOpts;
15use vortex_array::register_kernel;
16use vortex_dtype::IntegerPType;
17use vortex_dtype::match_each_integer_ptype;
18use vortex_dtype::match_each_unsigned_integer_ptype;
19use vortex_error::VortexResult;
20
21use crate::BitPackedArray;
22use crate::BitPackedVTable;
23use crate::unpack_iter::BitPacked;
24
25impl IsConstantKernel for BitPackedVTable {
26 fn is_constant(
27 &self,
28 array: &BitPackedArray,
29 opts: &IsConstantOpts,
30 ) -> VortexResult<Option<bool>> {
31 if opts.is_negligible_cost() {
32 return Ok(None);
33 }
34 match_each_integer_ptype!(array.ptype(), |P| {
35 bitpacked_is_constant::<P, { IS_CONST_LANE_WIDTH / size_of::<P>() }>(array)
36 })
37 .map(Some)
38 }
39}
40
41register_kernel!(IsConstantKernelAdapter(BitPackedVTable).lift());
42
43fn bitpacked_is_constant<T: BitPacked, const WIDTH: usize>(
44 array: &BitPackedArray,
45) -> VortexResult<bool> {
46 let mut bit_unpack_iterator = array.unpacked_chunks::<T>();
47 let patches = array.patches().map(|p| {
48 let values = p.values().to_primitive();
49 let indices = p.indices().to_primitive();
50 let offset = p.offset();
51 (indices, values, offset)
52 });
53
54 let mut header_constant_value = None;
55 let mut current_idx = 0;
56 if let Some(header) = bit_unpack_iterator.initial() {
57 if let Some((indices, patches, offset)) = &patches {
58 apply_patches(
59 header,
60 current_idx..header.len(),
61 indices,
62 patches.as_slice::<T>(),
63 *offset,
64 )
65 }
66
67 if !compute_is_constant::<_, WIDTH>(header) {
68 return Ok(false);
69 }
70 header_constant_value = Some(header[0]);
71 current_idx = header.len();
72 }
73
74 let mut first_chunk_value = None;
75 let mut chunks_iter = bit_unpack_iterator.full_chunks();
76 while let Some(chunk) = chunks_iter.next() {
77 if let Some((indices, patches, offset)) = &patches {
78 let chunk_len = chunk.len();
79 apply_patches(
80 chunk,
81 current_idx..current_idx + chunk_len,
82 indices,
83 patches.as_slice::<T>(),
84 *offset,
85 )
86 }
87
88 if !compute_is_constant::<_, WIDTH>(chunk) {
89 return Ok(false);
90 }
91
92 if let Some(chunk_value) = first_chunk_value {
93 if chunk_value != chunk[0] {
94 return Ok(false);
95 }
96 } else {
97 if let Some(header_value) = header_constant_value
98 && header_value != chunk[0]
99 {
100 return Ok(false);
101 }
102 first_chunk_value = Some(chunk[0]);
103 }
104
105 current_idx += chunk.len();
106 }
107
108 if let Some(trailer) = bit_unpack_iterator.trailer() {
109 if let Some((indices, patches, offset)) = &patches {
110 let chunk_len = trailer.len();
111 apply_patches(
112 trailer,
113 current_idx..current_idx + chunk_len,
114 indices,
115 patches.as_slice::<T>(),
116 *offset,
117 )
118 }
119
120 if !compute_is_constant::<_, WIDTH>(trailer) {
121 return Ok(false);
122 }
123
124 if let Some(previous_const_value) = header_constant_value.or(first_chunk_value)
125 && previous_const_value != trailer[0]
126 {
127 return Ok(false);
128 }
129 }
130
131 Ok(true)
132}
133
134fn apply_patches<T: BitPacked>(
135 values: &mut [T],
136 values_range: Range<usize>,
137 patch_indices: &PrimitiveArray,
138 patch_values: &[T],
139 indices_offset: usize,
140) {
141 match_each_unsigned_integer_ptype!(patch_indices.ptype(), |I| {
142 apply_patches_idx_typed(
143 values,
144 values_range,
145 patch_indices.as_slice::<I>(),
146 patch_values,
147 indices_offset,
148 )
149 });
150}
151
152fn apply_patches_idx_typed<T: BitPacked, I: IntegerPType>(
153 values: &mut [T],
154 values_range: Range<usize>,
155 patch_indices: &[I],
156 patch_values: &[T],
157 indices_offset: usize,
158) {
159 for (i, &v) in patch_indices
160 .iter()
161 .map(|i| i.as_() - indices_offset)
162 .zip_eq(patch_values)
163 .skip_while(|(i, _)| i < &values_range.start)
164 .take_while(|(i, _)| i < &values_range.end)
165 {
166 values[i - values_range.start] = v
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use vortex_array::IntoArray;
173 use vortex_array::compute::is_constant;
174 use vortex_buffer::buffer;
175
176 use crate::BitPackedArray;
177
178 #[test]
179 fn is_constant_with_patches() {
180 let array = BitPackedArray::encode(&buffer![4; 1025].into_array(), 2).unwrap();
181 assert!(is_constant(array.as_ref()).unwrap().unwrap());
182 }
183}