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 rules;
22
23#[cfg(test)]
24mod tests {
25 use prost::Message;
26 use vortex_array::dtype::PType;
27 use vortex_array::patches::PatchesMetadata;
28 use vortex_array::test_harness::check_metadata;
29
30 use crate::alp::array::ALPMetadata;
31
32 #[cfg_attr(miri, ignore)]
33 #[test]
34 fn test_alp_metadata() {
35 check_metadata(
36 "alp.metadata",
37 &ALPMetadata {
38 patches: Some(PatchesMetadata::new(
39 usize::MAX,
40 usize::MAX,
41 PType::U64,
42 None,
43 None,
44 None,
45 )),
46 exp_e: u32::MAX,
47 exp_f: u32::MAX,
48 }
49 .encode_to_vec(),
50 );
51 }
52}
53
54pub use array::*;
55pub use compress::alp_encode;
56pub use decompress::decompress_into_array;
57use vortex_array::dtype::NativePType;
58use vortex_array::scalar::PValue;
59use vortex_buffer::Buffer;
60use vortex_buffer::BufferMut;
61
62const SAMPLE_SIZE: usize = 32;
63
64#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
65pub struct Exponents {
66 pub e: u8,
67 pub f: u8,
68}
69
70impl Display for Exponents {
71 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
72 write!(f, "e: {}, f: {}", self.e, self.f)
73 }
74}
75
76mod private {
77 pub trait Sealed {}
78
79 impl Sealed for f32 {}
80 impl Sealed for f64 {}
81}
82
83pub trait ALPFloat: private::Sealed + Float + Display + NativePType {
84 type ALPInt: PrimInt + Display + ToPrimitive + Copy + NativePType + Into<PValue>;
85
86 const FRACTIONAL_BITS: u8;
87 const MAX_EXPONENT: u8;
88 const SWEET: Self;
89 const F10: &'static [Self];
90 const IF10: &'static [Self];
91
92 #[inline]
94 fn fast_round(self) -> Self {
95 (self + Self::SWEET) - Self::SWEET
96 }
97
98 fn as_int(self) -> Self::ALPInt;
100
101 fn from_int(n: Self::ALPInt) -> Self;
103
104 fn find_best_exponents(values: &[Self]) -> Exponents {
105 let mut best_exp = Exponents { e: 0, f: 0 };
106 let mut best_nbytes: usize = usize::MAX;
107
108 let sample = (values.len() > SAMPLE_SIZE).then(|| {
109 values
110 .iter()
111 .step_by(values.len() / SAMPLE_SIZE)
112 .cloned()
113 .collect_vec()
114 });
115
116 for e in (0..Self::MAX_EXPONENT).rev() {
117 for f in 0..e {
118 let (_, encoded, _, exc_patches, _) = Self::encode(
119 sample.as_deref().unwrap_or(values),
120 Some(Exponents { e, f }),
121 );
122
123 let size = Self::estimate_encoded_size(&encoded, &exc_patches);
124 if size < best_nbytes {
125 best_nbytes = size;
126 best_exp = Exponents { e, f };
127 } else if size == best_nbytes && e - f < best_exp.e - best_exp.f {
128 best_exp = Exponents { e, f };
129 }
130 }
131 }
132
133 best_exp
134 }
135
136 #[inline]
137 fn estimate_encoded_size(encoded: &[Self::ALPInt], patches: &[Self]) -> usize {
138 let bits_per_encoded = encoded
139 .iter()
140 .minmax()
141 .into_option()
142 .and_then(|(min, max)| max.checked_sub(min))
144 .and_then(|range_size: <Self as ALPFloat>::ALPInt| range_size.to_u64())
145 .and_then(|range_size| {
146 range_size
147 .checked_ilog2()
148 .map(|bits| (bits + 1) as usize)
149 .or(Some(0))
150 })
151 .unwrap_or(size_of::<Self::ALPInt>() * 8);
152
153 let encoded_bytes = (encoded.len() * bits_per_encoded).div_ceil(8);
154 let patch_bytes = patches.len() * (size_of::<Self>() + size_of::<u16>());
157
158 encoded_bytes + patch_bytes
159 }
160
161 #[expect(
162 clippy::type_complexity,
163 reason = "tuple return type is appropriate for multiple encoding outputs"
164 )]
165 fn encode(
166 values: &[Self],
167 exponents: Option<Exponents>,
168 ) -> (
169 Exponents,
170 Buffer<Self::ALPInt>,
171 Buffer<u64>,
172 Buffer<Self>,
173 BufferMut<u64>,
174 ) {
175 let exp = exponents.unwrap_or_else(|| Self::find_best_exponents(values));
176
177 let mut encoded_output = BufferMut::<Self::ALPInt>::with_capacity(values.len());
178
179 let mut patch_indices = BufferMut::<u64>::with_capacity(values.len() / 32);
181 let mut patch_values = BufferMut::<Self>::with_capacity(values.len() / 32);
182
183 let mut chunk_offsets = BufferMut::<u64>::with_capacity(values.len().div_ceil(1024));
185 let mut fill_value: Option<Self::ALPInt> = None;
186
187 for chunk in values.chunks(1024) {
188 chunk_offsets.push(patch_indices.len() as u64);
189 encode_chunk_unchecked(
190 chunk,
191 exp,
192 &mut encoded_output,
193 &mut patch_indices,
194 &mut patch_values,
195 &mut fill_value,
196 );
197 }
198
199 (
200 exp,
201 encoded_output.freeze(),
202 patch_indices.freeze(),
203 patch_values.freeze(),
204 chunk_offsets,
205 )
206 }
207
208 #[inline]
209 fn encode_single(value: Self, exponents: Exponents) -> Option<Self::ALPInt> {
210 let encoded = Self::encode_single_unchecked(value, exponents);
211 let decoded = Self::decode_single(encoded, exponents);
212 if decoded.is_eq(value) {
213 return Some(encoded);
214 }
215 None
216 }
217
218 fn encode_above(value: Self, exponents: Exponents) -> Self::ALPInt {
219 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
220 .ceil()
221 .as_int()
222 }
223
224 fn encode_below(value: Self, exponents: Exponents) -> Self::ALPInt {
225 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
226 .floor()
227 .as_int()
228 }
229
230 fn decode(encoded: &[Self::ALPInt], exponents: Exponents) -> Vec<Self> {
231 let mut values = Vec::with_capacity(encoded.len());
232 for encoded in encoded {
233 values.push(Self::decode_single(*encoded, exponents));
234 }
235 values
236 }
237
238 fn decode_buffer(encoded: BufferMut<Self::ALPInt>, exponents: Exponents) -> BufferMut<Self> {
239 encoded.map_each_in_place(move |encoded| Self::decode_single(encoded, exponents))
240 }
241
242 fn decode_into(encoded: &[Self::ALPInt], exponents: Exponents, output: &mut [Self]) {
243 assert_eq!(encoded.len(), output.len());
244
245 for i in 0..encoded.len() {
246 output[i] = Self::decode_single(encoded[i], exponents)
247 }
248 }
249
250 fn decode_slice_inplace(encoded: &mut [Self::ALPInt], exponents: Exponents) {
251 let decoded: &mut [Self] = unsafe { transmute(encoded) };
252 decoded.iter_mut().for_each(|v| {
253 *v = Self::decode_single(
254 unsafe { transmute_copy::<Self, Self::ALPInt>(v) },
255 exponents,
256 )
257 })
258 }
259
260 #[inline(always)]
261 fn decode_single(encoded: Self::ALPInt, exponents: Exponents) -> Self {
262 Self::from_int(encoded) * Self::F10[exponents.f as usize] * Self::IF10[exponents.e as usize]
263 }
264
265 #[inline(always)]
268 fn encode_single_unchecked(value: Self, exponents: Exponents) -> Self::ALPInt {
269 (value * Self::F10[exponents.e as usize] * Self::IF10[exponents.f as usize])
270 .fast_round()
271 .as_int()
272 }
273}
274
275#[expect(
276 clippy::cast_possible_truncation,
277 reason = "intentional truncation for ALP encoding"
278)]
279fn encode_chunk_unchecked<T: ALPFloat>(
280 chunk: &[T],
281 exp: Exponents,
282 encoded_output: &mut BufferMut<T::ALPInt>,
283 patch_indices: &mut BufferMut<u64>,
284 patch_values: &mut BufferMut<T>,
285 fill_value: &mut Option<T::ALPInt>,
286) {
287 let num_prev_encoded = encoded_output.len();
288 let num_prev_patches = patch_indices.len();
289 assert_eq!(patch_indices.len(), patch_values.len());
290 let has_filled = fill_value.is_some();
291
292 let mut chunk_patch_count = 0;
294 encoded_output.extend_trusted(chunk.iter().map(|&v| {
295 let encoded = T::encode_single_unchecked(v, exp);
296 let decoded = T::decode_single(encoded, exp);
297 let neq = !decoded.is_eq(v) as usize;
298 chunk_patch_count += neq;
299 encoded
300 }));
301 let chunk_patch_count = chunk_patch_count; assert_eq!(encoded_output.len(), num_prev_encoded + chunk.len());
303
304 if chunk_patch_count > 0 {
305 patch_indices.reserve(chunk_patch_count + 1);
308 patch_values.reserve(chunk_patch_count + 1);
309
310 let patch_indices_mut = patch_indices.spare_capacity_mut();
312 let patch_values_mut = patch_values.spare_capacity_mut();
313 let mut chunk_patch_index = 0;
314 for i in num_prev_encoded..encoded_output.len() {
315 let decoded = T::decode_single(encoded_output[i], exp);
316 patch_indices_mut[chunk_patch_index].write(i as u64);
318 patch_values_mut[chunk_patch_index].write(chunk[i - num_prev_encoded]);
319 chunk_patch_index += !decoded.is_eq(chunk[i - num_prev_encoded]) as usize;
320 }
321 assert_eq!(chunk_patch_index, chunk_patch_count);
322 unsafe {
323 patch_indices.set_len(num_prev_patches + chunk_patch_count);
324 patch_values.set_len(num_prev_patches + chunk_patch_count);
325 }
326 }
327
328 if fill_value.is_none() && (num_prev_encoded + chunk_patch_count < encoded_output.len()) {
331 assert_eq!(num_prev_encoded, num_prev_patches);
332 for i in num_prev_encoded..encoded_output.len() {
333 if i >= patch_indices.len() || patch_indices[i] != i as u64 {
334 *fill_value = Some(encoded_output[i]);
335 break;
336 }
337 }
338 }
339
340 if let Some(fill_value) = fill_value {
343 let start_patch = if !has_filled { 0 } else { num_prev_patches };
345 for patch_idx in &patch_indices[start_patch..] {
346 encoded_output[*patch_idx as usize] = *fill_value;
347 }
348 }
349}
350
351impl ALPFloat for f32 {
352 type ALPInt = i32;
353 const FRACTIONAL_BITS: u8 = 23;
354 const MAX_EXPONENT: u8 = 10;
355 const SWEET: Self =
356 (1 << Self::FRACTIONAL_BITS) as Self + (1 << (Self::FRACTIONAL_BITS - 1)) as Self;
357
358 const F10: &'static [Self] = &[
359 1.0,
360 10.0,
361 100.0,
362 1000.0,
363 10000.0,
364 100000.0,
365 1000000.0,
366 10000000.0,
367 100000000.0,
368 1000000000.0,
369 10000000000.0, ];
371 const IF10: &'static [Self] = &[
372 1.0,
373 0.1,
374 0.01,
375 0.001,
376 0.0001,
377 0.00001,
378 0.000001,
379 0.0000001,
380 0.00000001,
381 0.000000001,
382 0.0000000001, ];
384
385 #[inline(always)]
386 #[expect(
387 clippy::cast_possible_truncation,
388 reason = "intentional float to int truncation for ALP encoding"
389 )]
390 fn as_int(self) -> Self::ALPInt {
391 self as _
392 }
393
394 #[inline(always)]
395 fn from_int(n: Self::ALPInt) -> Self {
396 n as _
397 }
398}
399
400impl ALPFloat for f64 {
401 type ALPInt = i64;
402 const FRACTIONAL_BITS: u8 = 52;
403 const MAX_EXPONENT: u8 = 18; const SWEET: Self =
405 (1u64 << Self::FRACTIONAL_BITS) as Self + (1u64 << (Self::FRACTIONAL_BITS - 1)) as Self;
406 const F10: &'static [Self] = &[
407 1.0,
408 10.0,
409 100.0,
410 1000.0,
411 10000.0,
412 100000.0,
413 1000000.0,
414 10000000.0,
415 100000000.0,
416 1000000000.0,
417 10000000000.0,
418 100000000000.0,
419 1000000000000.0,
420 10000000000000.0,
421 100000000000000.0,
422 1000000000000000.0,
423 10000000000000000.0,
424 100000000000000000.0,
425 1000000000000000000.0,
426 10000000000000000000.0,
427 100000000000000000000.0,
428 1000000000000000000000.0,
429 10000000000000000000000.0,
430 100000000000000000000000.0, ];
432
433 const IF10: &'static [Self] = &[
434 1.0,
435 0.1,
436 0.01,
437 0.001,
438 0.0001,
439 0.00001,
440 0.000001,
441 0.0000001,
442 0.00000001,
443 0.000000001,
444 0.0000000001,
445 0.00000000001,
446 0.000000000001,
447 0.0000000000001,
448 0.00000000000001,
449 0.000000000000001,
450 0.0000000000000001,
451 0.00000000000000001,
452 0.000000000000000001,
453 0.0000000000000000001,
454 0.00000000000000000001,
455 0.000000000000000000001,
456 0.0000000000000000000001,
457 0.00000000000000000000001, ];
459
460 #[inline(always)]
461 #[expect(
462 clippy::cast_possible_truncation,
463 reason = "intentional float to int truncation for ALP encoding"
464 )]
465 fn as_int(self) -> Self::ALPInt {
466 self as _
467 }
468
469 #[inline(always)]
470 fn from_int(n: Self::ALPInt) -> Self {
471 n as _
472 }
473}