#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use crate::arch;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
const MIN_SLOTS_SIMD: usize = 8;
#[cfg(target_arch = "x86_64")]
const MIN_SLOTS_AVX512: usize = 16;
#[inline]
#[must_use]
#[allow(unsafe_code)]
pub fn slot_hamming_u32(a: &[u32], b: &[u32]) -> u32 {
assert_eq!(
a.len(),
b.len(),
"innr::slot_hamming_u32: slice length mismatch ({} vs {})",
a.len(),
b.len()
);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
let n = a.len();
#[cfg(target_arch = "x86_64")]
{
if n >= MIN_SLOTS_AVX512 && is_x86_feature_detected!("avx512f") {
return unsafe { arch::x86_64::slot_hamming_u32_avx512(a, b) };
}
if n >= MIN_SLOTS_SIMD && is_x86_feature_detected!("avx2") {
return unsafe { arch::x86_64::slot_hamming_u32_avx2(a, b) };
}
}
#[cfg(target_arch = "aarch64")]
{
if n >= MIN_SLOTS_SIMD {
return unsafe { arch::aarch64::slot_hamming_u32_neon(a, b) };
}
}
#[allow(unreachable_code)]
slot_hamming_u32_portable(a, b)
}
#[inline]
#[must_use]
pub fn slot_hamming_u32_portable(a: &[u32], b: &[u32]) -> u32 {
a.iter().zip(b.iter()).filter(|(x, y)| x != y).count() as u32
}
#[inline]
#[must_use]
pub fn slot_hamming<T: PartialEq>(a: &[T], b: &[T]) -> usize {
a.iter().zip(b.iter()).filter(|(x, y)| x != y).count()
}
#[inline]
#[must_use]
pub fn minhash_jaccard(a: &[u32], b: &[u32]) -> f32 {
assert_eq!(
a.len(),
b.len(),
"innr::minhash_jaccard: slice length mismatch ({} vs {})",
a.len(),
b.len()
);
let len = a.len();
if len == 0 {
return 1.0;
}
let diff = slot_hamming_u32(a, b);
let matches = len as u32 - diff;
matches as f32 / len as f32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_hamming_u32_basic() {
let a = [1u32, 2, 3, 4];
let b = [1u32, 0, 3, 9];
assert_eq!(slot_hamming_u32(&a, &b), 2);
}
#[test]
fn test_slot_hamming_u32_empty() {
assert_eq!(slot_hamming_u32(&[], &[]), 0);
}
#[test]
fn test_slot_hamming_u32_identical() {
let v = [7u32, 11, 13, 17, 19];
assert_eq!(slot_hamming_u32(&v, &v), 0);
}
#[test]
fn test_slot_hamming_u32_all_differ() {
let a = [1u32; 32];
let b = [2u32; 32];
assert_eq!(slot_hamming_u32(&a, &b), 32);
}
#[test]
fn test_slot_hamming_u32_symmetric() {
let a = [1u32, 5, 9, 13, 2, 6, 10, 14, 3];
let b = [1u32, 0, 9, 0, 2, 0, 10, 0, 3];
assert_eq!(slot_hamming_u32(&a, &b), slot_hamming_u32(&b, &a));
}
#[test]
fn test_slot_hamming_u32_boundary_sizes() {
for size in [1usize, 7, 8, 15, 16, 17, 31, 32, 33, 64, 128] {
let a: Vec<u32> = (0..size as u32).collect();
let b: Vec<u32> = (0..size as u32)
.map(|i| if i % 3 == 0 { i + 1000 } else { i })
.collect();
let expected = a.iter().zip(&b).filter(|(x, y)| x != y).count() as u32;
assert_eq!(
slot_hamming_u32(&a, &b),
expected,
"slot_hamming_u32 mismatch at size={size}"
);
}
}
#[test]
fn test_slot_hamming_u32_matches_portable() {
let a: Vec<u32> = (0..100u32).map(|i| i.wrapping_mul(2654435761)).collect();
let b: Vec<u32> = (0..100u32).map(|i| i.wrapping_mul(40503)).collect();
assert_eq!(slot_hamming_u32(&a, &b), slot_hamming_u32_portable(&a, &b));
}
#[test]
#[should_panic(expected = "innr::slot_hamming_u32: slice length mismatch")]
fn test_slot_hamming_u32_length_mismatch() {
let _ = slot_hamming_u32(&[1, 2], &[1, 2, 3]);
}
#[test]
fn test_slot_hamming_generic_u16() {
let a = [10u16, 20, 30];
let b = [10u16, 99, 30];
assert_eq!(slot_hamming(&a, &b), 1);
}
#[test]
fn test_slot_hamming_generic_u64() {
let a = [1u64, 2, 3, 4];
let b = [1u64, 9, 3, 9];
assert_eq!(slot_hamming(&a, &b), 2);
}
#[test]
fn test_slot_hamming_generic_agrees_with_u32() {
let a: Vec<u32> = (0..50u32).map(|i| i % 7).collect();
let b: Vec<u32> = (0..50u32).map(|i| i % 5).collect();
assert_eq!(slot_hamming(&a, &b) as u32, slot_hamming_u32(&a, &b));
}
#[test]
fn test_minhash_jaccard_basic() {
let a = [1u32, 2, 3, 4];
let b = [1u32, 2, 3, 9];
assert_eq!(minhash_jaccard(&a, &b), 0.75);
}
#[test]
fn test_minhash_jaccard_identical() {
let v = [5u32, 6, 7, 8];
assert_eq!(minhash_jaccard(&v, &v), 1.0);
}
#[test]
fn test_minhash_jaccard_disjoint() {
let a = [1u32, 2, 3, 4];
let b = [5u32, 6, 7, 8];
assert_eq!(minhash_jaccard(&a, &b), 0.0);
}
#[test]
fn test_minhash_jaccard_empty() {
assert_eq!(minhash_jaccard(&[], &[]), 1.0);
}
}
#[cfg(test)]
mod proptests {
use super::*;
use proptest::prelude::*;
const SIZES: &[usize] = &[0, 1, 7, 8, 15, 16, 31, 32, 64, 96, 128];
fn arb_u32_pair(len: usize) -> impl Strategy<Value = (Vec<u32>, Vec<u32>)> {
(
prop::collection::vec(0u32..8, len),
prop::collection::vec(0u32..8, len),
)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(300))]
#[test]
fn slot_hamming_u32_matches_portable(
(a, b) in prop::sample::select(SIZES).prop_flat_map(arb_u32_pair)
) {
prop_assert_eq!(slot_hamming_u32(&a, &b), slot_hamming_u32_portable(&a, &b));
}
#[test]
fn slot_hamming_u32_symmetric(
(a, b) in prop::sample::select(SIZES).prop_flat_map(arb_u32_pair)
) {
prop_assert_eq!(slot_hamming_u32(&a, &b), slot_hamming_u32(&b, &a));
}
#[test]
fn slot_hamming_u32_self_is_zero(
a in prop::sample::select(SIZES)
.prop_flat_map(|s| prop::collection::vec(0u32..8, s))
) {
prop_assert_eq!(slot_hamming_u32(&a, &a), 0);
}
#[test]
fn slot_hamming_u32_bounded(
(a, b) in prop::sample::select(SIZES).prop_flat_map(arb_u32_pair)
) {
prop_assert!(slot_hamming_u32(&a, &b) <= a.len() as u32);
}
#[test]
fn generic_agrees_with_u32(
(a, b) in prop::sample::select(SIZES).prop_flat_map(arb_u32_pair)
) {
prop_assert_eq!(slot_hamming(&a, &b) as u32, slot_hamming_u32(&a, &b));
}
#[test]
fn minhash_jaccard_in_unit_interval(
(a, b) in prop::sample::select(SIZES).prop_flat_map(arb_u32_pair)
) {
let j = minhash_jaccard(&a, &b);
prop_assert!((0.0..=1.0).contains(&j), "jaccard {} out of range", j);
}
}
}