use std::path::PathBuf;
use std::time::{Duration, Instant};
use amari_holographic::optical::{
BinaryHologram, CodebookConfig, GeometricLeeEncoder, LeeEncoderConfig, OpticalCodebook,
OpticalFieldAlgebra, OpticalRotorField, SymbolId,
};
use super::fingerprint::{FingerprintValidation, TMatrixFingerprint};
use super::hardware::{HardwareCalibration, HardwareError, OpticalHardware, OpticalMeasurement};
use super::journal::{
CompactedMemoryState, JournalError, MemoryJournal, MemoryOp, StoredAssociation,
};
use super::now_timestamp;
use super::symbolic::SymbolicExpression;
#[derive(Clone, Debug)]
pub struct CheckpointConfig {
pub interval: Duration,
pub max_ops_before_compact: usize,
pub journal_path: PathBuf,
}
impl Default for CheckpointConfig {
fn default() -> Self {
Self {
interval: Duration::from_mins(5),
max_ops_before_compact: 10_000,
journal_path: PathBuf::from("memory_journal.bin"),
}
}
}
impl CheckpointConfig {
pub fn with_path(path: impl Into<PathBuf>) -> Self {
Self {
journal_path: path.into(),
..Default::default()
}
}
pub fn interval(mut self, duration: Duration) -> Self {
self.interval = duration;
self
}
pub fn max_ops(mut self, max: usize) -> Self {
self.max_ops_before_compact = max;
self
}
}
pub struct CheckpointedOpticalMemory<H: OpticalHardware> {
hardware: H,
algebra: OpticalFieldAlgebra,
encoder: GeometricLeeEncoder,
codebook: OpticalCodebook,
calibration: Option<HardwareCalibration>,
memory_trace: OpticalRotorField,
logical_state: CompactedMemoryState,
journal: MemoryJournal,
unsaved_ops: Vec<MemoryOp>,
config: CheckpointConfig,
last_checkpoint: Instant,
}
impl<H: OpticalHardware> CheckpointedOpticalMemory<H> {
pub fn new(
hardware: H,
encoder_config: LeeEncoderConfig,
codebook_config: CodebookConfig,
checkpoint_config: CheckpointConfig,
) -> Result<Self, MemoryError> {
let algebra = OpticalFieldAlgebra::new(hardware.dimensions());
let memory_trace = algebra.identity();
let encoder = GeometricLeeEncoder::new(encoder_config.clone());
let codebook = OpticalCodebook::new(codebook_config.clone());
let journal = MemoryJournal::new(encoder_config, codebook_config);
let logical_state = journal.replay_to_state();
let mut memory = Self {
hardware,
algebra,
encoder,
codebook,
calibration: None,
memory_trace,
logical_state,
journal,
unsaved_ops: Vec::new(),
config: checkpoint_config,
last_checkpoint: Instant::now(),
};
memory.calibrate()?;
Ok(memory)
}
pub fn restore(hardware: H, checkpoint_config: CheckpointConfig) -> Result<Self, MemoryError> {
let journal =
MemoryJournal::load(&checkpoint_config.journal_path).map_err(MemoryError::Journal)?;
let mut codebook = OpticalCodebook::new(journal.codebook_config.clone());
let logical_state = journal.replay_to_state();
codebook.import_seeds(logical_state.symbol_seeds.clone());
let encoder = GeometricLeeEncoder::new(journal.encoder_config.clone());
let algebra = OpticalFieldAlgebra::new(hardware.dimensions());
let memory_trace = algebra.identity();
let mut memory = Self {
hardware,
algebra,
encoder,
codebook,
calibration: None,
memory_trace,
logical_state,
journal,
unsaved_ops: Vec::new(),
config: checkpoint_config,
last_checkpoint: Instant::now(),
};
memory.validate_and_calibrate()?;
Ok(memory)
}
pub fn store(
&mut self,
key: SymbolicExpression,
value: SymbolicExpression,
) -> Result<(), MemoryError> {
let timestamp = now_timestamp();
self.ensure_symbols_registered(&key)?;
self.ensure_symbols_registered(&value)?;
let key_field = self.instantiate(&key)?;
let value_field = self.instantiate(&value)?;
self.optical_store(&key_field, &value_field);
let assoc = StoredAssociation {
key: key.clone(),
value: value.clone(),
strength: 1.0,
created_at: timestamp,
last_accessed: timestamp,
};
if let Some(existing) = self.logical_state.find_by_key_mut(&key) {
*existing = assoc;
} else {
self.logical_state.associations.push(assoc);
}
self.unsaved_ops.push(MemoryOp::Store {
key,
value,
strength: 1.0,
timestamp,
});
self.maybe_checkpoint()?;
Ok(())
}
pub fn retrieve(
&mut self,
query: &SymbolicExpression,
) -> Result<Option<RetrievalResult>, MemoryError> {
let query_field = self.instantiate(query)?;
let assoc_data: Vec<(usize, SymbolicExpression, f32)> = self
.logical_state
.associations
.iter()
.enumerate()
.map(|(i, a)| (i, a.key.clone(), a.strength))
.collect();
let mut best_match: Option<(usize, f32)> = None;
for (i, key, strength) in assoc_data {
let key_field = self.instantiate(&key)?;
let sim = self.algebra.similarity(&query_field, &key_field);
let weighted_sim = sim * strength;
if let Some((_, best_sim)) = best_match {
if weighted_sim > best_sim {
best_match = Some((i, weighted_sim));
}
} else if weighted_sim > 0.5 {
best_match = Some((i, weighted_sim));
}
}
Ok(best_match.map(|(i, sim)| {
let assoc = &self.logical_state.associations[i];
RetrievalResult {
value: assoc.value.clone(),
similarity: sim,
strength: assoc.strength,
}
}))
}
pub fn register_symbol(&mut self, name: impl Into<String>) -> Result<SymbolId, MemoryError> {
let symbol = SymbolId::new(name);
if !self.codebook.contains(&symbol) {
self.codebook.register(symbol.clone());
let seed = self.codebook.get_seed(&symbol);
self.unsaved_ops.push(MemoryOp::RegisterSymbol {
symbol: symbol.clone(),
seed,
timestamp: now_timestamp(),
});
}
Ok(symbol)
}
pub fn checkpoint(&mut self) -> Result<(), MemoryError> {
self.journal.ops.append(&mut self.unsaved_ops);
self.journal.t_fingerprint = Some(
TMatrixFingerprint::capture(&mut self.hardware, TMatrixFingerprint::DEFAULT_N_PROBES)
.map_err(MemoryError::Hardware)?,
);
self.journal
.save(&self.config.journal_path)
.map_err(MemoryError::Journal)?;
if self.journal.ops.len() > self.config.max_ops_before_compact {
self.journal.compact();
self.journal
.save(&self.config.journal_path)
.map_err(MemoryError::Journal)?;
}
self.last_checkpoint = Instant::now();
Ok(())
}
pub fn decay(&mut self, factor: f32) -> Result<(), MemoryError> {
for assoc in &mut self.logical_state.associations {
assoc.strength *= factor;
}
self.logical_state
.associations
.retain(|a| a.strength > 0.01);
self.unsaved_ops.push(MemoryOp::Decay {
factor,
timestamp: now_timestamp(),
});
self.maybe_checkpoint()
}
pub fn forget(&mut self, key: &SymbolicExpression) -> Result<(), MemoryError> {
self.logical_state.associations.retain(|a| &a.key != key);
self.unsaved_ops.push(MemoryOp::Forget {
key: key.clone(),
timestamp: now_timestamp(),
});
self.maybe_checkpoint()
}
pub fn strengthen(&mut self, key: &SymbolicExpression, delta: f32) -> Result<(), MemoryError> {
if let Some(assoc) = self.logical_state.find_by_key_mut(key) {
assoc.strength += delta;
assoc.last_accessed = now_timestamp();
self.unsaved_ops.push(MemoryOp::Strengthen {
key: key.clone(),
delta,
timestamp: now_timestamp(),
});
}
self.maybe_checkpoint()
}
pub fn hardware_info(&self) -> HardwareInfo {
HardwareInfo {
id: self.hardware.id().to_string(),
dimensions: self.hardware.dimensions(),
n_modes: self.hardware.n_modes(),
is_ready: self.hardware.is_ready(),
is_calibrated: self.calibration.is_some(),
}
}
pub fn stats(&self) -> MemoryStats {
MemoryStats {
n_associations: self.logical_state.associations.len(),
n_symbols: self.logical_state.symbol_seeds.len(),
n_unsaved_ops: self.unsaved_ops.len(),
journal_ops: self.journal.ops.len(),
has_base_state: self.journal.base_state.is_some(),
}
}
pub fn associations(&self) -> &[StoredAssociation] {
&self.logical_state.associations
}
pub fn hardware_mut(&mut self) -> &mut H {
&mut self.hardware
}
pub fn encoder(&self) -> &GeometricLeeEncoder {
&self.encoder
}
pub fn codebook(&self) -> &OpticalCodebook {
&self.codebook
}
fn maybe_checkpoint(&mut self) -> Result<(), MemoryError> {
if self.last_checkpoint.elapsed() >= self.config.interval {
self.checkpoint()?;
}
Ok(())
}
fn calibrate(&mut self) -> Result<(), MemoryError> {
let cal = self
.hardware
.full_calibrate()
.map_err(MemoryError::Hardware)?;
self.calibration = Some(cal);
Ok(())
}
fn validate_and_calibrate(&mut self) -> Result<(), MemoryError> {
let validation = if let Some(ref fp) = self.journal.t_fingerprint {
fp.validate(&mut self.hardware)
.map_err(MemoryError::Hardware)?
} else {
FingerprintValidation::NoFingerprint
};
match validation {
FingerprintValidation::Valid => {
let cal = self
.hardware
.quick_calibrate()
.map_err(MemoryError::Hardware)?;
self.calibration = Some(cal);
}
_ => {
self.calibrate()?;
}
}
Ok(())
}
#[allow(clippy::unnecessary_wraps)]
fn ensure_symbols_registered(&mut self, expr: &SymbolicExpression) -> Result<(), MemoryError> {
for symbol in expr.referenced_symbols() {
if !self.codebook.contains(symbol) {
self.codebook.register(symbol.clone());
let seed = self.codebook.get_seed(symbol);
self.unsaved_ops.push(MemoryOp::RegisterSymbol {
symbol: symbol.clone(),
seed,
timestamp: now_timestamp(),
});
}
}
Ok(())
}
pub(crate) fn instantiate(
&mut self,
expr: &SymbolicExpression,
) -> Result<OpticalRotorField, MemoryError> {
match expr {
SymbolicExpression::Symbol(id) => self
.codebook
.get(id)
.cloned()
.ok_or_else(|| MemoryError::UnknownSymbol(id.clone())),
SymbolicExpression::Bind(a, b) => {
let field_a = self.instantiate(a)?;
let field_b = self.instantiate(b)?;
Ok(self.algebra.bind(&field_a, &field_b))
}
SymbolicExpression::Bundle(elements) => {
let fields: Vec<OpticalRotorField> = elements
.iter()
.map(|(_, e)| self.instantiate(e))
.collect::<Result<_, _>>()?;
let weights: Vec<f32> = elements.iter().map(|(w, _)| w.0).collect();
Ok(self.algebra.bundle(&fields, &weights))
}
}
}
fn optical_store(&mut self, key: &OpticalRotorField, value: &OpticalRotorField) {
let binding = self.algebra.bind(key, value);
self.memory_trace = self
.algebra
.bundle(&[self.memory_trace.clone(), binding], &[1.0, 1.0]);
}
#[must_use]
pub fn memory_trace(&self) -> &OpticalRotorField {
&self.memory_trace
}
pub fn measure_via_hardware(&mut self) -> Result<OpticalMeasurement, MemoryError> {
let hologram: BinaryHologram = self.encoder.encode(&self.memory_trace);
self.hardware
.display(&hologram)
.map_err(MemoryError::Hardware)?;
let measurement = self.hardware.measure().map_err(MemoryError::Hardware)?;
Ok(measurement)
}
}
#[derive(Clone, Debug)]
pub struct RetrievalResult {
pub value: SymbolicExpression,
pub similarity: f32,
pub strength: f32,
}
#[derive(Clone, Debug)]
pub struct HardwareInfo {
pub id: String,
pub dimensions: (usize, usize),
pub n_modes: usize,
pub is_ready: bool,
pub is_calibrated: bool,
}
#[derive(Clone, Debug)]
pub struct MemoryStats {
pub n_associations: usize,
pub n_symbols: usize,
pub n_unsaved_ops: usize,
pub journal_ops: usize,
pub has_base_state: bool,
}
#[derive(Debug)]
pub enum MemoryError {
Hardware(HardwareError),
Journal(JournalError),
UnknownSymbol(SymbolId),
DimensionMismatch,
NotCalibrated,
}
impl std::fmt::Display for MemoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Hardware(e) => write!(f, "hardware error: {e}"),
Self::Journal(e) => write!(f, "journal error: {e}"),
Self::UnknownSymbol(id) => write!(f, "unknown symbol: {id}"),
Self::DimensionMismatch => write!(f, "dimension mismatch"),
Self::NotCalibrated => write!(f, "hardware not calibrated"),
}
}
}
impl std::error::Error for MemoryError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Hardware(e) => Some(e),
Self::Journal(e) => Some(e),
_ => None,
}
}
}
impl From<HardwareError> for MemoryError {
fn from(e: HardwareError) -> Self {
Self::Hardware(e)
}
}
impl From<JournalError> for MemoryError {
fn from(e: JournalError) -> Self {
Self::Journal(e)
}
}