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