use crate::geometry::Geometry;
use crate::lattice::PaddedTileLattice;
#[inline]
fn splitmix64(seed: u64) -> u64 {
let mut z = seed.wrapping_add(0x9E37_79B9_7F4A_7C15);
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
#[derive(Clone)]
pub struct TensorBloom {
m: usize,
k: usize,
seed: u64,
geom: Geometry,
counts: Vec<f32>,
inserted: usize,
}
impl TensorBloom {
pub fn new(m: usize, k: usize, seed: u64) -> Self {
assert!(m > 0 && k > 0, "m and k must be positive");
TensorBloom {
m,
k,
seed,
geom: Geometry::TPU_V,
counts: vec![0.0; m],
inserted: 0,
}
}
#[inline]
pub fn slots(&self) -> usize {
self.m
}
#[inline]
pub fn hashes(&self) -> usize {
self.k
}
#[inline]
pub fn inserted(&self) -> usize {
self.inserted
}
fn positions(&self, item: u64) -> Vec<usize> {
let h1 = splitmix64(item ^ self.seed);
let h2 = splitmix64(item ^ self.seed.rotate_left(32) ^ 0xDEAD_BEEF);
let mut out = Vec::with_capacity(self.k);
for i in 0..self.k as u64 {
out.push((h1.wrapping_add(i.wrapping_mul(h2)) % self.m as u64) as usize);
}
out
}
fn distinct_positions(&self, item: u64) -> Vec<usize> {
let mut p = self.positions(item);
p.sort_unstable();
p.dedup();
p
}
pub fn insert(&mut self, item: u64) {
for pos in self.distinct_positions(item) {
self.counts[pos] += 1.0;
}
self.inserted += 1;
}
pub fn remove(&mut self, item: u64) {
for pos in self.distinct_positions(item) {
if self.counts[pos] > 0.0 {
self.counts[pos] -= 1.0;
}
}
self.inserted = self.inserted.saturating_sub(1);
}
pub fn contains(&self, item: u64) -> bool {
self.distinct_positions(item)
.into_iter()
.all(|pos| self.counts[pos] > 0.0)
}
fn present_vector(&self) -> PaddedTileLattice<f32> {
let present: Vec<f32> = self
.counts
.iter()
.map(|&c| if c > 0.0 { 1.0 } else { 0.0 })
.collect();
PaddedTileLattice::from_dense(self.m, 1, &present, self.geom).unwrap()
}
pub fn batch_contains(&self, items: &[u64]) -> Vec<bool> {
let b = items.len();
if b == 0 {
return Vec::new();
}
let mut sigs = vec![0.0f32; b * self.m];
let mut needed = Vec::with_capacity(b);
for (i, &item) in items.iter().enumerate() {
let positions = self.distinct_positions(item);
needed.push(positions.len() as f32);
for pos in positions {
sigs[i * self.m + pos] = 1.0;
}
}
let sig_lat = PaddedTileLattice::from_dense(b, self.m, &sigs, self.geom).unwrap();
let counts = sig_lat.matmul(&self.present_vector()).unwrap().to_dense();
counts
.iter()
.zip(&needed)
.map(|(&got, &need)| got >= need)
.collect()
}
pub fn estimated_fpr(&self) -> f64 {
let exponent = -(self.k as f64) * self.inserted as f64 / self.m as f64;
(1.0 - exponent.exp()).powi(self.k as i32)
}
}
impl core::fmt::Debug for TensorBloom {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"TensorBloom {{ m: {}, k: {}, inserted: {}, fpr~{:.4} }}",
self.m,
self.k,
self.inserted,
self.estimated_fpr()
)
}
}