use std::sync::atomic::{AtomicU64, Ordering};
use amari_holographic::BindingAlgebra;
use parking_lot::RwLock;
use crate::traits::MemoryTrace;
#[derive(Debug)]
pub struct DenseTrace<A: BindingAlgebra> {
trace: RwLock<A>,
item_count: AtomicU64,
beta: f64,
}
impl<A: BindingAlgebra> Clone for DenseTrace<A> {
fn clone(&self) -> Self {
Self {
trace: RwLock::new(self.trace.read().clone()),
item_count: AtomicU64::new(self.item_count.load(Ordering::Relaxed)),
beta: self.beta,
}
}
}
impl<A: BindingAlgebra> Default for DenseTrace<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: BindingAlgebra> DenseTrace<A> {
#[must_use]
pub fn new() -> Self {
Self {
trace: RwLock::new(A::zero()),
item_count: AtomicU64::new(0),
beta: 1.0,
}
}
#[must_use]
pub fn with_temperature(beta: f64) -> Self {
Self {
trace: RwLock::new(A::zero()),
item_count: AtomicU64::new(0),
beta,
}
}
#[must_use]
pub fn temperature(&self) -> f64 {
self.beta
}
}
fn scale_element<A: BindingAlgebra>(elem: &A, weight: f64) -> A {
let coeffs: Vec<f64> = elem
.to_coefficients()
.into_iter()
.map(|c| c * weight)
.collect();
A::from_coefficients(&coeffs).unwrap_or_else(|_| elem.clone())
}
impl<A: BindingAlgebra> MemoryTrace for DenseTrace<A> {
type Algebra = A;
fn dimension(&self) -> usize {
A::zero().dimension()
}
fn item_count(&self) -> usize {
self.item_count.load(Ordering::Relaxed) as usize
}
fn add(&mut self, item: &A, weight: f64) {
let mut trace = self.trace.write();
let scaled = scale_element(item, weight);
*trace = trace
.bundle(&scaled, self.beta)
.unwrap_or_else(|_| trace.clone());
self.item_count.fetch_add(1, Ordering::Relaxed);
}
fn merge(&mut self, other: &Self, weight: f64) {
let mut trace = self.trace.write();
let other_trace = other.trace.read();
let scaled = scale_element(&*other_trace, weight);
*trace = trace
.bundle(&scaled, self.beta)
.unwrap_or_else(|_| trace.clone());
self.item_count
.fetch_add(other.item_count.load(Ordering::Relaxed), Ordering::Relaxed);
}
fn clear(&mut self) {
*self.trace.write() = A::zero();
self.item_count.store(0, Ordering::Relaxed);
}
fn similarity(&self, query: &A) -> f64 {
self.trace.read().similarity(query)
}
fn unbind(&self, query: &A) -> A {
let trace = self.trace.read();
query
.inverse()
.map_or_else(|_| trace.clone(), |inv| inv.bind(&*trace))
}
fn as_algebra(&self) -> A {
self.trace.read().clone()
}
}
#[cfg(test)]
mod tests {
use super::*;
use amari_holographic::ProductCliffordAlgebra;
type TestAlgebra = ProductCliffordAlgebra<8>;
fn test_element(seed: &str) -> TestAlgebra {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
seed.hash(&mut hasher);
let mut state = hasher.finish();
let dim = TestAlgebra::identity().dimension();
let coeffs: Vec<f64> = (0..dim)
.map(|_| {
state = state.wrapping_mul(1_103_515_245).wrapping_add(12345);
((state as f64) / (u64::MAX as f64)) * 2.0 - 1.0
})
.collect();
TestAlgebra::from_coefficients(&coeffs)
.and_then(|e| e.normalize())
.unwrap()
}
#[test]
fn new_trace_is_empty() {
let trace = DenseTrace::<TestAlgebra>::new();
assert_eq!(trace.item_count(), 0);
assert!(trace.is_empty());
}
#[test]
fn add_increases_count() {
let mut trace = DenseTrace::<TestAlgebra>::new();
let item = test_element("test_item_1");
trace.add(&item, 1.0);
assert_eq!(trace.item_count(), 1);
}
#[test]
fn similar_after_add() {
let mut trace = DenseTrace::<TestAlgebra>::new();
let item = test_element("test_item_2");
trace.add(&item, 1.0);
let sim = trace.similarity(&item);
assert!(sim > 0.5, "similarity was {}", sim);
}
#[test]
fn clear_resets_trace() {
let mut trace = DenseTrace::<TestAlgebra>::new();
let item = test_element("test_item_3");
trace.add(&item, 1.0);
trace.clear();
assert_eq!(trace.item_count(), 0);
assert!(trace.is_empty());
}
}