use smallvec::SmallVec;
use crate::DataInput;
#[derive(Clone, Debug)]
pub enum MatrixHashType {
Packed64(u64),
Packed128(u128),
Rows(SmallVec<[u64; 8]>),
}
impl MatrixHashType {
#[inline(always)]
pub fn row_hash(&self, row: usize, mask_bits: u32, mask: u128) -> u128 {
match self {
MatrixHashType::Packed64(value) => {
let shifted = (*value >> (mask_bits as usize * row)) as u128;
shifted & mask
}
MatrixHashType::Packed128(value) => (value >> (mask_bits as usize * row)) & mask,
MatrixHashType::Rows(values) => {
debug_assert!(row < values.len(), "row index out of bounds for hash rows");
(values[row] as u128) & mask
}
}
}
#[inline(always)]
pub fn sign_for_row(&self, row: usize) -> i32 {
let bit = match self {
MatrixHashType::Packed64(value) => (value >> (63 - row)) & 1,
MatrixHashType::Packed128(value) => ((value >> (127 - row)) & 1) as u64,
MatrixHashType::Rows(values) => {
debug_assert!(row < values.len(), "row index out of bounds for hash rows");
(values[row] >> 63) & 1
}
};
(bit as i32 * 2) - 1
}
#[inline(always)]
pub fn lower_64(&self) -> u64 {
match self {
MatrixHashType::Packed64(value) => *value,
MatrixHashType::Packed128(value) => *value as u64,
MatrixHashType::Rows(values) => values.first().copied().unwrap_or(0),
}
}
}
pub trait MatrixFastHash: Clone {
fn assert_compatible(rows: usize, cols: usize);
fn row_hash(&self, row: usize, mask_bits: u32, mask: u128) -> u128;
fn col_for_row(&self, row: usize, cols: usize) -> usize;
fn sign_for_row(&self, row: usize) -> i32;
}
#[inline(always)]
pub(crate) fn cols_mask_bits(cols: usize) -> u32 {
if cols.is_power_of_two() {
cols.ilog2()
} else {
cols.ilog2() + 1
}
}
#[inline(always)]
pub(crate) fn cols_mask(cols: usize) -> u128 {
(1u128 << cols_mask_bits(cols)) - 1
}
#[inline(always)]
pub(crate) fn fold_to_col(raw: u128, cols: usize) -> usize {
if cols.is_power_of_two() {
raw as usize
} else {
raw as usize % cols
}
}
impl MatrixFastHash for MatrixHashType {
#[inline(always)]
fn assert_compatible(_rows: usize, _cols: usize) {}
#[inline(always)]
fn row_hash(&self, row: usize, mask_bits: u32, mask: u128) -> u128 {
MatrixHashType::row_hash(self, row, mask_bits, mask)
}
#[inline(always)]
fn col_for_row(&self, row: usize, cols: usize) -> usize {
let mask_bits = cols_mask_bits(cols);
let mask = cols_mask(cols);
fold_to_col(self.row_hash(row, mask_bits, mask), cols)
}
#[inline(always)]
fn sign_for_row(&self, row: usize) -> i32 {
MatrixHashType::sign_for_row(self, row)
}
}
impl MatrixFastHash for u64 {
#[inline(always)]
fn assert_compatible(rows: usize, cols: usize) {
let mask_bits = cols_mask_bits(cols) as usize;
let bits_per_row = mask_bits + 1;
let bits_required = bits_per_row.saturating_mul(rows);
assert!(
bits_required <= 64,
"SketchHasher hash type u64 cannot represent fast-path hash for rows={rows}, cols={cols}; use u128 or MatrixHashType"
);
}
#[inline(always)]
fn row_hash(&self, row: usize, mask_bits: u32, mask: u128) -> u128 {
((*self >> (mask_bits as usize * row)) as u128) & mask
}
#[inline(always)]
fn col_for_row(&self, row: usize, cols: usize) -> usize {
let mask_bits = cols_mask_bits(cols);
let mask = cols_mask(cols);
fold_to_col(((*self >> (mask_bits as usize * row)) as u128) & mask, cols)
}
#[inline(always)]
fn sign_for_row(&self, row: usize) -> i32 {
let bit = (self >> (63 - row)) & 1;
(bit as i32 * 2) - 1
}
}
impl MatrixFastHash for u128 {
#[inline(always)]
fn assert_compatible(rows: usize, cols: usize) {
let mask_bits = cols_mask_bits(cols) as usize;
let bits_per_row = mask_bits + 1;
let bits_required = bits_per_row.saturating_mul(rows);
assert!(
bits_required <= 128,
"SketchHasher hash type u128 cannot represent fast-path hash for rows={rows}, cols={cols}; use MatrixHashType"
);
}
#[inline(always)]
fn row_hash(&self, row: usize, mask_bits: u32, mask: u128) -> u128 {
(*self >> (mask_bits as usize * row)) & mask
}
#[inline(always)]
fn col_for_row(&self, row: usize, cols: usize) -> usize {
let mask_bits = cols_mask_bits(cols);
let mask = cols_mask(cols);
fold_to_col((*self >> (mask_bits as usize * row)) & mask, cols)
}
#[inline(always)]
fn sign_for_row(&self, row: usize) -> i32 {
let bit = (self >> (127 - row)) & 1;
(bit as i32 * 2) - 1
}
}
pub trait MatrixStorage {
type Counter: Clone;
fn rows(&self) -> usize;
fn cols(&self) -> usize;
fn update_one_counter<F, V>(&mut self, row: usize, col: usize, op: F, value: V)
where
F: Fn(&mut Self::Counter, V);
fn increment_by_row(&mut self, row: usize, col: usize, value: Self::Counter);
fn fast_insert<Hash, F, V>(&mut self, op: F, value: V, hashed_val: &Hash)
where
Hash: MatrixFastHash,
F: Fn(&mut Self::Counter, &V, usize),
V: Clone;
fn fast_query_min<Hash, F, R>(&self, hashed_val: &Hash, op: F) -> R
where
Hash: MatrixFastHash,
F: Fn(&Self::Counter, usize, &Hash) -> R,
R: PartialOrd;
fn fast_query_median<Hash, F>(&self, hashed_val: &Hash, op: F) -> f64
where
Hash: MatrixFastHash,
F: Fn(&Self::Counter, usize, &Hash) -> f64;
fn query_one_counter(&self, row: usize, col: usize) -> Self::Counter;
}
pub trait FastPathHasher<H>: MatrixStorage
where
H: crate::SketchHasher,
{
fn hash_for_matrix(&self, value: &DataInput) -> H::HashType;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn col_decode_matches_reference_for_all_dim_classes() {
let hash64 = 0x0123_4567_89AB_CDEF_u64;
let hash128 = 0x0011_2233_4455_6677_8899_AABB_CCDD_EEFF_u128;
let packed = MatrixHashType::Packed64(hash64);
let packed_wide = MatrixHashType::Packed128(hash128);
for &cols in &[1usize, 2, 3, 5, 7, 8, 10, 100, 1000, 2048, 4096, 5003] {
let mask_bits = if cols.is_power_of_two() {
cols.ilog2()
} else {
cols.ilog2() + 1
} as usize;
let mask = (1u128 << mask_bits) - 1;
let unit = mask_bits.max(1);
let rows_64 = (64 / unit).clamp(1, 6);
let rows_128 = (128 / unit).clamp(1, 6);
for row in 0..rows_128 {
let expected = |raw: u128| -> usize { (raw as usize) % cols };
if row < rows_64 {
let got_packed = packed.col_for_row(row, cols);
let want_packed = expected(packed.row_hash(row, mask_bits as u32, mask));
assert_eq!(got_packed, want_packed, "Packed64 cols={cols} row={row}");
assert_eq!(
hash64.col_for_row(row, cols),
expected(((hash64 >> (mask_bits * row)) as u128) & mask),
"u64 cols={cols} row={row}"
);
}
let got_wide = packed_wide.col_for_row(row, cols);
let want_wide = expected(packed_wide.row_hash(row, mask_bits as u32, mask));
assert_eq!(got_wide, want_wide, "Packed128 cols={cols} row={row}");
assert_eq!(
hash128.col_for_row(row, cols),
expected((hash128 >> (mask_bits * row)) & mask),
"u128 cols={cols} row={row}"
);
}
}
}
}