use std::sync::atomic::{AtomicU64, Ordering};
use amari_holographic::BindingAlgebra;
use parking_lot::RwLock;
use crate::error::MinuetResult;
use crate::traits::{
CapacityInfo, CapacityWarning, MemoryStore, MemoryTrace, RetrievalResult, StoreOptions,
StoreReceipt, TraceCapacityInfo,
};
use super::DenseTrace;
pub struct SimpleStore<A: BindingAlgebra> {
trace: RwLock<DenseTrace<A>>,
next_id: AtomicU64,
config: SimpleStoreConfig,
}
#[derive(Clone, Debug)]
pub struct SimpleStoreConfig {
pub bundle_temperature: f64,
pub warning_threshold: f64,
pub critical_threshold: f64,
}
impl Default for SimpleStoreConfig {
fn default() -> Self {
Self {
bundle_temperature: 1.0,
warning_threshold: 0.8,
critical_threshold: 0.95,
}
}
}
impl<A: BindingAlgebra> Default for SimpleStore<A> {
fn default() -> Self {
Self::new()
}
}
impl<A: BindingAlgebra> SimpleStore<A> {
#[must_use]
pub fn new() -> Self {
Self::with_config(SimpleStoreConfig::default())
}
#[must_use]
pub fn with_config(config: SimpleStoreConfig) -> Self {
Self {
trace: RwLock::new(DenseTrace::with_temperature(config.bundle_temperature)),
next_id: AtomicU64::new(0),
config,
}
}
#[must_use]
pub fn config(&self) -> &SimpleStoreConfig {
&self.config
}
}
impl<A: BindingAlgebra> MemoryStore for SimpleStore<A> {
type Trace = DenseTrace<A>;
type Algebra = A;
fn store(&self, key: &A, value: &A) -> MinuetResult<StoreReceipt> {
self.store_with_options(key, value, StoreOptions::new())
}
fn store_with_options(
&self,
key: &A,
value: &A,
options: StoreOptions,
) -> MinuetResult<StoreReceipt> {
let bound = key.bind(value);
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
let mut trace = self.trace.write();
trace.add(&bound, options.weight);
let snr = trace.estimated_snr();
let utilization = trace.utilization();
let warning = if utilization > self.config.critical_threshold {
Some(CapacityWarning::AtCapacity)
} else if utilization > self.config.warning_threshold {
Some(CapacityWarning::ApproachingCapacity { utilization })
} else {
None
};
Ok(StoreReceipt {
id,
post_snr: snr,
warning,
location: "main".into(),
})
}
fn store_batch(&self, pairs: &[(A, A)]) -> MinuetResult<Vec<StoreReceipt>> {
pairs.iter().map(|(k, v)| self.store(k, v)).collect()
}
fn retrieve(&self, key: &A) -> MinuetResult<RetrievalResult<A>> {
let trace = self.trace.read();
let raw = trace.unbind(key);
let confidence = (trace.estimated_snr() / 10.0).min(1.0);
Ok(RetrievalResult {
value: raw,
confidence,
attribution: vec![],
})
}
fn capacity_info(&self) -> CapacityInfo {
let trace = self.trace.read();
let items = trace.item_count();
let capacity = trace.theoretical_capacity();
let utilization = trace.utilization();
let snr = trace.estimated_snr();
CapacityInfo {
total_items: items,
theoretical_capacity: capacity,
utilization,
estimated_snr: snr,
per_trace: vec![TraceCapacityInfo {
name: "main".into(),
items,
capacity,
utilization,
}],
}
}
fn clear(&self) -> MinuetResult<()> {
self.trace.write().clear();
Ok(())
}
fn trace_count(&self) -> usize {
1
}
fn total_items(&self) -> usize {
self.trace.read().item_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
use amari_holographic::ProductCliffordAlgebra;
type TestAlgebra = ProductCliffordAlgebra<8>;
#[test]
fn store_and_retrieve() {
let store = SimpleStore::<TestAlgebra>::new();
let key = TestAlgebra::random_versor(2);
let value = TestAlgebra::random_versor(2);
let receipt = store.store(&key, &value).unwrap();
assert_eq!(receipt.id, 0);
let result = store.retrieve(&key).unwrap();
let sim = result.value.similarity(&value);
assert!(sim > 0.5, "similarity was {}", sim);
}
#[test]
fn capacity_info() {
let store = SimpleStore::<TestAlgebra>::new();
let info = store.capacity_info();
assert_eq!(info.total_items, 0);
assert!(info.theoretical_capacity > 0);
for _ in 0..5 {
let key = TestAlgebra::random_versor(2);
let value = TestAlgebra::random_versor(2);
store.store(&key, &value).unwrap();
}
let info = store.capacity_info();
assert_eq!(info.total_items, 5);
}
}