use crate::math::clamp;
use crate::types::memory::{AtomType, MemoryAtom};
pub const EPISODIC_ATOM_SCALE: f64 = 60.0;
pub const SEMANTIC_ATOM_SCALE: f64 = 200.0;
pub const MAX_FEEDBACK_BOOST: f64 = 0.15;
pub const TOPIC_INTERFERENCE_MULTIPLIER: f64 = 1.5;
pub const PRUNE_THRESHOLD: f64 = 0.05;
pub fn get_atom_scale(atom_type: AtomType) -> f64 {
match atom_type {
AtomType::Episodic => EPISODIC_ATOM_SCALE,
AtomType::Semantic => SEMANTIC_ATOM_SCALE,
AtomType::Procedural => SEMANTIC_ATOM_SCALE,
AtomType::Preference => SEMANTIC_ATOM_SCALE * 1.5, AtomType::Digest => SEMANTIC_ATOM_SCALE,
AtomType::PromptEvolution => SEMANTIC_ATOM_SCALE,
}
}
pub fn compute_effective_importance(
atom: &MemoryAtom,
feedback_boost: f64,
current_generation: Option<u32>,
recent_topics: &[String],
) -> f64 {
let base = atom.importance;
let stability = base * (1.0 + (1.0 + atom.access_count as f64).ln());
let retention = match (current_generation, atom.epoch) {
(Some(current_gen), Some(atom_gen)) => {
let atoms_since_access = current_gen.saturating_sub(atom_gen) as f64;
let has_topic_overlap = !recent_topics.is_empty()
&& atom.topics.iter().any(|t| {
recent_topics
.iter()
.any(|rt| rt.to_lowercase() == t.to_lowercase())
});
let effective_interference = if has_topic_overlap {
atoms_since_access * TOPIC_INTERFERENCE_MULTIPLIER
} else {
atoms_since_access
};
let scale = get_atom_scale(atom.atom_type);
let denom = stability * scale;
if denom > 0.0 {
(-effective_interference / denom).exp()
} else {
0.0
}
}
_ => 1.0, };
let clamped_boost = clamp(feedback_boost, -MAX_FEEDBACK_BOOST, MAX_FEEDBACK_BOOST);
let current = base * retention;
let adjusted = if clamped_boost >= 0.0 {
current + clamped_boost * (1.0 - current)
} else {
current * (1.0 + clamped_boost)
};
if atom.crystallized {
return clamp(adjusted, 0.7, 1.0);
}
clamp(adjusted, 0.0, 1.0)
}
pub fn is_prune_candidate(
atom: &MemoryAtom,
current_generation: Option<u32>,
threshold: f64,
) -> bool {
if atom.crystallized {
return false;
}
let importance = compute_effective_importance(atom, 0.0, current_generation, &[]);
importance < threshold
}
pub fn record_access(current_generation: Option<u32>) -> AccessUpdate {
AccessUpdate {
access_count_increment: 1,
generation: current_generation,
}
}
pub struct AccessUpdate {
pub access_count_increment: u32,
pub generation: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::memory::AtomSource;
fn make_atom(atom_type: AtomType, importance: f64, access_count: u32) -> MemoryAtom {
MemoryAtom {
id: "test".into(),
content: "test content".into(),
embedding: None,
atom_type,
source: AtomSource::default(),
importance,
access_count,
last_accessed_at: 0.0,
created_at: 0.0,
topics: vec!["rust".into(), "async".into()],
domain: None,
consolidated_from: None,
is_consolidated: false,
parent_id: None,
depth: None,
label: None,
child_ids: None,
superseded: false,
suppressed: false,
dormant: false,
tags: vec![],
encoding_context: None,
retrieval_reward: None,
reconsolidation_count: None,
arousal: None,
valence: None,
epoch: Some(0),
crystallized: false,
}
}
#[test]
fn no_decay_without_generation() {
let atom = make_atom(AtomType::Episodic, 0.8, 5);
let eff = compute_effective_importance(&atom, 0.0, None, &[]);
assert!(eff > 0.7);
}
#[test]
fn episodic_decays_faster_than_semantic() {
let episodic = make_atom(AtomType::Episodic, 0.5, 1);
let semantic = {
let mut a = make_atom(AtomType::Semantic, 0.5, 1);
a.atom_type = AtomType::Semantic;
a
};
let eff_ep = compute_effective_importance(&episodic, 0.0, Some(100), &[]);
let eff_sem = compute_effective_importance(&semantic, 0.0, Some(100), &[]);
assert!(eff_ep < eff_sem);
}
#[test]
fn topic_interference_accelerates_decay() {
let atom = make_atom(AtomType::Episodic, 0.5, 1);
let without = compute_effective_importance(&atom, 0.0, Some(50), &[]);
let with = compute_effective_importance(
&atom,
0.0,
Some(50),
&["rust".into()], );
assert!(with < without); }
#[test]
fn feedback_boost_positive() {
let atom = make_atom(AtomType::Episodic, 0.5, 1);
let base = compute_effective_importance(&atom, 0.0, None, &[]);
let boosted = compute_effective_importance(&atom, 0.1, None, &[]);
assert!(boosted > base);
}
#[test]
fn feedback_boost_negative() {
let atom = make_atom(AtomType::Episodic, 0.5, 1);
let base = compute_effective_importance(&atom, 0.0, None, &[]);
let penalized = compute_effective_importance(&atom, -0.1, None, &[]);
assert!(penalized < base);
}
#[test]
fn crystallized_floor() {
let mut atom = make_atom(AtomType::Semantic, 0.1, 0);
atom.crystallized = true;
let eff = compute_effective_importance(&atom, -0.15, Some(1000), &[]);
assert!(eff >= 0.7); }
#[test]
fn access_count_increases_stability() {
let low_access = make_atom(AtomType::Episodic, 0.5, 1);
let high_access = make_atom(AtomType::Episodic, 0.5, 100);
let eff_low = compute_effective_importance(&low_access, 0.0, Some(200), &[]);
let eff_high = compute_effective_importance(&high_access, 0.0, Some(200), &[]);
assert!(eff_high > eff_low); }
#[test]
fn prune_candidate() {
let mut atom = make_atom(AtomType::Episodic, 0.01, 0);
atom.epoch = Some(0);
assert!(is_prune_candidate(&atom, Some(10000), PRUNE_THRESHOLD));
}
#[test]
fn crystallized_never_pruned() {
let mut atom = make_atom(AtomType::Semantic, 0.01, 0);
atom.crystallized = true;
assert!(!is_prune_candidate(&atom, Some(10000), PRUNE_THRESHOLD));
}
}