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