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