use crate::Exceptions;
use fastlanes::BitPacking;
use num_traits::{Float, One, PrimInt, Unsigned, Zero};
use std::marker::PhantomData;
use std::ops::{Range, Shl, Shr};
#[inline]
pub const fn bit_width(value: u64) -> u8 {
if value == 0 {
1
} else {
value.ilog2().wrapping_add(1) as u8
}
}
pub const CUT_LIMIT: usize = 16;
pub const MAX_DICT_SIZE: u8 = 8;
const MAX_SAMPLE: usize = 4096;
const SAMPLE_BLOCK: usize = 64;
mod private {
pub trait Sealed {}
impl Sealed for f32 {}
impl Sealed for f64 {}
}
pub trait ALPRDFloat: private::Sealed + Float {
type UINT: PrimInt + BitPacking + Unsigned + One;
const BITS: usize = size_of::<Self>() * 8;
fn from_bits(bits: Self::UINT) -> Self;
fn to_bits(value: Self) -> Self::UINT;
fn to_u16(bits: Self::UINT) -> u16;
fn from_u16(value: u16) -> Self::UINT;
}
impl ALPRDFloat for f64 {
type UINT = u64;
#[inline]
fn from_bits(bits: Self::UINT) -> Self {
f64::from_bits(bits)
}
#[inline]
fn to_bits(value: Self) -> Self::UINT {
value.to_bits()
}
#[inline]
fn to_u16(bits: Self::UINT) -> u16 {
bits as u16
}
#[inline]
fn from_u16(value: u16) -> Self::UINT {
value as u64
}
}
impl ALPRDFloat for f32 {
type UINT = u32;
#[inline]
fn from_bits(bits: Self::UINT) -> Self {
f32::from_bits(bits)
}
#[inline]
fn to_bits(value: Self) -> Self::UINT {
value.to_bits()
}
#[inline]
fn to_u16(bits: Self::UINT) -> u16 {
bits as u16
}
#[inline]
fn from_u16(value: u16) -> Self::UINT {
value as u32
}
}
pub struct RDEncoder {
right_bit_width: u8,
codes: Vec<u16>,
reverse: ReverseDict,
}
const REVERSE_SLOTS: usize = 32;
const LANE_ONES: u128 = 0x0001_0001_0001_0001_0001_0001_0001_0001;
const LANE_HIGH_BITS: u128 = 0x8000_8000_8000_8000_8000_8000_8000_8000;
enum ReverseDict {
Window {
slots: [(u16, u16); REVERSE_SLOTS],
shift: u8,
},
Lanes(u128),
}
impl ReverseDict {
fn new(codes: &[u16]) -> Self {
let Some(&first) = codes.first() else {
return Self::Lanes(0);
};
for shift in 0..=(16 - REVERSE_SLOTS.ilog2()) as u8 {
let mut slots = [(first, 0u16); REVERSE_SLOTS];
let mut occupied = [false; REVERSE_SLOTS];
let separated = codes.iter().enumerate().all(|(code, &pattern)| {
let slot = Self::index(pattern, shift);
let free = !occupied[slot] || slots[slot].0 == pattern;
if !occupied[slot] {
occupied[slot] = true;
slots[slot] = (pattern, code as u16);
}
free
});
if separated {
return Self::Window { slots, shift };
}
}
let mut lanes = [first; MAX_DICT_SIZE as usize];
lanes[..codes.len()].copy_from_slice(codes);
Self::Lanes(
lanes
.iter()
.rev()
.fold(0u128, |word, &pattern| (word << 16) | u128::from(pattern)),
)
}
#[inline]
fn index(pattern: u16, shift: u8) -> usize {
((pattern >> shift) as usize) & (REVERSE_SLOTS - 1)
}
}
#[inline]
fn window_code(slots: &[(u16, u16); REVERSE_SLOTS], shift: u8, pattern: u16) -> Option<u16> {
let (stored, code) = slots[ReverseDict::index(pattern, shift)];
(stored == pattern).then_some(code)
}
#[inline]
fn lanes_code(lanes: u128, pattern: u16) -> Option<u16> {
let diff = u128::from(pattern).wrapping_mul(LANE_ONES) ^ lanes;
let matches = diff.wrapping_sub(LANE_ONES) & !diff & LANE_HIGH_BITS;
(matches != 0).then(|| (matches.trailing_zeros() / 16) as u16)
}
pub struct Split<F, U> {
left_parts: Vec<u16>,
left_exceptions: Exceptions<u16>,
left_dict: [u16; MAX_DICT_SIZE as usize],
left_dict_len: u8,
left_parts_bit_width: u8,
right_parts: Vec<U>,
right_parts_bit_width: u8,
phantom_data: PhantomData<F>,
}
impl<T, U> Split<T, U> {
pub fn into_parts(self) -> (Vec<u16>, Vec<u16>, Exceptions<u16>, Vec<U>, u8) {
let left_dict = self.left_dict[..self.left_dict_len as usize].to_vec();
(
self.left_parts,
left_dict,
self.left_exceptions,
self.right_parts,
self.right_parts_bit_width,
)
}
pub fn left_parts(&self) -> &[u16] {
&self.left_parts
}
pub fn left_dict(&self) -> &[u16] {
&self.left_dict[..self.left_dict_len as usize]
}
pub fn left_exceptions(&self) -> &Exceptions<u16> {
&self.left_exceptions
}
pub fn right_parts(&self) -> &[U] {
&self.right_parts
}
pub fn left_parts_bit_width(&self) -> u8 {
self.left_parts_bit_width
}
pub fn right_parts_bit_width(&self) -> u8 {
self.right_parts_bit_width
}
}
impl<F, U> Split<F, U>
where
F: ALPRDFloat<UINT = U>,
{
pub fn decode(&self) -> Vec<F> {
alp_rd_decode(
&self.left_parts,
self.left_dict(),
self.right_parts_bit_width,
&self.right_parts,
&self.left_exceptions.positions,
&self.left_exceptions.values,
)
}
}
impl RDEncoder {
pub fn new<T>(sample: &[T]) -> Self
where
T: ALPRDFloat,
{
assert!(
!sample.is_empty(),
"ALP-RD requires a non-empty sample to build a dictionary"
);
let plan = SamplePlan::subsample(sample.len(), MAX_SAMPLE, SAMPLE_BLOCK);
let dictionary = find_best_dictionary::<T>(sample, &plan);
Self::from_parts(dictionary.right_bit_width, dictionary.patterns().to_vec())
}
pub fn from_parts(right_bit_width: u8, codes: Vec<u16>) -> Self {
assert!(
codes.len() <= MAX_DICT_SIZE as usize,
"ALP-RD dictionary must hold at most MAX_DICT_SIZE entries"
);
let reverse = ReverseDict::new(&codes);
Self {
right_bit_width,
codes,
reverse,
}
}
#[inline]
pub fn right_bit_width(&self) -> u8 {
self.right_bit_width
}
#[inline]
pub fn left_bit_width(&self) -> u8 {
bit_width(self.codes.len().saturating_sub(1) as u64)
}
#[inline]
pub fn codes(&self) -> &[u16] {
&self.codes
}
pub fn split<T>(&self, doubles: &[T]) -> Split<T, T::UINT>
where
T: ALPRDFloat,
{
let (left_parts, right_parts, exception_pos, exception_values) = self.split_parts(doubles);
let left_exceptions = Exceptions::new(exception_values, exception_pos);
let mut left_dict = [0u16; MAX_DICT_SIZE as usize];
left_dict[..self.codes.len()].copy_from_slice(&self.codes);
Split {
left_parts,
left_exceptions,
left_dict,
left_dict_len: self.codes.len() as u8,
left_parts_bit_width: self.left_bit_width(),
right_parts,
right_parts_bit_width: self.right_bit_width,
phantom_data: PhantomData,
}
}
pub fn split_parts<T>(&self, doubles: &[T]) -> (Vec<u16>, Vec<T::UINT>, Vec<u64>, Vec<u16>)
where
T: ALPRDFloat,
{
assert!(
!self.codes.is_empty(),
"codes lookup table must be populated before RD encoding"
);
match &self.reverse {
ReverseDict::Window { slots, shift } => {
self.split_parts_with(doubles, |pattern| window_code(slots, *shift, pattern))
}
ReverseDict::Lanes(lanes) => {
self.split_parts_with(doubles, |pattern| lanes_code(*lanes, pattern))
}
}
}
#[inline(always)]
fn split_parts_with<T, L>(
&self,
doubles: &[T],
lookup: L,
) -> (Vec<u16>, Vec<T::UINT>, Vec<u64>, Vec<u16>)
where
T: ALPRDFloat,
L: Fn(u16) -> Option<u16>,
{
let mut left_parts: Vec<u16> = Vec::with_capacity(doubles.len());
let mut right_parts: Vec<T::UINT> = Vec::with_capacity(doubles.len());
let mut exception_pos: Vec<u64> = Vec::with_capacity(doubles.len() / 4);
let mut exception_values: Vec<u16> = Vec::with_capacity(doubles.len() / 4);
let right_mask = T::UINT::one().shl(self.right_bit_width as _) - T::UINT::one();
for (idx, v) in doubles.iter().copied().enumerate() {
let bits = T::to_bits(v);
right_parts.push(bits & right_mask);
let pattern = <T as ALPRDFloat>::to_u16(bits.shr(self.right_bit_width as _));
match lookup(pattern) {
Some(code) => left_parts.push(code),
None => {
exception_values.push(pattern);
exception_pos.push(idx as u64);
left_parts.push(0);
}
}
}
(left_parts, right_parts, exception_pos, exception_values)
}
}
pub fn alp_rd_decode<T: ALPRDFloat>(
left_parts: &[u16],
left_parts_dict: &[u16],
right_bit_width: u8,
right_parts: &[T::UINT],
exc_pos: &[u64],
exceptions: &[u16],
) -> Vec<T> {
assert_eq!(
left_parts.len(),
right_parts.len(),
"alp_rd_decode: left_parts.len != right_parts.len"
);
assert_eq!(
exc_pos.len(),
exceptions.len(),
"alp_rd_decode: exc_pos.len != exceptions.len"
);
let mut decoded: Vec<T::UINT> = right_parts.to_vec();
if exc_pos.is_empty() {
alp_rd_combine_codes_inplace::<T>(
&mut decoded,
left_parts,
left_parts_dict,
right_bit_width,
);
} else {
let mut left_parts = left_parts.to_vec();
alp_rd_dict_decode_inplace(&mut left_parts, left_parts_dict);
alp_rd_apply_patches(&mut left_parts, exc_pos, exceptions, 0);
alp_rd_combine_inplace::<T>(&mut decoded, &left_parts, right_bit_width);
}
decoded.into_iter().map(T::from_bits).collect()
}
#[inline]
pub fn alp_rd_dict_decode_inplace(left_parts: &mut [u16], left_parts_dict: &[u16]) {
for code in left_parts.iter_mut() {
*code = left_parts_dict[*code as usize];
}
}
#[inline]
pub fn alp_rd_apply_patches<I: PrimInt>(
left_parts: &mut [u16],
indices: &[I],
patch_values: &[u16],
offset: usize,
) {
assert_eq!(
indices.len(),
patch_values.len(),
"alp_rd_apply_patches: indices.len != patch_values.len"
);
indices
.iter()
.copied()
.zip(patch_values.iter().copied())
.for_each(|(idx, value)| {
let idx = idx
.to_usize()
.expect("alp_rd_apply_patches: index out of range")
- offset;
left_parts[idx] = value;
});
}
#[inline]
pub fn alp_rd_combine_inplace<T: ALPRDFloat>(
right_parts: &mut [T::UINT],
left_parts: &[u16],
right_bit_width: u8,
) {
assert_eq!(
left_parts.len(),
right_parts.len(),
"alp_rd_combine_inplace: left_parts.len != right_parts.len"
);
let shift = right_bit_width as usize;
for (right, left) in right_parts.iter_mut().zip(left_parts.iter().copied()) {
*right = (<T as ALPRDFloat>::from_u16(left) << shift) | *right;
}
}
#[inline]
pub fn alp_rd_combine_codes_inplace<T: ALPRDFloat>(
right_parts: &mut [T::UINT],
left_parts: &[u16],
left_parts_dict: &[u16],
right_bit_width: u8,
) {
assert_eq!(
left_parts.len(),
right_parts.len(),
"alp_rd_combine_codes_inplace: left_parts.len != right_parts.len"
);
assert!(
left_parts_dict.len() <= MAX_DICT_SIZE as usize,
"alp_rd_combine_codes_inplace: dictionary larger than MAX_DICT_SIZE"
);
let shift = right_bit_width as usize;
let mut shifted_dict = [T::UINT::zero(); MAX_DICT_SIZE as usize];
for (i, &entry) in left_parts_dict.iter().enumerate() {
shifted_dict[i] = <T as ALPRDFloat>::from_u16(entry) << shift;
}
const CODE_MASK: usize = MAX_DICT_SIZE as usize - 1;
for (right, code) in right_parts.iter_mut().zip(left_parts.iter().copied()) {
*right = shifted_dict[(code as usize) & CODE_MASK] | *right;
}
}
#[derive(Debug)]
struct SamplePlan {
ranges: Vec<Range<usize>>,
count: usize,
}
impl SamplePlan {
fn full(len: usize) -> Self {
let mut ranges = Vec::new();
if len > 0 {
ranges.push(0..len);
}
Self { ranges, count: len }
}
fn subsample(len: usize, max_sample: usize, block: usize) -> Self {
let block = block.clamp(1, max_sample.max(1));
if len <= max_sample {
return Self::full(len);
}
let n_blocks = (max_sample / block).max(1);
let spacing = if n_blocks > 1 {
(len - block) / (n_blocks - 1)
} else {
0
};
let ranges: Vec<Range<usize>> = (0..n_blocks)
.map(|i| {
let start = i * spacing;
start..start + block
})
.collect();
let count = ranges.iter().map(Range::len).sum();
Self { ranges, count }
}
fn ranges(&self) -> &[Range<usize>] {
&self.ranges
}
fn count(&self) -> usize {
self.count
}
}
fn find_best_dictionary<T: ALPRDFloat>(samples: &[T], plan: &SamplePlan) -> ALPRDDictionary {
let mut patterns = gather_patterns::<T>(samples, plan);
radix_sort_u16(&mut patterns);
let mut groups = run_length_encode(&patterns);
let mut best_est_size = f64::MAX;
let mut best_dict = ALPRDDictionary::default();
for p in (1..=CUT_LIMIT).rev() {
let dictionary = select_dictionary((T::BITS - p) as u8, &groups);
let estimated_size = estimate_compression_size(
dictionary.right_bit_width,
dictionary.left_bit_width,
plan.count() - dictionary.encodable,
plan.count(),
);
if estimated_size <= best_est_size {
best_est_size = estimated_size;
best_dict = dictionary;
}
merge_sibling_groups(&mut groups);
}
best_dict
}
fn gather_patterns<T: ALPRDFloat>(samples: &[T], plan: &SamplePlan) -> Vec<u16> {
let shift = (T::BITS - CUT_LIMIT) as u32;
let mut patterns = Vec::with_capacity(plan.count());
for range in plan.ranges() {
patterns.extend(
samples[range.start..range.end]
.iter()
.map(|value| <T as ALPRDFloat>::to_u16(T::to_bits(*value).shr(shift as _))),
);
}
patterns
}
fn radix_sort_u16(values: &mut Vec<u16>) {
const DIGIT_BITS: u32 = 8;
const DIGITS: usize = 1 << DIGIT_BITS;
let mut scratch: Vec<u16> = Vec::with_capacity(values.len());
for shift in [0, DIGIT_BITS] {
let mut offsets = [0u32; DIGITS];
for &value in values.iter() {
offsets[((value >> shift) as usize) & (DIGITS - 1)] += 1;
}
if offsets.iter().any(|&count| count as usize == values.len()) {
continue;
}
let mut start = 0;
for offset in offsets.iter_mut() {
let count = *offset;
*offset = start;
start += count;
}
scratch.clear();
scratch.resize(values.len(), 0);
for &value in values.iter() {
let digit = ((value >> shift) as usize) & (DIGITS - 1);
scratch[offsets[digit] as usize] = value;
offsets[digit] += 1;
}
std::mem::swap(values, &mut scratch);
}
}
fn run_length_encode(sorted: &[u16]) -> Vec<(u16, u32)> {
let mut groups: Vec<(u16, u32)> = Vec::new();
for &pattern in sorted {
match groups.last_mut() {
Some((last, count)) if *last == pattern => *count += 1,
_ => groups.push((pattern, 1)),
}
}
groups
}
fn merge_sibling_groups(groups: &mut Vec<(u16, u32)>) {
let mut merged = 0;
let mut read = 0;
while read < groups.len() {
let (pattern, mut count) = groups[read];
let pattern = pattern >> 1;
read += 1;
if let Some(&(sibling, sibling_count)) = groups.get(read)
&& sibling >> 1 == pattern
{
count += sibling_count;
read += 1;
}
groups[merged] = (pattern, count);
merged += 1;
}
groups.truncate(merged);
}
fn select_dictionary(right_bw: u8, groups: &[(u16, u32)]) -> ALPRDDictionary {
let mut patterns = [0u16; MAX_DICT_SIZE as usize];
let mut counts = [0u32; MAX_DICT_SIZE as usize];
let mut len = 0usize;
for &(pattern, count) in groups {
let Some(at) = counts[..len].iter().position(|&held| count > held) else {
if len < patterns.len() {
patterns[len] = pattern;
counts[len] = count;
len += 1;
}
continue;
};
len = (len + 1).min(patterns.len());
patterns[at..len].rotate_right(1);
counts[at..len].rotate_right(1);
patterns[at] = pattern;
counts[at] = count;
}
ALPRDDictionary {
patterns,
len: len as u8,
left_bit_width: bit_width(len.saturating_sub(1) as u64),
right_bit_width: right_bw,
encodable: counts[..len].iter().map(|&count| count as usize).sum(),
}
}
fn estimate_compression_size(
right_bw: u8,
left_bw: u8,
exception_count: usize,
sample_n: usize,
) -> f64 {
const EXC_POSITION_SIZE: usize = 16; const EXC_SIZE: usize = 16;
let exceptions_size = exception_count * (EXC_POSITION_SIZE + EXC_SIZE);
(right_bw as f64) + (left_bw as f64) + ((exceptions_size as f64) / (sample_n as f64))
}
#[derive(Debug, Default)]
struct ALPRDDictionary {
patterns: [u16; MAX_DICT_SIZE as usize],
len: u8,
left_bit_width: u8,
right_bit_width: u8,
encodable: usize,
}
impl ALPRDDictionary {
fn patterns(&self) -> &[u16] {
&self.patterns[..self.len as usize]
}
}
#[cfg(test)]
mod test {
use super::{
ALPRDFloat, CUT_LIMIT, MAX_SAMPLE, REVERSE_SLOTS, ReverseDict, SAMPLE_BLOCK, SamplePlan,
estimate_compression_size, find_best_dictionary, lanes_code, window_code,
};
use crate::{
MAX_DICT_SIZE, RDEncoder, alp_rd_apply_patches, alp_rd_combine_codes_inplace,
alp_rd_combine_inplace, alp_rd_decode, alp_rd_dict_decode_inplace, bit_width,
};
use std::cmp::Reverse;
use std::collections::HashMap;
struct Lcg(u64);
impl Lcg {
fn new() -> Self {
Self(0x517C_C1B7_2722_0A95)
}
fn next_bits(&mut self) -> u64 {
self.0 = self
.0
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
self.0
}
fn next_unit(&mut self) -> f64 {
(self.next_bits() >> 11) as f64 / (1u64 << 53) as f64
}
fn next_log_normal(&mut self) -> f64 {
(self.next_unit() * 6.0 - 1.0).exp() * 1000.0
}
}
struct CutPointCost {
bits_per_value: f64,
exception_rate: f64,
}
fn cut_point_cost(values: &[f64], right_bw: u8) -> CutPointCost {
const EXCEPTION_BITS: f64 = 32.0;
let mut counts = HashMap::new();
for value in values {
*counts
.entry((value.to_bits() >> right_bw) as u16)
.or_insert(0usize) += 1;
}
let mut sorted: Vec<usize> = counts.into_values().collect();
sorted.sort_unstable_by(|a, b| b.cmp(a));
let dict_len = (MAX_DICT_SIZE as usize).min(sorted.len());
let encodable: usize = sorted.iter().take(dict_len).sum();
let exception_rate = 1.0 - (encodable as f64 / values.len() as f64);
let left_bw = bit_width(dict_len.saturating_sub(1) as u64);
CutPointCost {
bits_per_value: right_bw as f64 + left_bw as f64 + exception_rate * EXCEPTION_BITS,
exception_rate,
}
}
fn subsample_vs_full_scan(values: &[f64]) -> (CutPointCost, CutPointCost) {
let subsampled = RDEncoder::new(values).right_bit_width();
let full =
find_best_dictionary::<f64>(values, &SamplePlan::full(values.len())).right_bit_width;
(
cut_point_cost(values, subsampled),
cut_point_cost(values, full),
)
}
fn reference_best_dictionary<T: ALPRDFloat>(
samples: &[T],
plan: &SamplePlan,
) -> (u8, Vec<u16>) {
let mut best: Option<(f64, u8, Vec<u16>)> = None;
for p in 1..=CUT_LIMIT {
let right_bw = (T::BITS - p) as u8;
let mut counts: HashMap<u16, usize> = HashMap::new();
for range in plan.ranges() {
for value in &samples[range.start..range.end] {
let pattern =
<T as ALPRDFloat>::to_u16(T::to_bits(*value) >> right_bw as usize);
*counts.entry(pattern).or_default() += 1;
}
}
let mut sorted: Vec<(u16, usize)> = counts.into_iter().collect();
sorted.sort_unstable_by_key(|&(pattern, count)| (Reverse(count), pattern));
let dict_len = (MAX_DICT_SIZE as usize).min(sorted.len());
let exceptions: usize = sorted[dict_len..].iter().map(|&(_, count)| count).sum();
let estimate = estimate_compression_size(
right_bw,
bit_width(dict_len.saturating_sub(1) as u64),
exceptions,
plan.count(),
);
if best
.as_ref()
.is_none_or(|&(previous, _, _)| estimate < previous)
{
let codes = sorted[..dict_len].iter().map(|&(bits, _)| bits).collect();
best = Some((estimate, right_bw, codes));
}
}
let (_, right_bw, codes) = best.expect("the search always considers a cut point");
(right_bw, codes)
}
fn distributions(len: usize) -> Vec<(&'static str, Vec<f64>)> {
let mut rng = Lcg::new();
let log_normal = (0..len).map(|_| rng.next_log_normal()).collect();
let mut rng = Lcg::new();
let narrow = (0..len).map(|_| 1.0 + rng.next_unit()).collect();
let mut rng = Lcg::new();
let bimodal = (0..len)
.map(|i| {
let magnitude = if i % 3 == 0 { -1e-8 } else { 1e12 };
magnitude * (1.0 + rng.next_unit())
})
.collect();
let constant = vec![7.5f64; len];
let mut rng = Lcg::new();
let high_cardinality = (0..len)
.map(|_| f64::from_bits(rng.next_bits() & 0x7FEF_FFFF_FFFF_FFFF))
.collect();
vec![
("log_normal", log_normal),
("narrow", narrow),
("bimodal", bimodal),
("constant", constant),
("high_cardinality", high_cardinality),
]
}
#[test]
fn test_search_matches_reference_search() {
for len in [1usize, 2, 63, 64, 1000, 4 * MAX_SAMPLE + 7] {
for (name, values) in distributions(len) {
for plan in [
SamplePlan::full(len),
SamplePlan::subsample(len, MAX_SAMPLE, SAMPLE_BLOCK),
] {
let (right_bw, codes) = reference_best_dictionary::<f64>(&values, &plan);
let actual = find_best_dictionary::<f64>(&values, &plan);
assert_eq!(
(actual.right_bit_width, actual.patterns()),
(right_bw, codes.as_slice()),
"{name} at len {len} with {} sampled",
plan.count()
);
let floats: Vec<f32> = values.iter().map(|&v| v as f32).collect();
let (right_bw, codes) = reference_best_dictionary::<f32>(&floats, &plan);
let actual = find_best_dictionary::<f32>(&floats, &plan);
assert_eq!(
(actual.right_bit_width, actual.patterns()),
(right_bw, codes.as_slice()),
"{name} as f32 at len {len} with {} sampled",
plan.count()
);
}
}
}
}
#[test]
fn test_reverse_dict_variants_match_a_scan() {
let mut rng = Lcg::new();
let dictionaries = [
vec![0u16],
vec![0x3FF0, 0x3FF1, 0x3FF2],
vec![0x1234, 0x1234, 0x0001],
vec![0x0000, 0x8000, 0x0001],
(0..MAX_DICT_SIZE as u16).map(|i| i * 0x1111).collect(),
(0..MAX_DICT_SIZE).map(|_| rng.next_bits() as u16).collect(),
];
for codes in dictionaries {
let reverse = ReverseDict::new(&codes);
for pattern in 0..=u16::MAX {
let expected = codes
.iter()
.position(|&bits| bits == pattern)
.map(|code| code as u16);
let actual = match &reverse {
ReverseDict::Window { slots, shift } => window_code(slots, *shift, pattern),
ReverseDict::Lanes(lanes) => lanes_code(*lanes, pattern),
};
assert_eq!(
actual, expected,
"dictionary {codes:x?}, pattern {pattern:#06x}"
);
}
}
}
#[test]
fn test_lane_fallback_round_trips() {
let codes = vec![0x0000u16, 0x8000, 0x0001];
assert!(
matches!(ReverseDict::new(&codes), ReverseDict::Lanes(_)),
"expected this dictionary to defeat every window"
);
let right_bw = 48;
let encoder = RDEncoder::from_parts(right_bw, codes.clone());
let values: Vec<f64> = codes
.iter()
.map(|&bits| f64::from_bits((u64::from(bits) << right_bw) | 0xABC))
.chain([f64::from_bits((0x4321u64 << right_bw) | 0xABC)])
.collect();
let split = encoder.split(&values);
assert_eq!(split.left_parts(), &[0, 1, 2, 0]);
assert_eq!(split.left_exceptions().positions(), &[3]);
assert_eq!(split.decode(), values);
}
#[test]
fn test_window_slots_are_unshared() {
let mut rng = Lcg::new();
for _ in 0..256 {
let mut codes: Vec<u16> = (0..MAX_DICT_SIZE).map(|_| rng.next_bits() as u16).collect();
codes.sort_unstable();
codes.dedup();
let ReverseDict::Window { shift, .. } = ReverseDict::new(&codes) else {
continue;
};
let mut claimed = [false; REVERSE_SLOTS];
for &pattern in &codes {
let slot = ReverseDict::index(pattern, shift);
assert!(
!claimed[slot],
"{codes:x?} shares slot {slot} at shift {shift}"
);
claimed[slot] = true;
}
}
}
#[test]
fn test_encode_decode() {
let values = vec![1.12345f64, 2.34567f64, 3.45678f64];
let encoder = RDEncoder::new(&values);
let split = encoder.split(&values);
let decoded = split.decode();
assert_eq!(decoded, values);
}
#[test]
fn test_encode_decode_with_exceptions() {
let values = vec![0.1f64, 0.2f64, 3e100f64];
let encoder = RDEncoder::new(&values[0..2]);
let split = encoder.split(&values);
assert_eq!(split.left_exceptions().positions(), &[2]);
assert_eq!(split.decode(), values);
}
#[test]
fn test_encode_decode_f32() {
let values = vec![0.1f32, 0.2f32, 3e25f32];
let encoder = RDEncoder::new(&values[0..2]);
let split = encoder.split(&values);
assert_eq!(split.left_exceptions().positions(), &[2]);
assert_eq!(split.decode(), values);
}
#[test]
fn test_from_parts_round_trips() {
let values = vec![1.12345f64, 2.34567f64, 3.45678f64];
let encoder = RDEncoder::new(&values);
let rebuilt = RDEncoder::from_parts(encoder.right_bit_width(), encoder.codes().to_vec());
assert_eq!(rebuilt.right_bit_width(), encoder.right_bit_width());
assert_eq!(rebuilt.codes(), encoder.codes());
assert_eq!(rebuilt.split(&values).decode(), values);
}
#[test]
fn test_bit_widths() {
let values = vec![1.12345f64, 2.34567f64, 3.45678f64];
let encoder = RDEncoder::new(&values);
let split = encoder.split(&values);
assert!(encoder.codes().len() <= MAX_DICT_SIZE as usize);
assert_eq!(split.left_parts_bit_width(), encoder.left_bit_width());
assert_eq!(
split.left_parts_bit_width() as usize,
bit_width((encoder.codes().len() - 1) as u64) as usize
);
assert_eq!(split.right_parts_bit_width(), encoder.right_bit_width());
assert!(
split
.right_parts()
.iter()
.all(|v| *v < (1u64 << split.right_parts_bit_width()))
);
}
#[test]
fn test_decode_primitives_match() {
let values = vec![0.1f64, 0.2f64, 3e100f64];
let encoder = RDEncoder::new(&values[0..2]);
let (left_parts, right_parts, exc_pos, exc_values) = encoder.split_parts(&values);
let dict = encoder.codes();
let right_bit_width = encoder.right_bit_width();
let mut left = left_parts.clone();
alp_rd_dict_decode_inplace(&mut left, dict);
alp_rd_apply_patches(&mut left, &exc_pos, &exc_values, 0);
let mut combined = right_parts.clone();
alp_rd_combine_inplace::<f64>(&mut combined, &left, right_bit_width);
let decoded: Vec<f64> = combined.into_iter().map(f64::from_bits).collect();
assert_eq!(
decoded,
alp_rd_decode::<f64>(
&left_parts,
dict,
right_bit_width,
&right_parts,
&exc_pos,
&exc_values
)
);
assert_eq!(decoded, values);
}
#[test]
fn test_combine_codes_matches_combine() {
let values = vec![1.12345f64, 2.34567f64, 3.45678f64];
let encoder = RDEncoder::new(&values);
let (left_parts, right_parts, exc_pos, _) = encoder.split_parts(&values);
assert!(exc_pos.is_empty());
let mut fast = right_parts.clone();
alp_rd_combine_codes_inplace::<f64>(
&mut fast,
&left_parts,
encoder.codes(),
encoder.right_bit_width(),
);
let mut left = left_parts;
alp_rd_dict_decode_inplace(&mut left, encoder.codes());
let mut slow = right_parts;
alp_rd_combine_inplace::<f64>(&mut slow, &left, encoder.right_bit_width());
assert_eq!(fast, slow);
assert_eq!(
fast.into_iter().map(f64::from_bits).collect::<Vec<_>>(),
values
);
}
#[test]
fn test_apply_patches_with_offset() {
let mut left = vec![0u16; 3];
alp_rd_apply_patches(&mut left, &[10u64, 12], &[7u16, 9], 10);
assert_eq!(left, vec![7, 0, 9]);
}
#[test]
fn test_bit_width_fn() {
assert_eq!(bit_width(0), 1);
assert_eq!(bit_width(1), 1);
assert_eq!(bit_width(2), 2);
assert_eq!(bit_width(7), 3);
assert_eq!(bit_width(u64::MAX), 64);
}
#[test]
#[should_panic(expected = "at most MAX_DICT_SIZE entries")]
fn test_from_parts_rejects_oversized_dictionary() {
RDEncoder::from_parts(52, vec![0u16; MAX_DICT_SIZE as usize + 1]);
}
#[test]
fn test_sample_plan_covers_short_inputs_fully() {
for len in [0usize, 1, 63, 64, 4095, MAX_SAMPLE] {
let plan = SamplePlan::subsample(len, MAX_SAMPLE, SAMPLE_BLOCK);
assert_eq!(plan.count(), len, "short inputs must be scanned in full");
let covered: usize = plan.ranges().iter().map(|r| r.len()).sum();
assert_eq!(covered, len);
}
}
#[test]
fn test_sample_plan_ranges_are_in_bounds_and_disjoint() {
for len in [
MAX_SAMPLE + 1,
2 * MAX_SAMPLE,
3 * MAX_SAMPLE + 1,
100_003,
1 << 20,
] {
let plan = SamplePlan::subsample(len, MAX_SAMPLE, SAMPLE_BLOCK);
assert!(
plan.count() <= MAX_SAMPLE,
"subsampling must honour the MAX_SAMPLE budget for len {len}"
);
assert_eq!(
plan.count(),
plan.ranges().iter().map(|r| r.len()).sum::<usize>()
);
let mut prev_end = 0;
for range in plan.ranges() {
assert!(
range.start >= prev_end,
"ranges must be ascending, disjoint"
);
assert!(
range.end <= len,
"range {}..{} exceeds {len}",
range.start,
range.end
);
prev_end = range.end;
}
assert!(!plan.ranges().is_empty());
}
}
#[test]
fn test_subsample_matches_full_scan_on_random_data() {
let mut rng = Lcg::new();
let values: Vec<f64> = (0..8 * MAX_SAMPLE).map(|_| rng.next_log_normal()).collect();
let (subsampled, full) = subsample_vs_full_scan(&values);
assert!(
subsampled.bits_per_value <= full.bits_per_value + 0.1,
"subsampling cost {:.3} bits/value against a full scan's {:.3}",
subsampled.bits_per_value,
full.bits_per_value
);
}
#[test]
fn test_subsample_matches_full_scan_on_periodic_data() {
for period in [2usize, 3, 4, 8] {
let len = period * MAX_SAMPLE;
let mut rng = Lcg::new();
let values: Vec<f64> = (0..len)
.map(|i| {
let magnitude = if i % period == 0 { 1e-8 } else { 1e12 };
magnitude * (1.0 + rng.next_unit())
})
.collect();
let (subsampled, full) = subsample_vs_full_scan(&values);
assert!(
subsampled.bits_per_value <= full.bits_per_value + 0.5,
"period {period}: subsampling cost {:.3} bits/value against a full scan's {:.3}",
subsampled.bits_per_value,
full.bits_per_value
);
assert!(
subsampled.exception_rate < 0.10,
"period {period}: subsampling chose a cut point leaving {:.2}% of the input \
un-encodable",
subsampled.exception_rate * 100.0
);
}
}
#[test]
fn test_large_input_round_trips_in_chunks() {
let mut rng = Lcg::new();
let values: Vec<f64> = (0..4 * MAX_SAMPLE + 7)
.map(|_| rng.next_log_normal())
.collect();
let encoder = RDEncoder::new(&values);
for chunk in values.chunks(1024) {
assert_eq!(&encoder.split(chunk).decode(), chunk);
}
}
#[test]
fn test_dictionary_is_reproducible() {
let values: Vec<f64> = (0..1024)
.map(|i| f64::from_bits(((i % 32) as u64) << 52 | 1))
.collect();
let expected = RDEncoder::new(&values);
for _ in 0..16 {
let actual = RDEncoder::new(&values);
assert_eq!(actual.codes(), expected.codes());
assert_eq!(actual.right_bit_width(), expected.right_bit_width());
}
}
#[test]
fn test_lookup_agrees_with_codes() {
let mut rng = Lcg::new();
let values: Vec<f64> = (0..2048).map(|_| rng.next_log_normal()).collect();
let encoder = RDEncoder::new(&values);
let right_bw = encoder.right_bit_width();
let patterns: Vec<f64> = encoder
.codes()
.iter()
.map(|&bits| f64::from_bits((bits as u64) << right_bw))
.collect();
let (left_parts, _, exc_pos, _) = encoder.split_parts(&patterns);
assert!(exc_pos.is_empty(), "dictionary patterns must not except");
assert_eq!(
left_parts,
(0..encoder.codes().len() as u16).collect::<Vec<_>>()
);
}
#[test]
fn test_inline_dict_matches_into_parts() {
let values = vec![1.12345f64, 2.34567f64, 3.45678f64];
let encoder = RDEncoder::new(&values);
let split = encoder.split(&values);
let inline_dict = split.left_dict().to_vec();
let (_, owned_dict, _, _, _) = split.into_parts();
assert_eq!(inline_dict, owned_dict);
assert_eq!(owned_dict, encoder.codes());
}
}