1use std::collections::HashMap;
12use std::fs;
13use std::path::Path;
14
15use crate::hebbian::{ActivationFingerprint, HebbianState};
16
17const RESONANCE_THRESHOLD: f32 = 0.3;
21const MAX_ECHOES: usize = 500;
23const ECHO_DECAY: f32 = 0.95;
25
26#[derive(Clone, Debug)]
30pub struct ResonanceEcho {
31 pub timestamp_ms: u64,
33 pub trigger_hash: u64,
35 pub echo_hash: u64,
37 pub similarity: f32,
39 pub shared_blocks: Vec<u32>,
41}
42
43#[derive(Clone, Debug, Default)]
45pub struct BlockResonance {
46 pub echo_count: u32,
48 pub strength: f32,
50}
51
52pub struct MirrorState {
54 pub echoes: Vec<ResonanceEcho>,
55 pub block_resonance: HashMap<u32, BlockResonance>,
56}
57
58impl MirrorState {
59 pub fn load_or_init(output_dir: &Path) -> Self {
61 load_mirror_state(output_dir).unwrap_or_else(|| Self {
62 echoes: Vec::new(),
63 block_resonance: HashMap::new(),
64 })
65 }
66
67 pub fn detect_resonance(
70 &mut self,
71 new_activations: &[(u32, f32)],
72 new_hash: u64,
73 fingerprints: &[ActivationFingerprint],
74 ) -> HashMap<u32, f32> {
75 let mut boosts: HashMap<u32, f32> = HashMap::new();
76
77 if new_activations.is_empty() || fingerprints.is_empty() {
78 return boosts;
79 }
80
81 let new_vec = activation_to_sparse(new_activations);
83
84 for fp in fingerprints {
86 if fp.query_hash == new_hash {
87 continue;
88 }
89
90 let past_vec = activation_to_sparse(&fp.activations);
91 let sim = sparse_cosine(&new_vec, &past_vec);
92
93 if sim >= RESONANCE_THRESHOLD {
94 let shared: Vec<u32> = new_vec
96 .keys()
97 .filter(|k| past_vec.contains_key(k))
98 .copied()
99 .collect();
100
101 for &block_idx in &shared {
103 let entry = boosts.entry(block_idx).or_insert(0.0);
104 *entry += sim * 0.1; let res = self.block_resonance.entry(block_idx).or_default();
107 res.echo_count += 1;
108 res.strength += sim;
109 }
110
111 let now_ms = crate::hebbian::now_epoch_ms_pub();
113 self.echoes.push(ResonanceEcho {
114 timestamp_ms: now_ms,
115 trigger_hash: new_hash,
116 echo_hash: fp.query_hash,
117 similarity: sim,
118 shared_blocks: shared,
119 });
120 }
121 }
122
123 if self.echoes.len() > MAX_ECHOES {
125 self.echoes.drain(0..self.echoes.len() - MAX_ECHOES);
126 }
127
128 boosts
129 }
130
131 pub fn decay(&mut self) {
133 self.block_resonance.retain(|_, res| {
134 res.strength *= ECHO_DECAY;
135 res.strength > 0.01
136 });
137 }
138
139 pub fn boost_for(&self, block_idx: u32) -> f32 {
141 self.block_resonance
142 .get(&block_idx)
143 .map(|r| r.strength.min(0.5)) .unwrap_or(0.0)
145 }
146
147 pub fn stats(&self) -> MirrorStats {
149 let total_echoes = self.echoes.len();
150 let resonant_blocks = self.block_resonance.len();
151 let avg_similarity = if self.echoes.is_empty() {
152 0.0
153 } else {
154 self.echoes.iter().map(|e| e.similarity).sum::<f32>() / self.echoes.len() as f32
155 };
156 let strongest = self
157 .block_resonance
158 .iter()
159 .max_by(|a, b| a.1.strength.partial_cmp(&b.1.strength).unwrap())
160 .map(|(&idx, res)| (idx, res.strength));
161
162 MirrorStats {
163 total_echoes,
164 resonant_blocks,
165 avg_similarity,
166 strongest_block: strongest,
167 }
168 }
169
170 pub fn most_resonant(&self, n: usize) -> Vec<(u32, &BlockResonance)> {
172 let mut blocks: Vec<(u32, &BlockResonance)> =
173 self.block_resonance.iter().map(|(&k, v)| (k, v)).collect();
174 blocks.sort_by(|a, b| b.1.strength.partial_cmp(&a.1.strength).unwrap());
175 blocks.truncate(n);
176 blocks
177 }
178
179 pub fn save(&self, output_dir: &Path) -> Result<(), String> {
181 save_mirror_state(output_dir, self)
182 }
183}
184
185pub struct MirrorStats {
186 pub total_echoes: usize,
187 pub resonant_blocks: usize,
188 pub avg_similarity: f32,
189 pub strongest_block: Option<(u32, f32)>,
190}
191
192fn activation_to_sparse(activations: &[(u32, f32)]) -> HashMap<u32, f32> {
195 activations.iter().copied().collect()
196}
197
198fn sparse_cosine(a: &HashMap<u32, f32>, b: &HashMap<u32, f32>) -> f32 {
199 let (smaller, larger) = if a.len() <= b.len() { (a, b) } else { (b, a) };
200
201 let mut dot = 0.0f32;
202 for (k, va) in smaller {
203 if let Some(vb) = larger.get(k) {
204 dot += va * vb;
205 }
206 }
207
208 let norm_a: f32 = a.values().map(|v| v * v).sum::<f32>().sqrt();
209 let norm_b: f32 = b.values().map(|v| v * v).sum::<f32>().sqrt();
210
211 if norm_a < 1e-9 || norm_b < 1e-9 {
212 return 0.0;
213 }
214
215 dot / (norm_a * norm_b)
216}
217
218fn load_mirror_state(output_dir: &Path) -> Option<MirrorState> {
228 let path = output_dir.join("resonance.bin");
229 let data = fs::read(&path).ok()?;
230 if data.len() < 12 || &data[0..4] != b"RES1" {
231 return None;
232 }
233
234 let echo_count = u32::from_le_bytes(data[4..8].try_into().unwrap()) as usize;
235 let block_count = u32::from_le_bytes(data[8..12].try_into().unwrap()) as usize;
236
237 let mut pos = 12;
238 let mut echoes = Vec::with_capacity(echo_count);
239 for _ in 0..echo_count {
240 if pos + 28 > data.len() {
241 break;
242 }
243 let timestamp_ms = u64::from_le_bytes(data[pos..pos + 8].try_into().unwrap());
244 let trigger_hash = u64::from_le_bytes(data[pos + 8..pos + 16].try_into().unwrap());
245 let echo_hash = u64::from_le_bytes(data[pos + 16..pos + 24].try_into().unwrap());
246 let similarity = f32::from_le_bytes(data[pos + 24..pos + 28].try_into().unwrap());
247 let shared_count =
248 u16::from_le_bytes(data[pos + 28..pos + 30].try_into().unwrap()) as usize;
249 pos += 30;
250
251 let mut shared_blocks = Vec::with_capacity(shared_count);
252 for _ in 0..shared_count {
253 if pos + 4 > data.len() {
254 break;
255 }
256 shared_blocks.push(u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap()));
257 pos += 4;
258 }
259
260 echoes.push(ResonanceEcho {
261 timestamp_ms,
262 trigger_hash,
263 echo_hash,
264 similarity,
265 shared_blocks,
266 });
267 }
268
269 let mut block_resonance = HashMap::with_capacity(block_count);
270 for _ in 0..block_count {
271 if pos + 12 > data.len() {
272 break;
273 }
274 let idx = u32::from_le_bytes(data[pos..pos + 4].try_into().unwrap());
275 let echo_count = u32::from_le_bytes(data[pos + 4..pos + 8].try_into().unwrap());
276 let strength = f32::from_le_bytes(data[pos + 8..pos + 12].try_into().unwrap());
277 pos += 12;
278 block_resonance.insert(
279 idx,
280 BlockResonance {
281 echo_count,
282 strength,
283 },
284 );
285 }
286
287 Some(MirrorState {
288 echoes,
289 block_resonance,
290 })
291}
292
293fn save_mirror_state(output_dir: &Path, state: &MirrorState) -> Result<(), String> {
294 let path = output_dir.join("resonance.bin");
295 let mut buf = Vec::new();
296
297 buf.extend_from_slice(b"RES1");
299 buf.extend_from_slice(&(state.echoes.len() as u32).to_le_bytes());
300 buf.extend_from_slice(&(state.block_resonance.len() as u32).to_le_bytes());
301
302 for echo in &state.echoes {
304 buf.extend_from_slice(&echo.timestamp_ms.to_le_bytes());
305 buf.extend_from_slice(&echo.trigger_hash.to_le_bytes());
306 buf.extend_from_slice(&echo.echo_hash.to_le_bytes());
307 buf.extend_from_slice(&echo.similarity.to_le_bytes());
308 buf.extend_from_slice(&(echo.shared_blocks.len() as u16).to_le_bytes());
309 for &block_idx in &echo.shared_blocks {
310 buf.extend_from_slice(&block_idx.to_le_bytes());
311 }
312 }
313
314 for (&idx, res) in &state.block_resonance {
316 buf.extend_from_slice(&idx.to_le_bytes());
317 buf.extend_from_slice(&res.echo_count.to_le_bytes());
318 buf.extend_from_slice(&res.strength.to_le_bytes());
319 }
320
321 fs::write(&path, &buf).map_err(|e| format!("write resonance.bin: {}", e))
322}
323
324pub fn mirror_boost(
329 hebb: &HebbianState,
330 mirror: &mut MirrorState,
331 new_activations: &[(u32, f32)],
332 query_hash: u64,
333) -> Vec<(u32, f32)> {
334 let boosts = mirror.detect_resonance(new_activations, query_hash, &hebb.fingerprints);
335
336 let mut result: Vec<(u32, f32)> = boosts.into_iter().collect();
338 result.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
339 result
340}
341
342#[cfg(test)]
343mod tests {
344 use super::*;
345
346 #[test]
347 fn test_sparse_cosine_identical() {
348 let a: HashMap<u32, f32> = [(0, 1.0), (1, 0.5), (2, 0.3)].into();
349 let sim = sparse_cosine(&a, &a);
350 assert!((sim - 1.0).abs() < 0.001);
351 }
352
353 #[test]
354 fn test_sparse_cosine_orthogonal() {
355 let a: HashMap<u32, f32> = [(0, 1.0), (1, 0.5)].into();
356 let b: HashMap<u32, f32> = [(2, 1.0), (3, 0.5)].into();
357 let sim = sparse_cosine(&a, &b);
358 assert!(sim.abs() < 0.001);
359 }
360
361 #[test]
362 fn test_sparse_cosine_partial_overlap() {
363 let a: HashMap<u32, f32> = [(0, 1.0), (1, 0.5), (2, 0.3)].into();
364 let b: HashMap<u32, f32> = [(1, 0.8), (2, 0.6), (3, 0.4)].into();
365 let sim = sparse_cosine(&a, &b);
366 assert!(sim > 0.0);
367 assert!(sim < 1.0);
368 }
369
370 #[test]
371 fn test_detect_resonance_no_fingerprints() {
372 let mut mirror = MirrorState {
373 echoes: Vec::new(),
374 block_resonance: HashMap::new(),
375 };
376 let boosts = mirror.detect_resonance(&[(0, 1.0), (1, 0.5)], 42, &[]);
377 assert!(boosts.is_empty());
378 }
379
380 #[test]
381 fn test_detect_resonance_with_match() {
382 let mut mirror = MirrorState {
383 echoes: Vec::new(),
384 block_resonance: HashMap::new(),
385 };
386
387 let past = ActivationFingerprint {
389 timestamp_ms: 1000,
390 query_hash: 100,
391 activations: vec![(0, 0.9), (1, 0.7), (2, 0.5)],
392 };
393
394 let new_act = vec![(0, 0.8), (1, 0.6), (3, 0.4)];
396 let boosts = mirror.detect_resonance(&new_act, 200, &[past]);
397
398 assert!(!boosts.is_empty());
400 assert!(boosts.contains_key(&0));
401 assert!(boosts.contains_key(&1));
402 assert!(!boosts.contains_key(&3)); assert_eq!(mirror.echoes.len(), 1);
406 assert_eq!(mirror.echoes[0].trigger_hash, 200);
407 assert_eq!(mirror.echoes[0].echo_hash, 100);
408 }
409
410 #[test]
411 fn test_decay() {
412 let mut mirror = MirrorState {
413 echoes: Vec::new(),
414 block_resonance: HashMap::new(),
415 };
416
417 mirror.block_resonance.insert(
418 0,
419 BlockResonance {
420 echo_count: 5,
421 strength: 1.0,
422 },
423 );
424 mirror.block_resonance.insert(
425 1,
426 BlockResonance {
427 echo_count: 1,
428 strength: 0.005,
429 },
430 );
431
432 mirror.decay();
433
434 assert!(mirror.block_resonance.contains_key(&0));
436 assert!(!mirror.block_resonance.contains_key(&1));
438 }
439
440 #[test]
441 fn test_save_load_roundtrip() {
442 let tmp = tempfile::tempdir().expect("create temp dir");
443 let dir = tmp.path();
444
445 let mut mirror = MirrorState {
446 echoes: Vec::new(),
447 block_resonance: HashMap::new(),
448 };
449
450 mirror.echoes.push(ResonanceEcho {
451 timestamp_ms: 12345,
452 trigger_hash: 100,
453 echo_hash: 200,
454 similarity: 0.75,
455 shared_blocks: vec![1, 5, 10],
456 });
457 mirror.block_resonance.insert(
458 1,
459 BlockResonance {
460 echo_count: 3,
461 strength: 0.8,
462 },
463 );
464 mirror.block_resonance.insert(
465 5,
466 BlockResonance {
467 echo_count: 1,
468 strength: 0.3,
469 },
470 );
471
472 mirror.save(dir).expect("save");
473
474 let loaded = MirrorState::load_or_init(dir);
475 assert_eq!(loaded.echoes.len(), 1);
476 assert_eq!(loaded.echoes[0].trigger_hash, 100);
477 assert_eq!(loaded.echoes[0].shared_blocks, vec![1, 5, 10]);
478 assert_eq!(loaded.block_resonance.len(), 2);
479 assert_eq!(loaded.block_resonance[&1].echo_count, 3);
480 assert!((loaded.block_resonance[&5].strength - 0.3).abs() < 0.001);
481 }
482
483 #[test]
484 fn test_most_resonant() {
485 let mut mirror = MirrorState {
486 echoes: Vec::new(),
487 block_resonance: HashMap::new(),
488 };
489
490 mirror.block_resonance.insert(
491 0,
492 BlockResonance {
493 echo_count: 1,
494 strength: 0.1,
495 },
496 );
497 mirror.block_resonance.insert(
498 1,
499 BlockResonance {
500 echo_count: 5,
501 strength: 0.9,
502 },
503 );
504 mirror.block_resonance.insert(
505 2,
506 BlockResonance {
507 echo_count: 3,
508 strength: 0.5,
509 },
510 );
511
512 let top = mirror.most_resonant(2);
513 assert_eq!(top.len(), 2);
514 assert_eq!(top[0].0, 1); assert_eq!(top[1].0, 2);
516 }
517
518 #[test]
519 fn test_mirror_boost_integration() {
520 let hebb = HebbianState {
521 activations: vec![crate::hebbian::ActivationRecord::default(); 5],
522 coactivations: HashMap::new(),
523 fingerprints: vec![ActivationFingerprint {
524 timestamp_ms: 1000,
525 query_hash: 100,
526 activations: vec![(0, 0.9), (1, 0.7), (2, 0.5)],
527 }],
528 };
529
530 let mut mirror = MirrorState {
531 echoes: Vec::new(),
532 block_resonance: HashMap::new(),
533 };
534
535 let new_act = vec![(0, 0.8), (1, 0.6), (3, 0.4)];
536 let result = mirror_boost(&hebb, &mut mirror, &new_act, 200);
537
538 assert!(!result.is_empty());
540 }
541}