1use std::fmt::Debug;
5use std::fmt::Display;
6use std::fmt::Formatter;
7use std::hash::Hash;
8use std::hash::Hasher;
9
10use prost::Message;
11use vortex_array::Array;
12use vortex_array::ArrayEq;
13use vortex_array::ArrayHash;
14use vortex_array::ArrayId;
15use vortex_array::ArrayParts;
16use vortex_array::ArrayRef;
17use vortex_array::ArraySlots;
18use vortex_array::ArrayView;
19use vortex_array::EqMode;
20use vortex_array::ExecutionCtx;
21use vortex_array::ExecutionResult;
22use vortex_array::IntoArray;
23use vortex_array::TypedArrayRef;
24use vortex_array::array_slots;
25use vortex_array::arrays::Primitive;
26use vortex_array::buffer::BufferHandle;
27use vortex_array::dtype::DType;
28use vortex_array::dtype::NativePType;
29use vortex_array::dtype::PType;
30use vortex_array::patches::PatchSlotIndices;
31use vortex_array::patches::Patches;
32use vortex_array::patches::PatchesData;
33use vortex_array::patches::PatchesMetadata;
34use vortex_array::require_child;
35use vortex_array::require_patches;
36use vortex_array::serde::ArrayChildren;
37use vortex_array::smallvec::smallvec;
38use vortex_array::vtable::VTable;
39use vortex_array::vtable::ValidityChild;
40use vortex_array::vtable::ValidityVTableFromChild;
41use vortex_error::VortexExpect;
42use vortex_error::VortexResult;
43use vortex_error::vortex_bail;
44use vortex_error::vortex_ensure;
45use vortex_error::vortex_panic;
46use vortex_session::VortexSession;
47use vortex_session::registry::CachedId;
48
49use crate::ALPFloat;
50use crate::alp::Exponents;
51use crate::alp::decompress::execute_decompress;
52use crate::alp::rules::RULES;
53
54pub type ALPArray = Array<ALP>;
56
57impl ArrayHash for ALPData {
58 fn array_hash<H: Hasher>(&self, state: &mut H, _accuracy: EqMode) {
59 self.exponents.hash(state);
60 self.patches_data.hash(state);
61 }
62}
63
64impl ArrayEq for ALPData {
65 fn array_eq(&self, other: &Self, _accuracy: EqMode) -> bool {
66 self.exponents == other.exponents && self.patches_data == other.patches_data
67 }
68}
69
70impl VTable for ALP {
71 type TypedArrayData = ALPData;
72
73 type OperationsVTable = Self;
74 type ValidityVTable = ValidityVTableFromChild;
75
76 fn id(&self) -> ArrayId {
77 static ID: CachedId = CachedId::new("vortex.alp");
78 *ID
79 }
80
81 fn validate(
82 &self,
83 data: &ALPData,
84 dtype: &DType,
85 len: usize,
86 slots: &[Option<ArrayRef>],
87 ) -> VortexResult<()> {
88 let alp_slots = ALPSlotsView::from_slots(slots);
89 let patches =
90 PatchesData::patches_from_slots(data.patches_data.as_ref(), len, slots, PATCH_SLOTS);
91 validate_parts(dtype, len, data.exponents, alp_slots.encoded, patches)
92 }
93
94 fn nbuffers(_array: ArrayView<'_, Self>) -> usize {
95 0
96 }
97
98 fn buffer(_array: ArrayView<'_, Self>, idx: usize) -> BufferHandle {
99 vortex_panic!("ALPArray buffer index {idx} out of bounds")
100 }
101
102 fn buffer_name(_array: ArrayView<'_, Self>, _idx: usize) -> Option<String> {
103 None
104 }
105
106 fn with_buffers(
107 &self,
108 array: ArrayView<'_, Self>,
109 buffers: &[BufferHandle],
110 ) -> VortexResult<ArrayParts<Self>> {
111 vortex_array::vtable::with_empty_buffers(self, array, buffers)
112 }
113
114 fn serialize(
115 array: ArrayView<'_, Self>,
116 _session: &VortexSession,
117 ) -> VortexResult<Option<Vec<u8>>> {
118 let exponents = array.exponents();
119 Ok(Some(
120 ALPMetadata {
121 exp_e: exponents.e as u32,
122 exp_f: exponents.f as u32,
123 patches: array
124 .patches()
125 .map(|p| p.to_metadata(array.len(), array.dtype()))
126 .transpose()?,
127 }
128 .encode_to_vec(),
129 ))
130 }
131
132 fn deserialize(
133 &self,
134 dtype: &DType,
135 len: usize,
136 metadata: &[u8],
137 _buffers: &[BufferHandle],
138 children: &dyn ArrayChildren,
139 _session: &VortexSession,
140 ) -> VortexResult<ArrayParts<Self>> {
141 let metadata = ALPMetadata::decode(metadata)?;
142 let encoded_ptype = match &dtype {
143 DType::Primitive(PType::F32, n) => DType::Primitive(PType::I32, *n),
144 DType::Primitive(PType::F64, n) => DType::Primitive(PType::I64, *n),
145 d => vortex_bail!(MismatchedTypes: "f32 or f64", d),
146 };
147 let encoded = children.get(0, &encoded_ptype, len)?;
148
149 let patches = metadata
150 .patches
151 .map(|p| {
152 let indices = children.get(1, &p.indices_dtype()?, p.len()?)?;
153 let values = children.get(2, dtype, p.len()?)?;
154 let chunk_offsets = p
155 .chunk_offsets_dtype()?
156 .map(|dtype| children.get(3, &dtype, usize::try_from(p.chunk_offsets_len())?))
157 .transpose()?;
158
159 Patches::new(len, p.offset()?, indices, values, chunk_offsets)
160 })
161 .transpose()?;
162
163 let slots = ALPData::make_slots(&encoded, patches.as_ref());
164 let data = ALPData::new(
165 Exponents {
166 e: u8::try_from(metadata.exp_e)?,
167 f: u8::try_from(metadata.exp_f)?,
168 },
169 patches,
170 );
171 Ok(ArrayParts::new(self.clone(), dtype.clone(), len, data).with_slots(slots))
172 }
173
174 fn slot_name(_array: ArrayView<'_, Self>, idx: usize) -> String {
175 ALPSlots::NAMES[idx].to_string()
176 }
177
178 fn execute(array: Array<Self>, ctx: &mut ExecutionCtx) -> VortexResult<ExecutionResult> {
179 let array = require_child!(array, array.encoded(), ALPSlots::ENCODED => Primitive);
180 require_patches!(
181 array,
182 ALPSlots::PATCH_INDICES,
183 ALPSlots::PATCH_VALUES,
184 ALPSlots::PATCH_CHUNK_OFFSETS
185 );
186
187 Ok(ExecutionResult::done(
188 execute_decompress(array, ctx)?.into_array(),
189 ))
190 }
191
192 fn reduce_parent(
193 array: ArrayView<'_, Self>,
194 parent: &ArrayRef,
195 child_idx: usize,
196 ) -> VortexResult<Option<ArrayRef>> {
197 RULES.evaluate(array, parent, child_idx)
198 }
199}
200
201#[array_slots(ALP)]
202pub struct ALPSlots {
203 #[slot(0)]
205 pub encoded: ArrayRef,
206 #[slot(1)]
208 pub patch_indices: Option<ArrayRef>,
209 #[slot(2)]
211 pub patch_values: Option<ArrayRef>,
212 #[slot(3)]
214 pub patch_chunk_offsets: Option<ArrayRef>,
215}
216
217const PATCH_SLOTS: PatchSlotIndices = PatchSlotIndices {
218 indices: ALPSlots::PATCH_INDICES,
219 values: ALPSlots::PATCH_VALUES,
220 chunk_offsets: ALPSlots::PATCH_CHUNK_OFFSETS,
221};
222
223#[derive(Clone, Debug)]
224pub struct ALPData {
225 patches_data: Option<PatchesData>,
226 exponents: Exponents,
227}
228
229impl Display for ALPData {
230 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
231 write!(f, "exponents: {}", self.exponents)?;
232 if let Some(pd) = &self.patches_data {
233 write!(f, ", patch_offset: {}", pd.offset())?;
234 }
235 Ok(())
236 }
237}
238
239#[derive(Clone, Debug)]
240pub struct ALP;
241
242#[derive(Clone, prost::Message)]
243pub struct ALPMetadata {
244 #[prost(uint32, tag = "1")]
245 pub(crate) exp_e: u32,
246 #[prost(uint32, tag = "2")]
247 pub(crate) exp_f: u32,
248 #[prost(message, optional, tag = "3")]
249 pub(crate) patches: Option<PatchesMetadata>,
250}
251
252impl ALPData {
253 fn validate_components(
254 encoded: &ArrayRef,
255 exponents: Exponents,
256 patches: Option<&Patches>,
257 ) -> VortexResult<()> {
258 vortex_ensure!(
259 matches!(
260 encoded.dtype(),
261 DType::Primitive(PType::I32 | PType::I64, _)
262 ),
263 "ALP encoded ints have invalid DType {}",
264 encoded.dtype(),
265 );
266
267 let Exponents { e, f } = exponents;
270 match encoded.dtype().as_ptype() {
271 PType::I32 => {
272 vortex_ensure!(exponents.e <= f32::MAX_EXPONENT, "e out of bounds: {e}");
273 vortex_ensure!(exponents.f <= f32::MAX_EXPONENT, "f out of bounds: {f}");
274 if let Some(patches) = patches {
275 Self::validate_patches::<f32>(patches, encoded)?;
276 }
277 }
278 PType::I64 => {
279 vortex_ensure!(e <= f64::MAX_EXPONENT, "e out of bounds: {e}");
280 vortex_ensure!(f <= f64::MAX_EXPONENT, "f out of bounds: {f}");
281
282 if let Some(patches) = patches {
283 Self::validate_patches::<f64>(patches, encoded)?;
284 }
285 }
286 _ => unreachable!(),
287 }
288
289 if let Some(patches) = patches {
291 vortex_ensure!(
292 patches.array_len() == encoded.len(),
293 "patches array_len != encoded len: {} != {}",
294 patches.array_len(),
295 encoded.len()
296 );
297
298 }
300
301 Ok(())
302 }
303
304 fn logical_dtype(encoded: &ArrayRef) -> VortexResult<DType> {
305 match encoded.dtype() {
306 DType::Primitive(PType::I32, nullability) => {
307 Ok(DType::Primitive(PType::F32, *nullability))
308 }
309 DType::Primitive(PType::I64, nullability) => {
310 Ok(DType::Primitive(PType::F64, *nullability))
311 }
312 _ => vortex_bail!("ALP encoded ints have invalid DType {}", encoded.dtype(),),
313 }
314 }
315
316 fn validate_patches<T: ALPFloat + NativePType>(
318 patches: &Patches,
319 encoded: &ArrayRef,
320 ) -> VortexResult<()> {
321 vortex_ensure!(
322 patches.array_len() == encoded.len(),
323 "patches array_len != encoded len: {} != {}",
324 patches.array_len(),
325 encoded.len()
326 );
327
328 let expected_type = DType::Primitive(T::PTYPE, encoded.dtype().nullability());
329 vortex_ensure!(
330 patches.dtype() == &expected_type,
331 "Expected patches type {expected_type}, actual {}",
332 patches.dtype(),
333 );
334
335 Ok(())
336 }
337}
338
339impl ALPData {
340 pub fn new(exponents: Exponents, patches: Option<Patches>) -> Self {
345 Self {
346 patches_data: patches.as_ref().map(PatchesData::from_patches),
347 exponents,
348 }
349 }
350
351 pub(crate) unsafe fn new_unchecked(exponents: Exponents, patches: Option<Patches>) -> Self {
362 Self::new(exponents, patches)
363 }
364}
365
366impl ALP {
368 pub fn new(encoded: ArrayRef, exponents: Exponents, patches: Option<Patches>) -> ALPArray {
369 let dtype = ALPData::logical_dtype(&encoded).vortex_expect("ALP encoded dtype");
370 let len = encoded.len();
371 let slots = ALPData::make_slots(&encoded, patches.as_ref());
372 unsafe {
373 Array::from_parts_unchecked(
374 ArrayParts::new(ALP, dtype, len, ALPData::new(exponents, patches))
375 .with_slots(slots),
376 )
377 }
378 }
379
380 pub fn try_new(
381 encoded: ArrayRef,
382 exponents: Exponents,
383 patches: Option<Patches>,
384 ) -> VortexResult<ALPArray> {
385 let dtype = ALPData::logical_dtype(&encoded)?;
386 let len = encoded.len();
387 let slots = ALPData::make_slots(&encoded, patches.as_ref());
388 let data = ALPData::new(exponents, patches);
389 Array::try_from_parts(ArrayParts::new(ALP, dtype, len, data).with_slots(slots))
390 }
391
392 pub unsafe fn new_unchecked(
395 encoded: ArrayRef,
396 exponents: Exponents,
397 patches: Option<Patches>,
398 ) -> ALPArray {
399 let dtype = ALPData::logical_dtype(&encoded).vortex_expect("ALP encoded dtype");
400 let len = encoded.len();
401 let slots = ALPData::make_slots(&encoded, patches.as_ref());
402 let data = unsafe { ALPData::new_unchecked(exponents, patches) };
403 unsafe {
404 Array::from_parts_unchecked(ArrayParts::new(ALP, dtype, len, data).with_slots(slots))
405 }
406 }
407}
408
409impl ALPData {
410 fn make_slots(encoded: &ArrayRef, patches: Option<&Patches>) -> ArraySlots {
411 let mut slots: ArraySlots = smallvec![Some(encoded.clone())];
412 PatchesData::push_slots(&mut slots, patches);
413 slots
414 }
415
416 #[inline]
417 pub fn exponents(&self) -> Exponents {
418 self.exponents
419 }
420}
421
422pub trait ALPArrayExt: ALPArraySlotsExt {
423 fn exponents(&self) -> Exponents {
424 self.exponents
425 }
426
427 fn patches(&self) -> Option<Patches> {
428 PatchesData::patches_from_slots(
429 self.patches_data.as_ref(),
430 self.as_ref().len(),
431 self.as_ref().slots(),
432 PATCH_SLOTS,
433 )
434 }
435}
436
437fn validate_parts(
438 dtype: &DType,
439 len: usize,
440 exponents: Exponents,
441 encoded: &ArrayRef,
442 patches: Option<Patches>,
443) -> VortexResult<()> {
444 let logical_dtype = ALPData::logical_dtype(encoded)?;
445 ALPData::validate_components(encoded, exponents, patches.as_ref())?;
446 vortex_ensure!(
447 encoded.len() == len,
448 "ALP encoded len {} != outer len {len}",
449 encoded.len(),
450 );
451 vortex_ensure!(
452 &logical_dtype == dtype,
453 "ALP dtype {} does not match encoded logical dtype {}",
454 dtype,
455 logical_dtype,
456 );
457 Ok(())
458}
459
460impl<T: TypedArrayRef<ALP>> ALPArrayExt for T {}
461
462pub trait ALPArrayOwnedExt {
463 fn into_parts(self) -> (ArrayRef, Exponents, Option<Patches>);
464}
465
466impl ALPArrayOwnedExt for Array<ALP> {
467 #[inline]
468 fn into_parts(self) -> (ArrayRef, Exponents, Option<Patches>) {
469 let patches = self.patches();
470 let exponents = self.exponents();
471 let encoded = self.encoded().clone();
472 (encoded, exponents, patches)
473 }
474}
475
476impl ValidityChild<ALP> for ALP {
477 fn validity_child(array: ArrayView<'_, ALP>) -> ArrayRef {
478 array.encoded().clone()
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use std::f64::consts::PI;
485 use std::sync::LazyLock;
486
487 use rstest::rstest;
488 use vortex_array::Canonical;
489 use vortex_array::IntoArray;
490 use vortex_array::VortexSessionExecute;
491 use vortex_array::arrays::PrimitiveArray;
492 use vortex_array::assert_arrays_eq;
493 use vortex_error::VortexExpect;
494 use vortex_session::VortexSession;
495
496 use super::*;
497 use crate::alp_encode;
498 use crate::decompress_into_array;
499
500 static SESSION: LazyLock<VortexSession> = LazyLock::new(|| {
501 let session = vortex_array::array_session();
502 crate::initialize(&session);
503 session
504 });
505
506 #[rstest]
507 #[case(0)]
508 #[case(1)]
509 #[case(100)]
510 #[case(1023)]
511 #[case(1024)]
512 #[case(1025)]
513 #[case(2047)]
514 #[case(2048)]
515 #[case(2049)]
516 fn test_execute_f32(#[case] size: usize) {
517 let mut ctx = SESSION.create_execution_ctx();
518 let values = PrimitiveArray::from_iter((0..size).map(|i| i as f32));
519 let encoded = alp_encode(values.as_view(), None, &mut ctx).unwrap();
520
521 let result_canonical = {
522 encoded
523 .clone()
524 .into_array()
525 .execute::<Canonical>(&mut ctx)
526 .unwrap()
527 };
528 let expected = decompress_into_array(encoded, &mut SESSION.create_execution_ctx()).unwrap();
530
531 assert_arrays_eq!(result_canonical.into_array(), expected, &mut ctx);
532 }
533
534 #[rstest]
535 #[case(0)]
536 #[case(1)]
537 #[case(100)]
538 #[case(1023)]
539 #[case(1024)]
540 #[case(1025)]
541 #[case(2047)]
542 #[case(2048)]
543 #[case(2049)]
544 fn test_execute_f64(#[case] size: usize) {
545 let values = PrimitiveArray::from_iter((0..size).map(|i| i as f64));
546 let encoded =
547 alp_encode(values.as_view(), None, &mut SESSION.create_execution_ctx()).unwrap();
548
549 let mut ctx = SESSION.create_execution_ctx();
550 let result_canonical = encoded
551 .clone()
552 .into_array()
553 .execute::<Canonical>(&mut ctx)
554 .unwrap();
555 let expected = decompress_into_array(encoded, &mut SESSION.create_execution_ctx()).unwrap();
557
558 assert_arrays_eq!(result_canonical.into_array(), expected, &mut ctx);
559 }
560
561 #[rstest]
562 #[case(100)]
563 #[case(1023)]
564 #[case(1024)]
565 #[case(1025)]
566 #[case(2047)]
567 #[case(2048)]
568 #[case(2049)]
569 fn test_execute_with_patches(#[case] size: usize) {
570 let values: Vec<f64> = (0..size)
571 .map(|i| match i % 4 {
572 0..=2 => 1.0,
573 _ => PI,
574 })
575 .collect();
576
577 let array = PrimitiveArray::from_iter(values);
578 let encoded =
579 alp_encode(array.as_view(), None, &mut SESSION.create_execution_ctx()).unwrap();
580 assert!(encoded.patches().unwrap().array_len() > 0);
581
582 let mut ctx = SESSION.create_execution_ctx();
583 let result_canonical = encoded
584 .clone()
585 .into_array()
586 .execute::<Canonical>(&mut ctx)
587 .unwrap();
588 let expected = decompress_into_array(encoded, &mut SESSION.create_execution_ctx()).unwrap();
590
591 assert_arrays_eq!(result_canonical.into_array(), expected, &mut ctx);
592 }
593
594 #[rstest]
595 #[case(0)]
596 #[case(1)]
597 #[case(100)]
598 #[case(1023)]
599 #[case(1024)]
600 #[case(1025)]
601 #[case(2047)]
602 #[case(2048)]
603 #[case(2049)]
604 fn test_execute_with_validity(#[case] size: usize) {
605 let values: Vec<Option<f32>> = (0..size)
606 .map(|i| if i % 2 == 1 { None } else { Some(1.0) })
607 .collect();
608
609 let array = PrimitiveArray::from_option_iter(values);
610 let encoded =
611 alp_encode(array.as_view(), None, &mut SESSION.create_execution_ctx()).unwrap();
612
613 let mut ctx = SESSION.create_execution_ctx();
614 let result_canonical = encoded
615 .clone()
616 .into_array()
617 .execute::<Canonical>(&mut ctx)
618 .unwrap();
619 let expected = decompress_into_array(encoded, &mut SESSION.create_execution_ctx()).unwrap();
621
622 assert_arrays_eq!(result_canonical.into_array(), expected, &mut ctx);
623 }
624
625 #[rstest]
626 #[case(100)]
627 #[case(1023)]
628 #[case(1024)]
629 #[case(1025)]
630 #[case(2047)]
631 #[case(2048)]
632 #[case(2049)]
633 fn test_execute_with_patches_and_validity(#[case] size: usize) {
634 let values: Vec<Option<f64>> = (0..size)
635 .map(|idx| match idx % 3 {
636 0 => Some(1.0),
637 1 => None,
638 _ => Some(PI),
639 })
640 .collect();
641
642 let array = PrimitiveArray::from_option_iter(values);
643 let encoded =
644 alp_encode(array.as_view(), None, &mut SESSION.create_execution_ctx()).unwrap();
645 assert!(encoded.patches().unwrap().array_len() > 0);
646
647 let mut ctx = SESSION.create_execution_ctx();
648 let result_canonical = encoded
649 .clone()
650 .into_array()
651 .execute::<Canonical>(&mut ctx)
652 .unwrap();
653 let expected = decompress_into_array(encoded, &mut SESSION.create_execution_ctx()).unwrap();
655
656 assert_arrays_eq!(result_canonical.into_array(), expected, &mut ctx);
657 }
658
659 #[rstest]
660 #[case(500, 100)]
661 #[case(1000, 200)]
662 #[case(2048, 512)]
663 fn test_execute_sliced_vector(#[case] size: usize, #[case] slice_start: usize) {
664 let values: Vec<Option<f64>> = (0..size)
665 .map(|i| {
666 if i % 5 == 0 {
667 None
668 } else if i % 4 == 3 {
669 Some(PI)
670 } else {
671 Some(1.0)
672 }
673 })
674 .collect();
675
676 let mut ctx = SESSION.create_execution_ctx();
677 let array = PrimitiveArray::from_option_iter(values.clone());
678 let encoded = alp_encode(array.as_view(), None, &mut ctx).unwrap();
679
680 let slice_end = size - slice_start;
681 let slice_len = slice_end - slice_start;
682 let sliced_encoded = encoded.slice(slice_start..slice_end).unwrap();
683
684 let result_canonical = sliced_encoded.execute::<Canonical>(&mut ctx).unwrap();
685 let result_primitive = result_canonical.into_primitive();
686
687 for idx in 0..slice_len {
688 let expected_value = values[slice_start + idx];
689
690 let result_valid = result_primitive
691 .validity()
692 .vortex_expect("result validity should be derivable")
693 .execute_is_valid(idx, &mut ctx)
694 .unwrap();
695 assert_eq!(
696 result_valid,
697 expected_value.is_some(),
698 "Validity mismatch at idx={idx}",
699 );
700
701 if let Some(expected_val) = expected_value {
702 let result_val = result_primitive.as_slice::<f64>()[idx];
703 assert_eq!(result_val, expected_val, "Value mismatch at idx={idx}",);
704 }
705 }
706 }
707
708 #[rstest]
709 #[case(500, 100)]
710 #[case(1000, 200)]
711 #[case(2048, 512)]
712 fn test_sliced_to_primitive(#[case] size: usize, #[case] slice_start: usize) {
713 let mut ctx = SESSION.create_execution_ctx();
714 let values: Vec<Option<f64>> = (0..size)
715 .map(|i| {
716 if i % 5 == 0 {
717 None
718 } else if i % 4 == 3 {
719 Some(PI)
720 } else {
721 Some(1.0)
722 }
723 })
724 .collect();
725
726 let array = PrimitiveArray::from_option_iter(values.clone());
727 let encoded = alp_encode(array.as_view(), None, &mut ctx).unwrap();
728
729 let slice_end = size - slice_start;
730 let slice_len = slice_end - slice_start;
731 let sliced_encoded = encoded.slice(slice_start..slice_end).unwrap();
732
733 let result_primitive = sliced_encoded.execute::<PrimitiveArray>(&mut ctx).unwrap();
734
735 for idx in 0..slice_len {
736 let expected_value = values[slice_start + idx];
737
738 let result_valid = result_primitive
739 .as_ref()
740 .validity()
741 .unwrap()
742 .execute_mask(result_primitive.as_ref().len(), &mut ctx)
743 .unwrap()
744 .value(idx);
745 assert_eq!(
746 result_valid,
747 expected_value.is_some(),
748 "Validity mismatch at idx={idx}",
749 );
750
751 if let Some(expected_val) = expected_value {
752 let buf = result_primitive.to_buffer::<f64>();
753 let result_val = buf.as_slice()[idx];
754 assert_eq!(result_val, expected_val, "Value mismatch at idx={idx}",);
755 }
756 }
757 }
758
759 #[test]
768 fn test_execute_decompress_with_patches_no_chunk_offsets_regression_5948() {
769 let values: Vec<f64> = vec![1.0, 2.0, PI, 4.0, 5.0];
771 let original = PrimitiveArray::from_iter(values);
772
773 let normally_encoded = alp_encode(
775 original.as_view(),
776 None,
777 &mut SESSION.create_execution_ctx(),
778 )
779 .unwrap();
780 assert!(
781 normally_encoded.patches().is_some(),
782 "Test requires patches to be present"
783 );
784
785 let original_patches = normally_encoded.patches().unwrap();
786 assert!(
787 original_patches.chunk_offsets().is_some(),
788 "Normal encoding should have chunk_offsets"
789 );
790
791 let patches_without_chunk_offsets = Patches::new(
793 original_patches.array_len(),
794 original_patches.offset(),
795 original_patches.indices().clone(),
796 original_patches.values().clone(),
797 None, )
799 .unwrap();
800
801 let alp_without_chunk_offsets = ALP::new(
803 normally_encoded.encoded().clone(),
804 normally_encoded.exponents(),
805 Some(patches_without_chunk_offsets),
806 );
807
808 let result_legacy = decompress_into_array(
810 alp_without_chunk_offsets.clone(),
811 &mut SESSION.create_execution_ctx(),
812 )
813 .unwrap();
814 let legacy_slice = result_legacy.as_slice::<f64>();
815
816 assert!(
818 (legacy_slice[2] - PI).abs() < 1e-10,
819 "Legacy path should have PI at index 2, got {}",
820 legacy_slice[2]
821 );
822
823 let result_execute = {
825 let mut ctx = SESSION.create_execution_ctx();
826 execute_decompress(alp_without_chunk_offsets, &mut ctx).unwrap()
827 };
828 let execute_slice = result_execute.as_slice::<f64>();
829
830 assert!(
832 (execute_slice[2] - PI).abs() < 1e-10,
833 "Execute path should have PI at index 2, but got {} (patches were dropped!)",
834 execute_slice[2]
835 );
836 }
837}