use std::cmp::Reverse;
use bytes::Bytes;
use crate::basic::Encoding;
use crate::data_type::DataType;
use crate::encodings::alp::{
ALP_COMPRESSION_MODE, ALP_DEFAULT_LOG_VECTOR_SIZE, ALP_HEADER_SIZE,
ALP_INTEGER_ENCODING_FOR_BIT_PACK, AlpExact, AlpFloat, AlpHeader, AlpInfo, ForInfo,
};
use crate::encodings::encoding::Encoder;
use crate::errors::{ParquetError, Result};
use crate::util::bit_util::{BitWriter, num_required_bits};
const VECTOR_SIZE: usize = 1 << ALP_DEFAULT_LOG_VECTOR_SIZE;
const SAMPLES_PER_VECTOR: usize = 256;
const SAMPLE_VECTORS: usize = 8;
const MAX_COMBINATIONS: usize = 5;
const SAMPLING_EARLY_EXIT_THRESHOLD: usize = 4;
fn exception_bits<F: AlpFloat>() -> u64 {
(F::Exact::WIDTH as u64 * 8) + 16
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct ExponentAndFactor {
exponent: u8,
factor: u8,
}
#[derive(Debug, Clone, Copy)]
struct Combination {
params: ExponentAndFactor,
num_appearances: u64,
estimated_size_bits: u64,
}
fn rank(c: &Combination) -> (u64, Reverse<u64>, u8, u8) {
(
c.num_appearances,
Reverse(c.estimated_size_bits),
c.params.exponent,
c.params.factor,
)
}
fn is_better(c1: &Combination, c2: &Combination) -> bool {
rank(c1) > rank(c2)
}
fn estimate_size_bits<F: AlpFloat>(
sample: &[F],
params: ExponentAndFactor,
penalize_exceptions: bool,
) -> Option<u64> {
let encode_scale = F::encode_scale(params.exponent, params.factor);
let decode_scale = F::decode_scale(params.exponent, params.factor);
let mut num_exceptions = 0u64;
let mut min = None;
let mut max = None;
for &value in sample {
let encoded = value.encode_value(encode_scale);
if F::decode_value(encoded, decode_scale) == value {
min = Some(min.map_or(encoded, |m: <F::Exact as AlpExact>::Signed| m.min(encoded)));
max = Some(max.map_or(encoded, |m: <F::Exact as AlpExact>::Signed| m.max(encoded)));
} else {
num_exceptions += 1;
}
}
let num_values = sample.len() as u64;
let num_non_exceptions = num_values - num_exceptions;
if penalize_exceptions && num_non_exceptions < 2 {
return None;
}
let (Some(min), Some(max)) = (min, max) else {
return Some(num_values * exception_bits::<F>());
};
let range =
F::Exact::reinterpret_from_signed(max).wrapping_sub(F::Exact::reinterpret_from_signed(min));
let bit_width = u64::from(num_required_bits(range.to_u64()));
Some(num_values * bit_width + num_exceptions * exception_bits::<F>())
}
fn sample_values<F: AlpFloat>(values: &[F], out: &mut Vec<F>) {
out.clear();
let stride = values.len().div_ceil(SAMPLES_PER_VECTOR).max(1);
out.extend(values.iter().step_by(stride).copied());
}
fn build_preset<F: AlpFloat>(values: &[F]) -> Vec<ExponentAndFactor> {
let num_vectors = values.len().div_ceil(VECTOR_SIZE);
let vector_stride = num_vectors.div_ceil(SAMPLE_VECTORS).max(1);
let mut sample = Vec::with_capacity(SAMPLES_PER_VECTOR);
let mut tally: Vec<Combination> = Vec::new();
for vector in values.chunks(VECTOR_SIZE).step_by(vector_stride) {
sample_values(vector, &mut sample);
let mut best = Combination {
params: ExponentAndFactor {
exponent: F::MAX_EXPONENT,
factor: F::MAX_EXPONENT,
},
num_appearances: 0,
estimated_size_bits: sample.len() as u64
* (exception_bits::<F>() + F::Exact::WIDTH as u64 * 8),
};
for exponent in 0..=F::MAX_EXPONENT {
for factor in 0..=exponent {
let params = ExponentAndFactor { exponent, factor };
let Some(estimated_size_bits) = estimate_size_bits(&sample, params, true) else {
continue;
};
let candidate = Combination {
params,
num_appearances: 0,
estimated_size_bits,
};
if is_better(&candidate, &best) {
best = candidate;
}
}
}
match tally.iter_mut().find(|c| c.params == best.params) {
Some(existing) => existing.num_appearances += 1,
None => tally.push(Combination {
num_appearances: 1,
..best
}),
}
}
for combination in &mut tally {
combination.estimated_size_bits = 0;
}
tally.sort_by_key(|c| Reverse(rank(c)));
tally.truncate(MAX_COMBINATIONS);
if tally.is_empty() {
return vec![ExponentAndFactor {
exponent: 0,
factor: 0,
}];
}
tally.into_iter().map(|c| c.params).collect()
}
fn select_params<F: AlpFloat>(
vector: &[F],
preset: &[ExponentAndFactor],
sample: &mut Vec<F>,
) -> ExponentAndFactor {
if preset.len() == 1 {
return preset[0];
}
sample_values(vector, sample);
let mut best = preset[0];
let mut best_size_bits = u64::MAX;
let mut worse_in_a_row = 0;
for ¶ms in preset {
let Some(size_bits) = estimate_size_bits(sample, params, false) else {
continue;
};
if size_bits >= best_size_bits {
worse_in_a_row += 1;
if worse_in_a_row == SAMPLING_EARLY_EXIT_THRESHOLD {
break;
}
continue;
}
best = params;
best_size_bits = size_bits;
worse_in_a_row = 0;
}
best
}
struct Scratch<F: AlpFloat> {
encoded: Vec<<F::Exact as AlpExact>::Signed>,
exc_mask: Vec<u8>,
exception_positions: Vec<u16>,
exception_values: Vec<F>,
sample: Vec<F>,
}
impl<F: AlpFloat> Scratch<F> {
fn new() -> Self {
Self {
encoded: Vec::new(),
exc_mask: Vec::new(),
exception_positions: Vec::new(),
exception_values: Vec::new(),
sample: Vec::new(),
}
}
fn estimated_memory_size(&self) -> usize {
self.encoded.capacity() * std::mem::size_of::<<F::Exact as AlpExact>::Signed>()
+ self.exc_mask.capacity()
+ self.exception_positions.capacity() * std::mem::size_of::<u16>()
+ self.exception_values.capacity() * std::mem::size_of::<F>()
+ self.sample.capacity() * std::mem::size_of::<F>()
}
}
fn encode_vector<F: AlpFloat>(
values: &[F],
params: ExponentAndFactor,
scratch: &mut Scratch<F>,
out: &mut Vec<u8>,
) -> Result<()> {
let encode_scale = F::encode_scale(params.exponent, params.factor);
let decode_scale = F::decode_scale(params.exponent, params.factor);
let Scratch {
encoded,
exc_mask,
exception_positions,
exception_values,
..
} = scratch;
let zero = F::Exact::default().reinterpret_as_signed();
let n_values = values.len();
encoded.resize(n_values, zero);
exc_mask.resize(n_values, 0);
exception_values.clear();
for ((&value, enc_slot), mask_slot) in values
.iter()
.zip(encoded.iter_mut())
.zip(exc_mask.iter_mut())
{
let encoded_value = value.encode_value(encode_scale);
*enc_slot = encoded_value;
*mask_slot = u8::from(F::decode_value(encoded_value, decode_scale) != value);
}
exception_positions.resize(n_values, 0);
let mut num_exceptions_usize = 0usize;
for (idx, &is_exception) in exc_mask.iter().enumerate() {
exception_positions[num_exceptions_usize] = idx as u16;
num_exceptions_usize += usize::from(is_exception);
}
exception_positions.truncate(num_exceptions_usize);
let num_exceptions = u16::try_from(exception_positions.len()).map_err(|_| {
general_err!(
"Invalid ALP vector: {} exceptions exceeds u16::MAX",
exception_positions.len()
)
})?;
let placeholder = first_non_exception_value::<F>(encoded, exception_positions);
for &position in exception_positions.iter() {
exception_values.push(values[position as usize]);
encoded[position as usize] = placeholder;
}
let min = encoded.iter().copied().min().unwrap_or(zero);
let max = encoded.iter().copied().max().unwrap_or(zero);
let frame_of_reference = F::Exact::reinterpret_from_signed(min);
let range = F::Exact::reinterpret_from_signed(max).wrapping_sub(frame_of_reference);
let bit_width = num_required_bits(range.to_u64());
let alp_info = AlpInfo {
exponent: params.exponent,
factor: params.factor,
num_exceptions,
};
let for_info = ForInfo::<F::Exact> {
frame_of_reference,
bit_width,
};
alp_info.extend_serialized(out);
for_info.extend_serialized(out);
if bit_width > 0 {
let mut writer = BitWriter::new_from_buf(std::mem::take(out));
for &encoded_value in encoded.iter() {
let delta =
F::Exact::reinterpret_from_signed(encoded_value).wrapping_sub(frame_of_reference);
writer.put_value(delta.to_u64(), bit_width as usize);
}
*out = writer.consume();
}
for &position in exception_positions.iter() {
out.extend_from_slice(&position.to_le_bytes());
}
for &value in exception_values.iter() {
value.to_exact_bits().extend_le_bytes(out);
}
Ok(())
}
fn first_non_exception_value<F: AlpFloat>(
encoded: &[<F::Exact as AlpExact>::Signed],
exception_positions: &[u16],
) -> <F::Exact as AlpExact>::Signed {
let mut candidate = 0usize;
for &position in exception_positions {
if position as usize != candidate {
break;
}
candidate += 1;
}
encoded
.get(candidate)
.copied()
.unwrap_or_else(|| F::Exact::default().reinterpret_as_signed())
}
fn encode_page<F: AlpFloat>(
values: &[F],
preset: &[ExponentAndFactor],
scratch: &mut Scratch<F>,
) -> Result<Vec<u8>> {
let header = AlpHeader {
compression_mode: ALP_COMPRESSION_MODE,
integer_encoding: ALP_INTEGER_ENCODING_FOR_BIT_PACK,
vector_size: VECTOR_SIZE,
num_elements: values.len(),
};
let num_vectors = header.num_vectors();
let mut page = Vec::with_capacity(ALP_HEADER_SIZE + num_vectors * 4 + values.len() * 4);
page.extend_from_slice(&header.serialize()?);
let offsets_start = page.len();
page.resize(offsets_start + num_vectors * std::mem::size_of::<u32>(), 0);
for (idx, vector) in values.chunks(VECTOR_SIZE).enumerate() {
let offset = u32::try_from(page.len() - ALP_HEADER_SIZE)
.map_err(|_| general_err!("Invalid ALP page: body exceeds u32 offset range"))?;
let offset_at = offsets_start + idx * std::mem::size_of::<u32>();
page[offset_at..offset_at + 4].copy_from_slice(&offset.to_le_bytes());
let params = select_params(vector, preset, &mut scratch.sample);
encode_vector(vector, params, scratch, &mut page)?;
}
Ok(page)
}
struct StreamingPage<F: AlpFloat> {
body: Vec<u8>,
vector_offsets: Vec<u32>,
carry: Vec<F>,
count: usize,
}
impl<F: AlpFloat> StreamingPage<F> {
fn new() -> Self {
Self {
body: Vec::new(),
vector_offsets: Vec::new(),
carry: Vec::new(),
count: 0,
}
}
fn estimated_memory_size(&self) -> usize {
self.body.capacity()
+ self.vector_offsets.capacity() * std::mem::size_of::<u32>()
+ self.carry.capacity() * std::mem::size_of::<F>()
}
fn push_vector(
&mut self,
vector: &[F],
preset: &[ExponentAndFactor],
scratch: &mut Scratch<F>,
) -> Result<()> {
let body_start = u32::try_from(self.body.len())
.map_err(|_| general_err!("Invalid ALP page: body exceeds u32 offset range"))?;
self.vector_offsets.push(body_start);
let params = select_params(vector, preset, &mut scratch.sample);
encode_vector(vector, params, scratch, &mut self.body)
}
fn put(
&mut self,
mut values: &[F],
preset: &[ExponentAndFactor],
scratch: &mut Scratch<F>,
) -> Result<()> {
self.count += values.len();
if !self.carry.is_empty() {
let need = VECTOR_SIZE - self.carry.len();
if values.len() < need {
self.carry.extend_from_slice(values);
return Ok(());
}
let (head, tail) = values.split_at(need);
self.carry.extend_from_slice(head);
let mut vector = std::mem::take(&mut self.carry);
self.push_vector(&vector, preset, scratch)?;
vector.clear();
self.carry = vector;
values = tail;
}
let (chunks, remainder) = values.as_chunks::<VECTOR_SIZE>();
for vector in chunks {
self.push_vector(vector, preset, scratch)?;
}
self.carry.extend_from_slice(remainder);
Ok(())
}
fn finish(
&mut self,
preset: &[ExponentAndFactor],
scratch: &mut Scratch<F>,
) -> Result<Vec<u8>> {
if !self.carry.is_empty() {
let mut vector = std::mem::take(&mut self.carry);
self.push_vector(&vector, preset, scratch)?;
vector.clear();
self.carry = vector;
}
let num_vectors = self.vector_offsets.len();
let offsets_section = num_vectors * std::mem::size_of::<u32>();
let header = AlpHeader {
compression_mode: ALP_COMPRESSION_MODE,
integer_encoding: ALP_INTEGER_ENCODING_FOR_BIT_PACK,
vector_size: VECTOR_SIZE,
num_elements: self.count,
};
let mut page = Vec::with_capacity(ALP_HEADER_SIZE + offsets_section + self.body.len());
page.extend_from_slice(&header.serialize()?);
for &body_start in &self.vector_offsets {
let page_offset = u32::try_from(offsets_section + body_start as usize)
.map_err(|_| general_err!("Invalid ALP page: body exceeds u32 offset range"))?;
page.extend_from_slice(&page_offset.to_le_bytes());
}
page.extend_from_slice(&self.body);
self.body.clear();
self.vector_offsets.clear();
self.count = 0;
Ok(page)
}
}
pub struct AlpEncoder<T: DataType>
where
T::T: AlpFloat,
{
values: Vec<T::T>,
preset: Option<Vec<ExponentAndFactor>>,
scratch: Scratch<T::T>,
streaming: StreamingPage<T::T>,
}
impl<T: DataType> AlpEncoder<T>
where
T::T: AlpFloat,
{
pub(crate) fn new() -> Self {
Self {
values: Vec::new(),
preset: None,
scratch: Scratch::new(),
streaming: StreamingPage::new(),
}
}
fn current_page_len(&self) -> usize {
if self.preset.is_none() {
self.values.len()
} else {
self.streaming.count
}
}
}
impl<T: DataType> Encoder<T> for AlpEncoder<T>
where
T::T: AlpFloat,
{
fn put(&mut self, values: &[T::T]) -> Result<()> {
let Self {
values: buffer,
preset,
scratch,
streaming,
} = self;
match preset.as_deref() {
None => buffer.extend_from_slice(values),
Some(preset) => streaming.put(values, preset, scratch)?,
}
Ok(())
}
fn encoding(&self) -> Encoding {
Encoding::ALP
}
fn estimated_data_encoded_size(&self) -> usize {
let len = self.current_page_len();
let num_vectors = len.div_ceil(VECTOR_SIZE);
ALP_HEADER_SIZE
+ num_vectors
* (std::mem::size_of::<u32>()
+ AlpInfo::STORED_SIZE
+ ForInfo::<<T::T as AlpFloat>::Exact>::stored_size())
+ len * (2 * <T::T as AlpFloat>::Exact::WIDTH + std::mem::size_of::<u16>())
}
fn estimated_memory_size(&self) -> usize {
self.values.capacity() * std::mem::size_of::<T::T>()
+ self.preset.as_ref().map_or(0, |p| {
p.capacity() * std::mem::size_of::<ExponentAndFactor>()
})
+ self.scratch.estimated_memory_size()
+ self.streaming.estimated_memory_size()
}
fn flush_buffer(&mut self) -> Result<Bytes> {
let Self {
values,
preset,
scratch,
streaming,
} = self;
let page = match preset {
None => {
let built = build_preset(values);
let page = encode_page(values, &built, scratch)?;
values.clear();
*preset = Some(built);
page
}
Some(preset) => streaming.finish(preset.as_slice(), scratch)?,
};
Ok(page.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data_type::{DoubleType, FloatType};
use crate::encodings::decoding::Decoder;
use crate::encodings::decoding::alp_decoder::AlpDecoder;
fn roundtrip<T: DataType>(values: &[T::T]) -> Vec<T::T>
where
T::T: AlpFloat,
<T::T as AlpFloat>::Exact: Send,
{
let mut encoder = AlpEncoder::<T>::new();
encoder.put(values).unwrap();
let page = encoder.flush_buffer().unwrap();
let mut decoder = AlpDecoder::<T>::new();
decoder.set_data(page, values.len()).unwrap();
let mut out = vec![T::T::default(); values.len()];
assert_eq!(decoder.get(&mut out).unwrap(), values.len());
out
}
fn assert_bits_eq<F: AlpFloat + std::fmt::Debug>(actual: &[F], expected: &[F]) {
assert_eq!(actual.len(), expected.len(), "length mismatch");
for (idx, (a, e)) in actual.iter().zip(expected.iter()).enumerate() {
assert_eq!(
a.to_exact_bits(),
e.to_exact_bits(),
"value mismatch at {idx}: expected {e:?}, got {a:?}"
);
}
}
#[test]
fn test_roundtrip_f64_decimals() {
let values: Vec<f64> = (0..500).map(|i| (i as f64) * 0.01 + 1.23).collect();
assert_bits_eq(&roundtrip::<DoubleType>(&values), &values);
}
#[test]
fn test_roundtrip_f32_decimals() {
let values: Vec<f32> = (0..500).map(|i| (i as f32) * 0.5 + 1.25).collect();
assert_bits_eq(&roundtrip::<FloatType>(&values), &values);
}
#[test]
fn test_roundtrip_multiple_vectors() {
let values: Vec<f64> = (0..2600).map(|i| (i as f64) * 0.001).collect();
assert_bits_eq(&roundtrip::<DoubleType>(&values), &values);
}
#[test]
fn test_roundtrip_exceptions() {
let values = vec![
1.5f64,
f64::NAN,
2.5,
f64::INFINITY,
-0.0,
f64::NEG_INFINITY,
3.5,
0.0,
f64::MAX,
f64::MIN,
];
let decoded = roundtrip::<DoubleType>(&values);
assert_bits_eq(&decoded, &values);
assert!(decoded[1].is_nan());
assert!(decoded[4].is_sign_negative());
}
#[test]
fn test_roundtrip_all_identical() {
let values = vec![42.42f64; 3000];
assert_bits_eq(&roundtrip::<DoubleType>(&values), &values);
}
#[test]
fn test_roundtrip_all_exceptions() {
let values = vec![f64::NAN; 100];
let decoded = roundtrip::<DoubleType>(&values);
assert!(decoded.iter().all(|v| v.is_nan()));
}
#[test]
fn test_roundtrip_single_value() {
assert_bits_eq(&roundtrip::<DoubleType>(&[3.25]), &[3.25]);
}
#[test]
fn test_roundtrip_empty() {
let mut encoder = AlpEncoder::<DoubleType>::new();
let page = encoder.flush_buffer().unwrap();
assert_eq!(page.len(), ALP_HEADER_SIZE);
let mut decoder = AlpDecoder::<DoubleType>::new();
decoder.set_data(page, 0).unwrap();
assert_eq!(decoder.values_left(), 0);
}
#[test]
fn test_roundtrip_multiple_pages() {
let mut encoder = AlpEncoder::<DoubleType>::new();
for page_idx in 0..3 {
let values: Vec<f64> = (0..1500)
.map(|i| (i as f64) * 0.01 + (page_idx as f64))
.collect();
encoder.put(&values).unwrap();
let page = encoder.flush_buffer().unwrap();
let mut decoder = AlpDecoder::<DoubleType>::new();
decoder.set_data(page, values.len()).unwrap();
let mut out = vec![0.0f64; values.len()];
assert_eq!(decoder.get(&mut out).unwrap(), values.len());
assert_bits_eq(&out, &values);
}
}
#[test]
fn test_selects_decimal_parameters() {
let values: Vec<f64> = (0..1024).map(|i| (i as f64) * 0.01).collect();
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&values).unwrap();
let page = encoder.flush_buffer().unwrap();
let exponent = page[ALP_HEADER_SIZE + 4];
let factor = page[ALP_HEADER_SIZE + 5];
let num_exceptions =
u16::from_le_bytes([page[ALP_HEADER_SIZE + 6], page[ALP_HEADER_SIZE + 7]]);
assert_eq!(
exponent - factor,
2,
"two-decimal data should encode at an effective scale of 10^2, \
got exponent {exponent} factor {factor}"
);
assert_eq!(num_exceptions, 0, "two-decimal data should not except");
let plain_size = values.len() * std::mem::size_of::<f64>();
assert!(
page.len() * 4 < plain_size,
"expected at least 4x compression, got {} bytes vs {plain_size} plain",
page.len()
);
}
#[test]
fn test_exception_placeholder_keeps_bit_width_tight() {
let mut values: Vec<f64> = (0..1024).map(|i| (i as f64) * 0.01).collect();
values[500] = f64::NAN;
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&values).unwrap();
let page = encoder.flush_buffer().unwrap();
let bit_width = page[ALP_HEADER_SIZE + 4 + AlpInfo::STORED_SIZE + 8];
assert!(
bit_width <= 17,
"one exception should not widen the frame; got bit_width {bit_width}"
);
assert_bits_eq(&roundtrip::<DoubleType>(&values), &values);
}
#[test]
fn test_streaming_matches_buffered() {
let page1: Vec<f64> = (0..3000).map(|i| (i as f64) * 0.01 + 1.23).collect();
let page2: Vec<f64> = (0..3000).map(|i| (i as f64) * 0.03 - 7.0).collect();
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&page1).unwrap();
let _ = encoder.flush_buffer().unwrap();
for chunk in page2.chunks(997) {
encoder.put(chunk).unwrap();
}
let streamed = encoder.flush_buffer().unwrap();
let preset = build_preset(&page1);
let mut scratch = Scratch::<f64>::new();
let reference = encode_page(&page2, &preset, &mut scratch).unwrap();
assert_eq!(
streamed.as_ref(),
reference.as_slice(),
"streamed page differs from single-pass encoding"
);
}
#[test]
fn test_streaming_irregular_puts() {
let page1: Vec<f64> = (0..2048).map(|i| (i as f64) * 0.01).collect();
let page2: Vec<f64> = (0..5000).map(|i| (i as f64) * 0.01 + 100.0).collect();
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&page1).unwrap();
let _ = encoder.flush_buffer().unwrap();
let sizes = [1usize, 1023, 2, 1024, 1025, 7, 900, 118];
let (mut offset, mut i) = (0usize, 0usize);
while offset < page2.len() {
let n = sizes[i % sizes.len()].min(page2.len() - offset);
encoder.put(&page2[offset..offset + n]).unwrap();
offset += n;
i += 1;
}
let page = encoder.flush_buffer().unwrap();
let mut decoder = AlpDecoder::<DoubleType>::new();
decoder.set_data(page, page2.len()).unwrap();
let mut out = vec![0.0f64; page2.len()];
assert_eq!(decoder.get(&mut out).unwrap(), page2.len());
assert_bits_eq(&out, &page2);
}
#[test]
fn test_streaming_page_ends_on_vector_boundary() {
let preset_page: Vec<f64> = (0..100).map(|i| (i as f64) * 0.01).collect();
let values: Vec<f64> = (0..2 * VECTOR_SIZE).map(|i| (i as f64) * 0.01).collect();
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&preset_page).unwrap();
let _ = encoder.flush_buffer().unwrap();
let empty_page_estimate = encoder.estimated_data_encoded_size();
encoder.put(&values).unwrap();
assert!(encoder.estimated_data_encoded_size() > empty_page_estimate);
let page = encoder.flush_buffer().unwrap();
let mut decoder = AlpDecoder::<DoubleType>::new();
decoder.set_data(page, values.len()).unwrap();
let mut out = vec![0.0f64; values.len()];
assert_eq!(decoder.get(&mut out).unwrap(), values.len());
assert_bits_eq(&out, &values);
}
#[test]
fn test_estimated_memory_size_tracks_buffers() {
let mut encoder = AlpEncoder::<DoubleType>::new();
assert_eq!(encoder.estimated_memory_size(), 0);
let values: Vec<f64> = (0..1500).map(|i| (i as f64) * 0.01).collect();
encoder.put(&values).unwrap();
assert!(encoder.estimated_memory_size() >= values.len() * std::mem::size_of::<f64>());
let _ = encoder.flush_buffer().unwrap();
let after_flush = encoder.estimated_memory_size();
encoder.put(&values).unwrap();
assert!(encoder.estimated_memory_size() > after_flush);
}
#[test]
fn test_estimated_data_size_covers_wide_packed_values_with_exceptions() {
let mut values = vec![f64::NAN; VECTOR_SIZE];
values[0] = f64::ENCODING_LOWER_LIMIT;
values[4] = 1024.0;
let mut encoder = AlpEncoder::<DoubleType>::new();
encoder.put(&values).unwrap();
let estimated_size = encoder.estimated_data_encoded_size();
let page = encoder.flush_buffer().unwrap();
let vector_start = ALP_HEADER_SIZE + std::mem::size_of::<u32>();
let num_exceptions = u16::from_le_bytes([page[vector_start + 2], page[vector_start + 3]]);
let bit_width = page[vector_start + AlpInfo::STORED_SIZE + <f64 as AlpFloat>::Exact::WIDTH];
assert_eq!(num_exceptions as usize, VECTOR_SIZE - 2);
assert_eq!(bit_width, 64);
assert!(
estimated_size >= page.len(),
"estimated {estimated_size} bytes, encoded {} bytes",
page.len()
);
}
#[test]
fn test_full_preset_with_all_exception_vector() {
let scales = [0.01, 0.01, 0.001, 0.01, 0.0001, 0.01, 0.00001, 0.01];
let mut values = Vec::with_capacity(9 * VECTOR_SIZE);
for scale in scales {
values.extend((0..VECTOR_SIZE).map(|i| (i as f64) * scale));
}
values.extend(std::iter::repeat_n(f64::NAN, VECTOR_SIZE));
let preset = build_preset(&values);
assert_eq!(preset.len(), MAX_COMBINATIONS);
let nan_vector = &values[8 * VECTOR_SIZE..];
let mut sample = Vec::new();
sample_values(nan_vector, &mut sample);
let candidate_costs: Vec<_> = preset
.iter()
.map(|¶ms| estimate_size_bits(&sample, params, false))
.collect();
assert!(candidate_costs.windows(2).all(|costs| costs[0] == costs[1]));
assert_bits_eq(&roundtrip::<DoubleType>(&values), &values);
}
}