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