use once_cell::sync::Lazy;
use rand::{rngs::StdRng, Rng, SeedableRng};
use serde::{Deserialize, Serialize};
use std::ops::{Add, Mul};
use std::sync::Mutex;
static GLOBAL_RNG: Lazy<Mutex<StdRng>> = Lazy::new(|| Mutex::new(StdRng::from_entropy()));
pub fn set_global_seed(seed: u64) {
let mut rng = GLOBAL_RNG.lock().unwrap();
*rng = StdRng::seed_from_u64(seed);
}
#[derive(Debug, Clone, Copy)]
pub enum TieBreaker {
AlwaysPositive,
AlwaysNegative,
Random,
}
pub trait VSA: Sized + Clone {
type Elem: Copy + std::fmt::Debug + PartialEq + Into<f32>;
fn generate(dim: usize, rng: &mut impl Rng) -> Self;
fn bundle(&self, other: &Self, tie_breaker: TieBreaker, rng: &mut impl Rng) -> Self;
fn bind(&self, other: &Self) -> Self;
fn cosine_similarity(&self, other: &Self) -> f32;
fn hamming_distance(&self, other: &Self) -> f32;
fn to_vec(&self) -> Vec<f32>;
fn bundle_many(vectors: &[Self], tie_breaker: TieBreaker, rng: &mut impl Rng) -> Self {
assert!(
!vectors.is_empty(),
"Cannot bundle an empty slice of hypervectors"
);
let mut result = vectors[0].clone();
for vec in &vectors[1..] {
result = result.bundle(vec, tie_breaker, rng);
}
result
}
fn bind_many(vectors: &[Self]) -> Self {
assert!(
!vectors.is_empty(),
"Cannot bind an empty slice of hypervectors"
);
let mut result = vectors[0].clone();
for vec in &vectors[1..] {
result = result.bind(vec);
}
result
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Hypervector<V: VSA> {
pub inner: V,
}
impl<V: VSA> Hypervector<V> {
pub fn generate(dim: usize) -> Self {
let mut rng = GLOBAL_RNG.lock().unwrap();
Self {
inner: V::generate(dim, &mut *rng),
}
}
pub fn generate_many(dim: usize, count: usize) -> Vec<Self> {
let mut rng = GLOBAL_RNG.lock().unwrap();
(0..count)
.map(|_| Self {
inner: V::generate(dim, &mut *rng),
})
.collect()
}
pub fn bundle(&self, other: &Self, tie_breaker: TieBreaker) -> Self {
let mut rng = GLOBAL_RNG.lock().unwrap();
Self {
inner: self.inner.bundle(&other.inner, tie_breaker, &mut *rng),
}
}
pub fn bind(&self, other: &Self) -> Self {
Self {
inner: self.inner.bind(&other.inner),
}
}
pub fn cosine_similarity(&self, other: &Self) -> f32 {
self.inner.cosine_similarity(&other.inner)
}
pub fn hamming_distance(&self, other: &Self) -> f32 {
self.inner.hamming_distance(&other.inner)
}
pub fn to_vec(&self) -> Vec<f32> {
self.inner.to_vec()
}
pub fn bundle_many(vectors: &[Self], tie_breaker: TieBreaker) -> Self {
let mut rng = GLOBAL_RNG.lock().unwrap();
let inners: Vec<V> = vectors.iter().map(|hv| hv.inner.clone()).collect();
Self {
inner: V::bundle_many(&inners, tie_breaker, &mut *rng),
}
}
pub fn bind_many(vectors: &[Self]) -> Self {
let inners: Vec<V> = vectors.iter().map(|hv| hv.inner.clone()).collect();
Self {
inner: V::bind_many(&inners),
}
}
}
impl<V: VSA> Add for Hypervector<V> {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let tie_breaker = TieBreaker::Random;
let mut rng = GLOBAL_RNG.lock().unwrap();
Self {
inner: self.inner.bundle(&rhs.inner, tie_breaker, &mut *rng),
}
}
}
impl<V: VSA> Mul for Hypervector<V> {
type Output = Self;
fn mul(self, rhs: Self) -> Self::Output {
Self {
inner: self.inner.bind(&rhs.inner),
}
}
}
pub mod encoder;
pub mod fhrr;
pub mod mbat;
pub mod ssp;
#[cfg(test)]
mod tests {
use super::*;
use crate::mbat;
type HV = Hypervector<mbat::MBAT>;
#[test]
fn test_codebook_pair_bindings() {
let dim = 10000;
let codebook: Vec<HV> = HV::generate_many(dim, 10);
let mut pair_bindings = Vec::new();
for i in 0..codebook.len() {
for j in (i + 1)..codebook.len() {
let binding = codebook[i].bind(&codebook[j]);
pair_bindings.push(((i, j), binding));
}
}
for &((i, j), ref binding) in &pair_bindings {
let recomputed = codebook[i].bind(&codebook[j]);
let sim: f32 = binding.cosine_similarity(&recomputed);
assert!(
(sim - 1.0).abs() < 1e-6,
"Recomputed binding differs for pair ({}, {})",
i,
j
);
}
for (idx1, &((i, j), ref binding1)) in pair_bindings.iter().enumerate() {
for (idx2, &((k, l), ref binding2)) in pair_bindings.iter().enumerate() {
if idx1 == idx2 {
continue;
}
let sim: f32 = binding1.cosine_similarity(&binding2);
assert!(
sim.abs() < 0.1,
"Binding for pair ({}, {}) has cosine similarity {} with binding for pair ({}, {})",
i,
j,
sim,
k,
l
);
}
}
}
fn generate_two(dim: usize) -> (HV, HV) {
(HV::generate(dim), HV::generate(dim))
}
#[test]
fn test_random_vectors_orthogonal() {
let dim = 10000;
let (a, b) = generate_two(dim);
let cos_sim = a.cosine_similarity(&b);
assert!(
cos_sim.abs() < 0.1,
"Expected near-orthogonality but got cosine similarity {}",
cos_sim
);
}
#[test]
fn test_bundling_similarity() {
let dim = 10000;
let a = HV::generate(dim);
let b = HV::generate(dim);
let bundled = a.bundle(&b, TieBreaker::Random);
let sim_a = bundled.cosine_similarity(&a);
let sim_b = bundled.cosine_similarity(&b);
assert!(
(sim_a - 0.5).abs() < 0.1,
"Bundled vector similarity with first constituent was {}",
sim_a
);
assert!(
(sim_b - 0.5).abs() < 0.1,
"Bundled vector similarity with second constituent was {}",
sim_b
);
}
#[test]
fn test_binding_orthogonality() {
let dim = 10000;
let a = HV::generate(dim);
let b = HV::generate(dim);
let bound = a.bind(&b);
let sim_a = a.cosine_similarity(&bound);
let sim_b = b.cosine_similarity(&bound);
assert!(
sim_a.abs() < 0.1,
"Binding similarity with first constituent was {}",
sim_a
);
assert!(
sim_b.abs() < 0.1,
"Binding similarity with second constituent was {}",
sim_b
);
}
#[test]
fn test_to_vec_conversion() {
let dim = 100;
let hv = HV::generate(dim);
let vec_f32 = hv.to_vec();
assert_eq!(vec_f32.len(), dim);
for &x in &vec_f32 {
assert!(x == 1.0 || x == -1.0, "Element {} is not 1.0 or -1.0", x);
}
}
}