Squashed 'vendor/ruvector/' content from commit b64c2172

git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
commit d803bfe2b1
7854 changed files with 3522914 additions and 0 deletions

View File

@@ -0,0 +1,198 @@
//! Phase 3 Transfer Timeline
//!
//! Records domain transfer events in the EXO temporal causal graph so the
//! system can review its own transfer history and anticipate the next
//! beneficial `(src, dst)` pair to activate.
use ruvector_domain_expansion::DomainId;
use crate::{
AnticipationHint, ConsolidationConfig, ConsolidationResult, TemporalConfig, TemporalMemory,
};
use exo_core::{Metadata, Pattern, PatternId, SubstrateTime};
const DIM: usize = 64;
// ─── embedding helpers ────────────────────────────────────────────────────────
/// FNV-1a hash of a string, normalised to [0, 1].
fn domain_hash(id: &str) -> f32 {
let mut h: u32 = 0x811c_9dc5;
for b in id.bytes() {
h ^= b as u32;
h = h.wrapping_mul(0x0100_0193);
}
h as f32 / u32::MAX as f32
}
/// Build a 64-dim pattern embedding for a transfer event.
///
/// Layout:
/// * `[0]` src domain hash (normalised)
/// * `[1]` dst domain hash (normalised)
/// * `[2]` cycle (log-normalised to [0, 1] over 1 000 cycles)
/// * `[3]` delta_reward (clamped to [0, 1])
/// * `[4..64]` sinusoidal harmonics of `(src_hash + dst_hash)`
fn build_embedding(src: &DomainId, dst: &DomainId, cycle: u64, delta_reward: f32) -> Vec<f32> {
let mut emb = vec![0.0f32; DIM];
let sh = domain_hash(&src.0);
let dh = domain_hash(&dst.0);
emb[0] = sh;
emb[1] = dh;
emb[2] = (cycle as f32).ln_1p() / (1_000.0_f32).ln_1p();
emb[3] = delta_reward.clamp(0.0, 1.0);
for i in 4..DIM {
let phase = (sh + dh) * i as f32 * std::f32::consts::PI / DIM as f32;
emb[i] = phase.sin() * 0.5 + 0.5;
}
emb
}
// ─── TransferTimeline ─────────────────────────────────────────────────────────
/// Records transfer events in the temporal causal graph and provides
/// anticipation hints for the next beneficial transfer.
pub struct TransferTimeline {
memory: TemporalMemory,
last_transfer_id: Option<PatternId>,
/// Total transfer events recorded (short-term + consolidated).
count: usize,
}
impl TransferTimeline {
/// Create with a low salience threshold so even weak transfers are kept.
pub fn new() -> Self {
let config = TemporalConfig {
consolidation: ConsolidationConfig {
salience_threshold: 0.1,
..Default::default()
},
..Default::default()
};
Self {
memory: TemporalMemory::new(config),
last_transfer_id: None,
count: 0,
}
}
/// Record a transfer event.
///
/// `delta_reward` is the improvement in arm reward after transfer
/// (`> 0` = positive transfer, `< 0` = negative transfer).
///
/// Each event is linked causally to the previous one so the temporal
/// causal graph can trace the full transfer trajectory.
pub fn record_transfer(
&mut self,
src: &DomainId,
dst: &DomainId,
cycle: u64,
delta_reward: f32,
) -> crate::Result<PatternId> {
let embedding = build_embedding(src, dst, cycle, delta_reward);
let salience = delta_reward.abs().clamp(0.1, 1.0);
let antecedents: Vec<PatternId> = self.last_transfer_id.iter().copied().collect();
let pattern = Pattern {
id: PatternId::new(),
embedding,
metadata: Metadata::default(),
timestamp: SubstrateTime::now(),
antecedents: antecedents.clone(),
salience,
};
let id = self.memory.store(pattern, &antecedents)?;
self.last_transfer_id = Some(id);
self.count += 1;
Ok(id)
}
/// Consolidate short-term transfer events to long-term memory.
pub fn consolidate(&self) -> ConsolidationResult {
self.memory.consolidate()
}
/// Return anticipation hints based on recent transfer causality.
///
/// If a previous transfer was recorded the hints suggest continuing
/// the same causal chain and sequential pattern.
pub fn anticipate_next(&self) -> Vec<AnticipationHint> {
match self.last_transfer_id {
Some(id) => vec![
AnticipationHint::CausalChain { context: id },
AnticipationHint::SequentialPattern { recent: vec![id] },
],
None => vec![],
}
}
/// Total number of transfer events recorded.
pub fn count(&self) -> usize {
self.count
}
/// Causal graph reference for advanced queries.
pub fn causal_graph(&self) -> &crate::CausalGraph {
self.memory.causal_graph()
}
}
impl Default for TransferTimeline {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_record_and_count() {
let mut tl = TransferTimeline::new();
let src = DomainId("retrieval".to_string());
let dst = DomainId("graph".to_string());
tl.record_transfer(&src, &dst, 1, 0.3).unwrap();
tl.record_transfer(&src, &dst, 2, 0.5).unwrap();
assert_eq!(tl.count(), 2);
}
#[test]
fn test_consolidate() {
let mut tl = TransferTimeline::new();
let src = DomainId("a".to_string());
let dst = DomainId("b".to_string());
for i in 0..5 {
tl.record_transfer(&src, &dst, i, 0.4).unwrap();
}
let result = tl.consolidate();
assert!(result.num_consolidated >= 1);
}
#[test]
fn test_anticipate_empty() {
let tl = TransferTimeline::new();
assert!(tl.anticipate_next().is_empty());
}
#[test]
fn test_anticipate_after_record() {
let mut tl = TransferTimeline::new();
let src = DomainId("x".to_string());
let dst = DomainId("y".to_string());
tl.record_transfer(&src, &dst, 1, 0.4).unwrap();
let hints = tl.anticipate_next();
assert!(!hints.is_empty());
}
#[test]
fn test_embedding_values() {
let src = DomainId("retrieval".to_string());
let dst = DomainId("graph".to_string());
let emb = build_embedding(&src, &dst, 42, 0.7);
assert_eq!(emb.len(), DIM);
assert!((emb[3] - 0.7).abs() < 1e-6);
}
}