Files
wifi-densepose/crates/ruvector-nervous-system/examples/tiers/t4_collective_dreaming.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

710 lines
22 KiB
Rust

//! # Tier 4: Collective Dreaming
//!
//! SOTA application: Swarm consolidation during downtime.
//!
//! ## The Problem
//! Traditional distributed systems:
//! - Active consensus requires all nodes awake
//! - No background synthesis of learned knowledge
//! - Memory fragmentation across nodes
//! - No collective "sleep" for maintenance
//!
//! ## What Changes
//! - Circadian-synchronized rest phases across swarm
//! - Hippocampal replay: consolidate recent experiences
//! - Cross-node memory exchange during low-traffic periods
//! - Emergent knowledge synthesis without central coordinator
//!
//! ## Why This Matters
//! - Swarm learns from collective experience
//! - Knowledge transfers between agents
//! - Background optimization during downtime
//! - Resilient to individual agent loss
//!
//! This is how biological systems scale learning.
use std::collections::{HashMap, HashSet, VecDeque};
use std::f32::consts::PI;
// ============================================================================
// Experience and Memory Structures
// ============================================================================
/// A single experience that can be replayed
#[derive(Clone, Debug)]
pub struct Experience {
/// When this happened
pub timestamp: u64,
/// What was observed (sparse code)
pub observation: Vec<u32>,
/// What action was taken
pub action: String,
/// What outcome occurred
pub outcome: f32,
/// How surprising was this (prediction error)
pub surprise: f32,
/// Source agent
pub source_agent: u32,
}
impl Experience {
/// Compute replay priority (more surprising = higher priority)
pub fn replay_priority(&self, current_time: u64, tau_hours: f32) -> f32 {
let age_hours = (current_time - self.timestamp) as f32 / 3600.0;
let recency = (-age_hours / tau_hours).exp();
self.surprise * recency
}
}
/// Memory trace that develops through consolidation
#[derive(Clone, Debug)]
pub struct MemoryTrace {
/// The experience being consolidated
pub experience: Experience,
/// Consolidation strength (0-1)
pub strength: f32,
/// Number of replays
pub replay_count: u32,
/// Cross-agent validation count
pub validation_count: u32,
/// Has been transferred to other agents
pub distributed: bool,
}
impl MemoryTrace {
pub fn new(exp: Experience) -> Self {
Self {
experience: exp,
strength: 0.0,
replay_count: 0,
validation_count: 0,
distributed: false,
}
}
/// Replay strengthens the trace
pub fn replay(&mut self) {
self.replay_count += 1;
// Strength increases with diminishing returns
self.strength = 1.0 - (-(self.replay_count as f32) / 5.0).exp();
}
/// Validation from another agent increases confidence
pub fn validate(&mut self) {
self.validation_count += 1;
self.strength = (self.strength + 0.1).min(1.0);
}
/// Is this memory consolidated enough to be long-term?
pub fn is_consolidated(&self) -> bool {
self.strength > 0.7 && self.replay_count >= 3
}
}
// ============================================================================
// Circadian Phase for Sleep Coordination
// ============================================================================
/// Phase state for each agent
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub enum SwarmPhase {
/// Processing new experiences
Awake,
/// Beginning to wind down
Drowsy,
/// Light consolidation (local replay)
LightSleep,
/// Deep consolidation (cross-agent transfer)
DeepSleep,
/// Waking up, integrating transfers
Waking,
}
impl SwarmPhase {
pub fn from_normalized_time(t: f32) -> Self {
let t = t % 1.0;
if t < 0.6 {
SwarmPhase::Awake
} else if t < 0.65 {
SwarmPhase::Drowsy
} else if t < 0.75 {
SwarmPhase::LightSleep
} else if t < 0.9 {
SwarmPhase::DeepSleep
} else {
SwarmPhase::Waking
}
}
pub fn can_process_new(&self) -> bool {
matches!(self, SwarmPhase::Awake | SwarmPhase::Waking)
}
pub fn can_replay(&self) -> bool {
matches!(self, SwarmPhase::LightSleep | SwarmPhase::DeepSleep)
}
pub fn can_transfer(&self) -> bool {
matches!(self, SwarmPhase::DeepSleep)
}
}
// ============================================================================
// Dreaming Agent
// ============================================================================
/// An agent that participates in collective dreaming
pub struct DreamingAgent {
/// Agent ID
pub id: u32,
/// Recent experiences (working memory)
pub working_memory: VecDeque<Experience>,
/// Memory traces being consolidated
pub consolidating: Vec<MemoryTrace>,
/// Long-term consolidated memories
pub long_term: Vec<MemoryTrace>,
/// Current phase
pub phase: SwarmPhase,
/// Phase in 24-hour cycle (0-1)
pub cycle_phase: f32,
/// Cycle duration in hours
pub cycle_hours: f32,
/// Timestamp
pub timestamp: u64,
/// Outgoing memory transfers
pub outbox: Vec<Experience>,
/// Incoming memory transfers
pub inbox: Vec<Experience>,
/// Statistics
pub stats: DreamingStats,
}
#[derive(Clone, Default, Debug)]
pub struct DreamingStats {
pub experiences_received: u64,
pub replays_performed: u64,
pub memories_consolidated: u64,
pub memories_transferred: u64,
pub memories_received_from_peers: u64,
}
impl DreamingAgent {
pub fn new(id: u32, cycle_hours: f32) -> Self {
Self {
id,
working_memory: VecDeque::new(),
consolidating: Vec::new(),
long_term: Vec::new(),
phase: SwarmPhase::Awake,
cycle_phase: (id as f32 * 0.1) % 1.0, // Stagger agents slightly
cycle_hours,
timestamp: 0,
outbox: Vec::new(),
inbox: Vec::new(),
stats: DreamingStats::default(),
}
}
/// Receive a new experience
pub fn experience(&mut self, obs: Vec<u32>, action: &str, outcome: f32, surprise: f32) {
if !self.phase.can_process_new() {
return; // Reject during sleep
}
let exp = Experience {
timestamp: self.timestamp,
observation: obs,
action: action.to_string(),
outcome,
surprise,
source_agent: self.id,
};
self.working_memory.push_back(exp.clone());
self.stats.experiences_received += 1;
// Transfer surprising experiences to consolidation queue
if surprise > 0.5 {
self.consolidating.push(MemoryTrace::new(exp));
}
// Limit working memory size
while self.working_memory.len() > 100 {
let old = self.working_memory.pop_front().unwrap();
// Move to consolidation if not already there
if old.surprise > 0.3 {
self.consolidating.push(MemoryTrace::new(old));
}
}
}
/// Advance time and run consolidation
pub fn tick(&mut self, dt_seconds: u64) {
self.timestamp += dt_seconds;
self.cycle_phase =
(self.cycle_phase + dt_seconds as f32 / (self.cycle_hours * 3600.0)) % 1.0;
self.phase = SwarmPhase::from_normalized_time(self.cycle_phase);
// Process based on phase
match self.phase {
SwarmPhase::LightSleep => {
self.light_sleep_consolidation();
}
SwarmPhase::DeepSleep => {
self.deep_sleep_consolidation();
}
SwarmPhase::Waking => {
self.integrate_transfers();
}
_ => {}
}
// Prune fully consolidated memories
self.prune_consolidating();
}
/// Light sleep: local replay of recent experiences
fn light_sleep_consolidation(&mut self) {
// Select experiences for replay by priority
let mut to_replay: Vec<_> = self
.consolidating
.iter()
.enumerate()
.map(|(i, trace)| (i, trace.experience.replay_priority(self.timestamp, 8.0)))
.collect();
to_replay.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
// Replay top experiences
for (idx, _) in to_replay.into_iter().take(5) {
self.consolidating[idx].replay();
self.stats.replays_performed += 1;
}
}
/// Deep sleep: cross-agent transfer of consolidated memories
fn deep_sleep_consolidation(&mut self) {
// Continue local replay
self.light_sleep_consolidation();
// Select memories for transfer (well-consolidated, not yet distributed)
for trace in &mut self.consolidating {
if trace.strength > 0.5 && !trace.distributed {
self.outbox.push(trace.experience.clone());
trace.distributed = true;
self.stats.memories_transferred += 1;
}
}
}
/// Waking: integrate memories received from peers
fn integrate_transfers(&mut self) {
while let Some(exp) = self.inbox.pop() {
// Check if we already have this experience
let dominated = self
.consolidating
.iter()
.any(|t| self.experiences_similar(&t.experience, &exp));
if !dominated {
let mut trace = MemoryTrace::new(exp);
trace.validate(); // Peer validation
self.consolidating.push(trace);
self.stats.memories_received_from_peers += 1;
} else {
// Validate existing similar memory - find index first to avoid borrow conflict
let idx = self
.consolidating
.iter()
.position(|t| Self::experiences_similar_static(&t.experience, &exp));
if let Some(i) = idx {
self.consolidating[i].validate();
}
}
}
}
fn experiences_similar(&self, a: &Experience, b: &Experience) -> bool {
Self::experiences_similar_static(a, b)
}
fn experiences_similar_static(a: &Experience, b: &Experience) -> bool {
// Simple Jaccard similarity on observations
let set_a: HashSet<_> = a.observation.iter().collect();
let set_b: HashSet<_> = b.observation.iter().collect();
let intersection = set_a.intersection(&set_b).count();
let union = set_a.union(&set_b).count();
if union == 0 {
return true;
}
(intersection as f32 / union as f32) > 0.8
}
fn prune_consolidating(&mut self) {
// Move consolidated memories to long-term
let mut to_move = Vec::new();
for (i, trace) in self.consolidating.iter().enumerate() {
if trace.is_consolidated() {
to_move.push(i);
}
}
// Move in reverse order to preserve indices
for i in to_move.into_iter().rev() {
let trace = self.consolidating.remove(i);
self.long_term.push(trace);
self.stats.memories_consolidated += 1;
}
// Limit long-term memory
while self.long_term.len() > 500 {
// Remove weakest
let weakest = self
.long_term
.iter()
.enumerate()
.min_by(|a, b| {
a.1.strength
.partial_cmp(&b.1.strength)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(i, _)| i);
if let Some(idx) = weakest {
self.long_term.remove(idx);
}
}
}
/// Receive memories from a peer
pub fn receive_from_peer(&mut self, experiences: Vec<Experience>) {
self.inbox.extend(experiences);
}
}
// ============================================================================
// Collective Dream Network
// ============================================================================
/// Coordinated swarm of dreaming agents
pub struct CollectiveDream {
/// All agents in the swarm
pub agents: Vec<DreamingAgent>,
/// Current timestamp
pub timestamp: u64,
/// Synchronization coupling strength
pub coupling: f32,
}
impl CollectiveDream {
pub fn new(num_agents: usize, cycle_hours: f32) -> Self {
let agents = (0..num_agents)
.map(|i| DreamingAgent::new(i as u32, cycle_hours))
.collect();
Self {
agents,
timestamp: 0,
coupling: 0.3,
}
}
/// Advance time for all agents
pub fn tick(&mut self, dt_seconds: u64) {
self.timestamp += dt_seconds;
// Advance each agent
for agent in &mut self.agents {
agent.tick(dt_seconds);
}
// Transfer memories between agents during deep sleep
self.memory_transfer();
// Synchronize phases (Kuramoto-style)
self.synchronize_phases();
}
fn memory_transfer(&mut self) {
// Collect outboxes
let mut all_transfers: Vec<(u32, Vec<Experience>)> = Vec::new();
for agent in &mut self.agents {
if !agent.outbox.is_empty() {
let transfers = std::mem::take(&mut agent.outbox);
all_transfers.push((agent.id, transfers));
}
}
// Distribute to other agents
for (source_id, experiences) in all_transfers {
for agent in &mut self.agents {
if agent.id != source_id && agent.phase.can_transfer() {
agent.receive_from_peer(experiences.clone());
}
}
}
}
fn synchronize_phases(&mut self) {
// Compute mean phase
let n = self.agents.len() as f32;
let mean_sin: f32 = self
.agents
.iter()
.map(|a| (a.cycle_phase * 2.0 * PI).sin())
.sum::<f32>()
/ n;
let mean_cos: f32 = self
.agents
.iter()
.map(|a| (a.cycle_phase * 2.0 * PI).cos())
.sum::<f32>()
/ n;
let _mean_phase = mean_sin.atan2(mean_cos) / (2.0 * PI);
// Each agent adjusts toward mean
for agent in &mut self.agents {
let current = agent.cycle_phase * 2.0 * PI;
let sin_diff = mean_sin * current.cos() - mean_cos * current.sin();
let adjustment = self.coupling * sin_diff / (2.0 * PI);
agent.cycle_phase = (agent.cycle_phase + adjustment).rem_euclid(1.0);
}
}
/// Get synchronization order parameter
pub fn synchronization(&self) -> f32 {
let n = self.agents.len() as f32;
let sum_sin: f32 = self
.agents
.iter()
.map(|a| (a.cycle_phase * 2.0 * PI).sin())
.sum();
let sum_cos: f32 = self
.agents
.iter()
.map(|a| (a.cycle_phase * 2.0 * PI).cos())
.sum();
(sum_sin * sum_sin + sum_cos * sum_cos).sqrt() / n
}
/// Get phase distribution
pub fn phase_distribution(&self) -> HashMap<SwarmPhase, usize> {
let mut dist = HashMap::new();
for agent in &self.agents {
*dist.entry(agent.phase.clone()).or_insert(0) += 1;
}
dist
}
/// Generate a collective experience for the swarm
pub fn swarm_experience(
&mut self,
agent_id: usize,
obs: Vec<u32>,
action: &str,
outcome: f32,
surprise: f32,
) {
if agent_id < self.agents.len() {
self.agents[agent_id].experience(obs, action, outcome, surprise);
}
}
/// Get total consolidated memories across swarm
pub fn total_consolidated(&self) -> usize {
self.agents.iter().map(|a| a.long_term.len()).sum()
}
/// Get collective statistics
pub fn collective_stats(&self) -> DreamingStats {
let mut stats = DreamingStats::default();
for agent in &self.agents {
stats.experiences_received += agent.stats.experiences_received;
stats.replays_performed += agent.stats.replays_performed;
stats.memories_consolidated += agent.stats.memories_consolidated;
stats.memories_transferred += agent.stats.memories_transferred;
stats.memories_received_from_peers += agent.stats.memories_received_from_peers;
}
stats
}
}
// ============================================================================
// Example Usage
// ============================================================================
fn main() {
println!("=== Tier 4: Collective Dreaming ===\n");
// Create swarm of 10 agents with 1-hour cycles (for demo)
let mut swarm = CollectiveDream::new(10, 1.0);
println!("Swarm initialized: {} agents", swarm.agents.len());
println!("Initial synchronization: {:.2}", swarm.synchronization());
// Simulate experiences during awake phase
println!("\n=== Awake Phase: Gathering Experiences ===");
for minute in 0..30 {
// Generate experiences for random agents
for _ in 0..5 {
let agent_id = (minute * 3 + 1) % 10;
let obs: Vec<u32> = (0..50).map(|i| ((minute + i) * 7) as u32 % 10000).collect();
let surprise = ((minute as f32 * 0.1).sin().abs() * 0.8) + 0.2;
swarm.swarm_experience(
agent_id,
obs,
&format!("action_{}", minute),
((minute as f32 * 0.05).cos() + 1.0) / 2.0,
surprise,
);
}
swarm.tick(60); // 1 minute
if minute % 10 == 9 {
let dist = swarm.phase_distribution();
println!(" Minute {}: phases = {:?}", minute + 1, dist);
}
}
// Continue through sleep cycle
println!("\n=== Sleep Cycle: Consolidation ===");
for minute in 30..60 {
swarm.tick(60);
if minute % 10 == 9 {
let dist = swarm.phase_distribution();
let stats = swarm.collective_stats();
println!(
" Minute {}: phases = {:?}, consolidated = {}, transferred = {}",
minute + 1,
dist,
stats.memories_consolidated,
stats.memories_transferred
);
}
}
// Let agents wake up and integrate
println!("\n=== Waking Phase: Integration ===");
for minute in 60..70 {
swarm.tick(60);
if minute % 5 == 4 {
let dist = swarm.phase_distribution();
let stats = swarm.collective_stats();
println!(
" Minute {}: phases = {:?}, peer memories = {}",
minute + 1,
dist,
stats.memories_received_from_peers
);
}
}
// Final statistics
println!("\n=== Final Statistics ===");
let stats = swarm.collective_stats();
println!("Total experiences: {}", stats.experiences_received);
println!("Replays performed: {}", stats.replays_performed);
println!("Memories consolidated: {}", stats.memories_consolidated);
println!("Memories transferred: {}", stats.memories_transferred);
println!(
"Memories from peers: {}",
stats.memories_received_from_peers
);
println!("Total long-term memories: {}", swarm.total_consolidated());
println!("Final synchronization: {:.2}", swarm.synchronization());
// Per-agent summary
println!("\n=== Per-Agent Memory ===");
for agent in &swarm.agents {
println!(
" Agent {}: {} LT memories, {} consolidating, phase {:?}",
agent.id,
agent.long_term.len(),
agent.consolidating.len(),
agent.phase
);
}
println!("\n=== Key Benefits ===");
println!("- Synchronized rest phases across swarm");
println!("- Hippocampal replay during sleep consolidates learning");
println!("- Cross-agent memory transfer shares knowledge");
println!("- No central coordinator needed");
println!("- Resilient to individual agent loss");
println!("\nThis is how biological systems scale collective learning.");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_phase_transitions() {
let mut agent = DreamingAgent::new(0, 1.0); // 1-hour cycle
// Start awake
assert!(matches!(agent.phase, SwarmPhase::Awake));
// Advance to sleep
agent.tick(2400); // 40 minutes
// Should be in some sleep phase
assert!(!matches!(agent.phase, SwarmPhase::Awake));
}
#[test]
fn test_consolidation() {
let mut agent = DreamingAgent::new(0, 0.5); // Fast cycle
// Add surprising experience
agent.experience(vec![1, 2, 3], "test", 1.0, 0.9);
assert!(!agent.consolidating.is_empty());
// Advance through sleep
for _ in 0..60 {
agent.tick(60);
}
// Some should be consolidated
// Note: may not consolidate in one cycle, that's OK
assert!(agent.stats.replays_performed > 0);
}
#[test]
fn test_memory_transfer() {
let mut swarm = CollectiveDream::new(3, 0.25); // Fast cycles
// Add experience to agent 0
swarm.agents[0].experience(vec![1, 2, 3], "test", 1.0, 0.9);
// Run through complete cycle
for _ in 0..90 {
swarm.tick(60);
}
// Check that memory was transferred
let stats = swarm.collective_stats();
// At least some transfer should happen
assert!(stats.replays_performed > 0);
}
#[test]
fn test_synchronization() {
let mut swarm = CollectiveDream::new(5, 1.0);
// Initially may not be synchronized
let initial = swarm.synchronization();
// Run for a while
for _ in 0..120 {
swarm.tick(60);
}
// Should become more synchronized
let final_sync = swarm.synchronization();
assert!(final_sync >= initial * 0.9); // At least maintain
}
}