use super::{HYPERVECTOR_BITS, HYPERVECTOR_U64_LEN};
use rand::Rng;
use std::fmt;
#[derive(Debug, thiserror::Error)]
pub enum HdcError {
#[error("Invalid hypervector dimension: expected {expected}, got {got}")]
InvalidDimension { expected: usize, got: usize },
#[error("Empty vector set provided")]
EmptyVectorSet,
#[error("Serialization error: {0}")]
SerializationError(String),
}
#[derive(Clone, PartialEq, Eq)]
pub struct Hypervector {
pub(crate) bits: [u64; HYPERVECTOR_U64_LEN],
}
impl Hypervector {
pub fn zero() -> Self {
Self {
bits: [0u64; HYPERVECTOR_U64_LEN],
}
}
pub fn random() -> Self {
let mut rng = rand::thread_rng();
let mut bits = [0u64; HYPERVECTOR_U64_LEN];
for word in bits.iter_mut() {
*word = rng.gen();
}
Self { bits }
}
pub fn from_seed(seed: u64) -> Self {
use rand::SeedableRng;
let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
let mut bits = [0u64; HYPERVECTOR_U64_LEN];
for word in bits.iter_mut() {
*word = rng.gen();
}
Self { bits }
}
#[inline]
pub fn bind(&self, other: &Self) -> Self {
let mut result = Self::zero();
for i in 0..HYPERVECTOR_U64_LEN {
result.bits[i] = self.bits[i] ^ other.bits[i];
}
result
}
#[inline]
pub fn similarity(&self, other: &Self) -> f32 {
let hamming = self.hamming_distance(other);
1.0 - (2.0 * hamming as f32 / HYPERVECTOR_BITS as f32)
}
#[inline]
pub fn hamming_distance(&self, other: &Self) -> u32 {
let mut d0 = 0u32;
let mut d1 = 0u32;
let mut d2 = 0u32;
let mut d3 = 0u32;
let chunks = HYPERVECTOR_U64_LEN / 4;
let remainder = HYPERVECTOR_U64_LEN % 4;
for i in 0..chunks {
let base = i * 4;
d0 += (self.bits[base] ^ other.bits[base]).count_ones();
d1 += (self.bits[base + 1] ^ other.bits[base + 1]).count_ones();
d2 += (self.bits[base + 2] ^ other.bits[base + 2]).count_ones();
d3 += (self.bits[base + 3] ^ other.bits[base + 3]).count_ones();
}
let base = chunks * 4;
for i in 0..remainder {
d0 += (self.bits[base + i] ^ other.bits[base + i]).count_ones();
}
d0 + d1 + d2 + d3
}
#[inline]
pub fn popcount(&self) -> u32 {
self.bits.iter().map(|&w| w.count_ones()).sum()
}
pub fn bundle(vectors: &[Self]) -> Result<Self, HdcError> {
if vectors.is_empty() {
return Err(HdcError::EmptyVectorSet);
}
if vectors.len() == 1 {
return Ok(vectors[0].clone());
}
let n = vectors.len();
let threshold = n / 2;
let mut result = Self::zero();
for word_idx in 0..HYPERVECTOR_U64_LEN {
let mut counts = [0u8; 64];
for vector in vectors {
let word = vector.bits[word_idx];
for bit_pos in 0..64 {
counts[bit_pos] += ((word >> bit_pos) & 1) as u8;
}
}
let mut result_word = 0u64;
for (bit_pos, &count) in counts.iter().enumerate() {
if count as usize > threshold {
result_word |= 1u64 << bit_pos;
}
}
result.bits[word_idx] = result_word;
}
Ok(result)
}
#[inline]
pub fn bundle_3(a: &Self, b: &Self, c: &Self) -> Self {
let mut result = Self::zero();
for i in 0..HYPERVECTOR_U64_LEN {
let wa = a.bits[i];
let wb = b.bits[i];
let wc = c.bits[i];
result.bits[i] = (wa & wb) | (wb & wc) | (wa & wc);
}
result
}
#[inline]
pub fn bits(&self) -> &[u64; HYPERVECTOR_U64_LEN] {
&self.bits
}
}
impl fmt::Debug for Hypervector {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Hypervector {{ bits: {} set / {} total }}",
self.popcount(),
HYPERVECTOR_BITS
)
}
}
impl Default for Hypervector {
fn default() -> Self {
Self::zero()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_zero_vector() {
let zero = Hypervector::zero();
assert_eq!(zero.popcount(), 0);
assert_eq!(zero.hamming_distance(&zero), 0);
}
#[test]
fn test_random_vector_properties() {
let v = Hypervector::random();
let count = v.popcount();
assert!(count > 4500 && count < 5500, "popcount: {}", count);
}
#[test]
fn test_from_seed_deterministic() {
let v1 = Hypervector::from_seed(42);
let v2 = Hypervector::from_seed(42);
let v3 = Hypervector::from_seed(43);
assert_eq!(v1, v2);
assert_ne!(v1, v3);
}
#[test]
fn test_bind_commutative() {
let a = Hypervector::random();
let b = Hypervector::random();
assert_eq!(a.bind(&b), b.bind(&a));
}
#[test]
fn test_bind_self_inverse() {
let a = Hypervector::random();
let b = Hypervector::random();
let bound = a.bind(&b);
let unbound = bound.bind(&b);
assert_eq!(a, unbound);
}
#[test]
fn test_similarity_bounds() {
let a = Hypervector::random();
let b = Hypervector::random();
let sim = a.similarity(&b);
assert!(
sim >= -1.0 && sim <= 1.0,
"similarity out of bounds: {}",
sim
);
}
#[test]
fn test_similarity_identical() {
let a = Hypervector::random();
let sim = a.similarity(&a);
assert!((sim - 1.0).abs() < 0.001);
}
#[test]
fn test_similarity_random_approximately_zero() {
let a = Hypervector::random();
let b = Hypervector::random();
let sim = a.similarity(&b);
assert!(sim > -0.2 && sim < 0.2, "similarity: {}", sim);
}
#[test]
fn test_hamming_distance_identical() {
let a = Hypervector::random();
assert_eq!(a.hamming_distance(&a), 0);
}
#[test]
fn test_bundle_single_vector() {
let v = Hypervector::random();
let bundled = Hypervector::bundle(&[v.clone()]).unwrap();
assert_eq!(bundled, v);
}
#[test]
fn test_bundle_empty_error() {
let result = Hypervector::bundle(&[]);
assert!(matches!(result, Err(HdcError::EmptyVectorSet)));
}
#[test]
fn test_bundle_majority_vote() {
let v1 = Hypervector::from_seed(1);
let v2 = Hypervector::from_seed(2);
let v3 = Hypervector::from_seed(3);
let bundled = Hypervector::bundle(&[v1.clone(), v2.clone(), v3]).unwrap();
assert!(bundled.similarity(&v1) > 0.3);
assert!(bundled.similarity(&v2) > 0.3);
}
#[test]
fn test_bundle_odd_count() {
let vectors: Vec<_> = (0..5).map(|i| Hypervector::from_seed(i)).collect();
let bundled = Hypervector::bundle(&vectors).unwrap();
for v in &vectors {
assert!(bundled.similarity(v) > 0.3);
}
}
#[test]
fn test_debug_format() {
let v = Hypervector::zero();
let debug = format!("{:?}", v);
assert!(debug.contains("bits: 0 set"));
}
}