1#![expect(clippy::cast_possible_truncation)]
5
6pub use array::*;
7use vortex_array::ExecutionCtx;
8use vortex_array::IntoArray;
9use vortex_array::patches::Patches;
10use vortex_array::validity::Validity;
11use vortex_fastlanes::bitpack_compress::bitpack_encode_unchecked;
12
13mod array;
14mod compute;
15mod kernel;
16mod ops;
17mod rules;
18mod slice;
19
20use std::ops::Shl;
21use std::ops::Shr;
22
23use itertools::Itertools;
24use num_traits::Float;
25use num_traits::One;
26use num_traits::PrimInt;
27use rustc_hash::FxBuildHasher;
28use vortex_array::ArrayView;
29use vortex_array::arrays::Primitive;
30use vortex_array::arrays::PrimitiveArray;
31use vortex_array::dtype::DType;
32use vortex_array::dtype::NativePType;
33use vortex_array::match_each_integer_ptype;
34use vortex_buffer::Buffer;
35use vortex_buffer::BufferMut;
36use vortex_error::VortexExpect;
37use vortex_error::VortexResult;
38use vortex_error::vortex_panic;
39use vortex_session::VortexSession;
40use vortex_utils::aliases::hash_map::HashMap;
41
42use crate::match_each_alp_float_ptype;
43
44macro_rules! bit_width {
45 ($value:expr) => {
46 if $value == 0 {
47 1
48 } else {
49 $value.ilog2().wrapping_add(1) as usize
50 }
51 };
52}
53
54const CUT_LIMIT: usize = 16;
56
57const MAX_DICT_SIZE: u8 = 8;
58
59pub(crate) fn initialize(session: &VortexSession) {
60 kernel::initialize(session);
61}
62
63mod private {
64 pub trait Sealed {}
65
66 impl Sealed for f32 {}
67 impl Sealed for f64 {}
68}
69
70pub trait ALPRDFloat: private::Sealed + Float + Copy + NativePType {
75 type UINT: NativePType + PrimInt + One + Copy;
77
78 const BITS: usize = size_of::<Self>() * 8;
80
81 fn from_bits(bits: Self::UINT) -> Self;
83
84 fn to_bits(value: Self) -> Self::UINT;
86
87 fn to_u16(bits: Self::UINT) -> u16;
89
90 fn from_u16(value: u16) -> Self::UINT;
92}
93
94impl ALPRDFloat for f64 {
95 type UINT = u64;
96
97 fn from_bits(bits: Self::UINT) -> Self {
98 f64::from_bits(bits)
99 }
100
101 fn to_bits(value: Self) -> Self::UINT {
102 value.to_bits()
103 }
104
105 fn to_u16(bits: Self::UINT) -> u16 {
106 bits as u16
107 }
108
109 fn from_u16(value: u16) -> Self::UINT {
110 value as u64
111 }
112}
113
114impl ALPRDFloat for f32 {
115 type UINT = u32;
116
117 fn from_bits(bits: Self::UINT) -> Self {
118 f32::from_bits(bits)
119 }
120
121 fn to_bits(value: Self) -> Self::UINT {
122 value.to_bits()
123 }
124
125 fn to_u16(bits: Self::UINT) -> u16 {
126 bits as u16
127 }
128
129 fn from_u16(value: u16) -> Self::UINT {
130 value as u32
131 }
132}
133
134pub struct RDEncoder {
153 right_bit_width: u8,
154 codes: Vec<u16>,
155}
156
157impl RDEncoder {
158 pub fn new<T>(sample: &[T]) -> Self
160 where
161 T: ALPRDFloat + NativePType,
162 T::UINT: NativePType,
163 {
164 let dictionary = find_best_dictionary::<T>(sample);
165
166 let mut codes = vec![0; dictionary.dictionary.len()];
167 dictionary.dictionary.into_iter().for_each(|(bits, code)| {
168 codes[code as usize] = bits
170 });
171
172 Self {
173 right_bit_width: dictionary.right_bit_width,
174 codes,
175 }
176 }
177
178 pub fn from_parts(right_bit_width: u8, codes: Vec<u16>) -> Self {
180 Self {
181 right_bit_width,
182 codes,
183 }
184 }
185
186 pub fn encode(&self, array: ArrayView<'_, Primitive>) -> ALPRDArray {
191 match_each_alp_float_ptype!(array.ptype(), |P| { self.encode_generic::<P>(array) })
192 }
193
194 fn encode_generic<T>(&self, array: ArrayView<'_, Primitive>) -> ALPRDArray
195 where
196 T: ALPRDFloat + NativePType,
197 T::UINT: NativePType,
198 {
199 assert!(
200 !self.codes.is_empty(),
201 "codes lookup table must be populated before RD encoding"
202 );
203
204 let doubles = array.as_slice::<T>();
205
206 let mut left_parts: BufferMut<u16> = BufferMut::with_capacity(doubles.len());
207 let mut right_parts: BufferMut<T::UINT> = BufferMut::with_capacity(doubles.len());
208 let mut exceptions_pos: BufferMut<u64> = BufferMut::with_capacity(doubles.len() / 4);
209 let mut exceptions: BufferMut<u16> = BufferMut::with_capacity(doubles.len() / 4);
210
211 let right_mask = T::UINT::one().shl(self.right_bit_width as _) - T::UINT::one();
213 let max_code = self.codes.len() - 1;
214 let left_bit_width = bit_width!(max_code);
215
216 for v in doubles.iter().copied() {
217 right_parts.push(T::to_bits(v) & right_mask);
218 left_parts.push(<T as ALPRDFloat>::to_u16(
219 T::to_bits(v).shr(self.right_bit_width as _),
220 ));
221 }
222
223 for (idx, left) in left_parts.iter_mut().enumerate() {
225 if let Some(code) = self.codes.iter().position(|v| *v == *left) {
227 *left = code as u16;
228 } else {
229 exceptions.push(*left);
230 exceptions_pos.push(idx as _);
231
232 *left = 0u16;
233 }
234 }
235
236 let primitive_left = PrimitiveArray::new(
238 left_parts,
239 array
240 .validity()
241 .vortex_expect("ALP RD validity should be derivable"),
242 );
243 let packed_left = unsafe {
245 bitpack_encode_unchecked(primitive_left, left_bit_width as _)
246 .vortex_expect("bitpack_encode_unchecked should succeed for left parts")
247 .into_array()
248 };
249
250 let primitive_right = PrimitiveArray::new(right_parts, Validity::NonNullable);
251 let packed_right = unsafe {
253 bitpack_encode_unchecked(primitive_right, self.right_bit_width as _)
254 .vortex_expect("bitpack_encode_unchecked should succeed for right parts")
255 .into_array()
256 };
257
258 let exceptions = (!exceptions_pos.is_empty()).then(|| {
262 let max_exc_pos = exceptions_pos.last().copied().unwrap_or_default();
263 let bw = bit_width!(max_exc_pos) as u8;
264
265 let exc_pos_array = PrimitiveArray::new(exceptions_pos, Validity::NonNullable);
266 let packed_pos = unsafe {
268 bitpack_encode_unchecked(exc_pos_array, bw)
269 .vortex_expect(
270 "bitpack_encode_unchecked should succeed for exception positions",
271 )
272 .into_array()
273 };
274
275 Patches::new(
276 doubles.len(),
277 0,
278 packed_pos,
279 exceptions.into_array(),
280 None,
282 )
283 .vortex_expect("Patches construction in encode")
284 });
285
286 ALPRD::try_new(
287 DType::Primitive(T::PTYPE, packed_left.dtype().nullability()),
288 packed_left,
289 Buffer::<u16>::copy_from(&self.codes),
290 packed_right,
291 self.right_bit_width,
292 exceptions,
293 )
294 .vortex_expect("ALPRDArray construction in encode")
295 }
296}
297
298pub fn alp_rd_decode<T: ALPRDFloat>(
304 mut left_parts: BufferMut<u16>,
305 left_parts_dict: &[u16],
306 right_bit_width: u8,
307 right_parts: BufferMut<T::UINT>,
308 left_parts_patches: Option<Patches>,
309 ctx: &mut ExecutionCtx,
310) -> VortexResult<Buffer<T>> {
311 if left_parts.len() != right_parts.len() {
312 vortex_panic!("alp_rd_decode: left_parts.len != right_parts.len");
313 }
314
315 let shift = right_bit_width as usize;
316
317 if let Some(patches) = left_parts_patches {
318 for code in left_parts.iter_mut() {
324 *code = left_parts_dict[*code as usize];
325 }
326
327 let indices = patches.indices().clone().execute::<PrimitiveArray>(ctx)?;
329 let patch_values = patches.values().clone().execute::<PrimitiveArray>(ctx)?;
330 alp_rd_apply_patches(&mut left_parts, &indices, &patch_values, patches.offset());
331
332 alp_rd_combine_inplace::<T>(
335 right_parts,
336 |right, &left| {
337 *right = (<T as ALPRDFloat>::from_u16(left) << shift) | *right;
338 },
339 left_parts.as_ref(),
340 )
341 } else {
342 let mut shifted_dict = [T::UINT::default(); MAX_DICT_SIZE as usize];
346 for (i, &entry) in left_parts_dict.iter().enumerate() {
347 shifted_dict[i] = <T as ALPRDFloat>::from_u16(entry) << shift;
348 }
349
350 alp_rd_combine_inplace::<T>(
352 right_parts,
353 |right, &code| {
354 *right = unsafe { *shifted_dict.get_unchecked(code as usize) } | *right;
356 },
357 left_parts.as_ref(),
358 )
359 }
360}
361
362fn alp_rd_apply_patches(
364 values: &mut BufferMut<u16>,
365 indices: &PrimitiveArray,
366 patch_values: &PrimitiveArray,
367 offset: usize,
368) {
369 match_each_integer_ptype!(indices.ptype(), |T| {
370 indices
371 .as_slice::<T>()
372 .iter()
373 .copied()
374 .map(|idx| idx - offset as T)
375 .zip(patch_values.as_slice::<u16>().iter())
376 .for_each(|(idx, v)| values[idx as usize] = *v);
377 })
378}
379
380fn alp_rd_combine_inplace<T: ALPRDFloat>(
383 mut right_parts: BufferMut<T::UINT>,
384 combine_fn: impl Fn(&mut T::UINT, &u16),
385 left_data: &[u16],
386) -> VortexResult<Buffer<T>> {
387 for (right, left) in right_parts.as_mut_slice().iter_mut().zip(left_data.iter()) {
388 combine_fn(right, left);
389 }
390 Ok(unsafe { right_parts.transmute::<T>() }.freeze())
392}
393fn find_best_dictionary<T: ALPRDFloat>(samples: &[T]) -> ALPRDDictionary {
396 let mut best_est_size = f64::MAX;
397 let mut best_dict = ALPRDDictionary::default();
398
399 for p in 1..=16 {
400 let candidate_right_bw = (T::BITS - p) as u8;
401 let (dictionary, exception_count) =
402 build_left_parts_dictionary::<T>(samples, candidate_right_bw, MAX_DICT_SIZE);
403 let estimated_size = estimate_compression_size(
404 dictionary.right_bit_width,
405 dictionary.left_bit_width,
406 exception_count,
407 samples.len(),
408 );
409 if estimated_size < best_est_size {
410 best_est_size = estimated_size;
411 best_dict = dictionary;
412 }
413 }
414
415 best_dict
416}
417
418fn build_left_parts_dictionary<T: ALPRDFloat>(
420 samples: &[T],
421 right_bw: u8,
422 max_dict_size: u8,
423) -> (ALPRDDictionary, usize) {
424 assert!(
425 right_bw >= (T::BITS - CUT_LIMIT) as _,
426 "left-parts must be <= 16 bits"
427 );
428
429 let mut counts = HashMap::new();
431 samples
432 .iter()
433 .copied()
434 .map(|v| <T as ALPRDFloat>::to_u16(T::to_bits(v).shr(right_bw as _)))
435 .for_each(|item| *counts.entry(item).or_default() += 1);
436
437 let mut sorted_bit_counts: Vec<(u16, usize)> = counts.into_iter().collect_vec();
439 sorted_bit_counts.sort_by_key(|(_, count)| count.wrapping_neg());
440
441 let mut dictionary = HashMap::with_capacity_and_hasher(max_dict_size as _, FxBuildHasher);
443 let mut code = 0u16;
444 while code < (max_dict_size as _) && (code as usize) < sorted_bit_counts.len() {
445 let (bits, _) = sorted_bit_counts[code as usize];
446 dictionary.insert(bits, code);
447 code += 1;
448 }
449
450 let exception_count: usize = sorted_bit_counts
452 .iter()
453 .skip(code as _)
454 .map(|(_, count)| *count)
455 .sum();
456
457 let max_code = dictionary.len() - 1;
459 let left_bw = bit_width!(max_code) as u8;
460
461 (
462 ALPRDDictionary {
463 dictionary,
464 right_bit_width: right_bw,
465 left_bit_width: left_bw,
466 },
467 exception_count,
468 )
469}
470
471fn estimate_compression_size(
473 right_bw: u8,
474 left_bw: u8,
475 exception_count: usize,
476 sample_n: usize,
477) -> f64 {
478 const EXC_POSITION_SIZE: usize = 16; const EXC_SIZE: usize = 16; let exceptions_size = exception_count * (EXC_POSITION_SIZE + EXC_SIZE);
482 (right_bw as f64) + (left_bw as f64) + ((exceptions_size as f64) / (sample_n as f64))
483}
484
485#[derive(Debug, Default)]
487struct ALPRDDictionary {
488 dictionary: HashMap<u16, u16, FxBuildHasher>,
490 left_bit_width: u8,
492 right_bit_width: u8,
494}