1use std::fmt::Display;
5use std::fmt::Formatter;
6use std::mem::size_of;
7use std::mem::transmute;
8use std::mem::transmute_copy;
9
10use itertools::Itertools;
11use num_traits::CheckedSub;
12use num_traits::Float;
13use num_traits::PrimInt;
14use num_traits::ToPrimitive;
15
16mod array;
17mod compress;
18pub(crate) mod compute;
19mod decompress;
20mod ops;
21mod plugin;
22mod rules;
23
24pub(crate) use plugin::ALPPatchedPlugin;
25
26#[cfg(test)]
27mod tests {
28 use prost::Message;
29 use vortex_array::dtype::PType;
30 use vortex_array::patches::PatchesMetadata;
31 use vortex_array::test_harness::check_metadata;
32
33 use crate::alp::ALPFloat;
34 use crate::alp::Exponents;
35 use crate::alp::array::ALPMetadata;
36
37 fn check_estimate_matches<T: ALPFloat>(values: &[T]) {
40 for e in 0..T::MAX_EXPONENT {
41 for f in 0..e {
42 let exp = Exponents { e, f };
43 let lightweight = T::estimate_encoded_size_for_exponents(values, exp);
44 let (_, encoded, _, patches, _) = T::encode(values, Some(exp));
45 let full = T::estimate_encoded_size(&encoded, &patches);
46 assert_eq!(
47 lightweight,
48 full,
49 "mismatch at e={e}, f={f}, len={}",
50 values.len()
51 );
52 }
53 }
54 }
55
56 #[test]
57 fn estimate_for_exponents_matches_full_encode() {
58 let mut f64s: Vec<f64> = (0..200).map(|i| i as f64 / 100.0).collect();
61 f64s.extend((0..60).map(|i| i as f64 / 7.0));
62 f64s.extend([1e17, -1e17, 0.0, 123.0]);
63 check_estimate_matches(&f64s);
64 check_estimate_matches::<f64>(&[123.456; 5]);
65 check_estimate_matches::<f64>(&[42.0]);
66 check_estimate_matches::<f64>(&[1.0 / 3.0; 8]);
68
69 let mut f32s: Vec<f32> = (0..200).map(|i| i as f32 / 100.0).collect();
70 f32s.extend((0..60).map(|i| i as f32 / 7.0));
71 f32s.extend([1e9, -1e9, 0.0, 123.0]);
72 check_estimate_matches(&f32s);
73 check_estimate_matches::<f32>(&[1.0 / 3.0; 8]);
74 }
75
76 #[cfg_attr(miri, ignore)]
77 #[test]
78 fn test_alp_metadata() {
79 check_metadata(
80 "alp.metadata",
81 &ALPMetadata {
82 patches: Some(PatchesMetadata::new(
83 usize::MAX,
84 usize::MAX,
85 PType::U64,
86 None,
87 None,
88 None,
89 )),
90 exp_e: u32::MAX,
91 exp_f: u32::MAX,
92 }
93 .encode_to_vec(),
94 );
95 }
96}
97
98pub use array::*;
99pub use compress::alp_encode;
100pub use decompress::decompress_into_array;
101use vortex_array::dtype::NativePType;
102use vortex_array::scalar::PValue;
103use vortex_buffer::Buffer;
104use vortex_buffer::BufferMut;
105use vortex_session::VortexSession;
106
107const SAMPLE_SIZE: usize = 32;
108
109pub(crate) fn initialize(session: &VortexSession) {
110 rules::initialize(session);
111}
112
113#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
114pub struct Exponents {
115 pub e: u8,
116 pub f: u8,
117}
118
119impl Display for Exponents {
120 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121 write!(f, "e: {}, f: {}", self.e, self.f)
122 }
123}
124
125mod private {
126 pub trait Sealed {}
127
128 impl Sealed for f32 {}
129 impl Sealed for f64 {}
130}
131
132fn update_bounds<I: Ord + Copy>(bounds: &mut Option<(I, I)>, value: I) {
134 *bounds = Some(bounds.map_or((value, value), |(min, max)| {
135 (min.min(value), max.max(value))
136 }));
137}
138
139pub trait ALPFloat: private::Sealed + Float + Display + NativePType {
140 type ALPInt: PrimInt + Display + ToPrimitive + Copy + NativePType + Into<PValue>;
141
142 const FRACTIONAL_BITS: u8;
143 const MAX_EXPONENT: u8;
144 const SWEET: Self;
145 const F10: &'static [Self];
146 const IF10: &'static [Self];
147
148 #[inline]
150 fn fast_round(self) -> Self {
151 (self + Self::SWEET) - Self::SWEET
152 }
153
154 fn as_int(self) -> Self::ALPInt;
156
157 fn from_int(n: Self::ALPInt) -> Self;
159
160 fn find_best_exponents(values: &[Self]) -> Exponents {
161 let mut best_exp = Exponents { e: 0, f: 0 };
162 let mut best_nbytes: usize = usize::MAX;
163
164 let sample = (values.len() > SAMPLE_SIZE).then(|| {
165 values
166 .iter()
167 .step_by(values.len() / SAMPLE_SIZE)
168 .cloned()
169 .collect_vec()
170 });
171 let sample = sample.as_deref().unwrap_or(values);
172
173 for e in (0..Self::MAX_EXPONENT).rev() {
174 for f in 0..e {
175 let exp = Exponents { e, f };
176 let size = Self::estimate_encoded_size_for_exponents(sample, exp);
177 if size < best_nbytes {
178 best_nbytes = size;
179 best_exp = exp;
180 } else if size == best_nbytes && e - f < best_exp.e - best_exp.f {
181 best_exp = exp;
182 }
183 }
184 }
185
186 best_exp
187 }
188
189 fn estimate_encoded_size_for_exponents(values: &[Self], exponents: Exponents) -> usize {
192 let mut kept: Option<(Self::ALPInt, Self::ALPInt)> = None;
196 let mut all: Option<(Self::ALPInt, Self::ALPInt)> = None;
197 let mut patch_count = 0usize;
198
199 for &value in values {
200 let encoded = Self::encode_single_unchecked(value, exponents);
201 update_bounds(&mut all, encoded);
202 if Self::decode_single(encoded, exponents).is_eq(value) {
203 update_bounds(&mut kept, encoded);
204 } else {
205 patch_count += 1;
206 }
207 }
208
209 let range = if patch_count == values.len() {
210 all
211 } else {
212 kept
213 };
214
215 let bits_per_encoded = range
216 .and_then(|(min, max)| max.checked_sub(&min))
217 .and_then(|range_size| range_size.to_u64())
218 .and_then(|range_size| {
219 range_size
220 .checked_ilog2()
221 .map(|bits| (bits + 1) as usize)
222 .or(Some(0))
223 })
224 .unwrap_or(size_of::<Self::ALPInt>() * 8);
225
226 let encoded_bytes = (values.len() * bits_per_encoded).div_ceil(8);
227 let patch_bytes = patch_count * (size_of::<Self>() + size_of::<u16>());
230
231 encoded_bytes + patch_bytes
232 }
233
234 #[inline]
235 fn estimate_encoded_size(encoded: &[Self::ALPInt], patches: &[Self]) -> usize {
236 let bits_per_encoded = encoded
237 .iter()
238 .minmax()
239 .into_option()
240 .and_then(|(min, max)| max.checked_sub(min))
242 .and_then(|range_size: <Self as ALPFloat>::ALPInt| range_size.to_u64())
243 .and_then(|range_size| {
244 range_size
245 .checked_ilog2()
246 .map(|bits| (bits + 1) as usize)
247 .or(Some(0))
248 })
249 .unwrap_or(size_of::<Self::ALPInt>() * 8);
250
251 let encoded_bytes = (encoded.len() * bits_per_encoded).div_ceil(8);
252 let patch_bytes = patches.len() * (size_of::<Self>() + size_of::<u16>());
255
256 encoded_bytes + patch_bytes
257 }
258
259 #[expect(
260 clippy::type_complexity,
261 reason = "tuple return type is appropriate for multiple encoding outputs"
262 )]
263 fn encode(
264 values: &[Self],
265 exponents: Option<Exponents>,
266 ) -> (
267 Exponents,
268 Buffer<Self::ALPInt>,
269 Buffer<u64>,
270 Buffer<Self>,
271 BufferMut<u64>,
272 ) {
273 let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values));
274
275 let mut encoded_output = BufferMut::<Self::ALPInt>::with_capacity(values.len());
276
277 let mut patch_indices = BufferMut::<u64>::with_capacity(values.len() / 32);
279 let mut patch_values = BufferMut::<Self>::with_capacity(values.len() / 32);
280
281 let mut chunk_offsets = BufferMut::<u64>::with_capacity(values.len().div_ceil(1024));
283 let mut fill_value: Option<Self::ALPInt> = None;
284
285 for chunk in values.chunks(1024) {
286 chunk_offsets.push(patch_indices.len() as u64);
287 encode_chunk_unchecked(
288 chunk,
289 exp,
290 &mut encoded_output,
291 &mut patch_indices,
292 &mut patch_values,
293 &mut fill_value,
294 );
295 }
296
297 (
298 exp,
299 encoded_output.freeze(),
300 patch_indices.freeze(),
301 patch_values.freeze(),
302 chunk_offsets,
303 )
304 }
305
306 #[inline]
307 fn encode_single(value: Self, exponents: Exponents) -> Option<Self::ALPInt> {
308 let encoded = Self::encode_single_unchecked(value, exponents);
309 let decoded = Self::decode_single(encoded, exponents);
310 if decoded.is_eq(value) {
311 return Some(encoded);
312 }
313 None
314 }
315
316 fn encode_above(value: Self, exponents: Exponents) -> Self::ALPInt {
317 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
318 .ceil()
319 .as_int()
320 }
321
322 fn encode_below(value: Self, exponents: Exponents) -> Self::ALPInt {
323 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
324 .floor()
325 .as_int()
326 }
327
328 fn decode(encoded: &[Self::ALPInt], exponents: Exponents) -> Vec<Self> {
329 let mut values = Vec::with_capacity(encoded.len());
330 for encoded in encoded {
331 values.push(Self::decode_single(*encoded, exponents));
332 }
333 values
334 }
335
336 fn decode_buffer(encoded: BufferMut<Self::ALPInt>, exponents: Exponents) -> BufferMut<Self> {
337 encoded.map_each_in_place(move |encoded| Self::decode_single(encoded, exponents))
338 }
339
340 fn decode_into(encoded: &[Self::ALPInt], exponents: Exponents, output: &mut [Self]) {
341 assert_eq!(encoded.len(), output.len());
342
343 for i in 0..encoded.len() {
344 output[i] = Self::decode_single(encoded[i], exponents)
345 }
346 }
347
348 fn decode_slice_inplace(encoded: &mut [Self::ALPInt], exponents: Exponents) {
349 let decoded: &mut [Self] = unsafe { transmute(encoded) };
350 decoded.iter_mut().for_each(|v| {
351 *v = Self::decode_single(
352 unsafe { transmute_copy::<Self, Self::ALPInt>(v) },
353 exponents,
354 )
355 })
356 }
357
358 #[inline(always)]
359 fn decode_single(encoded: Self::ALPInt, exponents: Exponents) -> Self {
360 Self::from_int(encoded) * Self::F10[exponents.f as usize] * Self::IF10[exponents.e as usize]
361 }
362
363 #[inline(always)]
366 fn encode_single_unchecked(value: Self, exponents: Exponents) -> Self::ALPInt {
367 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
368 .fast_round()
369 .as_int()
370 }
371}
372
373#[expect(
374 clippy::cast_possible_truncation,
375 reason = "intentional truncation for ALP encoding"
376)]
377fn encode_chunk_unchecked<T: ALPFloat>(
378 chunk: &[T],
379 exp: Exponents,
380 encoded_output: &mut BufferMut<T::ALPInt>,
381 patch_indices: &mut BufferMut<u64>,
382 patch_values: &mut BufferMut<T>,
383 fill_value: &mut Option<T::ALPInt>,
384) {
385 let num_prev_encoded = encoded_output.len();
386 let num_prev_patches = patch_indices.len();
387 assert_eq!(patch_indices.len(), patch_values.len());
388 let has_filled = fill_value.is_some();
389
390 let mut chunk_patch_count = 0;
392 encoded_output.extend_trusted(chunk.iter().map(|&v| {
393 let encoded = T::encode_single_unchecked(v, exp);
394 let decoded = T::decode_single(encoded, exp);
395 let neq = !decoded.is_eq(v) as usize;
396 chunk_patch_count += neq;
397 encoded
398 }));
399 let chunk_patch_count = chunk_patch_count; assert_eq!(encoded_output.len(), num_prev_encoded + chunk.len());
401
402 if chunk_patch_count > 0 {
403 patch_indices.reserve(chunk_patch_count + 1);
406 patch_values.reserve(chunk_patch_count + 1);
407
408 let patch_indices_mut = patch_indices.spare_capacity_mut();
410 let patch_values_mut = patch_values.spare_capacity_mut();
411 let mut chunk_patch_index = 0;
412 for i in num_prev_encoded..encoded_output.len() {
413 let decoded = T::decode_single(encoded_output[i], exp);
414 patch_indices_mut[chunk_patch_index].write(i as u64);
416 patch_values_mut[chunk_patch_index].write(chunk[i - num_prev_encoded]);
417 chunk_patch_index += !decoded.is_eq(chunk[i - num_prev_encoded]) as usize;
418 }
419 assert_eq!(chunk_patch_index, chunk_patch_count);
420 unsafe {
421 patch_indices.set_len(num_prev_patches + chunk_patch_count);
422 patch_values.set_len(num_prev_patches + chunk_patch_count);
423 }
424 }
425
426 if fill_value.is_none() && (num_prev_encoded + chunk_patch_count < encoded_output.len()) {
429 assert_eq!(num_prev_encoded, num_prev_patches);
430 for i in num_prev_encoded..encoded_output.len() {
431 if i >= patch_indices.len() || patch_indices[i] != i as u64 {
432 *fill_value = Some(encoded_output[i]);
433 break;
434 }
435 }
436 }
437
438 if let Some(fill_value) = fill_value {
441 let start_patch = if !has_filled { 0 } else { num_prev_patches };
443 for patch_idx in &patch_indices[start_patch..] {
444 encoded_output[*patch_idx as usize] = *fill_value;
445 }
446 }
447}
448
449impl ALPFloat for f32 {
450 type ALPInt = i32;
451 const FRACTIONAL_BITS: u8 = 23;
452 const MAX_EXPONENT: u8 = 10;
453 const SWEET: Self =
454 (1 << Self::FRACTIONAL_BITS) as Self + (1 << (Self::FRACTIONAL_BITS - 1)) as Self;
455
456 const F10: &'static [Self] = &[
457 1.0,
458 10.0,
459 100.0,
460 1000.0,
461 10000.0,
462 100000.0,
463 1000000.0,
464 10000000.0,
465 100000000.0,
466 1000000000.0,
467 10000000000.0, ];
469 const IF10: &'static [Self] = &[
470 1.0,
471 0.1,
472 0.01,
473 0.001,
474 0.0001,
475 0.00001,
476 0.000001,
477 0.0000001,
478 0.00000001,
479 0.000000001,
480 0.0000000001, ];
482
483 #[inline(always)]
484 #[expect(
485 clippy::cast_possible_truncation,
486 reason = "intentional float to int truncation for ALP encoding"
487 )]
488 fn as_int(self) -> Self::ALPInt {
489 self as _
490 }
491
492 #[inline(always)]
493 fn from_int(n: Self::ALPInt) -> Self {
494 n as _
495 }
496}
497
498impl ALPFloat for f64 {
499 type ALPInt = i64;
500 const FRACTIONAL_BITS: u8 = 52;
501 const MAX_EXPONENT: u8 = 18; const SWEET: Self =
503 (1u64 << Self::FRACTIONAL_BITS) as Self + (1u64 << (Self::FRACTIONAL_BITS - 1)) as Self;
504 const F10: &'static [Self] = &[
505 1.0,
506 10.0,
507 100.0,
508 1000.0,
509 10000.0,
510 100000.0,
511 1000000.0,
512 10000000.0,
513 100000000.0,
514 1000000000.0,
515 10000000000.0,
516 100000000000.0,
517 1000000000000.0,
518 10000000000000.0,
519 100000000000000.0,
520 1000000000000000.0,
521 10000000000000000.0,
522 100000000000000000.0,
523 1000000000000000000.0,
524 10000000000000000000.0,
525 100000000000000000000.0,
526 1000000000000000000000.0,
527 10000000000000000000000.0,
528 100000000000000000000000.0, ];
530
531 const IF10: &'static [Self] = &[
532 1.0,
533 0.1,
534 0.01,
535 0.001,
536 0.0001,
537 0.00001,
538 0.000001,
539 0.0000001,
540 0.00000001,
541 0.000000001,
542 0.0000000001,
543 0.00000000001,
544 0.000000000001,
545 0.0000000000001,
546 0.00000000000001,
547 0.000000000000001,
548 0.0000000000000001,
549 0.00000000000000001,
550 0.000000000000000001,
551 0.0000000000000000001,
552 0.00000000000000000001,
553 0.000000000000000000001,
554 0.0000000000000000000001,
555 0.00000000000000000000001, ];
557
558 #[inline(always)]
559 #[expect(
560 clippy::cast_possible_truncation,
561 reason = "intentional float to int truncation for ALP encoding"
562 )]
563 fn as_int(self) -> Self::ALPInt {
564 self as _
565 }
566
567 #[inline(always)]
568 fn from_int(n: Self::ALPInt) -> Self {
569 n as _
570 }
571}