use crate::{
bitpack::{bitpack_u64, packed_byte_size},
float::AlpFloat,
header::{raw_header_len, write_header},
params::bits_needed,
};
pub(crate) const MAX_RD_DICT_SIZE: usize = 8;
const RD_SAMPLE_POINTS: usize = 64;
const HASH_TABLE_SIZE: usize = 256;
const HASH_TABLE_MASK: usize = HASH_TABLE_SIZE - 1;
pub(crate) struct RdCandidate {
pub right_bw: u8,
pub left_bw: u8,
pub actual_dict_size: u8,
pub dict: [u16; MAX_RD_DICT_SIZE],
pub total_size: usize,
}
pub(crate) fn try_encode_rd<F: AlpFloat>(
slice: &[F],
left_indices: &mut Vec<u64>,
right_parts: &mut Vec<u64>,
exceptions: &mut Vec<(u16, u16)>,
) -> Option<RdCandidate> {
let count = slice.len();
if count == 0 || count > u16::MAX as usize {
return None;
}
let total_bits = F::RD_TOTAL_BITS;
let max_cut = F::RD_MAX_CUT;
let sample_step = (count / RD_SAMPLE_POINTS).max(1);
let mut best_cut = max_cut;
let mut best_est_cost = usize::MAX;
for cut in 1..=max_cut {
let right_bw = total_bits - cut;
let mut freq_keys = [0u16; MAX_RD_DICT_SIZE];
let mut freq_counts = [0u16; MAX_RD_DICT_SIZE];
let mut num_distinct = 0usize;
let mut exc_count = 0usize;
for &v in slice.iter().step_by(sample_step) {
let raw = v.to_u64_key();
let left = (raw >> right_bw) as u16;
if let Some(pos) = freq_keys[..num_distinct].iter().position(|&k| k == left) {
freq_counts[pos] += 1;
} else if num_distinct < MAX_RD_DICT_SIZE {
freq_keys[num_distinct] = left;
freq_counts[num_distinct] = 1;
num_distinct += 1;
} else {
exc_count += 1;
}
}
let left_bw = if num_distinct <= 1 {
0
} else {
bits_needed((num_distinct - 1) as u64)
};
let est_cost = packed_byte_size(count, right_bw)
+ packed_byte_size(count, left_bw)
+ exc_count * sample_step * 4;
if est_cost < best_est_cost {
best_est_cost = est_cost;
best_cut = cut;
}
}
let right_bw = total_bits - best_cut;
let mut table_keys = [0u16; HASH_TABLE_SIZE];
let mut table_counts = [0u16; HASH_TABLE_SIZE];
let mut table_occupied = [0u64; HASH_TABLE_SIZE / 64];
for &v in slice {
let raw = v.to_u64_key();
let left = (raw >> right_bw) as u16;
let mut idx = ((left as usize).wrapping_mul(0x9E37) >> 8) & HASH_TABLE_MASK;
for _ in 0..HASH_TABLE_SIZE {
let word_idx = idx >> 6;
let bit_mask = 1u64 << (idx & 63);
if (table_occupied[word_idx] & bit_mask) == 0 {
table_occupied[word_idx] |= bit_mask;
table_keys[idx] = left;
table_counts[idx] = 1;
break;
}
if table_keys[idx] == left {
table_counts[idx] = table_counts[idx].saturating_add(1);
break;
}
idx = (idx + 1) & HASH_TABLE_MASK;
}
}
let mut dict = [0u16; MAX_RD_DICT_SIZE];
let mut top_counts = [0u16; MAX_RD_DICT_SIZE];
let mut actual_dict_size = 0usize;
for (word_idx, &word_bits) in table_occupied.iter().enumerate() {
let mut bits = word_bits;
while bits != 0 {
let trailing = bits.trailing_zeros() as usize;
let idx = (word_idx << 6) | trailing;
bits &= bits - 1;
let key = table_keys[idx];
let count_val = table_counts[idx];
if actual_dict_size < MAX_RD_DICT_SIZE {
dict[actual_dict_size] = key;
top_counts[actual_dict_size] = count_val;
actual_dict_size += 1;
} else {
let mut min_idx = 0;
let mut min_count = top_counts[0];
for (j, &c) in top_counts.iter().enumerate().skip(1) {
if c < min_count {
min_count = c;
min_idx = j;
}
}
if count_val > min_count {
top_counts[min_idx] = count_val;
dict[min_idx] = key;
}
}
}
}
encode_rd_fast(
slice,
right_bw,
actual_dict_size,
dict,
left_indices,
right_parts,
exceptions,
)
}
pub(crate) fn encode_rd_fast<F: AlpFloat>(
slice: &[F],
right_bw: u8,
actual_dict_size: usize,
dict: [u16; MAX_RD_DICT_SIZE],
left_indices: &mut Vec<u64>,
right_parts: &mut Vec<u64>,
exceptions: &mut Vec<(u16, u16)>,
) -> Option<RdCandidate> {
let count = slice.len();
let right_mask = if right_bw == 64 {
u64::MAX
} else {
(1u64 << right_bw) - 1
};
let left_bw = if actual_dict_size <= 1 {
0
} else {
bits_needed((actual_dict_size - 1) as u64)
};
left_indices.clear();
if left_bw > 0 {
if left_indices.capacity() < count {
left_indices.reserve(count);
}
unsafe { left_indices.set_len(count) };
}
right_parts.clear();
if right_parts.capacity() < count {
right_parts.reserve(count);
}
unsafe { right_parts.set_len(count) };
exceptions.clear();
let left_ptr = left_indices.as_mut_ptr();
let right_ptr = right_parts.as_mut_ptr();
unsafe {
if actual_dict_size == 1 {
let d0 = dict[0];
for (pos, &v) in slice.iter().enumerate() {
let raw = v.to_u64_key();
let left = (raw >> right_bw) as u16;
let right = raw & right_mask;
*right_ptr.add(pos) = right;
if left != d0 {
exceptions.push((pos as u16, left));
}
}
} else if actual_dict_size == 2 {
let d0 = dict[0];
let d1 = dict[1];
for (pos, &v) in slice.iter().enumerate() {
let raw = v.to_u64_key();
let left = (raw >> right_bw) as u16;
let right = raw & right_mask;
*right_ptr.add(pos) = right;
if left == d0 {
*left_ptr.add(pos) = 0;
} else if left == d1 {
*left_ptr.add(pos) = 1;
} else {
exceptions.push((pos as u16, left));
}
}
} else {
for (pos, &v) in slice.iter().enumerate() {
let raw = v.to_u64_key();
let left = (raw >> right_bw) as u16;
let right = raw & right_mask;
*right_ptr.add(pos) = right;
let mut matched = false;
for (idx, &entry) in dict[..actual_dict_size].iter().enumerate() {
if entry == left {
*left_ptr.add(pos) = idx as u64;
matched = true;
break;
}
}
if !matched {
exceptions.push((pos as u16, left));
}
}
}
}
let hdr_len = raw_header_len(count);
let meta_len = 5; let dict_bytes = actual_dict_size * 2;
let left_bytes = if left_bw > 0 {
packed_byte_size(count, left_bw)
} else {
0
};
let right_bytes = packed_byte_size(count, right_bw);
let exc_bytes = exceptions.len() * 4;
let total_size = hdr_len + meta_len + dict_bytes + left_bytes + right_bytes + exc_bytes;
Some(RdCandidate {
right_bw,
left_bw,
actual_dict_size: actual_dict_size as u8,
dict,
total_size,
})
}
#[inline]
pub(crate) fn write_rd_chunk<F: AlpFloat>(
count: usize,
candidate: &RdCandidate,
left_indices: &[u64],
right_parts: &[u64],
exceptions: &[(u16, u16)],
dst: &mut Vec<u8>,
) {
dst.reserve(candidate.total_size);
write_header(F::TYPE_RD_BYTE, count, None, dst);
dst.push(candidate.right_bw);
dst.push(candidate.left_bw);
dst.push(candidate.actual_dict_size);
dst.extend_from_slice(&(exceptions.len() as u16).to_le_bytes());
for &val in &candidate.dict[..candidate.actual_dict_size as usize] {
dst.extend_from_slice(&val.to_le_bytes());
}
if candidate.left_bw > 0 {
bitpack_u64(left_indices, candidate.left_bw, dst);
}
bitpack_u64(right_parts, candidate.right_bw, dst);
let mut exc_buf = [0u8; 4];
for &(pos, left_val) in exceptions {
exc_buf[..2].copy_from_slice(&pos.to_le_bytes());
exc_buf[2..].copy_from_slice(&left_val.to_le_bytes());
dst.extend_from_slice(&exc_buf);
}
}