1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use itertools::Itertools;
11use prost::Message;
12use vortex_array::Array;
13use vortex_array::ArrayEq;
14use vortex_array::ArrayHash;
15use vortex_array::ArrayId;
16use vortex_array::ArrayParts;
17use vortex_array::ArrayRef;
18use vortex_array::ArraySlots;
19use vortex_array::ArrayView;
20use vortex_array::EqMode;
21use vortex_array::ExecutionCtx;
22use vortex_array::ExecutionResult;
23use vortex_array::IntoArray;
24use vortex_array::TypedArrayRef;
25use vortex_array::arrays::Primitive;
26use vortex_array::arrays::PrimitiveArray;
27use vortex_array::buffer::BufferHandle;
28use vortex_array::dtype::DType;
29use vortex_array::dtype::Nullability;
30use vortex_array::dtype::PType;
31use vortex_array::patches::PatchSlotIndices;
32use vortex_array::patches::Patches;
33use vortex_array::patches::PatchesData;
34use vortex_array::patches::PatchesMetadata;
35use vortex_array::require_child;
36use vortex_array::require_patches;
37use vortex_array::serde::ArrayChildren;
38use vortex_array::smallvec::smallvec;
39use vortex_array::validity::Validity;
40use vortex_array::vtable::VTable;
41use vortex_array::vtable::ValidityChild;
42use vortex_array::vtable::ValidityVTableFromChild;
43use vortex_buffer::Buffer;
44use vortex_error::VortexExpect;
45use vortex_error::VortexResult;
46use vortex_error::vortex_bail;
47use vortex_error::vortex_ensure;
48use vortex_error::vortex_err;
49use vortex_error::vortex_panic;
50use vortex_session::VortexSession;
51use vortex_session::registry::CachedId;
52
53use crate::alp_rd::rules::RULES;
54use crate::alp_rd_decode;
55
56pub type ALPRDArray = Array<ALPRD>;
58
59#[derive(Clone, prost::Message)]
60pub struct ALPRDMetadata {
61 #[prost(uint32, tag = "1")]
62 right_bit_width: u32,
63 #[prost(uint32, tag = "2")]
64 dict_len: u32,
65 #[prost(uint32, repeated, tag = "3")]
66 dict: Vec<u32>,
67 #[prost(enumeration = "PType", tag = "4")]
68 left_parts_ptype: i32,
69 #[prost(message, tag = "5")]
70 patches: Option<PatchesMetadata>,
71}
72
73impl ArrayHash for ALPRDData {
74 fn array_hash<H: Hasher>(&self, state: &mut H, accuracy: EqMode) {
75 self.left_parts_dictionary.array_hash(state, accuracy);
76 self.right_bit_width.hash(state);
77 self.patches_data.hash(state);
78 }
79}
80
81impl ArrayEq for ALPRDData {
82 fn array_eq(&self, other: &Self, accuracy: EqMode) -> bool {
83 self.left_parts_dictionary
84 .array_eq(&other.left_parts_dictionary, accuracy)
85 && self.right_bit_width == other.right_bit_width
86 && self.patches_data == other.patches_data
87 }
88}
89
90impl VTable for ALPRD {
91 type TypedArrayData = ALPRDData;
92
93 type OperationsVTable = Self;
94 type ValidityVTable = ValidityVTableFromChild;
95
96 fn id(&self) -> ArrayId {
97 static ID: CachedId = CachedId::new("vortex.alprd");
98 *ID
99 }
100
101 fn validate(
102 &self,
103 data: &ALPRDData,
104 dtype: &DType,
105 len: usize,
106 slots: &[Option<ArrayRef>],
107 ) -> VortexResult<()> {
108 validate_parts(
109 dtype,
110 len,
111 left_parts_from_slots(slots),
112 right_parts_from_slots(slots),
113 patches_from_slots(slots, data.patches_data.as_ref(), len).as_ref(),
114 )
115 }
116
117 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
118 0
119 }
120
121 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
122 vortex_panic!("ALPRDArray buffer index {idx} out of bounds")
123 }
124
125 fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
126 None
127 }
128
129 fn with_buffers(
130 &self,
131 array: ArrayView<'_, Self>,
132 buffers: &[BufferHandle],
133 ) -> VortexResult<ArrayParts<Self>> {
134 vortex_array::vtable::with_empty_buffers(self, array, buffers)
135 }
136
137 fn serialize(
138 array: ArrayView<'_, Self>,
139 _session: &VortexSession,
140 ) -> VortexResult<Option<Vec<u8>>> {
141 let dict = array
142 .left_parts_dictionary()
143 .iter()
144 .map(|&i| i as u32)
145 .collect::<Vec<_>>();
146
147 Ok(Some(
148 ALPRDMetadata {
149 right_bit_width: array.right_bit_width() as u32,
150 dict_len: array.left_parts_dictionary().len() as u32,
151 dict,
152 left_parts_ptype: array.left_parts().dtype().as_ptype() as i32,
153 patches: array
154 .left_parts_patches()
155 .map(|p| p.to_metadata(array.len(), p.dtype()))
156 .transpose()?,
157 }
158 .encode_to_vec(),
159 ))
160 }
161
162 #[allow(clippy::disallowed_methods)]
163 fn deserialize(
164 &self,
165 dtype: &DType,
166 len: usize,
167 metadata: &[u8],
168 _buffers: &[BufferHandle],
169 children: &dyn ArrayChildren,
170 _session: &VortexSession,
171 ) -> VortexResult<ArrayParts<Self>> {
172 let metadata = ALPRDMetadata::decode(metadata)?;
173 if children.len() < 2 {
174 vortex_bail!(
175 "Expected at least 2 children for ALPRD encoding, found {}",
176 children.len()
177 );
178 }
179
180 let left_parts_dtype = DType::Primitive(metadata.left_parts_ptype(), dtype.nullability());
181 let left_parts = children.get(0, &left_parts_dtype, len)?;
182 let left_parts_dictionary: Buffer<u16> = metadata.dict.as_slice()
183 [0..metadata.dict_len as usize]
184 .iter()
185 .map(|&i| {
186 u16::try_from(i)
187 .map_err(|_| vortex_err!("left_parts_dictionary code {i} does not fit in u16"))
188 })
189 .try_collect()?;
190
191 let right_parts_dtype = match &dtype {
192 DType::Primitive(PType::F32, _) => {
193 DType::Primitive(PType::U32, Nullability::NonNullable)
194 }
195 DType::Primitive(PType::F64, _) => {
196 DType::Primitive(PType::U64, Nullability::NonNullable)
197 }
198 _ => vortex_bail!("Expected f32 or f64 dtype, got {:?}", dtype),
199 };
200 let right_parts = children.get(1, &right_parts_dtype, len)?;
201
202 let left_parts_patches = metadata
203 .patches
204 .map(|p| {
205 let indices = children.get(2, &p.indices_dtype()?, p.len()?)?;
206 let values = children.get(3, &left_parts_dtype.as_nonnullable(), p.len()?)?;
207
208 Patches::new(
209 len,
210 p.offset()?,
211 indices,
212 values,
213 None,
215 )
216 })
217 .transpose()?;
218 let slots = ALPRDData::make_slots(&left_parts, &right_parts, left_parts_patches.as_ref());
219 let data = ALPRDData::new(
220 left_parts_dictionary,
221 u8::try_from(metadata.right_bit_width).map_err(|_| {
222 vortex_err!(
223 "right_bit_width {} out of u8 range",
224 metadata.right_bit_width
225 )
226 })?,
227 left_parts_patches,
228 );
229 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
230 }
231
232 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
233 SLOT_NAMES[idx].to_string()
234 }
235
236 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
237 let array = require_child!(array, array.left_parts(), 0 => Primitive);
238 let array = require_child!(array, array.right_parts(), 1 => Primitive);
239 require_patches!(
240 array,
241 LP_PATCH_INDICES_SLOT,
242 LP_PATCH_VALUES_SLOT,
243 LP_PATCH_CHUNK_OFFSETS_SLOT
244 );
245
246 let dtype = array.dtype().clone();
247 let right_bit_width = array.right_bit_width();
248 let ALPRDDataParts {
249 left_parts,
250 right_parts,
251 left_parts_dictionary,
252 left_parts_patches,
253 } = ALPRDArrayOwnedExt::into_data_parts(array);
254 let ptype = dtype.as_ptype();
255
256 let left_parts = left_parts
257 .try_downcast::<Primitive>()
258 .ok()
259 .vortex_expect("ALPRD execute: left_parts is primitive");
260 let right_parts = right_parts
261 .try_downcast::<Primitive>()
262 .ok()
263 .vortex_expect("ALPRD execute: right_parts is primitive");
264
265 let left_parts_dict = left_parts_dictionary;
267 let validity = left_parts
268 .as_ref()
269 .validity()?
270 .execute_mask(left_parts.as_ref().len(), ctx)?;
271
272 let decoded_array = if ptype == PType::F32 {
273 PrimitiveArray::new(
274 alp_rd_decode::<f32>(
275 left_parts.into_buffer_mut::<u16>(),
276 &left_parts_dict,
277 right_bit_width,
278 right_parts.into_buffer_mut::<u32>(),
279 left_parts_patches,
280 ctx,
281 )?,
282 Validity::from_mask(validity, dtype.nullability()),
283 )
284 } else {
285 PrimitiveArray::new(
286 alp_rd_decode::<f64>(
287 left_parts.into_buffer_mut::<u16>(),
288 &left_parts_dict,
289 right_bit_width,
290 right_parts.into_buffer_mut::<u64>(),
291 left_parts_patches,
292 ctx,
293 )?,
294 Validity::from_mask(validity, dtype.nullability()),
295 )
296 };
297
298 Ok(ExecutionResult::done(decoded_array.into_array()))
299 }
300
301 fn reduce_parent(
302 array: ArrayView<'_, Self>,
303 parent: &ArrayRef,
304 child_idx: usize,
305 ) -> VortexResult<Option<ArrayRef>> {
306 RULES.evaluate(array, parent, child_idx)
307 }
308}
309
310pub(super) const LEFT_PARTS_SLOT: usize = 0;
312pub(super) const RIGHT_PARTS_SLOT: usize = 1;
314pub(super) const LP_PATCH_SLOTS: PatchSlotIndices = PatchSlotIndices {
316 indices: LP_PATCH_INDICES_SLOT,
317 values: LP_PATCH_VALUES_SLOT,
318 chunk_offsets: LP_PATCH_CHUNK_OFFSETS_SLOT,
319};
320pub(super) const LP_PATCH_INDICES_SLOT: usize = 2;
321pub(super) const LP_PATCH_VALUES_SLOT: usize = 3;
323pub(super) const LP_PATCH_CHUNK_OFFSETS_SLOT: usize = 4;
325pub(super) const NUM_SLOTS: usize = 5;
326pub(super) const SLOT_NAMES: [&str; NUM_SLOTS] = [
327 "left_parts",
328 "right_parts",
329 "patch_indices",
330 "patch_values",
331 "patch_chunk_offsets",
332];
333
334#[derive(Clone, Debug)]
335pub struct ALPRDData {
336 patches_data: Option<PatchesData>,
337 left_parts_dictionary: Buffer<u16>,
338 right_bit_width: u8,
339}
340
341impl Display for ALPRDData {
342 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
343 write!(f, "right_bit_width: {}", self.right_bit_width)?;
344 if let Some(pd) = &self.patches_data {
345 write!(f, ", patch_offset: {}", pd.offset())?;
346 }
347 Ok(())
348 }
349}
350
351#[derive(Clone, Debug)]
352pub struct ALPRDDataParts {
353 pub left_parts: ArrayRef,
354 pub left_parts_patches: Option<Patches>,
355 pub left_parts_dictionary: Buffer<u16>,
356 pub right_parts: ArrayRef,
357}
358
359#[derive(Clone, Debug)]
360pub struct ALPRD;
361
362impl ALPRD {
363 pub fn try_new(
364 dtype: DType,
365 left_parts: ArrayRef,
366 left_parts_dictionary: Buffer<u16>,
367 right_parts: ArrayRef,
368 right_bit_width: u8,
369 left_parts_patches: Option<Patches>,
370 ) -> VortexResult<ALPRDArray> {
371 let len = left_parts.len();
372 let slots = ALPRDData::make_slots(&left_parts, &right_parts, left_parts_patches.as_ref());
373 let data = ALPRDData::new(left_parts_dictionary, right_bit_width, left_parts_patches);
374 Array::try_from_parts(ArrayParts::new(ALPRD, dtype, len, data).with_slots(slots))
375 }
376
377 pub unsafe fn new_unchecked(
380 dtype: DType,
381 left_parts: ArrayRef,
382 left_parts_dictionary: Buffer<u16>,
383 right_parts: ArrayRef,
384 right_bit_width: u8,
385 left_parts_patches: Option<Patches>,
386 ) -> ALPRDArray {
387 let len = left_parts.len();
388 let slots = ALPRDData::make_slots(&left_parts, &right_parts, left_parts_patches.as_ref());
389 let data = unsafe {
390 ALPRDData::new_unchecked(left_parts_dictionary, right_bit_width, left_parts_patches)
391 };
392 unsafe {
393 Array::from_parts_unchecked(ArrayParts::new(ALPRD, dtype, len, data).with_slots(slots))
394 }
395 }
396}
397
398impl ALPRDData {
399 pub fn new(
401 left_parts_dictionary: Buffer<u16>,
402 right_bit_width: u8,
403 left_parts_patches: Option<Patches>,
404 ) -> Self {
405 Self {
406 patches_data: left_parts_patches.as_ref().map(PatchesData::from_patches),
407 left_parts_dictionary,
408 right_bit_width,
409 }
410 }
411
412 pub(crate) unsafe fn new_unchecked(
415 left_parts_dictionary: Buffer<u16>,
416 right_bit_width: u8,
417 left_parts_patches: Option<Patches>,
418 ) -> Self {
419 Self::new(left_parts_dictionary, right_bit_width, left_parts_patches)
420 }
421
422 fn make_slots(
423 left_parts: &ArrayRef,
424 right_parts: &ArrayRef,
425 patches: Option<&Patches>,
426 ) -> ArraySlots {
427 let mut slots: ArraySlots = smallvec![Some(left_parts.clone()), Some(right_parts.clone())];
428 PatchesData::push_slots(&mut slots, patches);
429 slots
430 }
431
432 pub fn into_parts(self, left_parts: ArrayRef, right_parts: ArrayRef) -> ALPRDDataParts {
434 ALPRDDataParts {
435 left_parts,
436 left_parts_patches: None,
437 left_parts_dictionary: self.left_parts_dictionary,
438 right_parts,
439 }
440 }
441
442 #[inline]
443 pub fn right_bit_width(&self) -> u8 {
444 self.right_bit_width
445 }
446
447 #[inline]
449 pub fn left_parts_dictionary(&self) -> &Buffer<u16> {
450 &self.left_parts_dictionary
451 }
452}
453
454fn left_parts_from_slots(slots: &[Option<ArrayRef>]) -> &ArrayRef {
455 slots[LEFT_PARTS_SLOT]
456 .as_ref()
457 .vortex_expect("ALPRDArray left_parts slot")
458}
459
460fn right_parts_from_slots(slots: &[Option<ArrayRef>]) -> &ArrayRef {
461 slots[RIGHT_PARTS_SLOT]
462 .as_ref()
463 .vortex_expect("ALPRDArray right_parts slot")
464}
465
466fn patches_from_slots(
467 slots: &[Option<ArrayRef>],
468 patches_data: Option<&PatchesData>,
469 len: usize,
470) -> Option<Patches> {
471 PatchesData::patches_from_slots(patches_data, len, slots, LP_PATCH_SLOTS)
472}
473
474#[allow(clippy::disallowed_methods)]
475fn validate_parts(
476 dtype: &DType,
477 len: usize,
478 left_parts: &ArrayRef,
479 right_parts: &ArrayRef,
480 left_parts_patches: Option<&Patches>,
481) -> VortexResult<()> {
482 if !dtype.is_float() {
483 vortex_bail!("ALPRDArray given invalid DType ({dtype})");
484 }
485
486 vortex_ensure!(
487 left_parts.len() == len,
488 "left_parts len {} != outer len {len}",
489 left_parts.len(),
490 );
491 vortex_ensure!(
492 right_parts.len() == len,
493 "right_parts len {} != outer len {len}",
494 right_parts.len(),
495 );
496
497 if !left_parts.dtype().is_unsigned_int() {
498 vortex_bail!("left_parts dtype must be uint");
499 }
500 if dtype.is_nullable() != left_parts.dtype().is_nullable() {
501 vortex_bail!(
502 "ALPRDArray dtype nullability ({}) must match left_parts dtype nullability ({})",
503 dtype,
504 left_parts.dtype()
505 );
506 }
507
508 let expected_right_parts_dtype = match dtype {
509 DType::Primitive(PType::F32, _) => DType::Primitive(PType::U32, Nullability::NonNullable),
510 DType::Primitive(PType::F64, _) => DType::Primitive(PType::U64, Nullability::NonNullable),
511 _ => vortex_bail!("Expected f32 or f64 dtype, got {:?}", dtype),
512 };
513 vortex_ensure!(
514 right_parts.dtype() == &expected_right_parts_dtype,
515 "right_parts dtype {} does not match expected {}",
516 right_parts.dtype(),
517 expected_right_parts_dtype,
518 );
519
520 if let Some(patches) = left_parts_patches {
521 vortex_ensure!(
522 patches.array_len() == len,
523 "patches array_len {} != outer len {len}",
524 patches.array_len(),
525 );
526 let expected = left_parts.dtype().as_nonnullable();
531 vortex_ensure!(
532 patches.dtype() == &expected,
533 "patches dtype {} must be the non-nullable left_parts dtype {}",
534 patches.dtype(),
535 expected,
536 );
537 }
538
539 Ok(())
540}
541
542pub trait ALPRDArrayExt: TypedArrayRef<ALPRD> {
543 fn left_parts(&self) -> &ArrayRef {
544 left_parts_from_slots(self.as_ref().slots())
545 }
546
547 fn right_parts(&self) -> &ArrayRef {
548 right_parts_from_slots(self.as_ref().slots())
549 }
550
551 fn right_bit_width(&self) -> u8 {
552 ALPRDData::right_bit_width(self)
553 }
554
555 fn left_parts_patches(&self) -> Option<Patches> {
556 patches_from_slots(
557 self.as_ref().slots(),
558 self.patches_data.as_ref(),
559 self.as_ref().len(),
560 )
561 }
562
563 fn left_parts_dictionary(&self) -> &Buffer<u16> {
564 ALPRDData::left_parts_dictionary(self)
565 }
566}
567impl<T: TypedArrayRef<ALPRD>> ALPRDArrayExt for T {}
568
569pub trait ALPRDArrayOwnedExt {
570 fn into_data_parts(self) -> ALPRDDataParts;
571}
572
573impl ALPRDArrayOwnedExt for Array<ALPRD> {
574 fn into_data_parts(self) -> ALPRDDataParts {
575 let left_parts_patches = self.left_parts_patches();
576 let left_parts = self.left_parts().clone();
577 let right_parts = self.right_parts().clone();
578 let mut parts = ALPRDDataParts {
579 left_parts,
580 left_parts_patches: None,
581 left_parts_dictionary: self.left_parts_dictionary().clone(),
582 right_parts,
583 };
584 parts.left_parts_patches = left_parts_patches;
585 parts
586 }
587}
588
589impl ValidityChild<ALPRD> for ALPRD {
590 fn validity_child(array: ArrayView<'_, ALPRD>) -> ArrayRef {
591 array.left_parts().clone()
592 }
593}
594
595#[cfg(test)]
596mod test {
597 use std::sync::LazyLock;
598
599 use prost::Message;
600 use rstest::rstest;
601 use vortex_array::VortexSessionExecute;
602 use vortex_array::arrays::PrimitiveArray;
603 use vortex_array::assert_arrays_eq;
604 use vortex_array::dtype::PType;
605 use vortex_array::patches::PatchesMetadata;
606 use vortex_array::test_harness::check_metadata;
607 use vortex_session::VortexSession;
608
609 use super::ALPRDMetadata;
610 use crate::ALPRDFloat;
611 use crate::alp_rd;
612
613 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
614 let session = vortex_array::array_session();
615 crate::initialize(&session);
616 session
617 });
618
619 #[rstest]
620 #[case(vec![0.1f32.next_up(); 1024], 1.123_848_f32)]
621 #[case(vec![0.1f64.next_up(); 1024], 1.123_848_591_110_992_f64)]
622 fn test_array_encode_with_nulls_and_patches<T: ALPRDFloat>(
623 #[case] reals: Vec<T>,
624 #[case] seed: T,
625 ) {
626 let mut ctx = SESSION.create_execution_ctx();
627 assert_eq!(reals.len(), 1024, "test expects 1024-length fixture");
628 let mut reals: Vec<Option<T>> = reals.into_iter().map(Some).collect();
630 reals[1] = None;
631 reals[5] = None;
632 reals[900] = None;
633
634 let real_array = PrimitiveArray::from_option_iter(reals.iter().cloned());
636
637 let encoder: alp_rd::RDEncoder = alp_rd::RDEncoder::new(&[seed.powi(-2)]);
639
640 let rd_array = encoder.encode(real_array.as_view());
641
642 let decoded = rd_array
643 .as_array()
644 .clone()
645 .execute::<PrimitiveArray>(&mut ctx)
646 .unwrap();
647
648 assert_arrays_eq!(decoded, PrimitiveArray::from_option_iter(reals), &mut ctx);
649 }
650
651 #[cfg_attr(miri, ignore)]
652 #[test]
653 fn test_alprd_metadata() {
654 check_metadata(
655 "alprd.metadata",
656 &ALPRDMetadata {
657 right_bit_width: u32::MAX,
658 patches: Some(PatchesMetadata::new(
659 usize::MAX,
660 usize::MAX,
661 PType::U64,
662 None,
663 None,
664 None,
665 )),
666 dict: Vec::new(),
667 left_parts_ptype: PType::U64 as i32,
668 dict_len: 8,
669 }
670 .encode_to_vec(),
671 );
672 }
673}