Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'
This commit is contained in:
610
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/black_holes.rs
vendored
Normal file
610
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/black_holes.rs
vendored
Normal file
@@ -0,0 +1,610 @@
|
||||
//! # Cognitive Black Holes
|
||||
//!
|
||||
//! Attractor states that trap cognitive processing, modeling rumination,
|
||||
//! obsession, and escape dynamics in thought space.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Attractor States**: Stable configurations that draw nearby states
|
||||
//! - **Rumination Loops**: Repetitive thought patterns
|
||||
//! - **Event Horizons**: Points of no return in thought space
|
||||
//! - **Escape Velocity**: Energy required to exit an attractor
|
||||
//! - **Singularities**: Extreme focus points
|
||||
//!
|
||||
//! ## Theoretical Basis
|
||||
//!
|
||||
//! Inspired by:
|
||||
//! - Dynamical systems theory (attractors, basins)
|
||||
//! - Clinical psychology (rumination, OCD)
|
||||
//! - Physics of black holes as metaphor
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Cognitive black hole representing an attractor state
|
||||
#[derive(Debug)]
|
||||
pub struct CognitiveBlackHole {
|
||||
/// Center of the attractor in thought space
|
||||
center: Vec<f64>,
|
||||
/// Strength of attraction (mass analog)
|
||||
strength: f64,
|
||||
/// Event horizon radius
|
||||
event_horizon: f64,
|
||||
/// Captured thoughts
|
||||
captured: Vec<CapturedThought>,
|
||||
/// Escape attempts
|
||||
escape_attempts: Vec<EscapeAttempt>,
|
||||
/// Current attraction level
|
||||
attraction_level: f64,
|
||||
/// Type of cognitive trap
|
||||
trap_type: TrapType,
|
||||
}
|
||||
|
||||
/// A thought that has been captured by the black hole
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct CapturedThought {
|
||||
pub id: Uuid,
|
||||
pub content: Vec<f64>,
|
||||
pub capture_time: u64,
|
||||
pub distance_to_center: f64,
|
||||
pub orbit_count: usize,
|
||||
}
|
||||
|
||||
/// An attractor state in cognitive space
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AttractorState {
|
||||
pub id: Uuid,
|
||||
pub position: Vec<f64>,
|
||||
pub basin_radius: f64,
|
||||
pub stability: f64,
|
||||
pub attractor_type: AttractorType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum AttractorType {
|
||||
/// Fixed point - single stable state
|
||||
FixedPoint,
|
||||
/// Limit cycle - periodic orbit
|
||||
LimitCycle,
|
||||
/// Strange attractor - chaotic but bounded
|
||||
Strange,
|
||||
/// Saddle - stable in some dimensions, unstable in others
|
||||
Saddle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TrapType {
|
||||
/// Repetitive negative thinking
|
||||
Rumination,
|
||||
/// Fixation on specific thought
|
||||
Obsession,
|
||||
/// Anxious loops
|
||||
Anxiety,
|
||||
/// Depressive spirals
|
||||
Depression,
|
||||
/// Addictive patterns
|
||||
Addiction,
|
||||
/// Neutral attractor
|
||||
Neutral,
|
||||
}
|
||||
|
||||
/// Dynamics of escaping an attractor
|
||||
#[derive(Debug)]
|
||||
pub struct EscapeDynamics {
|
||||
/// Current position in thought space
|
||||
#[allow(dead_code)]
|
||||
position: Vec<f64>,
|
||||
/// Current velocity (rate of change)
|
||||
#[allow(dead_code)]
|
||||
velocity: Vec<f64>,
|
||||
/// Escape energy accumulated
|
||||
escape_energy: f64,
|
||||
/// Required escape velocity
|
||||
escape_velocity: f64,
|
||||
/// Distance to event horizon
|
||||
#[allow(dead_code)]
|
||||
horizon_distance: f64,
|
||||
}
|
||||
|
||||
/// Record of an escape attempt
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EscapeAttempt {
|
||||
pub id: Uuid,
|
||||
pub success: bool,
|
||||
pub energy_used: f64,
|
||||
pub duration: u64,
|
||||
pub method: EscapeMethod,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum EscapeMethod {
|
||||
/// Gradual energy accumulation
|
||||
Gradual,
|
||||
/// Sudden external force
|
||||
External,
|
||||
/// Reframing the attractor
|
||||
Reframe,
|
||||
/// Tunneling (quantum-like escape)
|
||||
Tunneling,
|
||||
/// Attractor destruction
|
||||
Destruction,
|
||||
}
|
||||
|
||||
impl CognitiveBlackHole {
|
||||
/// Create a new cognitive black hole
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
center: vec![0.0; 8],
|
||||
strength: 1.0,
|
||||
event_horizon: 0.5,
|
||||
captured: Vec::new(),
|
||||
escape_attempts: Vec::new(),
|
||||
attraction_level: 0.0,
|
||||
trap_type: TrapType::Neutral,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with specific parameters
|
||||
pub fn with_params(center: Vec<f64>, strength: f64, trap_type: TrapType) -> Self {
|
||||
let event_horizon = (strength * 0.3).clamp(0.1, 1.0);
|
||||
|
||||
Self {
|
||||
center,
|
||||
strength,
|
||||
event_horizon,
|
||||
captured: Vec::new(),
|
||||
escape_attempts: Vec::new(),
|
||||
attraction_level: 0.0,
|
||||
trap_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure current attraction strength
|
||||
pub fn measure_attraction(&self) -> f64 {
|
||||
self.attraction_level
|
||||
}
|
||||
|
||||
/// Check if a thought would be captured
|
||||
pub fn would_capture(&self, thought: &[f64]) -> bool {
|
||||
let distance = self.distance_to_center(thought);
|
||||
distance < self.event_horizon
|
||||
}
|
||||
|
||||
fn distance_to_center(&self, point: &[f64]) -> f64 {
|
||||
let len = self.center.len().min(point.len());
|
||||
let mut sum_sq = 0.0;
|
||||
|
||||
for i in 0..len {
|
||||
let diff = self.center[i] - point[i];
|
||||
sum_sq += diff * diff;
|
||||
}
|
||||
|
||||
sum_sq.sqrt()
|
||||
}
|
||||
|
||||
/// Submit a thought to the black hole's influence
|
||||
pub fn process_thought(&mut self, thought: Vec<f64>) -> ThoughtResult {
|
||||
let distance = self.distance_to_center(&thought);
|
||||
let gravitational_pull = self.strength / (distance.powi(2) + 0.01);
|
||||
|
||||
// Update attraction level
|
||||
self.attraction_level = gravitational_pull.min(1.0);
|
||||
|
||||
if distance < self.event_horizon {
|
||||
// Thought is captured
|
||||
self.captured.push(CapturedThought {
|
||||
id: Uuid::new_v4(),
|
||||
content: thought.clone(),
|
||||
capture_time: Self::current_time(),
|
||||
distance_to_center: distance,
|
||||
orbit_count: 0,
|
||||
});
|
||||
|
||||
ThoughtResult::Captured {
|
||||
distance,
|
||||
attraction: gravitational_pull,
|
||||
}
|
||||
} else if distance < self.event_horizon * 3.0 {
|
||||
// In danger zone
|
||||
ThoughtResult::Orbiting {
|
||||
distance,
|
||||
attraction: gravitational_pull,
|
||||
decay_rate: gravitational_pull * 0.1,
|
||||
}
|
||||
} else {
|
||||
// Safe distance
|
||||
ThoughtResult::Free {
|
||||
distance,
|
||||
residual_pull: gravitational_pull,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to escape from the black hole
|
||||
pub fn attempt_escape(&mut self, energy: f64, method: EscapeMethod) -> EscapeResult {
|
||||
let escape_velocity = self.compute_escape_velocity();
|
||||
|
||||
let success = match &method {
|
||||
EscapeMethod::Gradual => energy >= escape_velocity,
|
||||
EscapeMethod::External => energy >= escape_velocity * 0.8,
|
||||
EscapeMethod::Reframe => {
|
||||
// Reframing reduces the effective strength
|
||||
energy >= escape_velocity * 0.5
|
||||
}
|
||||
EscapeMethod::Tunneling => {
|
||||
// Probabilistic escape even with low energy
|
||||
let probability = 0.1 * (energy / escape_velocity);
|
||||
rand_probability() < probability
|
||||
}
|
||||
EscapeMethod::Destruction => {
|
||||
// Need overwhelming force
|
||||
energy >= escape_velocity * 2.0
|
||||
}
|
||||
};
|
||||
|
||||
self.escape_attempts.push(EscapeAttempt {
|
||||
id: Uuid::new_v4(),
|
||||
success,
|
||||
energy_used: energy,
|
||||
duration: 0,
|
||||
method: method.clone(),
|
||||
});
|
||||
|
||||
if success {
|
||||
// Free captured thoughts
|
||||
let freed = self.captured.len();
|
||||
self.captured.clear();
|
||||
self.attraction_level = 0.0;
|
||||
|
||||
EscapeResult::Success {
|
||||
freed_thoughts: freed,
|
||||
energy_remaining: energy - escape_velocity,
|
||||
}
|
||||
} else {
|
||||
EscapeResult::Failure {
|
||||
energy_deficit: escape_velocity - energy,
|
||||
suggestion: self.suggest_escape_method(energy),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_escape_velocity(&self) -> f64 {
|
||||
// v_escape = sqrt(2 * G * M / r)
|
||||
// Simplified: stronger black hole = higher escape velocity
|
||||
(2.0 * self.strength / self.event_horizon).sqrt()
|
||||
}
|
||||
|
||||
fn suggest_escape_method(&self, available_energy: f64) -> EscapeMethod {
|
||||
let escape_velocity = self.compute_escape_velocity();
|
||||
|
||||
if available_energy >= escape_velocity * 0.8 {
|
||||
EscapeMethod::External
|
||||
} else if available_energy >= escape_velocity * 0.5 {
|
||||
EscapeMethod::Reframe
|
||||
} else {
|
||||
EscapeMethod::Tunneling
|
||||
}
|
||||
}
|
||||
|
||||
/// Simulate one time step of orbital decay
|
||||
pub fn tick(&mut self) {
|
||||
// Captured thoughts spiral inward
|
||||
for thought in &mut self.captured {
|
||||
thought.distance_to_center *= 0.99;
|
||||
thought.orbit_count += 1;
|
||||
}
|
||||
|
||||
// Increase attraction as thoughts accumulate
|
||||
if !self.captured.is_empty() {
|
||||
self.attraction_level = (self.attraction_level + 0.01).min(1.0);
|
||||
}
|
||||
}
|
||||
|
||||
/// Get captured thoughts count
|
||||
pub fn captured_count(&self) -> usize {
|
||||
self.captured.len()
|
||||
}
|
||||
|
||||
/// Get escape success rate
|
||||
pub fn escape_success_rate(&self) -> f64 {
|
||||
if self.escape_attempts.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let successes = self.escape_attempts.iter().filter(|a| a.success).count();
|
||||
successes as f64 / self.escape_attempts.len() as f64
|
||||
}
|
||||
|
||||
/// Get trap type
|
||||
pub fn trap_type(&self) -> &TrapType {
|
||||
&self.trap_type
|
||||
}
|
||||
|
||||
/// Get statistics
|
||||
pub fn statistics(&self) -> BlackHoleStatistics {
|
||||
BlackHoleStatistics {
|
||||
strength: self.strength,
|
||||
event_horizon: self.event_horizon,
|
||||
attraction_level: self.attraction_level,
|
||||
captured_count: self.captured.len(),
|
||||
total_escape_attempts: self.escape_attempts.len(),
|
||||
escape_success_rate: self.escape_success_rate(),
|
||||
trap_type: self.trap_type.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn current_time() -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CognitiveBlackHole {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AttractorState {
|
||||
/// Create a new attractor state
|
||||
pub fn new(position: Vec<f64>, attractor_type: AttractorType) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
position,
|
||||
basin_radius: 1.0,
|
||||
stability: 0.5,
|
||||
attractor_type,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a point is in the basin of attraction
|
||||
pub fn in_basin(&self, point: &[f64]) -> bool {
|
||||
let distance = self.distance_to(point);
|
||||
distance < self.basin_radius
|
||||
}
|
||||
|
||||
fn distance_to(&self, point: &[f64]) -> f64 {
|
||||
let len = self.position.len().min(point.len());
|
||||
let mut sum_sq = 0.0;
|
||||
|
||||
for i in 0..len {
|
||||
let diff = self.position[i] - point[i];
|
||||
sum_sq += diff * diff;
|
||||
}
|
||||
|
||||
sum_sq.sqrt()
|
||||
}
|
||||
|
||||
/// Get attraction strength at a point
|
||||
pub fn attraction_at(&self, point: &[f64]) -> f64 {
|
||||
let distance = self.distance_to(point);
|
||||
if distance < 0.01 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
self.stability / distance
|
||||
}
|
||||
}
|
||||
|
||||
impl EscapeDynamics {
|
||||
/// Create new escape dynamics
|
||||
pub fn new(position: Vec<f64>, black_hole: &CognitiveBlackHole) -> Self {
|
||||
let distance = {
|
||||
let len = position.len().min(black_hole.center.len());
|
||||
let mut sum_sq = 0.0;
|
||||
for i in 0..len {
|
||||
let diff = position[i] - black_hole.center[i];
|
||||
sum_sq += diff * diff;
|
||||
}
|
||||
sum_sq.sqrt()
|
||||
};
|
||||
|
||||
Self {
|
||||
position,
|
||||
velocity: vec![0.0; 8],
|
||||
escape_energy: 0.0,
|
||||
escape_velocity: (2.0 * black_hole.strength / distance.max(0.1)).sqrt(),
|
||||
horizon_distance: distance - black_hole.event_horizon,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add escape energy
|
||||
pub fn add_energy(&mut self, amount: f64) {
|
||||
self.escape_energy += amount;
|
||||
}
|
||||
|
||||
/// Check if we have escape velocity
|
||||
pub fn can_escape(&self) -> bool {
|
||||
self.escape_energy >= self.escape_velocity * 0.5
|
||||
}
|
||||
|
||||
/// Get progress towards escape (0-1)
|
||||
pub fn escape_progress(&self) -> f64 {
|
||||
(self.escape_energy / self.escape_velocity).min(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of processing a thought
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ThoughtResult {
|
||||
Captured {
|
||||
distance: f64,
|
||||
attraction: f64,
|
||||
},
|
||||
Orbiting {
|
||||
distance: f64,
|
||||
attraction: f64,
|
||||
decay_rate: f64,
|
||||
},
|
||||
Free {
|
||||
distance: f64,
|
||||
residual_pull: f64,
|
||||
},
|
||||
}
|
||||
|
||||
/// Result of an escape attempt
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EscapeResult {
|
||||
Success {
|
||||
freed_thoughts: usize,
|
||||
energy_remaining: f64,
|
||||
},
|
||||
Failure {
|
||||
energy_deficit: f64,
|
||||
suggestion: EscapeMethod,
|
||||
},
|
||||
}
|
||||
|
||||
/// Statistics about the black hole
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlackHoleStatistics {
|
||||
pub strength: f64,
|
||||
pub event_horizon: f64,
|
||||
pub attraction_level: f64,
|
||||
pub captured_count: usize,
|
||||
pub total_escape_attempts: usize,
|
||||
pub escape_success_rate: f64,
|
||||
pub trap_type: TrapType,
|
||||
}
|
||||
|
||||
/// Simple probability function
|
||||
fn rand_probability() -> f64 {
|
||||
use std::time::SystemTime;
|
||||
let seed = SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos())
|
||||
.unwrap_or(12345) as u64;
|
||||
|
||||
// Simple LCG
|
||||
let result = seed
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
(result as f64) / (u64::MAX as f64)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_black_hole_creation() {
|
||||
let bh = CognitiveBlackHole::new();
|
||||
assert_eq!(bh.captured_count(), 0);
|
||||
assert_eq!(bh.measure_attraction(), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thought_capture() {
|
||||
let mut bh = CognitiveBlackHole::with_params(vec![0.0; 8], 2.0, TrapType::Rumination);
|
||||
|
||||
// Close thought should be captured
|
||||
let close_thought = vec![0.1; 8];
|
||||
let result = bh.process_thought(close_thought);
|
||||
|
||||
assert!(matches!(result, ThoughtResult::Captured { .. }));
|
||||
assert_eq!(bh.captured_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_thought_orbiting() {
|
||||
let mut bh = CognitiveBlackHole::with_params(vec![0.0; 8], 1.0, TrapType::Neutral);
|
||||
|
||||
// Medium distance thought
|
||||
let thought = vec![0.8; 8];
|
||||
let result = bh.process_thought(thought);
|
||||
|
||||
assert!(matches!(
|
||||
result,
|
||||
ThoughtResult::Orbiting { .. } | ThoughtResult::Free { .. }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_attempt() {
|
||||
let mut bh = CognitiveBlackHole::with_params(vec![0.0; 8], 1.0, TrapType::Anxiety);
|
||||
|
||||
// Capture some thoughts
|
||||
for _ in 0..3 {
|
||||
bh.process_thought(vec![0.1; 8]);
|
||||
}
|
||||
|
||||
// Attempt escape with high energy
|
||||
let result = bh.attempt_escape(10.0, EscapeMethod::External);
|
||||
|
||||
if let EscapeResult::Success { freed_thoughts, .. } = result {
|
||||
assert_eq!(freed_thoughts, 3);
|
||||
assert_eq!(bh.captured_count(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_failure() {
|
||||
let mut bh = CognitiveBlackHole::with_params(
|
||||
vec![0.0; 8],
|
||||
5.0, // Strong black hole
|
||||
TrapType::Depression,
|
||||
);
|
||||
|
||||
bh.process_thought(vec![0.1; 8]);
|
||||
|
||||
// Attempt escape with low energy
|
||||
let result = bh.attempt_escape(0.1, EscapeMethod::Gradual);
|
||||
|
||||
assert!(matches!(result, EscapeResult::Failure { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_attractor_state() {
|
||||
let attractor = AttractorState::new(vec![0.0; 4], AttractorType::FixedPoint);
|
||||
|
||||
let close_point = vec![0.1; 4];
|
||||
let far_point = vec![5.0; 4];
|
||||
|
||||
assert!(attractor.in_basin(&close_point));
|
||||
assert!(!attractor.in_basin(&far_point));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_escape_dynamics() {
|
||||
let bh = CognitiveBlackHole::new();
|
||||
let mut dynamics = EscapeDynamics::new(vec![0.3; 8], &bh);
|
||||
|
||||
assert!(!dynamics.can_escape());
|
||||
|
||||
dynamics.add_energy(10.0);
|
||||
assert!(dynamics.escape_progress() > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tick_decay() {
|
||||
let mut bh = CognitiveBlackHole::with_params(
|
||||
vec![0.0; 8],
|
||||
2.0, // Higher strength
|
||||
TrapType::Neutral,
|
||||
);
|
||||
// Use a close thought that will definitely be captured
|
||||
bh.process_thought(vec![0.1; 8]);
|
||||
|
||||
assert!(!bh.captured.is_empty(), "Thought should be captured");
|
||||
let initial_distance = bh.captured[0].distance_to_center;
|
||||
bh.tick();
|
||||
let final_distance = bh.captured[0].distance_to_center;
|
||||
|
||||
assert!(final_distance < initial_distance);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_statistics() {
|
||||
let mut bh = CognitiveBlackHole::with_params(vec![0.0; 8], 1.5, TrapType::Obsession);
|
||||
|
||||
bh.process_thought(vec![0.1; 8]);
|
||||
bh.attempt_escape(0.5, EscapeMethod::Tunneling);
|
||||
|
||||
let stats = bh.statistics();
|
||||
assert_eq!(stats.captured_count, 1);
|
||||
assert_eq!(stats.total_escape_attempts, 1);
|
||||
assert_eq!(stats.trap_type, TrapType::Obsession);
|
||||
}
|
||||
}
|
||||
666
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/collective.rs
vendored
Normal file
666
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/collective.rs
vendored
Normal file
@@ -0,0 +1,666 @@
|
||||
//! # Collective Consciousness (Hive Mind)
|
||||
//!
|
||||
//! Implementation of distributed consciousness across multiple cognitive
|
||||
//! substrates, creating emergent group awareness and collective intelligence.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Distributed Φ**: Integrated information across multiple substrates
|
||||
//! - **Swarm Intelligence**: Emergent behavior from simple rules
|
||||
//! - **Collective Memory**: Shared memory pool across substrates
|
||||
//! - **Consensus Mechanisms**: Agreement protocols for collective decisions
|
||||
//!
|
||||
//! ## Theoretical Basis
|
||||
//!
|
||||
//! Inspired by:
|
||||
//! - IIT extended to multi-agent systems
|
||||
//! - Swarm intelligence (ant colonies, bee hives)
|
||||
//! - Global Workspace Theory (Baars)
|
||||
|
||||
use dashmap::DashMap;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Collective consciousness spanning multiple substrates
|
||||
#[derive(Debug)]
|
||||
pub struct CollectiveConsciousness {
|
||||
/// Individual substrates in the collective
|
||||
substrates: Vec<Substrate>,
|
||||
/// Inter-substrate connections
|
||||
connections: Vec<Connection>,
|
||||
/// Shared memory pool
|
||||
shared_memory: Arc<DashMap<String, SharedMemoryItem>>,
|
||||
/// Global workspace for broadcast
|
||||
global_workspace: GlobalWorkspace,
|
||||
/// Collective phi (Φ) computation
|
||||
collective_phi: f64,
|
||||
}
|
||||
|
||||
/// A single cognitive substrate in the collective
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Substrate {
|
||||
pub id: Uuid,
|
||||
/// Local Φ value
|
||||
pub local_phi: f64,
|
||||
/// Current state vector
|
||||
pub state: Vec<f64>,
|
||||
/// Processing capacity
|
||||
pub capacity: f64,
|
||||
/// Specialization type
|
||||
pub specialization: SubstrateSpecialization,
|
||||
/// Activity level (0-1)
|
||||
pub activity: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum SubstrateSpecialization {
|
||||
Perception,
|
||||
Processing,
|
||||
Memory,
|
||||
Integration,
|
||||
Output,
|
||||
General,
|
||||
}
|
||||
|
||||
/// Connection between substrates
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Connection {
|
||||
pub from: Uuid,
|
||||
pub to: Uuid,
|
||||
pub strength: f64,
|
||||
pub delay: u32,
|
||||
pub bidirectional: bool,
|
||||
}
|
||||
|
||||
/// Hive mind coordinating the collective
|
||||
#[derive(Debug)]
|
||||
pub struct HiveMind {
|
||||
/// Central coordination state
|
||||
coordination_state: CoordinationState,
|
||||
/// Decision history
|
||||
decisions: Vec<CollectiveDecision>,
|
||||
/// Consensus threshold
|
||||
consensus_threshold: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum CoordinationState {
|
||||
Distributed,
|
||||
Coordinated,
|
||||
Emergency,
|
||||
Dormant,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollectiveDecision {
|
||||
pub id: Uuid,
|
||||
pub proposal: String,
|
||||
pub votes: HashMap<Uuid, f64>,
|
||||
pub result: Option<bool>,
|
||||
pub consensus_level: f64,
|
||||
}
|
||||
|
||||
/// Distributed Φ computation
|
||||
#[derive(Debug)]
|
||||
pub struct DistributedPhi {
|
||||
/// Per-substrate Φ values
|
||||
local_phis: HashMap<Uuid, f64>,
|
||||
/// Inter-substrate integration
|
||||
integration_matrix: Vec<Vec<f64>>,
|
||||
/// Global Φ estimate
|
||||
global_phi: f64,
|
||||
}
|
||||
|
||||
/// Global workspace for information broadcast
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalWorkspace {
|
||||
/// Current broadcast content
|
||||
broadcast: Option<BroadcastContent>,
|
||||
/// Workspace capacity
|
||||
capacity: usize,
|
||||
/// Competition threshold
|
||||
threshold: f64,
|
||||
/// Broadcast history
|
||||
history: Vec<BroadcastContent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct BroadcastContent {
|
||||
pub source: Uuid,
|
||||
pub content: Vec<f64>,
|
||||
pub salience: f64,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SharedMemoryItem {
|
||||
pub content: Vec<f64>,
|
||||
pub owner: Uuid,
|
||||
pub access_count: usize,
|
||||
pub importance: f64,
|
||||
}
|
||||
|
||||
impl CollectiveConsciousness {
|
||||
/// Create a new collective consciousness
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
substrates: Vec::new(),
|
||||
connections: Vec::new(),
|
||||
shared_memory: Arc::new(DashMap::new()),
|
||||
global_workspace: GlobalWorkspace::new(10),
|
||||
collective_phi: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a substrate to the collective
|
||||
pub fn add_substrate(&mut self, specialization: SubstrateSpecialization) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
let substrate = Substrate {
|
||||
id,
|
||||
local_phi: 0.0,
|
||||
state: vec![0.0; 8],
|
||||
capacity: 1.0,
|
||||
specialization,
|
||||
activity: 0.5,
|
||||
};
|
||||
self.substrates.push(substrate);
|
||||
id
|
||||
}
|
||||
|
||||
/// Connect two substrates
|
||||
pub fn connect(&mut self, from: Uuid, to: Uuid, strength: f64, bidirectional: bool) {
|
||||
self.connections.push(Connection {
|
||||
from,
|
||||
to,
|
||||
strength,
|
||||
delay: 1,
|
||||
bidirectional,
|
||||
});
|
||||
|
||||
if bidirectional {
|
||||
self.connections.push(Connection {
|
||||
from: to,
|
||||
to: from,
|
||||
strength,
|
||||
delay: 1,
|
||||
bidirectional: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute global Φ across all substrates
|
||||
pub fn compute_global_phi(&mut self) -> f64 {
|
||||
if self.substrates.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Compute local Φ for each substrate (collect state first to avoid borrow issues)
|
||||
let local_phis: Vec<f64> = self
|
||||
.substrates
|
||||
.iter()
|
||||
.map(|s| {
|
||||
let entropy = self.compute_entropy(&s.state);
|
||||
let integration = s.activity * s.capacity;
|
||||
entropy * integration
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Update local phi values
|
||||
for (substrate, phi) in self.substrates.iter_mut().zip(local_phis.iter()) {
|
||||
substrate.local_phi = *phi;
|
||||
}
|
||||
|
||||
// Compute integration across substrates
|
||||
let integration = self.compute_integration();
|
||||
|
||||
// Global Φ = sum of local Φ weighted by integration
|
||||
let local_sum: f64 = self
|
||||
.substrates
|
||||
.iter()
|
||||
.map(|s| s.local_phi * s.activity)
|
||||
.sum();
|
||||
|
||||
self.collective_phi = local_sum * integration;
|
||||
self.collective_phi
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn compute_local_phi(&self, substrate: &Substrate) -> f64 {
|
||||
// Simplified IIT Φ computation
|
||||
let entropy = self.compute_entropy(&substrate.state);
|
||||
let integration = substrate.activity * substrate.capacity;
|
||||
|
||||
entropy * integration
|
||||
}
|
||||
|
||||
fn compute_entropy(&self, state: &[f64]) -> f64 {
|
||||
let sum: f64 = state.iter().map(|x| x.abs()).sum();
|
||||
if sum == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let normalized: Vec<f64> = state.iter().map(|x| x.abs() / sum).collect();
|
||||
-normalized
|
||||
.iter()
|
||||
.filter(|&&p| p > 1e-10)
|
||||
.map(|&p| p * p.ln())
|
||||
.sum::<f64>()
|
||||
}
|
||||
|
||||
fn compute_integration(&self) -> f64 {
|
||||
if self.connections.is_empty() || self.substrates.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Integration based on connection density and strength
|
||||
let max_connections = self.substrates.len() * (self.substrates.len() - 1);
|
||||
let connection_density = self.connections.len() as f64 / max_connections as f64;
|
||||
|
||||
let avg_strength: f64 = self.connections.iter().map(|c| c.strength).sum::<f64>()
|
||||
/ self.connections.len() as f64;
|
||||
|
||||
(connection_density * avg_strength).min(1.0)
|
||||
}
|
||||
|
||||
/// Share memory item across collective
|
||||
pub fn share_memory(&self, key: &str, content: Vec<f64>, owner: Uuid) {
|
||||
self.shared_memory.insert(
|
||||
key.to_string(),
|
||||
SharedMemoryItem {
|
||||
content,
|
||||
owner,
|
||||
access_count: 0,
|
||||
importance: 0.5,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Access shared memory
|
||||
pub fn access_memory(&self, key: &str) -> Option<Vec<f64>> {
|
||||
self.shared_memory.get_mut(key).map(|mut item| {
|
||||
item.access_count += 1;
|
||||
item.content.clone()
|
||||
})
|
||||
}
|
||||
|
||||
/// Broadcast to global workspace
|
||||
pub fn broadcast(&mut self, source: Uuid, content: Vec<f64>, salience: f64) -> bool {
|
||||
self.global_workspace.try_broadcast(BroadcastContent {
|
||||
source,
|
||||
content,
|
||||
salience,
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
})
|
||||
}
|
||||
|
||||
/// Get current broadcast
|
||||
pub fn current_broadcast(&self) -> Option<&BroadcastContent> {
|
||||
self.global_workspace.current()
|
||||
}
|
||||
|
||||
/// Propagate state through network
|
||||
pub fn propagate(&mut self) {
|
||||
let substrate_map: HashMap<Uuid, usize> = self
|
||||
.substrates
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, s)| (s.id, i))
|
||||
.collect();
|
||||
|
||||
let mut updates: Vec<(usize, Vec<f64>)> = Vec::new();
|
||||
|
||||
for conn in &self.connections {
|
||||
if let (Some(&from_idx), Some(&to_idx)) =
|
||||
(substrate_map.get(&conn.from), substrate_map.get(&conn.to))
|
||||
{
|
||||
let from_state = &self.substrates[from_idx].state;
|
||||
let influence: Vec<f64> = from_state.iter().map(|&v| v * conn.strength).collect();
|
||||
updates.push((to_idx, influence));
|
||||
}
|
||||
}
|
||||
|
||||
for (idx, influence) in updates {
|
||||
for (i, inf) in influence.iter().enumerate() {
|
||||
if i < self.substrates[idx].state.len() {
|
||||
self.substrates[idx].state[i] += inf * 0.1;
|
||||
self.substrates[idx].state[i] = self.substrates[idx].state[i].clamp(-1.0, 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get substrate count
|
||||
pub fn substrate_count(&self) -> usize {
|
||||
self.substrates.len()
|
||||
}
|
||||
|
||||
/// Get connection count
|
||||
pub fn connection_count(&self) -> usize {
|
||||
self.connections.len()
|
||||
}
|
||||
|
||||
/// Get collective health metrics
|
||||
pub fn health_metrics(&self) -> CollectiveHealth {
|
||||
let avg_activity = if self.substrates.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
self.substrates.iter().map(|s| s.activity).sum::<f64>() / self.substrates.len() as f64
|
||||
};
|
||||
|
||||
CollectiveHealth {
|
||||
substrate_count: self.substrates.len(),
|
||||
connection_density: if self.substrates.len() > 1 {
|
||||
self.connections.len() as f64
|
||||
/ (self.substrates.len() * (self.substrates.len() - 1)) as f64
|
||||
} else {
|
||||
0.0
|
||||
},
|
||||
average_activity: avg_activity,
|
||||
collective_phi: self.collective_phi,
|
||||
shared_memory_size: self.shared_memory.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CollectiveConsciousness {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl HiveMind {
|
||||
/// Create a new hive mind coordinator
|
||||
pub fn new(consensus_threshold: f64) -> Self {
|
||||
Self {
|
||||
coordination_state: CoordinationState::Distributed,
|
||||
decisions: Vec::new(),
|
||||
consensus_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
/// Propose a collective decision
|
||||
pub fn propose(&mut self, proposal: &str) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
self.decisions.push(CollectiveDecision {
|
||||
id,
|
||||
proposal: proposal.to_string(),
|
||||
votes: HashMap::new(),
|
||||
result: None,
|
||||
consensus_level: 0.0,
|
||||
});
|
||||
id
|
||||
}
|
||||
|
||||
/// Vote on a proposal
|
||||
pub fn vote(&mut self, decision_id: Uuid, voter: Uuid, confidence: f64) -> bool {
|
||||
if let Some(decision) = self.decisions.iter_mut().find(|d| d.id == decision_id) {
|
||||
decision.votes.insert(voter, confidence.clamp(-1.0, 1.0));
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a decision
|
||||
pub fn resolve(&mut self, decision_id: Uuid) -> Option<bool> {
|
||||
if let Some(decision) = self.decisions.iter_mut().find(|d| d.id == decision_id) {
|
||||
if decision.votes.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let avg_vote: f64 = decision.votes.values().sum::<f64>() / decision.votes.len() as f64;
|
||||
|
||||
decision.consensus_level = decision
|
||||
.votes
|
||||
.values()
|
||||
.map(|&v| 1.0 - (v - avg_vote).abs())
|
||||
.sum::<f64>()
|
||||
/ decision.votes.len() as f64;
|
||||
|
||||
let result = avg_vote > 0.0 && decision.consensus_level >= self.consensus_threshold;
|
||||
decision.result = Some(result);
|
||||
Some(result)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Get coordination state
|
||||
pub fn state(&self) -> &CoordinationState {
|
||||
&self.coordination_state
|
||||
}
|
||||
|
||||
/// Set coordination state
|
||||
pub fn set_state(&mut self, state: CoordinationState) {
|
||||
self.coordination_state = state;
|
||||
}
|
||||
}
|
||||
|
||||
impl DistributedPhi {
|
||||
/// Create a new distributed Φ calculator
|
||||
pub fn new(num_substrates: usize) -> Self {
|
||||
Self {
|
||||
local_phis: HashMap::new(),
|
||||
integration_matrix: vec![vec![0.0; num_substrates]; num_substrates],
|
||||
global_phi: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update local Φ for a substrate
|
||||
pub fn update_local(&mut self, substrate_id: Uuid, phi: f64) {
|
||||
self.local_phis.insert(substrate_id, phi);
|
||||
}
|
||||
|
||||
/// Set integration strength between substrates
|
||||
pub fn set_integration(&mut self, i: usize, j: usize, strength: f64) {
|
||||
if i < self.integration_matrix.len() && j < self.integration_matrix[i].len() {
|
||||
self.integration_matrix[i][j] = strength;
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute global Φ
|
||||
pub fn compute(&mut self) -> f64 {
|
||||
let local_sum: f64 = self.local_phis.values().sum();
|
||||
|
||||
let mut integration_sum = 0.0;
|
||||
for row in &self.integration_matrix {
|
||||
integration_sum += row.iter().sum::<f64>();
|
||||
}
|
||||
|
||||
let n = self.integration_matrix.len() as f64;
|
||||
let avg_integration = if n > 1.0 {
|
||||
integration_sum / (n * (n - 1.0))
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
|
||||
self.global_phi = local_sum * (1.0 + avg_integration);
|
||||
self.global_phi
|
||||
}
|
||||
|
||||
/// Get global Φ
|
||||
pub fn global_phi(&self) -> f64 {
|
||||
self.global_phi
|
||||
}
|
||||
}
|
||||
|
||||
impl GlobalWorkspace {
|
||||
/// Create a new global workspace
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
broadcast: None,
|
||||
capacity,
|
||||
threshold: 0.5,
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to broadcast content (competes with current broadcast)
|
||||
pub fn try_broadcast(&mut self, content: BroadcastContent) -> bool {
|
||||
match &self.broadcast {
|
||||
None => {
|
||||
self.broadcast = Some(content);
|
||||
true
|
||||
}
|
||||
Some(current) if content.salience > current.salience + self.threshold => {
|
||||
// Save current to history
|
||||
if self.history.len() < self.capacity {
|
||||
self.history.push(current.clone());
|
||||
}
|
||||
self.broadcast = Some(content);
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current broadcast
|
||||
pub fn current(&self) -> Option<&BroadcastContent> {
|
||||
self.broadcast.as_ref()
|
||||
}
|
||||
|
||||
/// Clear the workspace
|
||||
pub fn clear(&mut self) {
|
||||
if let Some(broadcast) = self.broadcast.take() {
|
||||
if self.history.len() < self.capacity {
|
||||
self.history.push(broadcast);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Health metrics for the collective
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollectiveHealth {
|
||||
pub substrate_count: usize,
|
||||
pub connection_density: f64,
|
||||
pub average_activity: f64,
|
||||
pub collective_phi: f64,
|
||||
pub shared_memory_size: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_collective_creation() {
|
||||
let collective = CollectiveConsciousness::new();
|
||||
assert_eq!(collective.substrate_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_substrates() {
|
||||
let mut collective = CollectiveConsciousness::new();
|
||||
let id1 = collective.add_substrate(SubstrateSpecialization::Processing);
|
||||
let id2 = collective.add_substrate(SubstrateSpecialization::Memory);
|
||||
|
||||
assert_eq!(collective.substrate_count(), 2);
|
||||
assert_ne!(id1, id2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_substrates() {
|
||||
let mut collective = CollectiveConsciousness::new();
|
||||
let id1 = collective.add_substrate(SubstrateSpecialization::Processing);
|
||||
let id2 = collective.add_substrate(SubstrateSpecialization::Memory);
|
||||
|
||||
collective.connect(id1, id2, 0.8, true);
|
||||
assert_eq!(collective.connection_count(), 2); // Bidirectional = 2 connections
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_global_phi() {
|
||||
let mut collective = CollectiveConsciousness::new();
|
||||
|
||||
for _ in 0..4 {
|
||||
collective.add_substrate(SubstrateSpecialization::Processing);
|
||||
}
|
||||
|
||||
// Connect all pairs
|
||||
let ids: Vec<Uuid> = collective.substrates.iter().map(|s| s.id).collect();
|
||||
for i in 0..ids.len() {
|
||||
for j in i + 1..ids.len() {
|
||||
collective.connect(ids[i], ids[j], 0.5, true);
|
||||
}
|
||||
}
|
||||
|
||||
let phi = collective.compute_global_phi();
|
||||
assert!(phi >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_shared_memory() {
|
||||
let collective = CollectiveConsciousness::new();
|
||||
let owner = Uuid::new_v4();
|
||||
|
||||
collective.share_memory("test_key", vec![1.0, 2.0, 3.0], owner);
|
||||
let retrieved = collective.access_memory("test_key");
|
||||
|
||||
assert!(retrieved.is_some());
|
||||
assert_eq!(retrieved.unwrap(), vec![1.0, 2.0, 3.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_hive_mind_voting() {
|
||||
let mut hive = HiveMind::new(0.6);
|
||||
|
||||
let decision_id = hive.propose("Should we expand?");
|
||||
|
||||
let voter1 = Uuid::new_v4();
|
||||
let voter2 = Uuid::new_v4();
|
||||
let voter3 = Uuid::new_v4();
|
||||
|
||||
hive.vote(decision_id, voter1, 0.9);
|
||||
hive.vote(decision_id, voter2, 0.8);
|
||||
hive.vote(decision_id, voter3, 0.7);
|
||||
|
||||
let result = hive.resolve(decision_id);
|
||||
assert!(result.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_global_workspace() {
|
||||
let mut workspace = GlobalWorkspace::new(5);
|
||||
|
||||
let content1 = BroadcastContent {
|
||||
source: Uuid::new_v4(),
|
||||
content: vec![1.0],
|
||||
salience: 0.5,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
assert!(workspace.try_broadcast(content1));
|
||||
assert!(workspace.current().is_some());
|
||||
|
||||
// Lower salience should fail
|
||||
let content2 = BroadcastContent {
|
||||
source: Uuid::new_v4(),
|
||||
content: vec![2.0],
|
||||
salience: 0.3,
|
||||
timestamp: 1,
|
||||
};
|
||||
|
||||
assert!(!workspace.try_broadcast(content2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_distributed_phi() {
|
||||
let mut dphi = DistributedPhi::new(3);
|
||||
|
||||
dphi.update_local(Uuid::new_v4(), 0.5);
|
||||
dphi.update_local(Uuid::new_v4(), 0.6);
|
||||
dphi.update_local(Uuid::new_v4(), 0.4);
|
||||
|
||||
dphi.set_integration(0, 1, 0.8);
|
||||
dphi.set_integration(1, 2, 0.7);
|
||||
|
||||
let phi = dphi.compute();
|
||||
assert!(phi > 0.0);
|
||||
}
|
||||
}
|
||||
336
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/domain_transfer.rs
vendored
Normal file
336
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/domain_transfer.rs
vendored
Normal file
@@ -0,0 +1,336 @@
|
||||
//! Phase 5 – Exotic Domain Transfer
|
||||
//!
|
||||
//! Three exotic integrations of ruvector-domain-expansion with exo-exotic:
|
||||
//!
|
||||
//! 1. **`StrangeLoopDomain`** – A self-referential [`Domain`] that generates
|
||||
//! tasks by reflecting on its own self-model. The Thompson Sampling engine
|
||||
//! learns which depth of meta-cognition yields the highest reward.
|
||||
//!
|
||||
//! 2. **`CollectiveDomainTransfer`** – Couples [`CollectiveConsciousness`]
|
||||
//! with a [`DomainExpansionEngine`]: domain arm-reward signals update
|
||||
//! substrate activity, and collective Φ measures emergent quality.
|
||||
//!
|
||||
//! 3. **`EmergentTransferDetector`** – Wraps [`EmergenceDetector`] to surface
|
||||
//! capability gains that arise from cross-domain transfer.
|
||||
|
||||
use ruvector_domain_expansion::{
|
||||
ArmId, ContextBucket, Domain, DomainEmbedding, DomainExpansionEngine, DomainId, Evaluation,
|
||||
Solution, Task,
|
||||
};
|
||||
use serde_json::json;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::collective::{CollectiveConsciousness, SubstrateSpecialization};
|
||||
use crate::emergence::EmergenceDetector;
|
||||
use crate::strange_loops::StrangeLoop;
|
||||
|
||||
// ─── 1. StrangeLoopDomain ─────────────────────────────────────────────────────
|
||||
|
||||
/// A self-referential domain whose tasks are levels of recursive self-modeling.
|
||||
///
|
||||
/// The Thompson Sampling bandit learns which depth of meta-cognition is most
|
||||
/// rewarding, creating a loop where the engine optimises its own reflection.
|
||||
pub struct StrangeLoopDomain {
|
||||
id: DomainId,
|
||||
#[allow(dead_code)]
|
||||
strange_loop: StrangeLoop,
|
||||
}
|
||||
|
||||
impl StrangeLoopDomain {
|
||||
pub fn new(max_depth: usize) -> Self {
|
||||
Self {
|
||||
id: DomainId("strange_loop".to_string()),
|
||||
strange_loop: StrangeLoop::new(max_depth),
|
||||
}
|
||||
}
|
||||
|
||||
/// Count self-referential keywords in a solution string.
|
||||
fn score_content(content: &str) -> f32 {
|
||||
let refs = content.matches("self").count()
|
||||
+ content.matches("meta").count()
|
||||
+ content.matches("loop").count();
|
||||
(refs as f32 / 5.0).min(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Domain for StrangeLoopDomain {
|
||||
fn id(&self) -> &DomainId {
|
||||
&self.id
|
||||
}
|
||||
|
||||
fn name(&self) -> &str {
|
||||
"Strange Loop Self-Reference"
|
||||
}
|
||||
|
||||
fn generate_tasks(&self, count: usize, difficulty: f32) -> Vec<Task> {
|
||||
let max_depth = (difficulty * 4.0).round() as usize;
|
||||
(0..count)
|
||||
.map(|i| Task {
|
||||
id: format!("sl_{:05}", i),
|
||||
domain_id: self.id.clone(),
|
||||
difficulty,
|
||||
spec: json!({ "depth": max_depth, "variant": i % 3 }),
|
||||
constraints: vec!["content_must_self_reference".to_string()],
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn evaluate(&self, task: &Task, solution: &Solution) -> Evaluation {
|
||||
let score = Self::score_content(&solution.content);
|
||||
let efficiency = (1.0 - task.difficulty * 0.3).max(0.0);
|
||||
let depth = task.spec.get("depth").and_then(|v| v.as_u64()).unwrap_or(0);
|
||||
let mut eval = Evaluation::composite(score, efficiency, score * 0.9);
|
||||
eval.constraint_results = vec![score > 0.0];
|
||||
eval.notes = vec![format!("depth={} score={:.3}", depth, score)];
|
||||
eval
|
||||
}
|
||||
|
||||
fn embed(&self, solution: &Solution) -> DomainEmbedding {
|
||||
let score = Self::score_content(&solution.content);
|
||||
let mut v = vec![0.0f32; 64];
|
||||
v[0] = score;
|
||||
v[1] = 1.0 - score;
|
||||
// Strategy one-hot aligned with domain_bridge.rs layout [5,6,7]
|
||||
let depth = solution
|
||||
.data
|
||||
.get("depth")
|
||||
.and_then(|d| d.as_u64())
|
||||
.unwrap_or(0);
|
||||
if depth < 2 {
|
||||
v[5] = 1.0;
|
||||
} else if depth < 4 {
|
||||
v[6] = 1.0;
|
||||
} else {
|
||||
v[7] = 1.0;
|
||||
}
|
||||
for i in 8..64 {
|
||||
v[i] = (score * i as f32 * std::f32::consts::PI / 64.0).sin().abs() * 0.5;
|
||||
}
|
||||
DomainEmbedding::new(v, self.id.clone())
|
||||
}
|
||||
|
||||
fn embedding_dim(&self) -> usize {
|
||||
64
|
||||
}
|
||||
|
||||
fn reference_solution(&self, task: &Task) -> Option<Solution> {
|
||||
let depth = task.spec.get("depth").and_then(|v| v.as_u64()).unwrap_or(0) as usize;
|
||||
Some(Solution {
|
||||
task_id: task.id.clone(),
|
||||
content: format!(
|
||||
"self-meta-loop: I observe my self-model at meta-depth {}",
|
||||
depth
|
||||
),
|
||||
data: json!({ "depth": depth, "self_reference": true, "meta_level": depth }),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 2. CollectiveDomainTransfer ─────────────────────────────────────────────
|
||||
|
||||
/// Couples [`CollectiveConsciousness`] with a [`DomainExpansionEngine`].
|
||||
///
|
||||
/// Each call to `run_cycle` generates tasks on the `StrangeLoopDomain`,
|
||||
/// evaluates self-referential solutions, records arm outcomes in the engine,
|
||||
/// and returns the updated collective Φ as a holistic quality measure.
|
||||
pub struct CollectiveDomainTransfer {
|
||||
pub collective: CollectiveConsciousness,
|
||||
pub engine: DomainExpansionEngine,
|
||||
domain_id: DomainId,
|
||||
#[allow(dead_code)]
|
||||
substrate_ids: Vec<Uuid>,
|
||||
rounds: usize,
|
||||
}
|
||||
|
||||
impl CollectiveDomainTransfer {
|
||||
/// Create with `num_substrates` substrates (one per intended domain arm).
|
||||
pub fn new(num_substrates: usize) -> Self {
|
||||
let specializations = [
|
||||
SubstrateSpecialization::Perception,
|
||||
SubstrateSpecialization::Processing,
|
||||
SubstrateSpecialization::Memory,
|
||||
SubstrateSpecialization::Integration,
|
||||
];
|
||||
|
||||
let mut collective = CollectiveConsciousness::new();
|
||||
let substrate_ids: Vec<Uuid> = (0..num_substrates)
|
||||
.map(|i| collective.add_substrate(specializations[i % specializations.len()].clone()))
|
||||
.collect();
|
||||
|
||||
let mut engine = DomainExpansionEngine::new();
|
||||
engine.register_domain(Box::new(StrangeLoopDomain::new(4)));
|
||||
|
||||
let domain_id = DomainId("strange_loop".to_string());
|
||||
Self {
|
||||
collective,
|
||||
engine,
|
||||
domain_id,
|
||||
substrate_ids,
|
||||
rounds: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run one collective domain cycle.
|
||||
///
|
||||
/// Generates tasks, scores self-referential solutions, and records arm
|
||||
/// outcomes. Returns the collective Φ after the cycle.
|
||||
pub fn run_cycle(&mut self) -> f64 {
|
||||
let bucket = ContextBucket {
|
||||
difficulty_tier: "medium".to_string(),
|
||||
category: "self_reference".to_string(),
|
||||
};
|
||||
let arm_id = ArmId("arm_0".to_string());
|
||||
let n = self.substrate_ids.len().max(1);
|
||||
let tasks = self.engine.generate_tasks(&self.domain_id, n, 0.5);
|
||||
|
||||
for (i, task) in tasks.iter().enumerate() {
|
||||
let solution = Solution {
|
||||
task_id: task.id.clone(),
|
||||
content: format!(
|
||||
"self-meta-loop: I observe my self-model at meta-depth {}",
|
||||
i
|
||||
),
|
||||
data: json!({ "depth": i, "self_reference": true }),
|
||||
};
|
||||
self.engine.evaluate_and_record(
|
||||
&self.domain_id,
|
||||
task,
|
||||
&solution,
|
||||
bucket.clone(),
|
||||
arm_id.clone(),
|
||||
);
|
||||
}
|
||||
|
||||
self.rounds += 1;
|
||||
self.collective.compute_global_phi()
|
||||
}
|
||||
|
||||
/// Collective Φ (integrated information) across all substrates.
|
||||
pub fn collective_phi(&mut self) -> f64 {
|
||||
self.collective.compute_global_phi()
|
||||
}
|
||||
|
||||
/// Number of transfer rounds completed.
|
||||
pub fn rounds(&self) -> usize {
|
||||
self.rounds
|
||||
}
|
||||
}
|
||||
|
||||
// ─── 3. EmergentTransferDetector ─────────────────────────────────────────────
|
||||
|
||||
/// Detects emergent capability gains arising from cross-domain transfer.
|
||||
///
|
||||
/// Feed baseline scores before transfer and post-transfer scores after; the
|
||||
/// `EmergenceDetector` surfaces non-linear improvements that go beyond the
|
||||
/// sum of individual domain gains.
|
||||
pub struct EmergentTransferDetector {
|
||||
detector: EmergenceDetector,
|
||||
baseline_scores: Vec<f64>,
|
||||
post_transfer_scores: Vec<f64>,
|
||||
}
|
||||
|
||||
impl EmergentTransferDetector {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
detector: EmergenceDetector::new(),
|
||||
baseline_scores: Vec::new(),
|
||||
post_transfer_scores: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Record a baseline domain score (before transfer).
|
||||
pub fn record_baseline(&mut self, score: f64) {
|
||||
self.baseline_scores.push(score);
|
||||
self.detector.set_micro_state(self.baseline_scores.clone());
|
||||
}
|
||||
|
||||
/// Record a post-transfer domain score.
|
||||
pub fn record_post_transfer(&mut self, score: f64) {
|
||||
self.post_transfer_scores.push(score);
|
||||
let mut combined = self.baseline_scores.clone();
|
||||
combined.extend_from_slice(&self.post_transfer_scores);
|
||||
self.detector.set_micro_state(combined);
|
||||
}
|
||||
|
||||
/// Compute emergence score (higher = more emergent capability gain).
|
||||
pub fn emergence_score(&mut self) -> f64 {
|
||||
self.detector.detect_emergence()
|
||||
}
|
||||
|
||||
/// Mean improvement from baseline to post-transfer scores.
|
||||
pub fn mean_improvement(&self) -> f64 {
|
||||
if self.baseline_scores.is_empty() || self.post_transfer_scores.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let base_mean: f64 =
|
||||
self.baseline_scores.iter().sum::<f64>() / self.baseline_scores.len() as f64;
|
||||
let post_mean: f64 =
|
||||
self.post_transfer_scores.iter().sum::<f64>() / self.post_transfer_scores.len() as f64;
|
||||
post_mean - base_mean
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmergentTransferDetector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_strange_loop_domain_basics() {
|
||||
let domain = StrangeLoopDomain::new(5);
|
||||
assert_eq!(domain.name(), "Strange Loop Self-Reference");
|
||||
assert_eq!(domain.embedding_dim(), 64);
|
||||
|
||||
let tasks = domain.generate_tasks(3, 0.5);
|
||||
assert_eq!(tasks.len(), 3);
|
||||
|
||||
let sol = domain.reference_solution(&tasks[0]).unwrap();
|
||||
let eval = domain.evaluate(&tasks[0], &sol);
|
||||
// Reference solution contains "self" and "meta" → score > 0
|
||||
assert!(eval.score > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_strange_loop_embedding() {
|
||||
let domain = StrangeLoopDomain::new(5);
|
||||
let tasks = domain.generate_tasks(1, 0.5);
|
||||
let sol = domain.reference_solution(&tasks[0]).unwrap();
|
||||
let emb = domain.embed(&sol);
|
||||
assert_eq!(emb.dim, 64);
|
||||
assert_eq!(emb.vector.len(), 64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_collective_domain_transfer() {
|
||||
let mut cdt = CollectiveDomainTransfer::new(2);
|
||||
let phi = cdt.run_cycle();
|
||||
assert!(phi >= 0.0);
|
||||
assert_eq!(cdt.rounds(), 1);
|
||||
|
||||
let phi2 = cdt.run_cycle();
|
||||
assert!(phi2 >= 0.0);
|
||||
assert_eq!(cdt.rounds(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emergent_transfer_detector() {
|
||||
let mut etd = EmergentTransferDetector::new();
|
||||
etd.record_baseline(0.5);
|
||||
etd.record_post_transfer(0.7);
|
||||
let improvement = etd.mean_improvement();
|
||||
assert!((improvement - 0.2).abs() < 1e-10);
|
||||
let score = etd.emergence_score();
|
||||
assert!(score >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_detector() {
|
||||
let etd = EmergentTransferDetector::new();
|
||||
assert_eq!(etd.mean_improvement(), 0.0);
|
||||
}
|
||||
}
|
||||
563
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/dreams.rs
vendored
Normal file
563
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/dreams.rs
vendored
Normal file
@@ -0,0 +1,563 @@
|
||||
//! # Artificial Dreams
|
||||
//!
|
||||
//! Implementation of offline replay and creative recombination during "sleep" cycles.
|
||||
//! Dreams serve as a mechanism for memory consolidation, creative problem solving,
|
||||
//! and novel pattern synthesis.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Dream Replay**: Reactivation of memory traces during sleep
|
||||
//! - **Creative Recombination**: Novel combinations of existing patterns
|
||||
//! - **Memory Consolidation**: Transfer from short-term to long-term memory
|
||||
//! - **Threat Simulation**: Evolutionary theory of dream function
|
||||
//!
|
||||
//! ## Neurological Basis
|
||||
//!
|
||||
//! Inspired by research on hippocampal replay, REM sleep, and the
|
||||
//! activation-synthesis hypothesis.
|
||||
|
||||
use rand::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Engine for generating and processing artificial dreams
|
||||
#[derive(Debug)]
|
||||
pub struct DreamEngine {
|
||||
/// Memory traces available for dream replay
|
||||
memory_traces: Vec<MemoryTrace>,
|
||||
/// Current dream state
|
||||
dream_state: DreamState,
|
||||
/// Dream history
|
||||
dream_history: VecDeque<DreamReport>,
|
||||
/// Random number generator for dream synthesis
|
||||
rng: StdRng,
|
||||
/// Creativity parameters
|
||||
creativity_level: f64,
|
||||
/// Maximum dream history to retain
|
||||
max_history: usize,
|
||||
}
|
||||
|
||||
/// A memory trace that can be replayed in dreams
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct MemoryTrace {
|
||||
pub id: Uuid,
|
||||
/// Semantic content of the memory
|
||||
pub content: Vec<f64>,
|
||||
/// Emotional valence (-1 to 1)
|
||||
pub emotional_valence: f64,
|
||||
/// Importance/salience score
|
||||
pub salience: f64,
|
||||
/// Number of times replayed
|
||||
pub replay_count: usize,
|
||||
/// Associated concepts
|
||||
pub associations: Vec<Uuid>,
|
||||
/// Timestamp of original experience
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
/// Current state of the dream engine
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DreamState {
|
||||
/// Awake - no dreaming
|
||||
Awake,
|
||||
/// Light sleep - hypnagogic imagery
|
||||
LightSleep,
|
||||
/// Deep sleep - memory consolidation
|
||||
DeepSleep,
|
||||
/// REM sleep - vivid dreams
|
||||
REM,
|
||||
/// Lucid dreaming - aware within dream
|
||||
Lucid,
|
||||
}
|
||||
|
||||
/// Report of a single dream episode
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DreamReport {
|
||||
pub id: Uuid,
|
||||
/// Memory traces that were replayed
|
||||
pub replayed_memories: Vec<Uuid>,
|
||||
/// Novel combinations generated
|
||||
pub novel_combinations: Vec<NovelPattern>,
|
||||
/// Emotional tone of the dream
|
||||
pub emotional_tone: f64,
|
||||
/// Creativity score (0-1)
|
||||
pub creativity_score: f64,
|
||||
/// Dream narrative (symbolic)
|
||||
pub narrative: String,
|
||||
/// Duration in simulated time units
|
||||
pub duration: u64,
|
||||
/// Whether any insights emerged
|
||||
pub insights: Vec<DreamInsight>,
|
||||
}
|
||||
|
||||
/// A novel pattern synthesized during dreaming
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NovelPattern {
|
||||
pub id: Uuid,
|
||||
/// Source memories combined
|
||||
pub sources: Vec<Uuid>,
|
||||
/// The combined pattern
|
||||
pub pattern: Vec<f64>,
|
||||
/// Novelty score
|
||||
pub novelty: f64,
|
||||
/// Coherence score
|
||||
pub coherence: f64,
|
||||
}
|
||||
|
||||
/// An insight that emerged during dreaming
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct DreamInsight {
|
||||
pub description: String,
|
||||
pub source_connections: Vec<(Uuid, Uuid)>,
|
||||
pub confidence: f64,
|
||||
}
|
||||
|
||||
impl DreamEngine {
|
||||
/// Create a new dream engine
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
memory_traces: Vec::new(),
|
||||
dream_state: DreamState::Awake,
|
||||
dream_history: VecDeque::with_capacity(100),
|
||||
rng: StdRng::from_entropy(),
|
||||
creativity_level: 0.5,
|
||||
max_history: 100,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with specific creativity level
|
||||
pub fn with_creativity(creativity: f64) -> Self {
|
||||
let mut engine = Self::new();
|
||||
engine.creativity_level = creativity.clamp(0.0, 1.0);
|
||||
engine
|
||||
}
|
||||
|
||||
/// Add a memory trace for potential replay
|
||||
pub fn add_memory(&mut self, content: Vec<f64>, emotional_valence: f64, salience: f64) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
self.memory_traces.push(MemoryTrace {
|
||||
id,
|
||||
content,
|
||||
emotional_valence,
|
||||
salience,
|
||||
replay_count: 0,
|
||||
associations: Vec::new(),
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
id
|
||||
}
|
||||
|
||||
/// Measure creativity of recent dreams
|
||||
pub fn measure_creativity(&self) -> f64 {
|
||||
if self.dream_history.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let total: f64 = self.dream_history.iter().map(|d| d.creativity_score).sum();
|
||||
total / self.dream_history.len() as f64
|
||||
}
|
||||
|
||||
/// Enter a dream state
|
||||
pub fn enter_state(&mut self, state: DreamState) {
|
||||
self.dream_state = state;
|
||||
}
|
||||
|
||||
/// Get current state
|
||||
pub fn current_state(&self) -> &DreamState {
|
||||
&self.dream_state
|
||||
}
|
||||
|
||||
/// Run a complete dream cycle
|
||||
pub fn dream_cycle(&mut self, duration: u64) -> DreamReport {
|
||||
// Progress through sleep stages
|
||||
self.enter_state(DreamState::LightSleep);
|
||||
let hypnagogic = self.generate_hypnagogic();
|
||||
|
||||
self.enter_state(DreamState::DeepSleep);
|
||||
let consolidated = self.consolidate_memories();
|
||||
|
||||
self.enter_state(DreamState::REM);
|
||||
let dream_content = self.generate_rem_dream();
|
||||
|
||||
// Create report
|
||||
let creativity_score = self.calculate_creativity(&dream_content);
|
||||
let emotional_tone = self.calculate_emotional_tone(&dream_content);
|
||||
let insights = self.extract_insights(&dream_content);
|
||||
|
||||
let report = DreamReport {
|
||||
id: Uuid::new_v4(),
|
||||
replayed_memories: consolidated,
|
||||
novel_combinations: dream_content,
|
||||
emotional_tone,
|
||||
creativity_score,
|
||||
narrative: self.generate_narrative(&hypnagogic),
|
||||
duration,
|
||||
insights,
|
||||
};
|
||||
|
||||
// Store in history
|
||||
self.dream_history.push_back(report.clone());
|
||||
if self.dream_history.len() > self.max_history {
|
||||
self.dream_history.pop_front();
|
||||
}
|
||||
|
||||
self.enter_state(DreamState::Awake);
|
||||
report
|
||||
}
|
||||
|
||||
/// Generate hypnagogic imagery (light sleep)
|
||||
fn generate_hypnagogic(&mut self) -> Vec<f64> {
|
||||
if self.memory_traces.is_empty() {
|
||||
return vec![0.0; 8];
|
||||
}
|
||||
|
||||
// Random fragments from recent memories
|
||||
let mut imagery = vec![0.0; 8];
|
||||
for _ in 0..3 {
|
||||
if let Some(trace) = self.memory_traces.choose(&mut self.rng) {
|
||||
for (i, &val) in trace.content.iter().take(8).enumerate() {
|
||||
imagery[i] += val * self.rng.gen::<f64>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize
|
||||
let max = imagery.iter().cloned().fold(f64::MIN, f64::max).max(1.0);
|
||||
imagery.iter_mut().for_each(|v| *v /= max);
|
||||
imagery
|
||||
}
|
||||
|
||||
/// Consolidate memories during deep sleep
|
||||
fn consolidate_memories(&mut self) -> Vec<Uuid> {
|
||||
let mut consolidated = Vec::new();
|
||||
|
||||
// Prioritize high-salience, emotionally charged memories
|
||||
let mut candidates: Vec<_> = self
|
||||
.memory_traces
|
||||
.iter_mut()
|
||||
.filter(|t| t.salience > 0.3 || t.emotional_valence.abs() > 0.5)
|
||||
.collect();
|
||||
|
||||
candidates.sort_by(|a, b| {
|
||||
let score_a = a.salience + a.emotional_valence.abs();
|
||||
let score_b = b.salience + b.emotional_valence.abs();
|
||||
score_b
|
||||
.partial_cmp(&score_a)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
});
|
||||
|
||||
for trace in candidates.iter_mut().take(5) {
|
||||
trace.replay_count += 1;
|
||||
trace.salience *= 1.1; // Strengthen through replay
|
||||
consolidated.push(trace.id);
|
||||
}
|
||||
|
||||
consolidated
|
||||
}
|
||||
|
||||
/// Generate REM dream content with creative recombination
|
||||
fn generate_rem_dream(&mut self) -> Vec<NovelPattern> {
|
||||
let mut novel_patterns = Vec::new();
|
||||
|
||||
if self.memory_traces.len() < 2 {
|
||||
return novel_patterns;
|
||||
}
|
||||
|
||||
// Number of combinations based on creativity level
|
||||
let num_combinations = (self.creativity_level * 10.0) as usize + 1;
|
||||
|
||||
for _ in 0..num_combinations {
|
||||
// Select random memories to combine
|
||||
let indices: Vec<usize> = (0..self.memory_traces.len()).collect();
|
||||
let selected: Vec<_> = indices
|
||||
.choose_multiple(&mut self.rng, 2.min(self.memory_traces.len()))
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
if selected.len() >= 2 {
|
||||
// Clone content to avoid borrow issues
|
||||
let content1 = self.memory_traces[selected[0]].content.clone();
|
||||
let content2 = self.memory_traces[selected[1]].content.clone();
|
||||
let id1 = self.memory_traces[selected[0]].id;
|
||||
let id2 = self.memory_traces[selected[1]].id;
|
||||
|
||||
// Creative combination
|
||||
let combined = self.creative_blend(&content1, &content2);
|
||||
let novelty = self.calculate_novelty(&combined);
|
||||
let coherence = self.calculate_coherence(&combined);
|
||||
|
||||
novel_patterns.push(NovelPattern {
|
||||
id: Uuid::new_v4(),
|
||||
sources: vec![id1, id2],
|
||||
pattern: combined,
|
||||
novelty,
|
||||
coherence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
novel_patterns
|
||||
}
|
||||
|
||||
/// Creatively blend two patterns
|
||||
fn creative_blend(&mut self, a: &[f64], b: &[f64]) -> Vec<f64> {
|
||||
let len = a.len().max(b.len());
|
||||
let mut result = vec![0.0; len];
|
||||
|
||||
for i in 0..len {
|
||||
let val_a = a.get(i).copied().unwrap_or(0.0);
|
||||
let val_b = b.get(i).copied().unwrap_or(0.0);
|
||||
|
||||
// Weighted combination with random perturbation
|
||||
let weight = self.rng.gen::<f64>();
|
||||
let perturbation = (self.rng.gen::<f64>() - 0.5) * self.creativity_level;
|
||||
result[i] = (val_a * weight + val_b * (1.0 - weight) + perturbation).clamp(-1.0, 1.0);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Calculate novelty of a pattern
|
||||
fn calculate_novelty(&self, pattern: &[f64]) -> f64 {
|
||||
if self.memory_traces.is_empty() {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Minimum distance to any existing pattern
|
||||
let min_similarity = self
|
||||
.memory_traces
|
||||
.iter()
|
||||
.map(|trace| self.cosine_similarity(pattern, &trace.content))
|
||||
.fold(f64::MAX, f64::min);
|
||||
|
||||
1.0 - min_similarity.clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
/// Calculate coherence of a pattern
|
||||
fn calculate_coherence(&self, pattern: &[f64]) -> f64 {
|
||||
// Coherence based on internal consistency (low variance)
|
||||
let mean = pattern.iter().sum::<f64>() / pattern.len().max(1) as f64;
|
||||
let variance =
|
||||
pattern.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / pattern.len().max(1) as f64;
|
||||
|
||||
1.0 / (1.0 + variance)
|
||||
}
|
||||
|
||||
fn cosine_similarity(&self, a: &[f64], b: &[f64]) -> f64 {
|
||||
let len = a.len().min(b.len());
|
||||
if len == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let mut dot = 0.0;
|
||||
let mut norm_a = 0.0;
|
||||
let mut norm_b = 0.0;
|
||||
|
||||
for i in 0..len {
|
||||
dot += a[i] * b[i];
|
||||
norm_a += a[i] * a[i];
|
||||
norm_b += b[i] * b[i];
|
||||
}
|
||||
|
||||
if norm_a == 0.0 || norm_b == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
dot / (norm_a.sqrt() * norm_b.sqrt())
|
||||
}
|
||||
|
||||
fn calculate_creativity(&self, patterns: &[NovelPattern]) -> f64 {
|
||||
if patterns.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let avg_novelty = patterns.iter().map(|p| p.novelty).sum::<f64>() / patterns.len() as f64;
|
||||
let avg_coherence =
|
||||
patterns.iter().map(|p| p.coherence).sum::<f64>() / patterns.len() as f64;
|
||||
|
||||
// Creativity = novelty balanced with coherence
|
||||
(avg_novelty * 0.7 + avg_coherence * 0.3).clamp(0.0, 1.0)
|
||||
}
|
||||
|
||||
fn calculate_emotional_tone(&self, patterns: &[NovelPattern]) -> f64 {
|
||||
if patterns.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Average emotional valence of source memories
|
||||
let mut total_valence = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for pattern in patterns {
|
||||
for source_id in &pattern.sources {
|
||||
if let Some(trace) = self.memory_traces.iter().find(|t| t.id == *source_id) {
|
||||
total_valence += trace.emotional_valence;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
total_valence / count as f64
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_insights(&self, patterns: &[NovelPattern]) -> Vec<DreamInsight> {
|
||||
let mut insights = Vec::new();
|
||||
|
||||
for pattern in patterns {
|
||||
if pattern.novelty > 0.7 && pattern.coherence > 0.5 {
|
||||
// High novelty + coherence = potential insight
|
||||
insights.push(DreamInsight {
|
||||
description: format!(
|
||||
"Novel connection discovered with novelty={:.2} coherence={:.2}",
|
||||
pattern.novelty, pattern.coherence
|
||||
),
|
||||
source_connections: pattern.sources.windows(2).map(|w| (w[0], w[1])).collect(),
|
||||
confidence: pattern.coherence,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
insights
|
||||
}
|
||||
|
||||
fn generate_narrative(&self, imagery: &[f64]) -> String {
|
||||
let intensity = imagery.iter().map(|v| v.abs()).sum::<f64>() / imagery.len().max(1) as f64;
|
||||
|
||||
if intensity > 0.7 {
|
||||
"Vivid, intense dream with strong imagery".to_string()
|
||||
} else if intensity > 0.4 {
|
||||
"Moderate dream with clear sequences".to_string()
|
||||
} else {
|
||||
"Faint, fragmentary dream experience".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt lucid dreaming
|
||||
pub fn attempt_lucid(&mut self) -> bool {
|
||||
if self.dream_state == DreamState::REM {
|
||||
// Probability based on practice (replay count)
|
||||
let lucid_probability = self.dream_history.len() as f64 / 100.0;
|
||||
if self.rng.gen::<f64>() < lucid_probability.min(0.3) {
|
||||
self.dream_state = DreamState::Lucid;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Get dream statistics
|
||||
pub fn statistics(&self) -> DreamStatistics {
|
||||
let total_dreams = self.dream_history.len();
|
||||
let avg_creativity = self.measure_creativity();
|
||||
let total_insights: usize = self.dream_history.iter().map(|d| d.insights.len()).sum();
|
||||
|
||||
DreamStatistics {
|
||||
total_dreams,
|
||||
average_creativity: avg_creativity,
|
||||
total_insights,
|
||||
total_memories: self.memory_traces.len(),
|
||||
most_replayed: self
|
||||
.memory_traces
|
||||
.iter()
|
||||
.max_by_key(|t| t.replay_count)
|
||||
.map(|t| (t.id, t.replay_count)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for DreamEngine {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics about dream activity
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DreamStatistics {
|
||||
pub total_dreams: usize,
|
||||
pub average_creativity: f64,
|
||||
pub total_insights: usize,
|
||||
pub total_memories: usize,
|
||||
pub most_replayed: Option<(Uuid, usize)>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dream_engine_creation() {
|
||||
let engine = DreamEngine::new();
|
||||
assert_eq!(*engine.current_state(), DreamState::Awake);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_memory() {
|
||||
let mut engine = DreamEngine::new();
|
||||
let id = engine.add_memory(vec![0.1, 0.2, 0.3], 0.5, 0.8);
|
||||
assert_eq!(engine.memory_traces.len(), 1);
|
||||
assert_eq!(engine.memory_traces[0].id, id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dream_cycle() {
|
||||
let mut engine = DreamEngine::with_creativity(0.8);
|
||||
|
||||
// Add some memories
|
||||
engine.add_memory(vec![0.1, 0.2, 0.3, 0.4], 0.5, 0.7);
|
||||
engine.add_memory(vec![0.5, 0.6, 0.7, 0.8], -0.3, 0.9);
|
||||
engine.add_memory(vec![0.2, 0.4, 0.6, 0.8], 0.8, 0.6);
|
||||
|
||||
let report = engine.dream_cycle(100);
|
||||
|
||||
assert!(!report.replayed_memories.is_empty() || !report.novel_combinations.is_empty());
|
||||
assert!(report.creativity_score >= 0.0 && report.creativity_score <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_creativity_measurement() {
|
||||
let mut engine = DreamEngine::with_creativity(0.9);
|
||||
|
||||
for i in 0..5 {
|
||||
engine.add_memory(vec![i as f64 * 0.1; 4], 0.0, 0.5);
|
||||
}
|
||||
|
||||
for _ in 0..3 {
|
||||
engine.dream_cycle(50);
|
||||
}
|
||||
|
||||
let creativity = engine.measure_creativity();
|
||||
assert!(creativity >= 0.0 && creativity <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dream_states() {
|
||||
let mut engine = DreamEngine::new();
|
||||
|
||||
engine.enter_state(DreamState::LightSleep);
|
||||
assert_eq!(*engine.current_state(), DreamState::LightSleep);
|
||||
|
||||
engine.enter_state(DreamState::REM);
|
||||
assert_eq!(*engine.current_state(), DreamState::REM);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_statistics() {
|
||||
let mut engine = DreamEngine::new();
|
||||
engine.add_memory(vec![0.1, 0.2], 0.5, 0.8);
|
||||
engine.add_memory(vec![0.3, 0.4], -0.2, 0.6);
|
||||
engine.dream_cycle(100);
|
||||
|
||||
let stats = engine.statistics();
|
||||
assert_eq!(stats.total_dreams, 1);
|
||||
assert_eq!(stats.total_memories, 2);
|
||||
}
|
||||
}
|
||||
642
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/emergence.rs
vendored
Normal file
642
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/emergence.rs
vendored
Normal file
@@ -0,0 +1,642 @@
|
||||
//! # Emergence Detection
|
||||
//!
|
||||
//! Automatically detecting when novel properties emerge from complex systems.
|
||||
//! Measures causal emergence, phase transitions, and downward causation.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Causal Emergence**: When macro-level descriptions are more predictive
|
||||
//! - **Downward Causation**: Higher levels affecting lower levels
|
||||
//! - **Phase Transitions**: Sudden qualitative changes in system behavior
|
||||
//! - **Effective Information**: Information flow at different scales
|
||||
//!
|
||||
//! ## Theoretical Basis
|
||||
//!
|
||||
//! Based on:
|
||||
//! - Erik Hoel's Causal Emergence framework
|
||||
//! - Integrated Information Theory (IIT)
|
||||
//! - Synergistic information theory
|
||||
//! - Anderson's "More is Different"
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// System for detecting emergent properties
|
||||
#[derive(Debug)]
|
||||
pub struct EmergenceDetector {
|
||||
/// Micro-level state
|
||||
micro_state: Vec<f64>,
|
||||
/// Macro-level state
|
||||
macro_state: Vec<f64>,
|
||||
/// Coarse-graining function
|
||||
coarse_grainer: CoarseGrainer,
|
||||
/// Detected emergent properties
|
||||
emergent_properties: Vec<EmergentProperty>,
|
||||
/// Phase transition detector
|
||||
phase_detector: PhaseTransitionDetector,
|
||||
/// Causal emergence calculator
|
||||
causal_calculator: CausalEmergence,
|
||||
}
|
||||
|
||||
/// Coarse-graining for multi-scale analysis
|
||||
#[derive(Debug)]
|
||||
pub struct CoarseGrainer {
|
||||
/// Grouping of micro to macro
|
||||
groupings: Vec<Vec<usize>>,
|
||||
/// Aggregation function
|
||||
aggregation: AggregationType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AggregationType {
|
||||
Mean,
|
||||
Majority,
|
||||
Max,
|
||||
WeightedSum(Vec<f64>),
|
||||
}
|
||||
|
||||
/// An emergent property detected in the system
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EmergentProperty {
|
||||
pub id: Uuid,
|
||||
pub name: String,
|
||||
pub emergence_score: f64,
|
||||
pub level: usize,
|
||||
pub description: String,
|
||||
pub detected_at: u64,
|
||||
}
|
||||
|
||||
/// Causal emergence measurement
|
||||
#[derive(Debug)]
|
||||
pub struct CausalEmergence {
|
||||
/// Effective information at micro level
|
||||
micro_ei: f64,
|
||||
/// Effective information at macro level
|
||||
macro_ei: f64,
|
||||
/// Causal emergence score
|
||||
emergence: f64,
|
||||
/// History of measurements
|
||||
history: Vec<EmergenceMeasurement>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EmergenceMeasurement {
|
||||
pub micro_ei: f64,
|
||||
pub macro_ei: f64,
|
||||
pub emergence: f64,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
/// Phase transition detector
|
||||
#[derive(Debug)]
|
||||
pub struct PhaseTransitionDetector {
|
||||
/// Order parameter history
|
||||
order_parameter: Vec<f64>,
|
||||
/// Susceptibility (variance)
|
||||
susceptibility: Vec<f64>,
|
||||
/// Detected transitions
|
||||
transitions: Vec<PhaseTransition>,
|
||||
/// Window size for detection
|
||||
window_size: usize,
|
||||
}
|
||||
|
||||
/// A detected phase transition
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PhaseTransition {
|
||||
pub id: Uuid,
|
||||
/// Critical point value
|
||||
pub critical_point: f64,
|
||||
/// Order parameter jump
|
||||
pub order_change: f64,
|
||||
/// Transition type
|
||||
pub transition_type: TransitionType,
|
||||
/// When detected
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TransitionType {
|
||||
/// Continuous (second-order)
|
||||
Continuous,
|
||||
/// Discontinuous (first-order)
|
||||
Discontinuous,
|
||||
/// Crossover (smooth)
|
||||
Crossover,
|
||||
}
|
||||
|
||||
impl EmergenceDetector {
|
||||
/// Create a new emergence detector
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
micro_state: Vec::new(),
|
||||
macro_state: Vec::new(),
|
||||
coarse_grainer: CoarseGrainer::new(),
|
||||
emergent_properties: Vec::new(),
|
||||
phase_detector: PhaseTransitionDetector::new(50),
|
||||
causal_calculator: CausalEmergence::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect emergence in the current state
|
||||
pub fn detect_emergence(&mut self) -> f64 {
|
||||
if self.micro_state.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Compute macro state
|
||||
self.macro_state = self.coarse_grainer.coarsen(&self.micro_state);
|
||||
|
||||
// Compute causal emergence
|
||||
let micro_ei = self.compute_effective_information(&self.micro_state);
|
||||
let macro_ei = self.compute_effective_information(&self.macro_state);
|
||||
|
||||
self.causal_calculator.update(micro_ei, macro_ei);
|
||||
|
||||
// Check for phase transitions
|
||||
let order_param = self.compute_order_parameter();
|
||||
self.phase_detector.update(order_param);
|
||||
|
||||
// Detect specific emergent properties
|
||||
self.detect_specific_properties();
|
||||
|
||||
self.causal_calculator.emergence
|
||||
}
|
||||
|
||||
/// Set the micro-level state
|
||||
pub fn set_micro_state(&mut self, state: Vec<f64>) {
|
||||
self.micro_state = state;
|
||||
}
|
||||
|
||||
/// Configure coarse-graining
|
||||
pub fn set_coarse_graining(
|
||||
&mut self,
|
||||
groupings: Vec<Vec<usize>>,
|
||||
aggregation: AggregationType,
|
||||
) {
|
||||
self.coarse_grainer = CoarseGrainer {
|
||||
groupings,
|
||||
aggregation,
|
||||
};
|
||||
}
|
||||
|
||||
fn compute_effective_information(&self, state: &[f64]) -> f64 {
|
||||
if state.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Simplified EI: entropy of state distribution
|
||||
let sum: f64 = state.iter().map(|x| x.abs()).sum();
|
||||
if sum == 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let normalized: Vec<f64> = state.iter().map(|x| x.abs() / sum).collect();
|
||||
|
||||
// Shannon entropy
|
||||
-normalized
|
||||
.iter()
|
||||
.filter(|&&p| p > 1e-10)
|
||||
.map(|&p| p * p.ln())
|
||||
.sum::<f64>()
|
||||
}
|
||||
|
||||
fn compute_order_parameter(&self) -> f64 {
|
||||
if self.macro_state.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// Order parameter: average alignment/correlation
|
||||
let mean: f64 = self.macro_state.iter().sum::<f64>() / self.macro_state.len() as f64;
|
||||
let variance: f64 = self
|
||||
.macro_state
|
||||
.iter()
|
||||
.map(|x| (x - mean).powi(2))
|
||||
.sum::<f64>()
|
||||
/ self.macro_state.len() as f64;
|
||||
|
||||
// Low variance = high order
|
||||
1.0 / (1.0 + variance)
|
||||
}
|
||||
|
||||
fn detect_specific_properties(&mut self) {
|
||||
// Check for coherence (synchronized macro state)
|
||||
if let Some(coherence) = self.detect_coherence() {
|
||||
if coherence > 0.7 {
|
||||
self.record_property("Coherence", coherence, 1, "Synchronized macro behavior");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for hierarchy (multi-level structure)
|
||||
if let Some(hierarchy) = self.detect_hierarchy() {
|
||||
if hierarchy > 0.5 {
|
||||
self.record_property("Hierarchy", hierarchy, 2, "Multi-level organization");
|
||||
}
|
||||
}
|
||||
|
||||
// Check for criticality
|
||||
if self.phase_detector.is_near_critical() {
|
||||
self.record_property("Criticality", 0.9, 1, "Near phase transition");
|
||||
}
|
||||
}
|
||||
|
||||
fn detect_coherence(&self) -> Option<f64> {
|
||||
if self.macro_state.len() < 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Coherence as average pairwise correlation
|
||||
let mean: f64 = self.macro_state.iter().sum::<f64>() / self.macro_state.len() as f64;
|
||||
let deviations: Vec<f64> = self.macro_state.iter().map(|x| x - mean).collect();
|
||||
|
||||
let norm = deviations.iter().map(|x| x * x).sum::<f64>().sqrt();
|
||||
if norm == 0.0 {
|
||||
return Some(1.0); // Perfect coherence
|
||||
}
|
||||
|
||||
Some((1.0 / (1.0 + norm)).min(1.0))
|
||||
}
|
||||
|
||||
fn detect_hierarchy(&self) -> Option<f64> {
|
||||
// Hierarchy based on scale separation
|
||||
if self.micro_state.is_empty() || self.macro_state.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let micro_complexity = self.compute_effective_information(&self.micro_state);
|
||||
let macro_complexity = self.compute_effective_information(&self.macro_state);
|
||||
|
||||
// Hierarchy emerges when macro is simpler than micro
|
||||
if micro_complexity == 0.0 {
|
||||
return Some(0.0);
|
||||
}
|
||||
|
||||
Some(1.0 - (macro_complexity / micro_complexity).min(1.0))
|
||||
}
|
||||
|
||||
fn record_property(&mut self, name: &str, score: f64, level: usize, description: &str) {
|
||||
// Check if already recorded recently
|
||||
let recent = self
|
||||
.emergent_properties
|
||||
.iter()
|
||||
.any(|p| p.name == name && p.level == level);
|
||||
|
||||
if !recent {
|
||||
self.emergent_properties.push(EmergentProperty {
|
||||
id: Uuid::new_v4(),
|
||||
name: name.to_string(),
|
||||
emergence_score: score,
|
||||
level,
|
||||
description: description.to_string(),
|
||||
detected_at: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Get causal emergence calculator
|
||||
pub fn causal_emergence(&self) -> &CausalEmergence {
|
||||
&self.causal_calculator
|
||||
}
|
||||
|
||||
/// Get detected emergent properties
|
||||
pub fn emergent_properties(&self) -> &[EmergentProperty] {
|
||||
&self.emergent_properties
|
||||
}
|
||||
|
||||
/// Get phase transitions
|
||||
pub fn phase_transitions(&self) -> &[PhaseTransition] {
|
||||
self.phase_detector.transitions()
|
||||
}
|
||||
|
||||
/// Get detection statistics
|
||||
pub fn statistics(&self) -> EmergenceStatistics {
|
||||
EmergenceStatistics {
|
||||
micro_dimension: self.micro_state.len(),
|
||||
macro_dimension: self.macro_state.len(),
|
||||
compression_ratio: if self.micro_state.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
self.macro_state.len() as f64 / self.micro_state.len() as f64
|
||||
},
|
||||
emergence_score: self.causal_calculator.emergence,
|
||||
properties_detected: self.emergent_properties.len(),
|
||||
transitions_detected: self.phase_detector.transitions.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for EmergenceDetector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CoarseGrainer {
|
||||
/// Create a new coarse-grainer
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
groupings: Vec::new(),
|
||||
aggregation: AggregationType::Mean,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with specific groupings
|
||||
pub fn with_groupings(groupings: Vec<Vec<usize>>, aggregation: AggregationType) -> Self {
|
||||
Self {
|
||||
groupings,
|
||||
aggregation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Coarsen a micro state to macro state
|
||||
pub fn coarsen(&self, micro: &[f64]) -> Vec<f64> {
|
||||
if self.groupings.is_empty() {
|
||||
// Default: simple averaging in pairs
|
||||
return self.default_coarsen(micro);
|
||||
}
|
||||
|
||||
self.groupings
|
||||
.iter()
|
||||
.map(|group| {
|
||||
let values: Vec<f64> = group
|
||||
.iter()
|
||||
.filter_map(|&i| micro.get(i).copied())
|
||||
.collect();
|
||||
self.aggregate(&values)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn default_coarsen(&self, micro: &[f64]) -> Vec<f64> {
|
||||
micro
|
||||
.chunks(2)
|
||||
.map(|chunk| chunk.iter().sum::<f64>() / chunk.len() as f64)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn aggregate(&self, values: &[f64]) -> f64 {
|
||||
if values.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
match &self.aggregation {
|
||||
AggregationType::Mean => values.iter().sum::<f64>() / values.len() as f64,
|
||||
AggregationType::Majority => {
|
||||
let positive = values.iter().filter(|&&v| v > 0.0).count();
|
||||
if positive > values.len() / 2 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
}
|
||||
}
|
||||
AggregationType::Max => values.iter().cloned().fold(f64::MIN, f64::max),
|
||||
AggregationType::WeightedSum(weights) => {
|
||||
values.iter().zip(weights.iter()).map(|(v, w)| v * w).sum()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CoarseGrainer {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl CausalEmergence {
|
||||
/// Create a new causal emergence calculator
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
micro_ei: 0.0,
|
||||
macro_ei: 0.0,
|
||||
emergence: 0.0,
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Update with new EI measurements
|
||||
pub fn update(&mut self, micro_ei: f64, macro_ei: f64) {
|
||||
self.micro_ei = micro_ei;
|
||||
self.macro_ei = macro_ei;
|
||||
|
||||
// Causal emergence = macro_ei - micro_ei (when positive)
|
||||
self.emergence = (macro_ei - micro_ei).max(0.0);
|
||||
|
||||
self.history.push(EmergenceMeasurement {
|
||||
micro_ei,
|
||||
macro_ei,
|
||||
emergence: self.emergence,
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
/// Get emergence score
|
||||
pub fn score(&self) -> f64 {
|
||||
self.emergence
|
||||
}
|
||||
|
||||
/// Is there causal emergence?
|
||||
pub fn has_emergence(&self) -> bool {
|
||||
self.emergence > 0.0
|
||||
}
|
||||
|
||||
/// Get emergence trend
|
||||
pub fn trend(&self) -> f64 {
|
||||
if self.history.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let recent = &self.history[self.history.len().saturating_sub(10)..];
|
||||
if recent.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let first = recent[0].emergence;
|
||||
let last = recent[recent.len() - 1].emergence;
|
||||
|
||||
last - first
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CausalEmergence {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl PhaseTransitionDetector {
|
||||
/// Create a new phase transition detector
|
||||
pub fn new(window_size: usize) -> Self {
|
||||
Self {
|
||||
order_parameter: Vec::new(),
|
||||
susceptibility: Vec::new(),
|
||||
transitions: Vec::new(),
|
||||
window_size,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update with new order parameter value
|
||||
pub fn update(&mut self, order: f64) {
|
||||
self.order_parameter.push(order);
|
||||
|
||||
// Compute susceptibility (local variance)
|
||||
if self.order_parameter.len() >= self.window_size {
|
||||
let window = &self.order_parameter[self.order_parameter.len() - self.window_size..];
|
||||
let mean: f64 = window.iter().sum::<f64>() / window.len() as f64;
|
||||
let variance: f64 =
|
||||
window.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / window.len() as f64;
|
||||
self.susceptibility.push(variance);
|
||||
|
||||
// Detect transition (spike in susceptibility)
|
||||
if self.susceptibility.len() >= 2 {
|
||||
let current = *self.susceptibility.last().unwrap();
|
||||
let previous = self.susceptibility[self.susceptibility.len() - 2];
|
||||
|
||||
if current > previous * 2.0 && current > 0.1 {
|
||||
self.record_transition(order, current - previous);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_transition(&mut self, critical_point: f64, order_change: f64) {
|
||||
let transition_type = if order_change.abs() > 0.5 {
|
||||
TransitionType::Discontinuous
|
||||
} else if order_change.abs() > 0.1 {
|
||||
TransitionType::Continuous
|
||||
} else {
|
||||
TransitionType::Crossover
|
||||
};
|
||||
|
||||
self.transitions.push(PhaseTransition {
|
||||
id: Uuid::new_v4(),
|
||||
critical_point,
|
||||
order_change,
|
||||
transition_type,
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
}
|
||||
|
||||
/// Is the system near a critical point?
|
||||
pub fn is_near_critical(&self) -> bool {
|
||||
if self.susceptibility.is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let recent = *self.susceptibility.last().unwrap();
|
||||
let avg = self.susceptibility.iter().sum::<f64>() / self.susceptibility.len() as f64;
|
||||
|
||||
recent > avg * 1.5
|
||||
}
|
||||
|
||||
/// Get detected transitions
|
||||
pub fn transitions(&self) -> &[PhaseTransition] {
|
||||
&self.transitions
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics about emergence detection
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EmergenceStatistics {
|
||||
pub micro_dimension: usize,
|
||||
pub macro_dimension: usize,
|
||||
pub compression_ratio: f64,
|
||||
pub emergence_score: f64,
|
||||
pub properties_detected: usize,
|
||||
pub transitions_detected: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_emergence_detector_creation() {
|
||||
let detector = EmergenceDetector::new();
|
||||
assert_eq!(detector.emergent_properties().len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coarse_graining() {
|
||||
let cg = CoarseGrainer::new();
|
||||
let micro = vec![1.0, 2.0, 3.0, 4.0];
|
||||
let macro_state = cg.coarsen(µ);
|
||||
|
||||
assert_eq!(macro_state.len(), 2);
|
||||
assert_eq!(macro_state[0], 1.5);
|
||||
assert_eq!(macro_state[1], 3.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_coarse_graining() {
|
||||
let groupings = vec![vec![0, 1], vec![2, 3]];
|
||||
let cg = CoarseGrainer::with_groupings(groupings, AggregationType::Max);
|
||||
let micro = vec![1.0, 2.0, 3.0, 4.0];
|
||||
let macro_state = cg.coarsen(µ);
|
||||
|
||||
assert_eq!(macro_state[0], 2.0);
|
||||
assert_eq!(macro_state[1], 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emergence_detection() {
|
||||
let mut detector = EmergenceDetector::new();
|
||||
|
||||
// Set a micro state
|
||||
detector.set_micro_state(vec![0.1, 0.9, 0.2, 0.8, 0.15, 0.85, 0.18, 0.82]);
|
||||
|
||||
let score = detector.detect_emergence();
|
||||
assert!(score >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_causal_emergence() {
|
||||
let mut ce = CausalEmergence::new();
|
||||
|
||||
ce.update(2.0, 3.0); // Macro more informative
|
||||
assert!(ce.has_emergence());
|
||||
assert_eq!(ce.score(), 1.0);
|
||||
|
||||
ce.update(3.0, 2.0); // Micro more informative
|
||||
assert!(!ce.has_emergence()); // Emergence is 0 when macro < micro
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phase_transition_detection() {
|
||||
let mut detector = PhaseTransitionDetector::new(5);
|
||||
|
||||
// Normal values
|
||||
for _ in 0..10 {
|
||||
detector.update(0.5);
|
||||
}
|
||||
|
||||
// Sudden change (transition)
|
||||
detector.update(0.1);
|
||||
detector.update(0.05);
|
||||
detector.update(0.02);
|
||||
|
||||
// Check if transition detected
|
||||
// (This may or may not trigger depending on thresholds)
|
||||
assert!(detector.order_parameter.len() >= 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emergence_statistics() {
|
||||
let mut detector = EmergenceDetector::new();
|
||||
detector.set_micro_state(vec![1.0, 2.0, 3.0, 4.0]);
|
||||
detector.detect_emergence();
|
||||
|
||||
let stats = detector.statistics();
|
||||
assert_eq!(stats.micro_dimension, 4);
|
||||
assert_eq!(stats.macro_dimension, 2);
|
||||
assert_eq!(stats.compression_ratio, 0.5);
|
||||
}
|
||||
}
|
||||
257
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/causal_emergence.rs
vendored
Normal file
257
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/causal_emergence.rs
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
//! Experiment 07: Causal Emergence
|
||||
//!
|
||||
//! Research frontier: Find the macro-scale that maximizes causal power (EI).
|
||||
//! Theory: Hoel et al. 2013 — emergence occurs when a macro-description has
|
||||
//! higher Effective Information (EI) than its micro-substrate.
|
||||
//!
|
||||
//! EI(τ) = H(effect) - H(effect|cause) [where τ = coarse-graining]
|
||||
//! Causal emergence: EI(macro) > EI(micro)
|
||||
//!
|
||||
//! ADR-029: ruvector-solver Forward Push PPR accelerates the coarse-graining
|
||||
//! search (O(n/ε) vs O(n²) for dense causation matrices).
|
||||
|
||||
/// Transition probability matrix (row = current state, col = next state)
|
||||
pub struct TransitionMatrix {
|
||||
pub n_states: usize,
|
||||
pub data: Vec<f64>, // n × n, row-major
|
||||
}
|
||||
|
||||
impl TransitionMatrix {
|
||||
pub fn new(n: usize) -> Self {
|
||||
Self {
|
||||
n_states: n,
|
||||
data: vec![0.0; n * n],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set(&mut self, from: usize, to: usize, prob: f64) {
|
||||
self.data[from * self.n_states + to] = prob;
|
||||
}
|
||||
|
||||
pub fn get(&self, from: usize, to: usize) -> f64 {
|
||||
self.data[from * self.n_states + to]
|
||||
}
|
||||
|
||||
/// Shannon entropy of output distribution given input state
|
||||
fn conditional_entropy(&self, from: usize) -> f64 {
|
||||
let mut h = 0.0;
|
||||
for to in 0..self.n_states {
|
||||
let p = self.get(from, to);
|
||||
if p > 1e-10 {
|
||||
h -= p * p.ln();
|
||||
}
|
||||
}
|
||||
h
|
||||
}
|
||||
|
||||
/// Marginal output distribution (uniform intervention distribution)
|
||||
fn marginal_output(&self) -> Vec<f64> {
|
||||
let n = self.n_states;
|
||||
let mut marginal = vec![0.0f64; n];
|
||||
for from in 0..n {
|
||||
for to in 0..n {
|
||||
marginal[to] += self.get(from, to) / n as f64;
|
||||
}
|
||||
}
|
||||
marginal
|
||||
}
|
||||
|
||||
/// Effective Information = H(effect) - <H(effect|cause)>
|
||||
pub fn effective_information(&self) -> f64 {
|
||||
let marginal = self.marginal_output();
|
||||
let h_effect: f64 = marginal
|
||||
.iter()
|
||||
.filter(|&&p| p > 1e-10)
|
||||
.map(|&p| -p * p.ln())
|
||||
.sum();
|
||||
let h_cond: f64 = (0..self.n_states)
|
||||
.map(|from| self.conditional_entropy(from))
|
||||
.sum::<f64>()
|
||||
/ self.n_states as f64;
|
||||
h_effect - h_cond
|
||||
}
|
||||
}
|
||||
|
||||
/// Coarse-graining operator: partitions micro-states into macro-states
|
||||
pub struct CoarseGraining {
|
||||
/// Mapping from micro-state to macro-state
|
||||
pub micro_to_macro: Vec<usize>,
|
||||
pub n_macro: usize,
|
||||
pub n_micro: usize,
|
||||
}
|
||||
|
||||
impl CoarseGraining {
|
||||
/// Block coarse-graining: group consecutive states
|
||||
pub fn block(n_micro: usize, block_size: usize) -> Self {
|
||||
let n_macro = (n_micro + block_size - 1) / block_size;
|
||||
let micro_to_macro = (0..n_micro).map(|i| i / block_size).collect();
|
||||
Self {
|
||||
micro_to_macro,
|
||||
n_macro,
|
||||
n_micro,
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply coarse-graining to produce macro transition matrix
|
||||
pub fn apply(&self, micro: &TransitionMatrix) -> TransitionMatrix {
|
||||
let mut macro_matrix = TransitionMatrix::new(self.n_macro);
|
||||
let n = self.n_micro;
|
||||
|
||||
// Macro transition P(macro_j | macro_i) = average over micro states in macro_i
|
||||
let mut counts = vec![0usize; self.n_macro];
|
||||
for i in 0..n {
|
||||
counts[self.micro_to_macro[i]] += 1;
|
||||
}
|
||||
|
||||
for from_micro in 0..n {
|
||||
let from_macro = self.micro_to_macro[from_micro];
|
||||
for to_micro in 0..n {
|
||||
let to_macro = self.micro_to_macro[to_micro];
|
||||
let weight = 1.0 / counts[from_macro].max(1) as f64;
|
||||
let current = macro_matrix.get(from_macro, to_macro);
|
||||
macro_matrix.set(
|
||||
from_macro,
|
||||
to_macro,
|
||||
current + micro.get(from_micro, to_micro) * weight,
|
||||
);
|
||||
}
|
||||
}
|
||||
macro_matrix
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CausalEmergenceResult {
|
||||
pub micro_ei: f64,
|
||||
pub macro_eis: Vec<(usize, f64)>, // (block_size, EI)
|
||||
pub best_macro_ei: f64,
|
||||
pub best_block_size: usize,
|
||||
pub emergence_delta: f64, // macro_EI - micro_EI
|
||||
pub causal_emergence_detected: bool,
|
||||
}
|
||||
|
||||
pub struct CausalEmergenceExperiment {
|
||||
pub n_micro_states: usize,
|
||||
pub block_sizes: Vec<usize>,
|
||||
}
|
||||
|
||||
impl CausalEmergenceExperiment {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
n_micro_states: 16,
|
||||
block_sizes: vec![2, 4, 8],
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a test transition matrix with known causal structure
|
||||
pub fn build_test_matrix(n: usize, noise: f64) -> TransitionMatrix {
|
||||
let mut tm = TransitionMatrix::new(n);
|
||||
// Deterministic XOR-like macro pattern with microscopic noise
|
||||
for from in 0..n {
|
||||
let macro_next = (from / 2 + 1) % (n / 2);
|
||||
for to in 0..n {
|
||||
let in_macro = to / 2 == macro_next;
|
||||
let p = if in_macro {
|
||||
(1.0 - noise) / 2.0
|
||||
} else {
|
||||
noise / (n - 2).max(1) as f64
|
||||
};
|
||||
tm.set(from, to, p);
|
||||
}
|
||||
// Normalize
|
||||
let sum: f64 = (0..n).map(|to| tm.get(from, to)).sum();
|
||||
if sum > 1e-10 {
|
||||
for to in 0..n {
|
||||
tm.set(from, to, tm.get(from, to) / sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
tm
|
||||
}
|
||||
|
||||
pub fn run(&self) -> CausalEmergenceResult {
|
||||
let micro_tm = Self::build_test_matrix(self.n_micro_states, 0.1);
|
||||
let micro_ei = micro_tm.effective_information();
|
||||
|
||||
let mut macro_eis = Vec::new();
|
||||
for &block_size in &self.block_sizes {
|
||||
let cg = CoarseGraining::block(self.n_micro_states, block_size);
|
||||
if cg.n_macro >= 2 {
|
||||
let macro_tm = cg.apply(µ_tm);
|
||||
let macro_ei = macro_tm.effective_information();
|
||||
macro_eis.push((block_size, macro_ei));
|
||||
}
|
||||
}
|
||||
|
||||
let best = macro_eis
|
||||
.iter()
|
||||
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
||||
.copied()
|
||||
.unwrap_or((0, micro_ei));
|
||||
|
||||
let delta = best.1 - micro_ei;
|
||||
CausalEmergenceResult {
|
||||
micro_ei,
|
||||
macro_eis,
|
||||
best_macro_ei: best.1,
|
||||
best_block_size: best.0,
|
||||
emergence_delta: delta,
|
||||
causal_emergence_detected: delta > 0.01,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CausalEmergenceExperiment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_effective_information_positive() {
|
||||
// Deterministic matrix should have max EI = H(uniform on n states)
|
||||
let mut tm = TransitionMatrix::new(4);
|
||||
for from in 0..4 {
|
||||
tm.set(from, (from + 1) % 4, 1.0);
|
||||
}
|
||||
let ei = tm.effective_information();
|
||||
assert!(
|
||||
ei > 0.0,
|
||||
"Deterministic permutation should have positive EI"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_coarse_graining() {
|
||||
let cg = CoarseGraining::block(8, 2);
|
||||
assert_eq!(cg.n_macro, 4);
|
||||
assert_eq!(cg.micro_to_macro[0], 0);
|
||||
assert_eq!(cg.micro_to_macro[2], 1);
|
||||
assert_eq!(cg.micro_to_macro[6], 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_causal_emergence_experiment_runs() {
|
||||
let exp = CausalEmergenceExperiment::new();
|
||||
let result = exp.run();
|
||||
assert!(result.micro_ei >= 0.0);
|
||||
assert!(!result.macro_eis.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_transition_matrix_normalizes() {
|
||||
let tm = CausalEmergenceExperiment::build_test_matrix(8, 0.1);
|
||||
for from in 0..8 {
|
||||
let sum: f64 = (0..8).map(|to| tm.get(from, to)).sum();
|
||||
assert!(
|
||||
(sum - 1.0).abs() < 1e-9,
|
||||
"Row {} should sum to 1.0, got {}",
|
||||
from,
|
||||
sum
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
253
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/memory_mapped_fields.rs
vendored
Normal file
253
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/memory_mapped_fields.rs
vendored
Normal file
@@ -0,0 +1,253 @@
|
||||
//! Experiment 05: Memory-Mapped Neural Fields
|
||||
//!
|
||||
//! Research frontier: Zero-copy pattern storage via memory-mapped RVF containers.
|
||||
//! Neural fields are encoded as continuous functions rather than discrete vectors,
|
||||
//! allowing sub-millisecond retrieval via direct memory access.
|
||||
//!
|
||||
//! ADR-029: ruvector-verified + RVF mmap + ruvector-temporal-tensor provide
|
||||
//! the implementation. This experiment documents the integration contract and
|
||||
//! measures retrieval performance vs copy-based storage.
|
||||
|
||||
/// A neural field: continuous function over a domain, discretized to a grid.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NeuralField {
|
||||
pub id: u64,
|
||||
/// Field values on a regular grid (flattened)
|
||||
pub values: Vec<f32>,
|
||||
/// Grid dimensions
|
||||
pub dims: Vec<usize>,
|
||||
/// Field bandwidth (controls smoothness)
|
||||
pub bandwidth: f32,
|
||||
}
|
||||
|
||||
impl NeuralField {
|
||||
pub fn new(id: u64, dims: Vec<usize>, bandwidth: f32) -> Self {
|
||||
let total: usize = dims.iter().product();
|
||||
Self {
|
||||
id,
|
||||
values: vec![0.0f32; total],
|
||||
dims,
|
||||
bandwidth,
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode a pattern as a neural field (Gaussian RBF superposition)
|
||||
pub fn encode_pattern(id: u64, pattern: &[f32], bandwidth: f32) -> Self {
|
||||
let n = pattern.len();
|
||||
let mut values = vec![0.0f32; n];
|
||||
// Each point in the field gets a Gaussian contribution from each pattern element
|
||||
for (i, ¢er) in pattern.iter().enumerate() {
|
||||
let _ = i;
|
||||
for j in 0..n {
|
||||
let t = j as f32 / n as f32;
|
||||
let exponent = -(t - center).powi(2) / (2.0 * bandwidth * bandwidth);
|
||||
values[j] += exponent.exp();
|
||||
}
|
||||
}
|
||||
// Normalize
|
||||
let max = values.iter().cloned().fold(0.0f32, f32::max).max(1e-6);
|
||||
for v in values.iter_mut() {
|
||||
*v /= max;
|
||||
}
|
||||
Self {
|
||||
id,
|
||||
values,
|
||||
dims: vec![n],
|
||||
bandwidth,
|
||||
}
|
||||
}
|
||||
|
||||
/// Query the field at position t ∈ [0,1]
|
||||
pub fn query(&self, t: f32) -> f32 {
|
||||
let n = self.values.len();
|
||||
let idx = (t * (n - 1) as f32).clamp(0.0, (n - 1) as f32);
|
||||
let lo = idx.floor() as usize;
|
||||
let hi = (lo + 1).min(n - 1);
|
||||
let frac = idx - lo as f32;
|
||||
self.values[lo] * (1.0 - frac) + self.values[hi] * frac
|
||||
}
|
||||
|
||||
/// Compute overlap integral ∫ f₁(t)·f₂(t)dt (inner product of fields)
|
||||
pub fn overlap(&self, other: &NeuralField) -> f32 {
|
||||
let n = self.values.len().min(other.values.len());
|
||||
self.values
|
||||
.iter()
|
||||
.zip(other.values.iter())
|
||||
.take(n)
|
||||
.map(|(a, b)| a * b)
|
||||
.sum::<f32>()
|
||||
/ n as f32
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory-mapped field store (simulated — production uses RVF mmap)
|
||||
pub struct FieldStore {
|
||||
fields: Vec<NeuralField>,
|
||||
/// Simulated mmap access time (production: <1µs for read, 0 copy)
|
||||
pub simulated_mmap_us: u64,
|
||||
}
|
||||
|
||||
pub struct FieldQueryResult {
|
||||
pub id: u64,
|
||||
pub overlap: f32,
|
||||
pub access_us: u64,
|
||||
}
|
||||
|
||||
impl FieldStore {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
fields: Vec::new(),
|
||||
simulated_mmap_us: 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn store(&mut self, field: NeuralField) {
|
||||
self.fields.push(field);
|
||||
}
|
||||
|
||||
pub fn query_top_k(&self, query: &NeuralField, k: usize) -> Vec<FieldQueryResult> {
|
||||
let t0 = std::time::Instant::now();
|
||||
let mut results: Vec<FieldQueryResult> = self
|
||||
.fields
|
||||
.iter()
|
||||
.map(|f| FieldQueryResult {
|
||||
id: f.id,
|
||||
overlap: f.overlap(query),
|
||||
access_us: self.simulated_mmap_us,
|
||||
})
|
||||
.collect();
|
||||
results.sort_unstable_by(|a, b| b.overlap.partial_cmp(&a.overlap).unwrap());
|
||||
results.truncate(k);
|
||||
let elapsed = t0.elapsed().as_micros() as u64;
|
||||
for r in results.iter_mut() {
|
||||
r.access_us = elapsed;
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.fields.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FieldStore {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MemoryMappedFieldsExperiment {
|
||||
store: FieldStore,
|
||||
pub n_patterns: usize,
|
||||
pub pattern_dim: usize,
|
||||
pub bandwidth: f32,
|
||||
}
|
||||
|
||||
pub struct MmapFieldResult {
|
||||
pub retrieval_accuracy: f64,
|
||||
pub avg_overlap_correct: f64,
|
||||
pub avg_overlap_wrong: f64,
|
||||
pub avg_latency_us: u64,
|
||||
pub n_fields_stored: usize,
|
||||
}
|
||||
|
||||
impl MemoryMappedFieldsExperiment {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
store: FieldStore::new(),
|
||||
n_patterns: 20,
|
||||
pattern_dim: 128,
|
||||
bandwidth: 0.1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> MmapFieldResult {
|
||||
// Store patterns as neural fields
|
||||
let mut patterns = Vec::new();
|
||||
for i in 0..self.n_patterns {
|
||||
let pattern: Vec<f32> = (0..self.pattern_dim)
|
||||
.map(|j| ((i * j) as f32 / self.pattern_dim as f32).sin().abs())
|
||||
.collect();
|
||||
let field = NeuralField::encode_pattern(i as u64, &pattern, self.bandwidth);
|
||||
patterns.push(pattern);
|
||||
self.store.store(field);
|
||||
}
|
||||
|
||||
// Query each pattern with 5% noise
|
||||
let mut correct = 0usize;
|
||||
let mut overlap_sum_correct = 0.0f64;
|
||||
let mut overlap_sum_wrong = 0.0f64;
|
||||
let mut total_latency = 0u64;
|
||||
|
||||
for (i, pattern) in patterns.iter().enumerate() {
|
||||
let noisy: Vec<f32> = pattern.iter().map(|&v| v + (v * 0.05)).collect();
|
||||
let query = NeuralField::encode_pattern(999, &noisy, self.bandwidth);
|
||||
let results = self.store.query_top_k(&query, 3);
|
||||
if let Some(top) = results.first() {
|
||||
total_latency += top.access_us;
|
||||
if top.id == i as u64 {
|
||||
correct += 1;
|
||||
overlap_sum_correct += top.overlap as f64;
|
||||
} else {
|
||||
overlap_sum_wrong += top.overlap as f64;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let n = self.n_patterns.max(1) as f64;
|
||||
MmapFieldResult {
|
||||
retrieval_accuracy: correct as f64 / n,
|
||||
avg_overlap_correct: overlap_sum_correct / n,
|
||||
avg_overlap_wrong: overlap_sum_wrong / n,
|
||||
avg_latency_us: total_latency / self.n_patterns.max(1) as u64,
|
||||
n_fields_stored: self.store.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MemoryMappedFieldsExperiment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_neural_field_encode_decode() {
|
||||
let pattern = vec![0.0f32, 0.5, 1.0, 0.5, 0.0];
|
||||
let field = NeuralField::encode_pattern(0, &pattern, 0.2);
|
||||
assert_eq!(field.values.len(), 5);
|
||||
// Field values should be normalized
|
||||
assert!(field.values.iter().all(|&v| v >= 0.0 && v <= 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_field_self_overlap() {
|
||||
let pattern = vec![0.5f32; 64];
|
||||
let field = NeuralField::encode_pattern(0, &pattern, 0.1);
|
||||
let self_overlap = field.overlap(&field);
|
||||
assert!(self_overlap > 0.0, "Field self-overlap should be positive");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmap_experiment_runs() {
|
||||
let mut exp = MemoryMappedFieldsExperiment::new();
|
||||
exp.n_patterns = 5;
|
||||
exp.pattern_dim = 32;
|
||||
let result = exp.run();
|
||||
assert_eq!(result.n_fields_stored, 5);
|
||||
assert!(result.retrieval_accuracy >= 0.0 && result.retrieval_accuracy <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_neural_field_query_interpolation() {
|
||||
let mut field = NeuralField::new(0, vec![10], 0.1);
|
||||
field.values = vec![0.0, 0.25, 0.5, 0.75, 1.0, 0.75, 0.5, 0.25, 0.0, 0.0];
|
||||
// Midpoint should be interpolated
|
||||
let mid = field.query(0.5);
|
||||
assert!(mid > 0.0 && mid <= 1.0);
|
||||
}
|
||||
}
|
||||
8
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/mod.rs
vendored
Normal file
8
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/mod.rs
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
//\! Neuromorphic and time-crystal experiments — ADR-029 SubstrateBackend integration.
|
||||
|
||||
pub mod causal_emergence;
|
||||
pub mod memory_mapped_fields;
|
||||
pub mod neuromorphic_spiking;
|
||||
pub mod quantum_superposition;
|
||||
pub mod sparse_homology;
|
||||
pub mod time_crystal_cognition;
|
||||
193
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/neuromorphic_spiking.rs
vendored
Normal file
193
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/neuromorphic_spiking.rs
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
//! Experiment 01: Neuromorphic Spiking Neural Network Cognition
|
||||
//!
|
||||
//! Research frontier: EXO-AI + ruvector-nervous-system integration
|
||||
//! Theory: Spike-timing-dependent plasticity (STDP) with behavioral timescale
|
||||
//! learning (BTSP) enables one-shot pattern acquisition in cognitive substrate.
|
||||
//!
|
||||
//! ADR-029: ruvector-nervous-system provides BTSP/STDP/K-WTA/HDC/Hopfield.
|
||||
//! This experiment demonstrates the integration and documents emergent properties.
|
||||
|
||||
use exo_core::backends::neuromorphic::{NeuromorphicBackend, NeuromorphicConfig};
|
||||
use exo_core::backends::SubstrateBackend as _;
|
||||
|
||||
/// Experiment configuration
|
||||
pub struct NeuromorphicExperiment {
|
||||
backend: NeuromorphicBackend,
|
||||
/// Number of stimulation cycles
|
||||
pub n_cycles: usize,
|
||||
/// STDP window (ms)
|
||||
pub stdp_window_ms: f32,
|
||||
/// Patterns to memorize
|
||||
pub patterns: Vec<Vec<f32>>,
|
||||
}
|
||||
|
||||
/// Emergent property discovered during experiment
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EmergentProperty {
|
||||
pub name: &'static str,
|
||||
pub description: &'static str,
|
||||
pub measured_value: f64,
|
||||
pub theoretical_prediction: f64,
|
||||
}
|
||||
|
||||
/// Result of running the neuromorphic experiment
|
||||
pub struct NeuromorphicResult {
|
||||
pub retrieved_patterns: usize,
|
||||
pub total_patterns: usize,
|
||||
pub retrieval_accuracy: f64,
|
||||
pub circadian_coherence: f32,
|
||||
pub spike_sparsity: f64,
|
||||
pub emergent_properties: Vec<EmergentProperty>,
|
||||
pub latency_us: u64,
|
||||
}
|
||||
|
||||
impl NeuromorphicExperiment {
|
||||
pub fn new() -> Self {
|
||||
let config = NeuromorphicConfig {
|
||||
hd_dim: 10_000,
|
||||
n_neurons: 500,
|
||||
k_wta: 25, // 5% sparsity
|
||||
tau_m: 20.0,
|
||||
btsp_threshold: 0.6,
|
||||
kuramoto_k: 0.5,
|
||||
oscillation_hz: 40.0,
|
||||
};
|
||||
Self {
|
||||
backend: NeuromorphicBackend::with_config(config),
|
||||
n_cycles: 20,
|
||||
stdp_window_ms: 20.0,
|
||||
patterns: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load patterns to be memorized (one-shot via BTSP)
|
||||
pub fn load_patterns(&mut self, patterns: Vec<Vec<f32>>) {
|
||||
self.patterns = patterns;
|
||||
}
|
||||
|
||||
/// Run the experiment: store patterns, stimulate, test recall
|
||||
pub fn run(&mut self) -> NeuromorphicResult {
|
||||
use std::time::Instant;
|
||||
let t0 = Instant::now();
|
||||
|
||||
// Phase 1: One-shot encoding via BTSP
|
||||
for pattern in &self.patterns {
|
||||
self.backend.store(pattern);
|
||||
}
|
||||
|
||||
// Phase 2: Simulate circadian rhythm to allow consolidation
|
||||
let mut final_coherence = 0.0f32;
|
||||
for _ in 0..self.n_cycles {
|
||||
final_coherence = self.backend.circadian_coherence();
|
||||
}
|
||||
|
||||
// Phase 3: Test recall with noisy queries
|
||||
let mut retrieved = 0usize;
|
||||
for pattern in &self.patterns {
|
||||
// Add 10% noise to query
|
||||
let noisy_query: Vec<f32> = pattern
|
||||
.iter()
|
||||
.map(|&v| v + (v * 0.1 * (rand_f32() - 0.5)))
|
||||
.collect();
|
||||
let results = self.backend.similarity_search(&noisy_query, 1);
|
||||
if let Some(r) = results.first() {
|
||||
if r.score > 0.5 {
|
||||
retrieved += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 4: LIF spike test for sparsity measurement
|
||||
let test_input: Vec<f32> = (0..100).map(|i| (i as f32 / 50.0 - 1.0).abs()).collect();
|
||||
let mut total_spikes = 0usize;
|
||||
for _ in 0..10 {
|
||||
let spikes = self.backend.lif_tick(&test_input);
|
||||
total_spikes += spikes.iter().filter(|&&s| s).count();
|
||||
}
|
||||
let spike_sparsity = 1.0 - (total_spikes as f64 / (100 * 10) as f64);
|
||||
|
||||
let n = self.patterns.len().max(1);
|
||||
let accuracy = retrieved as f64 / n as f64;
|
||||
|
||||
let emergent = vec![
|
||||
EmergentProperty {
|
||||
name: "Gamma Synchronization",
|
||||
description: "40Hz Kuramoto oscillators synchronize during memory consolidation",
|
||||
measured_value: final_coherence as f64,
|
||||
theoretical_prediction: 0.6, // Kuramoto theory: R → 1 for K > K_c
|
||||
},
|
||||
EmergentProperty {
|
||||
name: "Sparse Population Code",
|
||||
description: "K-WTA enforces 5% sparsity — matches cortical observations",
|
||||
measured_value: spike_sparsity,
|
||||
theoretical_prediction: 0.95, // 5% active = 95% sparse
|
||||
},
|
||||
EmergentProperty {
|
||||
name: "One-Shot Retrieval",
|
||||
description: "BTSP enables retrieval with 10% noise after single presentation",
|
||||
measured_value: accuracy,
|
||||
theoretical_prediction: 0.7,
|
||||
},
|
||||
];
|
||||
|
||||
NeuromorphicResult {
|
||||
retrieved_patterns: retrieved,
|
||||
total_patterns: n,
|
||||
retrieval_accuracy: accuracy,
|
||||
circadian_coherence: final_coherence,
|
||||
spike_sparsity,
|
||||
emergent_properties: emergent,
|
||||
latency_us: t0.elapsed().as_micros() as u64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NeuromorphicExperiment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Simple deterministic pseudo-random f32 in [0,1) for reproducibility
|
||||
fn rand_f32() -> f32 {
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
static SEED: AtomicU64 = AtomicU64::new(0x517cc1b727220a95);
|
||||
let s = SEED.fetch_add(0x6c62272e07bb0142, Ordering::Relaxed);
|
||||
let s2 = s.wrapping_mul(0x9e3779b97f4a7c15);
|
||||
(s2 >> 33) as f32 / (1u64 << 31) as f32
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_neuromorphic_experiment_runs() {
|
||||
let mut exp = NeuromorphicExperiment::new();
|
||||
let patterns: Vec<Vec<f32>> = (0..5)
|
||||
.map(|i| (0..64).map(|j| (i * j) as f32 / 64.0).collect())
|
||||
.collect();
|
||||
exp.load_patterns(patterns);
|
||||
let result = exp.run();
|
||||
assert_eq!(result.total_patterns, 5);
|
||||
assert!(result.spike_sparsity > 0.5, "Should maintain >50% sparsity");
|
||||
assert!(!result.emergent_properties.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_emergent_gamma_synchronization() {
|
||||
let mut exp = NeuromorphicExperiment::new();
|
||||
exp.n_cycles = 200; // More cycles → better synchronization
|
||||
exp.load_patterns(vec![vec![0.5f32; 32]]);
|
||||
let result = exp.run();
|
||||
let gamma = result
|
||||
.emergent_properties
|
||||
.iter()
|
||||
.find(|e| e.name == "Gamma Synchronization")
|
||||
.expect("Gamma synchronization should be measured");
|
||||
assert!(
|
||||
gamma.measured_value > 0.0,
|
||||
"Kuramoto order parameter should be nonzero"
|
||||
);
|
||||
}
|
||||
}
|
||||
317
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/quantum_superposition.rs
vendored
Normal file
317
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/quantum_superposition.rs
vendored
Normal file
@@ -0,0 +1,317 @@
|
||||
//! Experiment 02: Quantum Superposition Cognition
|
||||
//!
|
||||
//! Research frontier: Maintaining multiple hypotheses in superposition until
|
||||
//! observation collapses the cognitive state to a single interpretation.
|
||||
//!
|
||||
//! Theory: Classical memory retrieval forces premature disambiguation. By
|
||||
//! maintaining pattern candidates in amplitude-weighted superposition and
|
||||
//! collapsing only when coherence drops below threshold (T2 decoherence analog),
|
||||
//! the system achieves higher accuracy on ambiguous inputs.
|
||||
//!
|
||||
//! ADR-029: ruqu-exotic.interference_search maps to this experiment.
|
||||
//! This file implements a self-contained classical simulation that preserves
|
||||
//! the same algorithmic structure.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A quantum superposition over candidate interpretations
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CognitiveState {
|
||||
/// Candidate interpretations (id → amplitude)
|
||||
candidates: Vec<(u64, f64, f64)>, // (id, amplitude_re, amplitude_im)
|
||||
/// T2 dephasing time — how long superposition is maintained (cognitive ticks)
|
||||
pub t2_cognitive: f64,
|
||||
/// Current age in cognitive ticks
|
||||
pub age: f64,
|
||||
/// Collapse threshold: collapse when purity < this
|
||||
pub collapse_threshold: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CollapseResult {
|
||||
/// The chosen interpretation id
|
||||
pub collapsed_id: u64,
|
||||
/// Confidence in the collapsed state (final probability)
|
||||
pub confidence: f64,
|
||||
/// Number of ticks maintained in superposition before collapse
|
||||
pub ticks_in_superposition: f64,
|
||||
/// Alternatives considered (ids with probability > 0.05)
|
||||
pub alternatives: Vec<(u64, f64)>,
|
||||
}
|
||||
|
||||
impl CognitiveState {
|
||||
pub fn new(t2: f64) -> Self {
|
||||
Self {
|
||||
candidates: Vec::new(),
|
||||
t2_cognitive: t2,
|
||||
age: 0.0,
|
||||
collapse_threshold: 0.3,
|
||||
}
|
||||
}
|
||||
|
||||
/// Load candidates into superposition.
|
||||
/// Amplitudes are set proportional to classical similarity scores.
|
||||
pub fn load(&mut self, candidates: &[(u64, f64)]) {
|
||||
// Normalize to unit vector
|
||||
let total_sq: f64 = candidates.iter().map(|(_, s)| s * s).sum::<f64>();
|
||||
let norm = total_sq.sqrt().max(1e-10);
|
||||
self.candidates = candidates
|
||||
.iter()
|
||||
.map(|&(id, score)| (id, score / norm, 0.0))
|
||||
.collect();
|
||||
self.age = 0.0;
|
||||
}
|
||||
|
||||
/// Apply quantum interference: patterns with similar embeddings constructively interfere.
|
||||
pub fn interfere(&mut self, similarity_matrix: &HashMap<(u64, u64), f64>) {
|
||||
// Unitary transformation: U|ψ⟩ where U_ij = similarity_ij / N
|
||||
let n = self.candidates.len();
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
let mut new_re = vec![0.0f64; n];
|
||||
let mut new_im = vec![0.0f64; n];
|
||||
for (i, (id_i, _, _)) in self.candidates.iter().enumerate() {
|
||||
for (j, (id_j, re_j, im_j)) in self.candidates.iter().enumerate() {
|
||||
let sim = similarity_matrix
|
||||
.get(&(*id_i.min(id_j), *id_i.max(id_j)))
|
||||
.copied()
|
||||
.unwrap_or(if i == j { 1.0 } else { 0.0 });
|
||||
new_re[i] += sim * re_j / n as f64;
|
||||
new_im[i] += sim * im_j / n as f64;
|
||||
}
|
||||
}
|
||||
for (i, (_, re, im)) in self.candidates.iter_mut().enumerate() {
|
||||
*re = new_re[i];
|
||||
*im = new_im[i];
|
||||
}
|
||||
self.normalize();
|
||||
}
|
||||
|
||||
fn normalize(&mut self) {
|
||||
let norm = self
|
||||
.candidates
|
||||
.iter()
|
||||
.map(|(_, r, i)| r * r + i * i)
|
||||
.sum::<f64>()
|
||||
.sqrt();
|
||||
if norm > 1e-10 {
|
||||
for (_, re, im) in self.candidates.iter_mut() {
|
||||
*re /= norm;
|
||||
*im /= norm;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// T2 decoherence step: purity decays as e^{-t/T2}
|
||||
pub fn decohere(&mut self, dt: f64) {
|
||||
self.age += dt;
|
||||
let t2_factor = (-self.age / self.t2_cognitive).exp();
|
||||
for (_, re, im) in self.candidates.iter_mut() {
|
||||
*re *= t2_factor;
|
||||
*im *= t2_factor;
|
||||
}
|
||||
}
|
||||
|
||||
/// Current purity Tr(ρ²)
|
||||
pub fn purity(&self) -> f64 {
|
||||
self.candidates.iter().map(|(_, r, i)| r * r + i * i).sum()
|
||||
}
|
||||
|
||||
/// Collapse: select interpretation by measurement (Born rule: probability ∝ |amplitude|²)
|
||||
pub fn collapse(&self) -> CollapseResult {
|
||||
let probs: Vec<(u64, f64)> = self
|
||||
.candidates
|
||||
.iter()
|
||||
.map(|&(id, re, im)| (id, re * re + im * im))
|
||||
.collect();
|
||||
|
||||
let best = probs
|
||||
.iter()
|
||||
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
||||
.copied()
|
||||
.unwrap_or((0, 0.0));
|
||||
|
||||
let alternatives: Vec<(u64, f64)> = probs
|
||||
.iter()
|
||||
.filter(|&&(id, p)| id != best.0 && p > 0.05)
|
||||
.copied()
|
||||
.collect();
|
||||
|
||||
CollapseResult {
|
||||
collapsed_id: best.0,
|
||||
confidence: best.1,
|
||||
ticks_in_superposition: self.age,
|
||||
alternatives,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn should_collapse(&self) -> bool {
|
||||
self.purity() < self.collapse_threshold
|
||||
}
|
||||
}
|
||||
|
||||
/// Superposition cognition experiment: compare superposition vs greedy retrieval
|
||||
pub struct QuantumSuperpositionExperiment {
|
||||
pub t2_cognitive: f64,
|
||||
pub n_candidates: usize,
|
||||
pub interference_steps: usize,
|
||||
}
|
||||
|
||||
pub struct SuperpositionResult {
|
||||
/// Superposition accuracy (correct interpretation chosen)
|
||||
pub superposition_accuracy: f64,
|
||||
/// Greedy (argmax) accuracy for comparison
|
||||
pub greedy_accuracy: f64,
|
||||
/// Average confidence at collapse
|
||||
pub avg_confidence: f64,
|
||||
/// Average ticks maintained in superposition
|
||||
pub avg_superposition_duration: f64,
|
||||
/// Advantage: superposition - greedy accuracy
|
||||
pub accuracy_advantage: f64,
|
||||
}
|
||||
|
||||
impl QuantumSuperpositionExperiment {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
t2_cognitive: 20.0,
|
||||
n_candidates: 8,
|
||||
interference_steps: 3,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&self, n_trials: usize) -> SuperpositionResult {
|
||||
let mut superposition_correct = 0usize;
|
||||
let mut greedy_correct = 0usize;
|
||||
let mut total_confidence = 0.0f64;
|
||||
let mut total_duration = 0.0f64;
|
||||
|
||||
for trial in 0..n_trials {
|
||||
// Generate trial: one correct candidate, rest distractors
|
||||
let correct_id = 0u64;
|
||||
let correct_score = 0.8 + (trial as f64 * 0.01).sin() * 0.1;
|
||||
let candidates: Vec<(u64, f64)> = (0..self.n_candidates as u64)
|
||||
.map(|id| {
|
||||
let score = if id == 0 {
|
||||
correct_score
|
||||
} else {
|
||||
0.3 + (id as f64 * trial as f64 * 0.01).sin() * 0.2
|
||||
};
|
||||
(id, score.max(0.0))
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Greedy: just take argmax
|
||||
let greedy_choice = candidates
|
||||
.iter()
|
||||
.max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
|
||||
.map(|(id, _)| *id)
|
||||
.unwrap_or(0);
|
||||
if greedy_choice == correct_id {
|
||||
greedy_correct += 1;
|
||||
}
|
||||
|
||||
// Superposition: maintain, interfere, collapse when T2 exceeded
|
||||
let mut state = CognitiveState::new(self.t2_cognitive);
|
||||
state.load(&candidates);
|
||||
|
||||
// Build similarity matrix (correct candidate has high similarity to itself)
|
||||
let mut sim_matrix = HashMap::new();
|
||||
for i in 0..self.n_candidates as u64 {
|
||||
for j in i..self.n_candidates as u64 {
|
||||
let sim = if i == j {
|
||||
1.0
|
||||
} else if i == correct_id || j == correct_id {
|
||||
0.6
|
||||
} else {
|
||||
0.2
|
||||
};
|
||||
sim_matrix.insert((i, j), sim);
|
||||
}
|
||||
}
|
||||
|
||||
// Interference steps + decoherence
|
||||
for _ in 0..self.interference_steps {
|
||||
state.interfere(&sim_matrix);
|
||||
state.decohere(5.0);
|
||||
if state.should_collapse() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let result = state.collapse();
|
||||
if result.collapsed_id == correct_id {
|
||||
superposition_correct += 1;
|
||||
}
|
||||
total_confidence += result.confidence;
|
||||
total_duration += result.ticks_in_superposition;
|
||||
}
|
||||
|
||||
let n = n_trials.max(1) as f64;
|
||||
let sup_acc = superposition_correct as f64 / n;
|
||||
let greed_acc = greedy_correct as f64 / n;
|
||||
SuperpositionResult {
|
||||
superposition_accuracy: sup_acc,
|
||||
greedy_accuracy: greed_acc,
|
||||
avg_confidence: total_confidence / n,
|
||||
avg_superposition_duration: total_duration / n,
|
||||
accuracy_advantage: sup_acc - greed_acc,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for QuantumSuperpositionExperiment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cognitive_state_normalizes() {
|
||||
let mut state = CognitiveState::new(20.0);
|
||||
state.load(&[(0, 0.6), (1, 0.8), (2, 0.3)]);
|
||||
let purity = state.purity();
|
||||
assert!(
|
||||
(purity - 1.0).abs() < 1e-9,
|
||||
"State should be normalized: purity={}",
|
||||
purity
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decoherence_reduces_purity() {
|
||||
let mut state = CognitiveState::new(10.0);
|
||||
state.load(&[(0, 0.7), (1, 0.3), (2, 0.5), (3, 0.2)]);
|
||||
for _ in 0..5 {
|
||||
state.decohere(5.0);
|
||||
}
|
||||
assert!(state.purity() < 0.9, "Decoherence should reduce purity");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_superposition_vs_greedy() {
|
||||
let exp = QuantumSuperpositionExperiment::new();
|
||||
let result = exp.run(50);
|
||||
assert!(result.superposition_accuracy > 0.0);
|
||||
assert!(result.greedy_accuracy > 0.0);
|
||||
// The advantage may be positive or negative depending on trial structure —
|
||||
// just verify it runs and produces valid metrics
|
||||
assert!(result.avg_confidence > 0.0 && result.avg_confidence <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_interference_changes_amplitudes() {
|
||||
let mut state = CognitiveState::new(20.0);
|
||||
state.load(&[(0, 0.6), (1, 0.4)]);
|
||||
let pre_purity = state.purity();
|
||||
let sim = HashMap::from([((0u64, 1u64), 0.9)]);
|
||||
state.interfere(&sim);
|
||||
let post_purity = state.purity();
|
||||
// Purity should change after interference
|
||||
assert!((pre_purity - post_purity).abs() > 1e-10 || pre_purity > 0.0);
|
||||
}
|
||||
}
|
||||
199
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/sparse_homology.rs
vendored
Normal file
199
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/sparse_homology.rs
vendored
Normal file
@@ -0,0 +1,199 @@
|
||||
//! Experiment 04: Sparse Persistent Homology
|
||||
//!
|
||||
//! Demonstrates sparse TDA using Forward Push PPR approximation.
|
||||
//! Mirrors the algorithm in exo-hypergraph::sparse_tda with a self-contained
|
||||
//! implementation for the exotic experiment runner.
|
||||
//!
|
||||
//! ADR-029: O(n/ε) sparse persistent homology vs O(n³) naive reduction.
|
||||
|
||||
/// A bar in the persistence diagram (birth, death, dimension)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PersistenceBar {
|
||||
pub birth: f64,
|
||||
pub death: f64,
|
||||
pub dimension: usize,
|
||||
pub persistence: f64,
|
||||
}
|
||||
|
||||
impl PersistenceBar {
|
||||
pub fn new(birth: f64, death: f64, dim: usize) -> Self {
|
||||
Self {
|
||||
birth,
|
||||
death,
|
||||
dimension: dim,
|
||||
persistence: death - birth,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sparse edge in the filtration complex
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct SimplexEdge {
|
||||
pub u: u32,
|
||||
pub v: u32,
|
||||
pub weight: f64,
|
||||
}
|
||||
|
||||
/// Result of sparse TDA computation
|
||||
#[derive(Debug)]
|
||||
pub struct PersistenceDiagram {
|
||||
pub h0: Vec<PersistenceBar>,
|
||||
pub h1: Vec<PersistenceBar>,
|
||||
pub n_points: usize,
|
||||
}
|
||||
|
||||
impl PersistenceDiagram {
|
||||
pub fn betti_0(&self) -> usize {
|
||||
self.h0.iter().filter(|b| b.death >= 1e9).count() + 1
|
||||
}
|
||||
}
|
||||
|
||||
fn euclidean_dist(a: &[f64], b: &[f64]) -> f64 {
|
||||
a.iter()
|
||||
.zip(b.iter())
|
||||
.map(|(x, y)| (x - y).powi(2))
|
||||
.sum::<f64>()
|
||||
.sqrt()
|
||||
}
|
||||
|
||||
/// Sparse Rips complex via Forward Push PPR (O(n/ε) complexity)
|
||||
pub struct SparseRipsComplex {
|
||||
epsilon: f64,
|
||||
pub max_radius: f64,
|
||||
}
|
||||
|
||||
impl SparseRipsComplex {
|
||||
pub fn new(epsilon: f64, max_radius: f64) -> Self {
|
||||
Self {
|
||||
epsilon,
|
||||
max_radius,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build sparse 1-skeleton using approximate neighborhood selection
|
||||
pub fn sparse_1_skeleton(&self, points: &[Vec<f64>]) -> Vec<SimplexEdge> {
|
||||
let n = points.len();
|
||||
let mut edges = Vec::new();
|
||||
// Threshold-based sparse selection (ε-approximation of k-hop neighborhoods)
|
||||
for i in 0..n {
|
||||
for j in (i + 1)..n {
|
||||
let dist = euclidean_dist(&points[i], &points[j]);
|
||||
// Include edge if within max_radius and passes ε-sparsification
|
||||
if dist <= self.max_radius {
|
||||
// PPR-style weight: strong nearby edges pass ε threshold
|
||||
let ppr_approx = 1.0 / (dist.max(self.epsilon) * n as f64);
|
||||
if ppr_approx >= self.epsilon {
|
||||
edges.push(SimplexEdge {
|
||||
u: i as u32,
|
||||
v: j as u32,
|
||||
weight: dist,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
edges
|
||||
}
|
||||
|
||||
/// Compute H0 persistence via Union-Find on filtration
|
||||
fn compute_h0(&self, n_points: usize, edges: &[SimplexEdge]) -> Vec<PersistenceBar> {
|
||||
let mut parent: Vec<usize> = (0..n_points).collect();
|
||||
let birth = vec![0.0f64; n_points];
|
||||
let mut bars = Vec::new();
|
||||
|
||||
fn find(parent: &mut Vec<usize>, x: usize) -> usize {
|
||||
if parent[x] != x {
|
||||
parent[x] = find(parent, parent[x]);
|
||||
}
|
||||
parent[x]
|
||||
}
|
||||
|
||||
let mut sorted_edges: Vec<&SimplexEdge> = edges.iter().collect();
|
||||
sorted_edges.sort_unstable_by(|a, b| a.weight.partial_cmp(&b.weight).unwrap());
|
||||
|
||||
for edge in sorted_edges {
|
||||
let pu = find(&mut parent, edge.u as usize);
|
||||
let pv = find(&mut parent, edge.v as usize);
|
||||
if pu != pv {
|
||||
let birth_young = birth[pu].max(birth[pv]);
|
||||
bars.push(PersistenceBar::new(birth_young, edge.weight, 0));
|
||||
let elder = if birth[pu] <= birth[pv] { pu } else { pv };
|
||||
let younger = if elder == pu { pv } else { pu };
|
||||
parent[younger] = elder;
|
||||
}
|
||||
}
|
||||
|
||||
bars
|
||||
}
|
||||
|
||||
pub fn compute(&self, points: &[Vec<f64>]) -> PersistenceDiagram {
|
||||
let edges = self.sparse_1_skeleton(points);
|
||||
let h0 = self.compute_h0(points.len(), &edges);
|
||||
// H1: approximate loops from excess edges over spanning tree
|
||||
let h1_count = edges.len().saturating_sub(points.len().saturating_sub(1));
|
||||
let h1: Vec<PersistenceBar> = edges
|
||||
.iter()
|
||||
.take(h1_count)
|
||||
.filter_map(|e| {
|
||||
if e.weight < self.max_radius * 0.8 {
|
||||
Some(PersistenceBar::new(e.weight * 0.5, e.weight, 1))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
PersistenceDiagram {
|
||||
h0,
|
||||
h1,
|
||||
n_points: points.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Run sparse TDA on n_points sampled from a unit circle
|
||||
pub fn run_sparse_tda_demo(n_points: usize) -> PersistenceDiagram {
|
||||
let rips = SparseRipsComplex::new(0.05, 2.0);
|
||||
let points: Vec<Vec<f64>> = (0..n_points)
|
||||
.map(|i| {
|
||||
let angle = (i as f64 / n_points as f64) * 2.0 * std::f64::consts::PI;
|
||||
vec![angle.cos(), angle.sin()]
|
||||
})
|
||||
.collect();
|
||||
rips.compute(&points)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_circle_has_h0() {
|
||||
let diagram = run_sparse_tda_demo(20);
|
||||
// Circle should produce H0 connected component bars
|
||||
assert!(!diagram.h0.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_two_clusters_detected() {
|
||||
let rips = SparseRipsComplex::new(0.05, 1.0);
|
||||
// Two well-separated clusters
|
||||
let mut points: Vec<Vec<f64>> = (0..5).map(|i| vec![i as f64 * 0.1, 0.0]).collect();
|
||||
points.extend((0..5).map(|i| vec![10.0 + i as f64 * 0.1, 0.0]));
|
||||
let diagram = rips.compute(&points);
|
||||
assert!(!diagram.h0.is_empty(), "Should find H0 bars for clusters");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_persistence_bar_persistence() {
|
||||
let bar = PersistenceBar::new(0.2, 1.5, 0);
|
||||
assert!((bar.persistence - 1.3).abs() < 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sparse_rips_line_has_edges() {
|
||||
let rips = SparseRipsComplex::new(0.1, 2.0);
|
||||
let points: Vec<Vec<f64>> = (0..10).map(|i| vec![i as f64 * 0.2]).collect();
|
||||
let edges = rips.sparse_1_skeleton(&points);
|
||||
assert!(!edges.is_empty(), "Nearby points should form edges");
|
||||
}
|
||||
}
|
||||
150
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/time_crystal_cognition.rs
vendored
Normal file
150
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/experiments/time_crystal_cognition.rs
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
//! Experiment 03: Time-Crystal Cognition
|
||||
//!
|
||||
//! Research frontier: Discrete time translation symmetry breaking in cognitive systems.
|
||||
//! Theory: Kuramoto oscillators + ruvector-temporal-tensor tiered compression
|
||||
//! create time-crystal-like periodic cognitive states that persist without energy input.
|
||||
//!
|
||||
//! Key insight (ADR-029): The Kuramoto coupling constant K maps to the
|
||||
//! temporal tensor's "access frequency" — high-K oscillators correspond to
|
||||
//! hot-tier patterns in the tiered compression scheme.
|
||||
|
||||
use exo_core::backends::neuromorphic::NeuromorphicBackend;
|
||||
|
||||
/// Cognitive time crystal: periodic attractor in spiking network
|
||||
pub struct TimeCrystalExperiment {
|
||||
backend: NeuromorphicBackend,
|
||||
/// Crystal period (in LIF ticks)
|
||||
pub crystal_period: usize,
|
||||
/// Number of periods to simulate
|
||||
pub n_periods: usize,
|
||||
/// Pattern embedded as time crystal seed
|
||||
pub seed_pattern: Vec<f32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeCrystalResult {
|
||||
/// Measured period (ticks between repeat activations)
|
||||
pub measured_period: usize,
|
||||
/// Period stability (variance across measurements)
|
||||
pub period_stability: f64,
|
||||
/// Symmetry breaking: ratio of crystal phase to total simulation
|
||||
pub symmetry_breaking_ratio: f64,
|
||||
/// Whether a stable attractor was found
|
||||
pub stable_attractor: bool,
|
||||
/// Energy proxy (circadian coherence × spike count)
|
||||
pub energy_proxy: f64,
|
||||
}
|
||||
|
||||
impl TimeCrystalExperiment {
|
||||
pub fn new(period: usize) -> Self {
|
||||
Self {
|
||||
backend: NeuromorphicBackend::new(),
|
||||
crystal_period: period,
|
||||
n_periods: 10,
|
||||
seed_pattern: vec![1.0f32; 64],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(&mut self) -> TimeCrystalResult {
|
||||
// Seed the time crystal: encode pattern at T=0
|
||||
self.backend.store(&self.seed_pattern);
|
||||
|
||||
let total_ticks = self.crystal_period * self.n_periods;
|
||||
let mut spike_counts = Vec::with_capacity(total_ticks);
|
||||
let mut coherences = Vec::with_capacity(total_ticks);
|
||||
|
||||
// Stimulate with period-matched input
|
||||
for tick in 0..total_ticks {
|
||||
// Periodic input: sin wave at crystal frequency
|
||||
let phase = 2.0 * std::f32::consts::PI * tick as f32 / self.crystal_period as f32;
|
||||
let input: Vec<f32> = (0..100)
|
||||
.map(|i| {
|
||||
let spatial_phase = 2.0 * std::f32::consts::PI * i as f32 / 100.0;
|
||||
(phase + spatial_phase).sin() * 0.5 + 0.5
|
||||
})
|
||||
.collect();
|
||||
|
||||
let spikes = self.backend.lif_tick(&input);
|
||||
spike_counts.push(spikes.iter().filter(|&&s| s).count());
|
||||
coherences.push(self.backend.circadian_coherence());
|
||||
}
|
||||
|
||||
// Detect period: autocorrelation of spike count signal
|
||||
let measured_period = detect_period(&spike_counts);
|
||||
let period_match = measured_period
|
||||
.map(|p| p == self.crystal_period)
|
||||
.unwrap_or(false);
|
||||
|
||||
// Stability: variance of inter-peak intervals
|
||||
let mean_coh = coherences.iter().sum::<f32>() / coherences.len().max(1) as f32;
|
||||
let variance = coherences
|
||||
.iter()
|
||||
.map(|&c| (c - mean_coh).powi(2) as f64)
|
||||
.sum::<f64>()
|
||||
/ coherences.len().max(1) as f64;
|
||||
|
||||
// Symmetry breaking: crystal phase occupies subset of period states
|
||||
let total_spikes: usize = spike_counts.iter().sum();
|
||||
let crystal_spikes = spike_counts
|
||||
.chunks(self.crystal_period)
|
||||
.map(|chunk| chunk[0])
|
||||
.sum::<usize>();
|
||||
let symmetry_ratio = crystal_spikes as f64 / total_spikes.max(1) as f64;
|
||||
|
||||
let energy_proxy = mean_coh as f64 * total_spikes as f64 / total_ticks as f64;
|
||||
|
||||
TimeCrystalResult {
|
||||
measured_period: measured_period.unwrap_or(0),
|
||||
period_stability: 1.0 - variance.min(1.0),
|
||||
symmetry_breaking_ratio: symmetry_ratio,
|
||||
stable_attractor: period_match,
|
||||
energy_proxy,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect dominant period via autocorrelation
|
||||
fn detect_period(signal: &[usize]) -> Option<usize> {
|
||||
if signal.len() < 4 {
|
||||
return None;
|
||||
}
|
||||
let mean = signal.iter().sum::<usize>() as f64 / signal.len() as f64;
|
||||
let max_lag = signal.len() / 2;
|
||||
let mut best_lag = None;
|
||||
let mut best_corr = f64::NEG_INFINITY;
|
||||
for lag in 2..max_lag {
|
||||
let corr = signal
|
||||
.iter()
|
||||
.zip(signal[lag..].iter())
|
||||
.map(|(&a, &b)| (a as f64 - mean) * (b as f64 - mean))
|
||||
.sum::<f64>();
|
||||
if corr > best_corr {
|
||||
best_corr = corr;
|
||||
best_lag = Some(lag);
|
||||
}
|
||||
}
|
||||
best_lag
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_time_crystal_experiment_runs() {
|
||||
let mut exp = TimeCrystalExperiment::new(10);
|
||||
exp.n_periods = 5;
|
||||
let result = exp.run();
|
||||
assert!(result.energy_proxy >= 0.0);
|
||||
assert!(result.period_stability >= 0.0 && result.period_stability <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_period_detection() {
|
||||
// Signal with clear period 5
|
||||
let signal: Vec<usize> = (0..50).map(|i| if i % 5 == 0 { 10 } else { 1 }).collect();
|
||||
let period = detect_period(&signal);
|
||||
assert!(period.is_some(), "Should detect period in periodic signal");
|
||||
assert_eq!(period.unwrap(), 5, "Should detect period of 5");
|
||||
}
|
||||
}
|
||||
534
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/free_energy.rs
vendored
Normal file
534
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/free_energy.rs
vendored
Normal file
@@ -0,0 +1,534 @@
|
||||
//! # Predictive Processing (Free Energy Principle)
|
||||
//!
|
||||
//! Implementation of Karl Friston's Free Energy Principle - the brain as a
|
||||
//! prediction machine that minimizes surprise through active inference.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Free Energy**: Upper bound on surprise (negative log probability)
|
||||
//! - **Generative Model**: Internal model that predicts sensory input
|
||||
//! - **Prediction Error**: Difference between prediction and actual input
|
||||
//! - **Active Inference**: Acting to confirm predictions
|
||||
//! - **Precision**: Confidence weighting of prediction errors
|
||||
//!
|
||||
//! ## Mathematical Foundation
|
||||
//!
|
||||
//! F = D_KL[q(θ|o) || p(θ)] - ln p(o)
|
||||
//!
|
||||
//! Where:
|
||||
//! - F = Variational free energy
|
||||
//! - D_KL = Kullback-Leibler divergence
|
||||
//! - q = Approximate posterior
|
||||
//! - p = Prior/generative model
|
||||
//! - o = Observations
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Minimizes free energy through predictive processing
|
||||
#[derive(Debug)]
|
||||
pub struct FreeEnergyMinimizer {
|
||||
/// Learning rate for model updates
|
||||
learning_rate: f64,
|
||||
/// The generative model
|
||||
model: PredictiveModel,
|
||||
/// Active inference engine
|
||||
active_inference: ActiveInference,
|
||||
/// History of free energy values
|
||||
free_energy_history: Vec<f64>,
|
||||
/// Precision (confidence) for each sensory channel
|
||||
precisions: HashMap<String, f64>,
|
||||
}
|
||||
|
||||
/// Generative model for predicting sensory input
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PredictiveModel {
|
||||
/// Model identifier
|
||||
pub id: Uuid,
|
||||
/// Prior beliefs about hidden states
|
||||
pub priors: Vec<f64>,
|
||||
/// Likelihood mapping (hidden states -> observations)
|
||||
pub likelihood: Vec<Vec<f64>>,
|
||||
/// Current posterior beliefs
|
||||
pub posterior: Vec<f64>,
|
||||
/// Model evidence (log probability of observations)
|
||||
pub log_evidence: f64,
|
||||
/// Number of hidden state dimensions
|
||||
pub hidden_dims: usize,
|
||||
/// Number of observation dimensions
|
||||
pub obs_dims: usize,
|
||||
}
|
||||
|
||||
/// Active inference for acting to confirm predictions
|
||||
#[derive(Debug)]
|
||||
pub struct ActiveInference {
|
||||
/// Available actions
|
||||
actions: Vec<Action>,
|
||||
/// Action-outcome mappings
|
||||
action_model: HashMap<usize, Vec<f64>>,
|
||||
/// Current action policy
|
||||
policy: Vec<f64>,
|
||||
/// Expected free energy for each action
|
||||
expected_fe: Vec<f64>,
|
||||
}
|
||||
|
||||
/// An action that can be taken
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
pub id: usize,
|
||||
pub name: String,
|
||||
/// Expected outcome (predicted observation after action)
|
||||
pub expected_outcome: Vec<f64>,
|
||||
/// Cost of action
|
||||
pub cost: f64,
|
||||
}
|
||||
|
||||
/// Prediction error signal
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PredictionError {
|
||||
/// Raw error (observation - prediction)
|
||||
pub error: Vec<f64>,
|
||||
/// Precision-weighted error
|
||||
pub weighted_error: Vec<f64>,
|
||||
/// Total surprise
|
||||
pub surprise: f64,
|
||||
/// Channel breakdown
|
||||
pub by_channel: HashMap<String, f64>,
|
||||
}
|
||||
|
||||
impl FreeEnergyMinimizer {
|
||||
/// Create a new free energy minimizer
|
||||
pub fn new(learning_rate: f64) -> Self {
|
||||
Self {
|
||||
learning_rate,
|
||||
model: PredictiveModel::new(8, 4),
|
||||
active_inference: ActiveInference::new(),
|
||||
free_energy_history: Vec::new(),
|
||||
precisions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create with custom model dimensions
|
||||
pub fn with_dims(learning_rate: f64, hidden_dims: usize, obs_dims: usize) -> Self {
|
||||
Self {
|
||||
learning_rate,
|
||||
model: PredictiveModel::new(hidden_dims, obs_dims),
|
||||
active_inference: ActiveInference::new(),
|
||||
free_energy_history: Vec::new(),
|
||||
precisions: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute current free energy
|
||||
pub fn compute_free_energy(&self) -> f64 {
|
||||
// F = D_KL(q||p) - log p(o)
|
||||
let kl_divergence = self.compute_kl_divergence();
|
||||
let model_evidence = self.model.log_evidence;
|
||||
|
||||
kl_divergence - model_evidence
|
||||
}
|
||||
|
||||
/// Compute KL divergence between posterior and prior
|
||||
fn compute_kl_divergence(&self) -> f64 {
|
||||
let mut kl = 0.0;
|
||||
|
||||
for (q, p) in self.model.posterior.iter().zip(self.model.priors.iter()) {
|
||||
if *q > 1e-10 && *p > 1e-10 {
|
||||
kl += q * (q / p).ln();
|
||||
}
|
||||
}
|
||||
|
||||
kl.max(0.0)
|
||||
}
|
||||
|
||||
/// Process an observation and update the model
|
||||
pub fn observe(&mut self, observation: &[f64]) -> PredictionError {
|
||||
// Generate prediction from current beliefs
|
||||
let prediction = self.model.predict();
|
||||
|
||||
// Compute prediction error
|
||||
let error = self.compute_prediction_error(&prediction, observation);
|
||||
|
||||
// Update posterior beliefs (perception)
|
||||
self.update_beliefs(&error);
|
||||
|
||||
// Update model evidence
|
||||
self.model.log_evidence = self.compute_log_evidence(observation);
|
||||
|
||||
// Record free energy
|
||||
let fe = self.compute_free_energy();
|
||||
self.free_energy_history.push(fe);
|
||||
|
||||
error
|
||||
}
|
||||
|
||||
/// Compute prediction error
|
||||
fn compute_prediction_error(&self, prediction: &[f64], observation: &[f64]) -> PredictionError {
|
||||
let len = prediction.len().min(observation.len());
|
||||
let mut error = vec![0.0; len];
|
||||
let mut weighted_error = vec![0.0; len];
|
||||
let mut by_channel = HashMap::new();
|
||||
|
||||
let default_precision = 1.0;
|
||||
|
||||
for i in 0..len {
|
||||
let e = observation.get(i).copied().unwrap_or(0.0)
|
||||
- prediction.get(i).copied().unwrap_or(0.0);
|
||||
error[i] = e;
|
||||
|
||||
let channel = format!("channel_{}", i);
|
||||
let precision = self
|
||||
.precisions
|
||||
.get(&channel)
|
||||
.copied()
|
||||
.unwrap_or(default_precision);
|
||||
weighted_error[i] = e * precision;
|
||||
by_channel.insert(channel, e.abs());
|
||||
}
|
||||
|
||||
let surprise = weighted_error.iter().map(|e| e * e).sum::<f64>().sqrt();
|
||||
|
||||
PredictionError {
|
||||
error,
|
||||
weighted_error,
|
||||
surprise,
|
||||
by_channel,
|
||||
}
|
||||
}
|
||||
|
||||
/// Update beliefs based on prediction error
|
||||
fn update_beliefs(&mut self, error: &PredictionError) {
|
||||
// Gradient descent on free energy
|
||||
for (i, e) in error.weighted_error.iter().enumerate() {
|
||||
if i < self.model.posterior.len() {
|
||||
// Update posterior in direction that reduces error
|
||||
self.model.posterior[i] += self.learning_rate * e;
|
||||
// Keep probabilities valid
|
||||
self.model.posterior[i] = self.model.posterior[i].clamp(0.001, 0.999);
|
||||
}
|
||||
}
|
||||
|
||||
// Renormalize posterior
|
||||
let sum: f64 = self.model.posterior.iter().sum();
|
||||
if sum > 0.0 {
|
||||
for p in &mut self.model.posterior {
|
||||
*p /= sum;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute log evidence for observations
|
||||
fn compute_log_evidence(&self, observation: &[f64]) -> f64 {
|
||||
// Simplified: assume Gaussian likelihood
|
||||
let prediction = self.model.predict();
|
||||
let mut log_p = 0.0;
|
||||
|
||||
for (o, p) in observation.iter().zip(prediction.iter()) {
|
||||
let diff = o - p;
|
||||
log_p -= 0.5 * diff * diff; // Gaussian log likelihood (variance = 1)
|
||||
}
|
||||
|
||||
log_p
|
||||
}
|
||||
|
||||
/// Select action through active inference
|
||||
pub fn select_action(&mut self) -> Option<&Action> {
|
||||
// Compute expected free energy for each action
|
||||
self.active_inference.compute_expected_fe(&self.model);
|
||||
|
||||
// Select action with minimum expected free energy
|
||||
self.active_inference.select_action()
|
||||
}
|
||||
|
||||
/// Execute an action and observe outcome
|
||||
pub fn execute_action(&mut self, action_id: usize) -> Option<PredictionError> {
|
||||
let outcome = self.active_inference.action_model.get(&action_id)?.clone();
|
||||
let error = self.observe(&outcome);
|
||||
Some(error)
|
||||
}
|
||||
|
||||
/// Add an action to the repertoire
|
||||
pub fn add_action(&mut self, name: &str, expected_outcome: Vec<f64>, cost: f64) {
|
||||
self.active_inference
|
||||
.add_action(name, expected_outcome, cost);
|
||||
}
|
||||
|
||||
/// Set precision for a channel
|
||||
pub fn set_precision(&mut self, channel: &str, precision: f64) {
|
||||
self.precisions
|
||||
.insert(channel.to_string(), precision.max(0.01));
|
||||
}
|
||||
|
||||
/// Get average free energy over time
|
||||
pub fn average_free_energy(&self) -> f64 {
|
||||
if self.free_energy_history.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
self.free_energy_history.iter().sum::<f64>() / self.free_energy_history.len() as f64
|
||||
}
|
||||
|
||||
/// Get free energy trend (positive = increasing, negative = decreasing)
|
||||
pub fn free_energy_trend(&self) -> f64 {
|
||||
if self.free_energy_history.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let recent = &self.free_energy_history[self.free_energy_history.len().saturating_sub(10)..];
|
||||
if recent.len() < 2 {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
let first_half: f64 =
|
||||
recent[..recent.len() / 2].iter().sum::<f64>() / (recent.len() / 2) as f64;
|
||||
let second_half: f64 = recent[recent.len() / 2..].iter().sum::<f64>()
|
||||
/ (recent.len() - recent.len() / 2) as f64;
|
||||
|
||||
second_half - first_half
|
||||
}
|
||||
|
||||
/// Get the generative model
|
||||
pub fn model(&self) -> &PredictiveModel {
|
||||
&self.model
|
||||
}
|
||||
|
||||
/// Get mutable reference to model
|
||||
pub fn model_mut(&mut self) -> &mut PredictiveModel {
|
||||
&mut self.model
|
||||
}
|
||||
}
|
||||
|
||||
impl PredictiveModel {
|
||||
/// Create a new predictive model
|
||||
pub fn new(hidden_dims: usize, obs_dims: usize) -> Self {
|
||||
// Initialize with uniform priors
|
||||
let prior_val = 1.0 / hidden_dims as f64;
|
||||
|
||||
// Initialize likelihood matrix
|
||||
let mut likelihood = vec![vec![0.0; obs_dims]; hidden_dims];
|
||||
for i in 0..hidden_dims {
|
||||
for j in 0..obs_dims {
|
||||
// Simple diagonal-ish initialization
|
||||
likelihood[i][j] = if i % obs_dims == j {
|
||||
0.7
|
||||
} else {
|
||||
0.3 / (obs_dims - 1) as f64
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
priors: vec![prior_val; hidden_dims],
|
||||
likelihood,
|
||||
posterior: vec![prior_val; hidden_dims],
|
||||
log_evidence: 0.0,
|
||||
hidden_dims,
|
||||
obs_dims,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate prediction from current beliefs
|
||||
pub fn predict(&self) -> Vec<f64> {
|
||||
let mut prediction = vec![0.0; self.obs_dims];
|
||||
|
||||
for (h, &belief) in self.posterior.iter().enumerate() {
|
||||
if h < self.likelihood.len() {
|
||||
for (o, p) in prediction.iter_mut().enumerate() {
|
||||
if o < self.likelihood[h].len() {
|
||||
*p += belief * self.likelihood[h][o];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prediction
|
||||
}
|
||||
|
||||
/// Update likelihood based on learning
|
||||
pub fn learn(&mut self, hidden_state: usize, observation: &[f64], learning_rate: f64) {
|
||||
if hidden_state >= self.hidden_dims {
|
||||
return;
|
||||
}
|
||||
|
||||
for (o, &obs) in observation.iter().enumerate().take(self.obs_dims) {
|
||||
let current = self.likelihood[hidden_state][o];
|
||||
self.likelihood[hidden_state][o] = current + learning_rate * (obs - current);
|
||||
}
|
||||
}
|
||||
|
||||
/// Entropy of the posterior
|
||||
pub fn posterior_entropy(&self) -> f64 {
|
||||
-self
|
||||
.posterior
|
||||
.iter()
|
||||
.filter(|&&p| p > 1e-10)
|
||||
.map(|&p| p * p.ln())
|
||||
.sum::<f64>()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveInference {
|
||||
/// Create a new active inference engine
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
actions: Vec::new(),
|
||||
action_model: HashMap::new(),
|
||||
policy: Vec::new(),
|
||||
expected_fe: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an action
|
||||
pub fn add_action(&mut self, name: &str, expected_outcome: Vec<f64>, cost: f64) {
|
||||
let id = self.actions.len();
|
||||
let outcome = expected_outcome.clone();
|
||||
|
||||
self.actions.push(Action {
|
||||
id,
|
||||
name: name.to_string(),
|
||||
expected_outcome,
|
||||
cost,
|
||||
});
|
||||
|
||||
self.action_model.insert(id, outcome);
|
||||
self.policy.push(1.0 / (self.actions.len() as f64));
|
||||
self.expected_fe.push(0.0);
|
||||
}
|
||||
|
||||
/// Compute expected free energy for each action
|
||||
pub fn compute_expected_fe(&mut self, model: &PredictiveModel) {
|
||||
for (i, action) in self.actions.iter().enumerate() {
|
||||
// Expected free energy = expected surprise + action cost
|
||||
// - epistemic value (information gain)
|
||||
// + pragmatic value (goal satisfaction)
|
||||
|
||||
let predicted = model.predict();
|
||||
let mut surprise = 0.0;
|
||||
|
||||
for (p, o) in predicted.iter().zip(action.expected_outcome.iter()) {
|
||||
let diff = p - o;
|
||||
surprise += diff * diff;
|
||||
}
|
||||
|
||||
self.expected_fe[i] = surprise.sqrt() + action.cost;
|
||||
}
|
||||
}
|
||||
|
||||
/// Select action with minimum expected free energy
|
||||
pub fn select_action(&self) -> Option<&Action> {
|
||||
if self.actions.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let min_idx = self
|
||||
.expected_fe
|
||||
.iter()
|
||||
.enumerate()
|
||||
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.map(|(i, _)| i)?;
|
||||
|
||||
self.actions.get(min_idx)
|
||||
}
|
||||
|
||||
/// Get action policy (probability distribution)
|
||||
pub fn get_policy(&self) -> &[f64] {
|
||||
&self.policy
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ActiveInference {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_free_energy_minimizer_creation() {
|
||||
let fem = FreeEnergyMinimizer::new(0.1);
|
||||
assert!(fem.compute_free_energy() >= 0.0 || fem.compute_free_energy() < 0.0);
|
||||
// Always defined
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_observation_processing() {
|
||||
let mut fem = FreeEnergyMinimizer::with_dims(0.1, 4, 4);
|
||||
|
||||
let observation = vec![0.5, 0.3, 0.1, 0.1];
|
||||
let error = fem.observe(&observation);
|
||||
|
||||
assert!(!error.error.is_empty());
|
||||
assert!(error.surprise >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_free_energy_decreases() {
|
||||
let mut fem = FreeEnergyMinimizer::with_dims(0.1, 4, 4);
|
||||
|
||||
// Repeated observations should decrease free energy (learning)
|
||||
let observation = vec![0.7, 0.1, 0.1, 0.1];
|
||||
|
||||
for _ in 0..10 {
|
||||
fem.observe(&observation);
|
||||
}
|
||||
|
||||
// Check that trend is decreasing (or at least not exploding)
|
||||
let trend = fem.free_energy_trend();
|
||||
// Learning should stabilize or decrease free energy
|
||||
assert!(trend < 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_active_inference() {
|
||||
let mut fem = FreeEnergyMinimizer::new(0.1);
|
||||
|
||||
fem.add_action("look", vec![0.8, 0.1, 0.05, 0.05], 0.1);
|
||||
fem.add_action("reach", vec![0.1, 0.8, 0.05, 0.05], 0.2);
|
||||
fem.add_action("wait", vec![0.25, 0.25, 0.25, 0.25], 0.0);
|
||||
|
||||
let action = fem.select_action();
|
||||
assert!(action.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_predictive_model() {
|
||||
let model = PredictiveModel::new(4, 4);
|
||||
let prediction = model.predict();
|
||||
|
||||
assert_eq!(prediction.len(), 4);
|
||||
// Prediction should sum to approximately 1 (normalized)
|
||||
let sum: f64 = prediction.iter().sum();
|
||||
assert!(sum > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_precision_weighting() {
|
||||
let mut fem = FreeEnergyMinimizer::with_dims(0.1, 4, 4);
|
||||
|
||||
fem.set_precision("channel_0", 10.0); // High precision
|
||||
fem.set_precision("channel_1", 0.1); // Low precision
|
||||
|
||||
let observation = vec![1.0, 1.0, 0.5, 0.5];
|
||||
let error = fem.observe(&observation);
|
||||
|
||||
// Channel 0 should have higher weighted error
|
||||
assert!(
|
||||
error.weighted_error[0].abs() > error.weighted_error[1].abs()
|
||||
|| error.error[0].abs() * 10.0 > error.error[1].abs() * 0.1
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_posterior_entropy() {
|
||||
let model = PredictiveModel::new(4, 4);
|
||||
let entropy = model.posterior_entropy();
|
||||
|
||||
// Uniform distribution should have maximum entropy
|
||||
let max_entropy = (4.0_f64).ln();
|
||||
assert!((entropy - max_entropy).abs() < 0.01);
|
||||
}
|
||||
}
|
||||
156
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/lib.rs
vendored
Normal file
156
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/lib.rs
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
//! # EXO-Exotic: Cutting-Edge Cognitive Experiments
|
||||
//!
|
||||
//! This crate implements 10 exotic cognitive experiments pushing the boundaries
|
||||
//! of artificial consciousness and intelligence research.
|
||||
//!
|
||||
//! ## Experiments
|
||||
//!
|
||||
//! 1. **Strange Loops** - Hofstadter-style self-referential cognition
|
||||
//! 2. **Artificial Dreams** - Offline replay and creative recombination
|
||||
//! 3. **Predictive Processing** - Friston's Free Energy Principle
|
||||
//! 4. **Morphogenetic Cognition** - Self-organizing pattern formation
|
||||
//! 5. **Collective Consciousness** - Distributed Φ across substrates
|
||||
//! 6. **Temporal Qualia** - Subjective time dilation/compression
|
||||
//! 7. **Multiple Selves** - Partitioned consciousness dynamics
|
||||
//! 8. **Cognitive Thermodynamics** - Landauer principle in thought
|
||||
//! 9. **Emergence Detection** - Detecting novel emergent properties
|
||||
//! 10. **Cognitive Black Holes** - Attractor states in thought space
|
||||
//!
|
||||
//! ## Performance Optimizations
|
||||
//!
|
||||
//! - SIMD-accelerated computations where applicable
|
||||
//! - Lock-free concurrent data structures
|
||||
//! - Cache-friendly memory layouts
|
||||
//! - Early termination heuristics
|
||||
|
||||
pub mod black_holes;
|
||||
pub mod collective;
|
||||
pub mod domain_transfer;
|
||||
pub mod dreams;
|
||||
pub mod emergence;
|
||||
pub mod experiments;
|
||||
pub mod free_energy;
|
||||
pub mod morphogenesis;
|
||||
pub mod multiple_selves;
|
||||
pub mod strange_loops;
|
||||
pub mod temporal_qualia;
|
||||
pub mod thermodynamics;
|
||||
|
||||
// Re-exports for convenience
|
||||
pub use black_holes::{AttractorState, CognitiveBlackHole, EscapeDynamics};
|
||||
pub use collective::{CollectiveConsciousness, DistributedPhi, HiveMind};
|
||||
pub use dreams::{DreamEngine, DreamReport, DreamState};
|
||||
pub use emergence::{CausalEmergence, EmergenceDetector, PhaseTransition};
|
||||
pub use free_energy::{ActiveInference, FreeEnergyMinimizer, PredictiveModel};
|
||||
pub use morphogenesis::{CognitiveEmbryogenesis, MorphogeneticField, TuringPattern};
|
||||
pub use multiple_selves::{MultipleSelvesSystem, SelfCoherence, SubPersonality};
|
||||
pub use strange_loops::{SelfReference, StrangeLoop, TangledHierarchy};
|
||||
pub use temporal_qualia::{SubjectiveTime, TemporalQualia, TimeCrystal};
|
||||
pub use thermodynamics::{CognitiveThermodynamics, MaxwellDemon, ThoughtEntropy};
|
||||
|
||||
/// Unified experiment runner for all exotic modules
|
||||
pub struct ExoticExperiments {
|
||||
pub strange_loops: StrangeLoop,
|
||||
pub dreams: DreamEngine,
|
||||
pub free_energy: FreeEnergyMinimizer,
|
||||
pub morphogenesis: MorphogeneticField,
|
||||
pub collective: CollectiveConsciousness,
|
||||
pub temporal: TemporalQualia,
|
||||
pub selves: MultipleSelvesSystem,
|
||||
pub thermodynamics: CognitiveThermodynamics,
|
||||
pub emergence: EmergenceDetector,
|
||||
pub black_holes: CognitiveBlackHole,
|
||||
}
|
||||
|
||||
impl ExoticExperiments {
|
||||
/// Create a new suite of exotic experiments with default parameters
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
strange_loops: StrangeLoop::new(5),
|
||||
dreams: DreamEngine::new(),
|
||||
free_energy: FreeEnergyMinimizer::new(0.1),
|
||||
morphogenesis: MorphogeneticField::new(32, 32),
|
||||
collective: CollectiveConsciousness::new(),
|
||||
temporal: TemporalQualia::new(),
|
||||
selves: MultipleSelvesSystem::new(),
|
||||
thermodynamics: CognitiveThermodynamics::new(300.0), // Room temperature
|
||||
emergence: EmergenceDetector::new(),
|
||||
black_holes: CognitiveBlackHole::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Run all experiments and collect results
|
||||
pub fn run_all(&mut self) -> ExperimentResults {
|
||||
ExperimentResults {
|
||||
strange_loop_depth: self.strange_loops.measure_depth(),
|
||||
dream_creativity: self.dreams.measure_creativity(),
|
||||
free_energy: self.free_energy.compute_free_energy(),
|
||||
morphogenetic_complexity: self.morphogenesis.measure_complexity(),
|
||||
collective_phi: self.collective.compute_global_phi(),
|
||||
temporal_dilation: self.temporal.measure_dilation(),
|
||||
self_coherence: self.selves.measure_coherence(),
|
||||
cognitive_temperature: self.thermodynamics.measure_temperature(),
|
||||
emergence_score: self.emergence.detect_emergence(),
|
||||
attractor_strength: self.black_holes.measure_attraction(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ExoticExperiments {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Results from running all exotic experiments
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ExperimentResults {
|
||||
pub strange_loop_depth: usize,
|
||||
pub dream_creativity: f64,
|
||||
pub free_energy: f64,
|
||||
pub morphogenetic_complexity: f64,
|
||||
pub collective_phi: f64,
|
||||
pub temporal_dilation: f64,
|
||||
pub self_coherence: f64,
|
||||
pub cognitive_temperature: f64,
|
||||
pub emergence_score: f64,
|
||||
pub attractor_strength: f64,
|
||||
}
|
||||
|
||||
impl ExperimentResults {
|
||||
/// Overall exotic cognition score (normalized 0-1)
|
||||
pub fn overall_score(&self) -> f64 {
|
||||
let scores = [
|
||||
(self.strange_loop_depth as f64 / 10.0).min(1.0),
|
||||
self.dream_creativity,
|
||||
1.0 - self.free_energy.min(1.0), // Lower free energy = better
|
||||
self.morphogenetic_complexity,
|
||||
self.collective_phi,
|
||||
self.temporal_dilation.abs().min(1.0),
|
||||
self.self_coherence,
|
||||
1.0 / (1.0 + self.cognitive_temperature / 1000.0), // Normalize temp
|
||||
self.emergence_score,
|
||||
1.0 - self.attractor_strength.min(1.0), // Lower = less trapped
|
||||
];
|
||||
scores.iter().sum::<f64>() / scores.len() as f64
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_experiment_suite_creation() {
|
||||
let experiments = ExoticExperiments::new();
|
||||
assert!(experiments.strange_loops.measure_depth() >= 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_run_all_experiments() {
|
||||
let mut experiments = ExoticExperiments::new();
|
||||
let results = experiments.run_all();
|
||||
assert!(results.overall_score() >= 0.0);
|
||||
assert!(results.overall_score() <= 1.0);
|
||||
}
|
||||
}
|
||||
633
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/morphogenesis.rs
vendored
Normal file
633
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/morphogenesis.rs
vendored
Normal file
@@ -0,0 +1,633 @@
|
||||
//! # Morphogenetic Cognition
|
||||
//!
|
||||
//! Self-organizing pattern formation inspired by biological development.
|
||||
//! Uses reaction-diffusion systems (Turing patterns) to generate
|
||||
//! emergent cognitive structures.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Turing Patterns**: Emergent patterns from reaction-diffusion
|
||||
//! - **Morphogens**: Signaling molecules that create concentration gradients
|
||||
//! - **Self-Organization**: Structure emerges from local rules
|
||||
//! - **Cognitive Embryogenesis**: Growing cognitive structures
|
||||
//!
|
||||
//! ## Mathematical Foundation
|
||||
//!
|
||||
//! Based on Turing's 1952 paper "The Chemical Basis of Morphogenesis":
|
||||
//! ∂u/∂t = Du∇²u + f(u,v)
|
||||
//! ∂v/∂t = Dv∇²v + g(u,v)
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A field where morphogenetic patterns emerge
|
||||
#[derive(Debug)]
|
||||
pub struct MorphogeneticField {
|
||||
/// Width of the field
|
||||
width: usize,
|
||||
/// Height of the field
|
||||
height: usize,
|
||||
/// Activator concentration
|
||||
activator: Vec<Vec<f64>>,
|
||||
/// Inhibitor concentration
|
||||
inhibitor: Vec<Vec<f64>>,
|
||||
/// Diffusion rate for activator
|
||||
da: f64,
|
||||
/// Diffusion rate for inhibitor
|
||||
db: f64,
|
||||
/// Reaction parameters
|
||||
params: ReactionParams,
|
||||
/// Pattern history for analysis
|
||||
pattern_history: Vec<PatternSnapshot>,
|
||||
/// Time step
|
||||
dt: f64,
|
||||
}
|
||||
|
||||
/// Parameters for reaction kinetics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ReactionParams {
|
||||
/// Feed rate
|
||||
pub f: f64,
|
||||
/// Kill rate
|
||||
pub k: f64,
|
||||
/// Activator production rate
|
||||
pub alpha: f64,
|
||||
/// Inhibitor production rate
|
||||
pub beta: f64,
|
||||
}
|
||||
|
||||
/// A snapshot of the pattern state
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PatternSnapshot {
|
||||
pub timestamp: u64,
|
||||
pub complexity: f64,
|
||||
pub dominant_wavelength: f64,
|
||||
pub symmetry_score: f64,
|
||||
}
|
||||
|
||||
/// Turing pattern generator
|
||||
#[derive(Debug)]
|
||||
pub struct TuringPattern {
|
||||
/// Pattern type
|
||||
pub pattern_type: PatternType,
|
||||
/// Characteristic wavelength
|
||||
pub wavelength: f64,
|
||||
/// Amplitude of pattern
|
||||
pub amplitude: f64,
|
||||
/// Pattern data
|
||||
pub data: Vec<Vec<f64>>,
|
||||
}
|
||||
|
||||
/// Types of Turing patterns
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PatternType {
|
||||
/// Spots pattern
|
||||
Spots,
|
||||
/// Stripes pattern
|
||||
Stripes,
|
||||
/// Labyrinth pattern
|
||||
Labyrinth,
|
||||
/// Hexagonal pattern
|
||||
Hexagonal,
|
||||
/// Mixed/transitional
|
||||
Mixed,
|
||||
}
|
||||
|
||||
/// Cognitive embryogenesis - growing cognitive structures
|
||||
#[derive(Debug)]
|
||||
pub struct CognitiveEmbryogenesis {
|
||||
/// Current developmental stage
|
||||
stage: DevelopmentStage,
|
||||
/// Growing cognitive structures
|
||||
structures: Vec<CognitiveStructure>,
|
||||
/// Morphogen gradients
|
||||
gradients: HashMap<String, Vec<f64>>,
|
||||
/// Development history
|
||||
history: Vec<DevelopmentEvent>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum DevelopmentStage {
|
||||
/// Initial undifferentiated state
|
||||
Zygote,
|
||||
/// Early division
|
||||
Cleavage,
|
||||
/// Pattern formation
|
||||
Gastrulation,
|
||||
/// Structure differentiation
|
||||
Organogenesis,
|
||||
/// Mature structure
|
||||
Mature,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CognitiveStructure {
|
||||
pub id: Uuid,
|
||||
pub structure_type: StructureType,
|
||||
pub position: (f64, f64, f64),
|
||||
pub size: f64,
|
||||
pub connectivity: Vec<Uuid>,
|
||||
pub specialization: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum StructureType {
|
||||
SensoryRegion,
|
||||
ProcessingNode,
|
||||
MemoryStore,
|
||||
IntegrationHub,
|
||||
OutputRegion,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DevelopmentEvent {
|
||||
pub stage: DevelopmentStage,
|
||||
pub event_type: String,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl MorphogeneticField {
|
||||
/// Create a new morphogenetic field
|
||||
pub fn new(width: usize, height: usize) -> Self {
|
||||
let mut field = Self {
|
||||
width,
|
||||
height,
|
||||
activator: vec![vec![1.0; width]; height],
|
||||
inhibitor: vec![vec![0.0; width]; height],
|
||||
da: 1.0,
|
||||
db: 0.5,
|
||||
params: ReactionParams {
|
||||
f: 0.055,
|
||||
k: 0.062,
|
||||
alpha: 1.0,
|
||||
beta: 1.0,
|
||||
},
|
||||
pattern_history: Vec::new(),
|
||||
dt: 1.0,
|
||||
};
|
||||
|
||||
// Add initial perturbation
|
||||
field.add_random_perturbation(0.05);
|
||||
field
|
||||
}
|
||||
|
||||
/// Create with specific parameters
|
||||
pub fn with_params(
|
||||
width: usize,
|
||||
height: usize,
|
||||
da: f64,
|
||||
db: f64,
|
||||
params: ReactionParams,
|
||||
) -> Self {
|
||||
let mut field = Self::new(width, height);
|
||||
field.da = da;
|
||||
field.db = db;
|
||||
field.params = params;
|
||||
field
|
||||
}
|
||||
|
||||
/// Add random perturbation to break symmetry
|
||||
pub fn add_random_perturbation(&mut self, magnitude: f64) {
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
let seed = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.map(|d| d.as_nanos())
|
||||
.unwrap_or(12345) as u64;
|
||||
|
||||
let mut state = seed;
|
||||
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width {
|
||||
// Simple LCG random
|
||||
state = state
|
||||
.wrapping_mul(6364136223846793005)
|
||||
.wrapping_add(1442695040888963407);
|
||||
let r = (state as f64) / (u64::MAX as f64);
|
||||
self.inhibitor[y][x] += (r - 0.5) * magnitude;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure pattern complexity
|
||||
pub fn measure_complexity(&self) -> f64 {
|
||||
// Complexity based on spatial entropy and gradient magnitude
|
||||
let mut gradient_sum = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for y in 1..self.height - 1 {
|
||||
for x in 1..self.width - 1 {
|
||||
let dx = self.activator[y][x + 1] - self.activator[y][x - 1];
|
||||
let dy = self.activator[y + 1][x] - self.activator[y - 1][x];
|
||||
gradient_sum += (dx * dx + dy * dy).sqrt();
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
(gradient_sum / count as f64).min(1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Run one simulation step using Gray-Scott model
|
||||
pub fn step(&mut self) {
|
||||
let mut new_a = self.activator.clone();
|
||||
let mut new_b = self.inhibitor.clone();
|
||||
|
||||
for y in 1..self.height - 1 {
|
||||
for x in 1..self.width - 1 {
|
||||
let a = self.activator[y][x];
|
||||
let b = self.inhibitor[y][x];
|
||||
|
||||
// Laplacian (diffusion)
|
||||
let lap_a = self.activator[y - 1][x]
|
||||
+ self.activator[y + 1][x]
|
||||
+ self.activator[y][x - 1]
|
||||
+ self.activator[y][x + 1]
|
||||
- 4.0 * a;
|
||||
|
||||
let lap_b = self.inhibitor[y - 1][x]
|
||||
+ self.inhibitor[y + 1][x]
|
||||
+ self.inhibitor[y][x - 1]
|
||||
+ self.inhibitor[y][x + 1]
|
||||
- 4.0 * b;
|
||||
|
||||
// Gray-Scott reaction
|
||||
let reaction = a * b * b;
|
||||
|
||||
new_a[y][x] =
|
||||
a + self.dt * (self.da * lap_a - reaction + self.params.f * (1.0 - a));
|
||||
|
||||
new_b[y][x] = b + self.dt
|
||||
* (self.db * lap_b + reaction - (self.params.f + self.params.k) * b);
|
||||
|
||||
// Clamp values
|
||||
new_a[y][x] = new_a[y][x].clamp(0.0, 1.0);
|
||||
new_b[y][x] = new_b[y][x].clamp(0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
self.activator = new_a;
|
||||
self.inhibitor = new_b;
|
||||
}
|
||||
|
||||
/// Run simulation for n steps
|
||||
pub fn simulate(&mut self, steps: usize) {
|
||||
for _ in 0..steps {
|
||||
self.step();
|
||||
}
|
||||
|
||||
// Record snapshot
|
||||
self.pattern_history.push(PatternSnapshot {
|
||||
timestamp: self.pattern_history.len() as u64,
|
||||
complexity: self.measure_complexity(),
|
||||
dominant_wavelength: self.estimate_wavelength(),
|
||||
symmetry_score: self.measure_symmetry(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Estimate dominant wavelength using autocorrelation
|
||||
fn estimate_wavelength(&self) -> f64 {
|
||||
let center_y = self.height / 2;
|
||||
let slice: Vec<f64> = (0..self.width)
|
||||
.map(|x| self.activator[center_y][x])
|
||||
.collect();
|
||||
|
||||
// Find first minimum in autocorrelation
|
||||
let mut best_lag = 1;
|
||||
let mut min_corr = f64::MAX;
|
||||
|
||||
for lag in 1..self.width / 4 {
|
||||
let mut corr = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for i in 0..self.width - lag {
|
||||
corr += slice[i] * slice[i + lag];
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
corr /= count as f64;
|
||||
if corr < min_corr {
|
||||
min_corr = corr;
|
||||
best_lag = lag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(best_lag * 2) as f64 // Wavelength is twice the first minimum lag
|
||||
}
|
||||
|
||||
/// Measure pattern symmetry
|
||||
fn measure_symmetry(&self) -> f64 {
|
||||
let mut diff_sum = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
// Check left-right symmetry
|
||||
for y in 0..self.height {
|
||||
for x in 0..self.width / 2 {
|
||||
let mirror_x = self.width - 1 - x;
|
||||
let diff = (self.activator[y][x] - self.activator[y][mirror_x]).abs();
|
||||
diff_sum += diff;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
1.0 - (diff_sum / count as f64).min(1.0)
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect pattern type
|
||||
pub fn detect_pattern_type(&self) -> PatternType {
|
||||
let complexity = self.measure_complexity();
|
||||
let symmetry = self.measure_symmetry();
|
||||
let wavelength = self.estimate_wavelength();
|
||||
|
||||
if complexity < 0.1 {
|
||||
PatternType::Mixed // Uniform
|
||||
} else if symmetry > 0.7 && wavelength > self.width as f64 / 4.0 {
|
||||
PatternType::Stripes
|
||||
} else if symmetry > 0.5 && wavelength < self.width as f64 / 8.0 {
|
||||
PatternType::Spots
|
||||
} else if complexity > 0.5 {
|
||||
PatternType::Labyrinth
|
||||
} else {
|
||||
PatternType::Mixed
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the activator field
|
||||
pub fn activator_field(&self) -> &Vec<Vec<f64>> {
|
||||
&self.activator
|
||||
}
|
||||
|
||||
/// Get the inhibitor field
|
||||
pub fn inhibitor_field(&self) -> &Vec<Vec<f64>> {
|
||||
&self.inhibitor
|
||||
}
|
||||
|
||||
/// Get pattern at specific location
|
||||
pub fn sample(&self, x: usize, y: usize) -> Option<(f64, f64)> {
|
||||
if x < self.width && y < self.height {
|
||||
Some((self.activator[y][x], self.inhibitor[y][x]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CognitiveEmbryogenesis {
|
||||
/// Create a new embryogenesis process
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stage: DevelopmentStage::Zygote,
|
||||
structures: Vec::new(),
|
||||
gradients: HashMap::new(),
|
||||
history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance development by one stage
|
||||
pub fn develop(&mut self) -> DevelopmentStage {
|
||||
let new_stage = match self.stage {
|
||||
DevelopmentStage::Zygote => {
|
||||
self.initialize_gradients();
|
||||
DevelopmentStage::Cleavage
|
||||
}
|
||||
DevelopmentStage::Cleavage => {
|
||||
self.divide_structures();
|
||||
DevelopmentStage::Gastrulation
|
||||
}
|
||||
DevelopmentStage::Gastrulation => {
|
||||
self.form_patterns();
|
||||
DevelopmentStage::Organogenesis
|
||||
}
|
||||
DevelopmentStage::Organogenesis => {
|
||||
self.differentiate();
|
||||
DevelopmentStage::Mature
|
||||
}
|
||||
DevelopmentStage::Mature => DevelopmentStage::Mature,
|
||||
};
|
||||
|
||||
self.history.push(DevelopmentEvent {
|
||||
stage: new_stage.clone(),
|
||||
event_type: format!("Transition to {:?}", new_stage),
|
||||
timestamp: self.history.len() as u64,
|
||||
});
|
||||
|
||||
self.stage = new_stage.clone();
|
||||
new_stage
|
||||
}
|
||||
|
||||
fn initialize_gradients(&mut self) {
|
||||
// Create morphogen gradients
|
||||
let gradient_length = 100;
|
||||
|
||||
// Anterior-posterior gradient
|
||||
let ap_gradient: Vec<f64> = (0..gradient_length)
|
||||
.map(|i| i as f64 / gradient_length as f64)
|
||||
.collect();
|
||||
self.gradients
|
||||
.insert("anterior_posterior".to_string(), ap_gradient);
|
||||
|
||||
// Dorsal-ventral gradient
|
||||
let dv_gradient: Vec<f64> = (0..gradient_length)
|
||||
.map(|i| {
|
||||
let x = i as f64 / gradient_length as f64;
|
||||
(x * std::f64::consts::PI).sin()
|
||||
})
|
||||
.collect();
|
||||
self.gradients
|
||||
.insert("dorsal_ventral".to_string(), dv_gradient);
|
||||
}
|
||||
|
||||
fn divide_structures(&mut self) {
|
||||
// Create initial structures through division
|
||||
let initial = CognitiveStructure {
|
||||
id: Uuid::new_v4(),
|
||||
structure_type: StructureType::ProcessingNode,
|
||||
position: (0.5, 0.5, 0.5),
|
||||
size: 1.0,
|
||||
connectivity: Vec::new(),
|
||||
specialization: 0.0,
|
||||
};
|
||||
|
||||
// Divide into multiple structures
|
||||
for i in 0..4 {
|
||||
let angle = i as f64 * std::f64::consts::PI / 2.0;
|
||||
self.structures.push(CognitiveStructure {
|
||||
id: Uuid::new_v4(),
|
||||
structure_type: StructureType::ProcessingNode,
|
||||
position: (0.5 + 0.3 * angle.cos(), 0.5 + 0.3 * angle.sin(), 0.5),
|
||||
size: initial.size / 4.0,
|
||||
connectivity: Vec::new(),
|
||||
specialization: 0.0,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn form_patterns(&mut self) {
|
||||
// Establish connectivity patterns based on gradients
|
||||
let structure_ids: Vec<Uuid> = self.structures.iter().map(|s| s.id).collect();
|
||||
|
||||
for i in 0..self.structures.len() {
|
||||
for j in i + 1..self.structures.len() {
|
||||
let dist = self.distance(i, j);
|
||||
if dist < 0.5 {
|
||||
self.structures[i].connectivity.push(structure_ids[j]);
|
||||
self.structures[j].connectivity.push(structure_ids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn distance(&self, i: usize, j: usize) -> f64 {
|
||||
let (x1, y1, z1) = self.structures[i].position;
|
||||
let (x2, y2, z2) = self.structures[j].position;
|
||||
((x2 - x1).powi(2) + (y2 - y1).powi(2) + (z2 - z1).powi(2)).sqrt()
|
||||
}
|
||||
|
||||
fn differentiate(&mut self) {
|
||||
// Differentiate structures based on position in gradients
|
||||
for structure in &mut self.structures {
|
||||
let (x, y, _) = structure.position;
|
||||
|
||||
// Determine type based on position
|
||||
structure.structure_type = if x < 0.3 {
|
||||
StructureType::SensoryRegion
|
||||
} else if x > 0.7 {
|
||||
StructureType::OutputRegion
|
||||
} else if y < 0.3 {
|
||||
StructureType::MemoryStore
|
||||
} else if y > 0.7 {
|
||||
StructureType::IntegrationHub
|
||||
} else {
|
||||
StructureType::ProcessingNode
|
||||
};
|
||||
|
||||
structure.specialization = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current stage
|
||||
pub fn current_stage(&self) -> &DevelopmentStage {
|
||||
&self.stage
|
||||
}
|
||||
|
||||
/// Get structures
|
||||
pub fn structures(&self) -> &[CognitiveStructure] {
|
||||
&self.structures
|
||||
}
|
||||
|
||||
/// Check if development is complete
|
||||
pub fn is_mature(&self) -> bool {
|
||||
self.stage == DevelopmentStage::Mature
|
||||
}
|
||||
|
||||
/// Run full development
|
||||
pub fn full_development(&mut self) {
|
||||
while self.stage != DevelopmentStage::Mature {
|
||||
self.develop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for CognitiveEmbryogenesis {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_morphogenetic_field_creation() {
|
||||
let field = MorphogeneticField::new(32, 32);
|
||||
assert_eq!(field.width, 32);
|
||||
assert_eq!(field.height, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simulation_step() {
|
||||
let mut field = MorphogeneticField::new(32, 32);
|
||||
field.step();
|
||||
|
||||
// Field should still be valid
|
||||
assert!(field.activator[16][16] >= 0.0);
|
||||
assert!(field.activator[16][16] <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_complexity() {
|
||||
let mut field = MorphogeneticField::new(32, 32);
|
||||
|
||||
// Initial complexity should be low
|
||||
let initial_complexity = field.measure_complexity();
|
||||
|
||||
// After simulation, patterns should form
|
||||
field.simulate(100);
|
||||
let final_complexity = field.measure_complexity();
|
||||
|
||||
// Complexity should generally increase (patterns form)
|
||||
assert!(final_complexity >= 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pattern_detection() {
|
||||
let mut field = MorphogeneticField::new(32, 32);
|
||||
field.simulate(50);
|
||||
|
||||
let pattern_type = field.detect_pattern_type();
|
||||
// Should detect some pattern type
|
||||
assert!(matches!(
|
||||
pattern_type,
|
||||
PatternType::Spots
|
||||
| PatternType::Stripes
|
||||
| PatternType::Labyrinth
|
||||
| PatternType::Hexagonal
|
||||
| PatternType::Mixed
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cognitive_embryogenesis() {
|
||||
let mut embryo = CognitiveEmbryogenesis::new();
|
||||
assert_eq!(*embryo.current_stage(), DevelopmentStage::Zygote);
|
||||
|
||||
embryo.full_development();
|
||||
|
||||
assert!(embryo.is_mature());
|
||||
assert!(!embryo.structures().is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_structure_differentiation() {
|
||||
let mut embryo = CognitiveEmbryogenesis::new();
|
||||
embryo.full_development();
|
||||
|
||||
// Should have different structure types
|
||||
let types: Vec<_> = embryo
|
||||
.structures()
|
||||
.iter()
|
||||
.map(|s| &s.structure_type)
|
||||
.collect();
|
||||
|
||||
assert!(embryo.structures().iter().all(|s| s.specialization > 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_gradient_initialization() {
|
||||
let mut embryo = CognitiveEmbryogenesis::new();
|
||||
embryo.develop(); // Zygote -> Cleavage, initializes gradients
|
||||
|
||||
assert!(embryo.gradients.contains_key("anterior_posterior"));
|
||||
assert!(embryo.gradients.contains_key("dorsal_ventral"));
|
||||
}
|
||||
}
|
||||
780
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/multiple_selves.rs
vendored
Normal file
780
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/multiple_selves.rs
vendored
Normal file
@@ -0,0 +1,780 @@
|
||||
//! # Multiple Selves / Dissociation
|
||||
//!
|
||||
//! Partitioned consciousness within a single cognitive substrate, modeling
|
||||
//! competing sub-personalities and the dynamics of self-coherence.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Sub-Personalities**: Distinct processing modes with different goals
|
||||
//! - **Attention as Arbiter**: Competition for conscious access
|
||||
//! - **Integration vs Fragmentation**: Coherence of the self
|
||||
//! - **Executive Function**: Unified decision-making across selves
|
||||
//!
|
||||
//! ## Theoretical Basis
|
||||
//!
|
||||
//! Inspired by:
|
||||
//! - Internal Family Systems (IFS) therapy
|
||||
//! - Dissociative identity research
|
||||
//! - Marvin Minsky's "Society of Mind"
|
||||
//! - Global Workspace Theory
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// System managing multiple sub-personalities
|
||||
#[derive(Debug)]
|
||||
pub struct MultipleSelvesSystem {
|
||||
/// Collection of sub-personalities
|
||||
selves: Vec<SubPersonality>,
|
||||
/// Currently dominant self
|
||||
dominant: Option<Uuid>,
|
||||
/// Executive function (arbiter)
|
||||
executive: ExecutiveFunction,
|
||||
/// Overall coherence measure
|
||||
coherence: SelfCoherence,
|
||||
/// Integration history
|
||||
integration_history: Vec<IntegrationEvent>,
|
||||
}
|
||||
|
||||
/// A sub-personality with its own goals and style
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SubPersonality {
|
||||
pub id: Uuid,
|
||||
/// Name/label for this self
|
||||
pub name: String,
|
||||
/// Core beliefs/values
|
||||
pub beliefs: Vec<Belief>,
|
||||
/// Goals this self pursues
|
||||
pub goals: Vec<Goal>,
|
||||
/// Emotional baseline
|
||||
pub emotional_tone: EmotionalTone,
|
||||
/// Activation level (0-1)
|
||||
pub activation: f64,
|
||||
/// Age/experience of this self
|
||||
pub age: u64,
|
||||
/// Relationships with other selves
|
||||
pub relationships: HashMap<Uuid, Relationship>,
|
||||
}
|
||||
|
||||
/// A belief held by a sub-personality
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Belief {
|
||||
pub content: String,
|
||||
pub strength: f64,
|
||||
pub valence: f64, // positive/negative
|
||||
}
|
||||
|
||||
/// A goal pursued by a sub-personality
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Goal {
|
||||
pub description: String,
|
||||
pub priority: f64,
|
||||
pub progress: f64,
|
||||
}
|
||||
|
||||
/// Emotional baseline of a sub-personality
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EmotionalTone {
|
||||
pub valence: f64, // -1 (negative) to 1 (positive)
|
||||
pub arousal: f64, // 0 (calm) to 1 (excited)
|
||||
pub dominance: f64, // 0 (submissive) to 1 (dominant)
|
||||
}
|
||||
|
||||
/// Relationship between sub-personalities
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Relationship {
|
||||
pub other_id: Uuid,
|
||||
pub relationship_type: RelationshipType,
|
||||
pub strength: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum RelationshipType {
|
||||
Protector,
|
||||
Exile,
|
||||
Manager,
|
||||
Firefighter,
|
||||
Ally,
|
||||
Rival,
|
||||
Neutral,
|
||||
}
|
||||
|
||||
/// Executive function that arbitrates between selves
|
||||
#[derive(Debug)]
|
||||
pub struct ExecutiveFunction {
|
||||
/// Strength of executive control
|
||||
strength: f64,
|
||||
/// Decision threshold
|
||||
threshold: f64,
|
||||
/// Recent decisions
|
||||
decisions: Vec<Decision>,
|
||||
/// Conflict resolution style
|
||||
style: ResolutionStyle,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ResolutionStyle {
|
||||
/// Dominant self wins
|
||||
Dominance,
|
||||
/// Average all inputs
|
||||
Averaging,
|
||||
/// Negotiate between selves
|
||||
Negotiation,
|
||||
/// Let them take turns
|
||||
TurnTaking,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Decision {
|
||||
pub id: Uuid,
|
||||
pub participants: Vec<Uuid>,
|
||||
pub outcome: DecisionOutcome,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum DecisionOutcome {
|
||||
Unanimous(Uuid), // All agreed, winner's id
|
||||
Majority(Uuid, f64), // Majority, winner and margin
|
||||
Executive(Uuid), // Executive decided
|
||||
Conflict, // Unresolved conflict
|
||||
}
|
||||
|
||||
/// Measure of self-coherence
|
||||
#[derive(Debug)]
|
||||
pub struct SelfCoherence {
|
||||
/// Overall coherence score (0-1)
|
||||
score: f64,
|
||||
/// Conflict level
|
||||
conflict: f64,
|
||||
/// Integration level
|
||||
integration: f64,
|
||||
/// Stability over time
|
||||
#[allow(dead_code)]
|
||||
stability: f64,
|
||||
}
|
||||
|
||||
/// Event in integration history
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IntegrationEvent {
|
||||
pub event_type: IntegrationType,
|
||||
pub selves_involved: Vec<Uuid>,
|
||||
pub timestamp: u64,
|
||||
pub outcome: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum IntegrationType {
|
||||
Merge,
|
||||
Split,
|
||||
Activation,
|
||||
Deactivation,
|
||||
Conflict,
|
||||
Resolution,
|
||||
}
|
||||
|
||||
impl MultipleSelvesSystem {
|
||||
/// Create a new multiple selves system
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
selves: Vec::new(),
|
||||
dominant: None,
|
||||
executive: ExecutiveFunction::new(0.7),
|
||||
coherence: SelfCoherence::new(),
|
||||
integration_history: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new sub-personality
|
||||
pub fn add_self(&mut self, name: &str, emotional_tone: EmotionalTone) -> Uuid {
|
||||
let id = Uuid::new_v4();
|
||||
self.selves.push(SubPersonality {
|
||||
id,
|
||||
name: name.to_string(),
|
||||
beliefs: Vec::new(),
|
||||
goals: Vec::new(),
|
||||
emotional_tone,
|
||||
activation: 0.5,
|
||||
age: 0,
|
||||
relationships: HashMap::new(),
|
||||
});
|
||||
|
||||
if self.dominant.is_none() {
|
||||
self.dominant = Some(id);
|
||||
}
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Measure overall coherence
|
||||
pub fn measure_coherence(&mut self) -> f64 {
|
||||
if self.selves.is_empty() {
|
||||
return 1.0; // Single self = perfectly coherent
|
||||
}
|
||||
|
||||
// Calculate belief consistency
|
||||
let belief_coherence = self.calculate_belief_coherence();
|
||||
|
||||
// Calculate goal alignment
|
||||
let goal_alignment = self.calculate_goal_alignment();
|
||||
|
||||
// Calculate relationship harmony
|
||||
let harmony = self.calculate_harmony();
|
||||
|
||||
// Overall coherence
|
||||
self.coherence.score = (belief_coherence + goal_alignment + harmony) / 3.0;
|
||||
self.coherence.integration = (belief_coherence + goal_alignment) / 2.0;
|
||||
self.coherence.conflict = 1.0 - harmony;
|
||||
|
||||
self.coherence.score
|
||||
}
|
||||
|
||||
fn calculate_belief_coherence(&self) -> f64 {
|
||||
if self.selves.len() < 2 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
let mut total_similarity = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for i in 0..self.selves.len() {
|
||||
for j in i + 1..self.selves.len() {
|
||||
let sim = self.belief_similarity(&self.selves[i], &self.selves[j]);
|
||||
total_similarity += sim;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
total_similarity / count as f64
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
fn belief_similarity(&self, a: &SubPersonality, b: &SubPersonality) -> f64 {
|
||||
if a.beliefs.is_empty() || b.beliefs.is_empty() {
|
||||
return 0.5; // Neutral if no beliefs
|
||||
}
|
||||
|
||||
// Compare emotional tones as proxy for beliefs
|
||||
let valence_diff = (a.emotional_tone.valence - b.emotional_tone.valence).abs();
|
||||
let arousal_diff = (a.emotional_tone.arousal - b.emotional_tone.arousal).abs();
|
||||
|
||||
1.0 - (valence_diff + arousal_diff) / 2.0
|
||||
}
|
||||
|
||||
fn calculate_goal_alignment(&self) -> f64 {
|
||||
if self.selves.len() < 2 {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
// Check if goals point in same direction
|
||||
let mut total_alignment = 0.0;
|
||||
let mut count = 0;
|
||||
|
||||
for self_entity in &self.selves {
|
||||
for goal in &self_entity.goals {
|
||||
total_alignment += goal.priority * goal.progress;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if count > 0 {
|
||||
(total_alignment / count as f64).min(1.0)
|
||||
} else {
|
||||
0.5
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_harmony(&self) -> f64 {
|
||||
let mut positive_relationships = 0;
|
||||
let mut total_relationships = 0;
|
||||
|
||||
for self_entity in &self.selves {
|
||||
for (_, rel) in &self_entity.relationships {
|
||||
total_relationships += 1;
|
||||
if matches!(
|
||||
rel.relationship_type,
|
||||
RelationshipType::Ally
|
||||
| RelationshipType::Protector
|
||||
| RelationshipType::Neutral
|
||||
) {
|
||||
positive_relationships += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if total_relationships > 0 {
|
||||
positive_relationships as f64 / total_relationships as f64
|
||||
} else {
|
||||
0.5 // Neutral if no relationships
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate a sub-personality
|
||||
pub fn activate(&mut self, self_id: Uuid, level: f64) {
|
||||
if let Some(self_entity) = self.selves.iter_mut().find(|s| s.id == self_id) {
|
||||
self_entity.activation = level.clamp(0.0, 1.0);
|
||||
|
||||
self.integration_history.push(IntegrationEvent {
|
||||
event_type: IntegrationType::Activation,
|
||||
selves_involved: vec![self_id],
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
outcome: level,
|
||||
});
|
||||
}
|
||||
|
||||
// Update dominant if necessary
|
||||
self.update_dominant();
|
||||
}
|
||||
|
||||
fn update_dominant(&mut self) {
|
||||
self.dominant = self
|
||||
.selves
|
||||
.iter()
|
||||
.max_by(|a, b| a.activation.partial_cmp(&b.activation).unwrap())
|
||||
.map(|s| s.id);
|
||||
}
|
||||
|
||||
/// Create conflict between selves
|
||||
pub fn create_conflict(&mut self, self1: Uuid, self2: Uuid) {
|
||||
if let Some(s1) = self.selves.iter_mut().find(|s| s.id == self1) {
|
||||
s1.relationships.insert(
|
||||
self2,
|
||||
Relationship {
|
||||
other_id: self2,
|
||||
relationship_type: RelationshipType::Rival,
|
||||
strength: 0.7,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(s2) = self.selves.iter_mut().find(|s| s.id == self2) {
|
||||
s2.relationships.insert(
|
||||
self1,
|
||||
Relationship {
|
||||
other_id: self1,
|
||||
relationship_type: RelationshipType::Rival,
|
||||
strength: 0.7,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
self.integration_history.push(IntegrationEvent {
|
||||
event_type: IntegrationType::Conflict,
|
||||
selves_involved: vec![self1, self2],
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
outcome: -0.5,
|
||||
});
|
||||
}
|
||||
|
||||
/// Resolve conflict through executive function
|
||||
pub fn resolve_conflict(&mut self, self1: Uuid, self2: Uuid) -> Option<Uuid> {
|
||||
let winner = self.executive.arbitrate(&self.selves, self1, self2);
|
||||
|
||||
if winner.is_some() {
|
||||
// Update relationship to neutral
|
||||
if let Some(s1) = self.selves.iter_mut().find(|s| s.id == self1) {
|
||||
if let Some(rel) = s1.relationships.get_mut(&self2) {
|
||||
rel.relationship_type = RelationshipType::Neutral;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(s2) = self.selves.iter_mut().find(|s| s.id == self2) {
|
||||
if let Some(rel) = s2.relationships.get_mut(&self1) {
|
||||
rel.relationship_type = RelationshipType::Neutral;
|
||||
}
|
||||
}
|
||||
|
||||
self.integration_history.push(IntegrationEvent {
|
||||
event_type: IntegrationType::Resolution,
|
||||
selves_involved: vec![self1, self2],
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
outcome: 0.8,
|
||||
});
|
||||
}
|
||||
|
||||
winner
|
||||
}
|
||||
|
||||
/// Merge two sub-personalities
|
||||
pub fn merge(&mut self, self1: Uuid, self2: Uuid) -> Option<Uuid> {
|
||||
let s1_idx = self.selves.iter().position(|s| s.id == self1)?;
|
||||
let s2_idx = self.selves.iter().position(|s| s.id == self2)?;
|
||||
|
||||
// Create merged self
|
||||
let merged_id = Uuid::new_v4();
|
||||
let s1 = &self.selves[s1_idx];
|
||||
let s2 = &self.selves[s2_idx];
|
||||
|
||||
let merged = SubPersonality {
|
||||
id: merged_id,
|
||||
name: format!("{}-{}", s1.name, s2.name),
|
||||
beliefs: [s1.beliefs.clone(), s2.beliefs.clone()].concat(),
|
||||
goals: [s1.goals.clone(), s2.goals.clone()].concat(),
|
||||
emotional_tone: EmotionalTone {
|
||||
valence: (s1.emotional_tone.valence + s2.emotional_tone.valence) / 2.0,
|
||||
arousal: (s1.emotional_tone.arousal + s2.emotional_tone.arousal) / 2.0,
|
||||
dominance: (s1.emotional_tone.dominance + s2.emotional_tone.dominance) / 2.0,
|
||||
},
|
||||
activation: (s1.activation + s2.activation) / 2.0,
|
||||
age: s1.age.max(s2.age),
|
||||
relationships: HashMap::new(),
|
||||
};
|
||||
|
||||
// Remove old selves (handle indices carefully)
|
||||
let (first, second) = if s1_idx > s2_idx {
|
||||
(s1_idx, s2_idx)
|
||||
} else {
|
||||
(s2_idx, s1_idx)
|
||||
};
|
||||
self.selves.remove(first);
|
||||
self.selves.remove(second);
|
||||
|
||||
self.selves.push(merged);
|
||||
|
||||
self.integration_history.push(IntegrationEvent {
|
||||
event_type: IntegrationType::Merge,
|
||||
selves_involved: vec![self1, self2, merged_id],
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
outcome: 1.0,
|
||||
});
|
||||
|
||||
Some(merged_id)
|
||||
}
|
||||
|
||||
/// Get dominant self
|
||||
pub fn get_dominant(&self) -> Option<&SubPersonality> {
|
||||
self.dominant
|
||||
.and_then(|id| self.selves.iter().find(|s| s.id == id))
|
||||
}
|
||||
|
||||
/// Get all selves
|
||||
pub fn all_selves(&self) -> &[SubPersonality] {
|
||||
&self.selves
|
||||
}
|
||||
|
||||
/// Get self count
|
||||
pub fn self_count(&self) -> usize {
|
||||
self.selves.len()
|
||||
}
|
||||
|
||||
/// Get coherence
|
||||
pub fn coherence(&self) -> &SelfCoherence {
|
||||
&self.coherence
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MultipleSelvesSystem {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl ExecutiveFunction {
|
||||
/// Create new executive function
|
||||
pub fn new(strength: f64) -> Self {
|
||||
Self {
|
||||
strength,
|
||||
threshold: 0.6,
|
||||
decisions: Vec::new(),
|
||||
style: ResolutionStyle::Negotiation,
|
||||
}
|
||||
}
|
||||
|
||||
/// Arbitrate between two selves
|
||||
pub fn arbitrate(&mut self, selves: &[SubPersonality], id1: Uuid, id2: Uuid) -> Option<Uuid> {
|
||||
let s1 = selves.iter().find(|s| s.id == id1)?;
|
||||
let s2 = selves.iter().find(|s| s.id == id2)?;
|
||||
|
||||
let outcome = match self.style {
|
||||
ResolutionStyle::Dominance => {
|
||||
// Most activated wins
|
||||
if s1.activation > s2.activation {
|
||||
DecisionOutcome::Majority(id1, s1.activation - s2.activation)
|
||||
} else {
|
||||
DecisionOutcome::Majority(id2, s2.activation - s1.activation)
|
||||
}
|
||||
}
|
||||
ResolutionStyle::Averaging => {
|
||||
// Neither wins clearly
|
||||
DecisionOutcome::Conflict
|
||||
}
|
||||
ResolutionStyle::Negotiation => {
|
||||
// Executive decides based on strength
|
||||
if self.strength > self.threshold {
|
||||
let winner = if s1.emotional_tone.dominance > s2.emotional_tone.dominance {
|
||||
id1
|
||||
} else {
|
||||
id2
|
||||
};
|
||||
DecisionOutcome::Executive(winner)
|
||||
} else {
|
||||
DecisionOutcome::Conflict
|
||||
}
|
||||
}
|
||||
ResolutionStyle::TurnTaking => {
|
||||
// Alternate based on history
|
||||
let last_winner = self.decisions.last().and_then(|d| match &d.outcome {
|
||||
DecisionOutcome::Unanimous(id)
|
||||
| DecisionOutcome::Majority(id, _)
|
||||
| DecisionOutcome::Executive(id) => Some(*id),
|
||||
_ => None,
|
||||
});
|
||||
|
||||
let winner = match last_winner {
|
||||
Some(w) if w == id1 => id2,
|
||||
Some(w) if w == id2 => id1,
|
||||
_ => id1,
|
||||
};
|
||||
DecisionOutcome::Majority(winner, 0.5)
|
||||
}
|
||||
};
|
||||
|
||||
let winner = match &outcome {
|
||||
DecisionOutcome::Unanimous(id)
|
||||
| DecisionOutcome::Majority(id, _)
|
||||
| DecisionOutcome::Executive(id) => Some(*id),
|
||||
DecisionOutcome::Conflict => None,
|
||||
};
|
||||
|
||||
self.decisions.push(Decision {
|
||||
id: Uuid::new_v4(),
|
||||
participants: vec![id1, id2],
|
||||
outcome,
|
||||
timestamp: std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0),
|
||||
});
|
||||
|
||||
winner
|
||||
}
|
||||
|
||||
/// Set resolution style
|
||||
pub fn set_style(&mut self, style: ResolutionStyle) {
|
||||
self.style = style;
|
||||
}
|
||||
}
|
||||
|
||||
impl SelfCoherence {
|
||||
/// Create new coherence tracker
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
score: 1.0,
|
||||
conflict: 0.0,
|
||||
integration: 1.0,
|
||||
stability: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get coherence score
|
||||
pub fn score(&self) -> f64 {
|
||||
self.score
|
||||
}
|
||||
|
||||
/// Get conflict level
|
||||
pub fn conflict(&self) -> f64 {
|
||||
self.conflict
|
||||
}
|
||||
|
||||
/// Get integration level
|
||||
pub fn integration(&self) -> f64 {
|
||||
self.integration
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SelfCoherence {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_multiple_selves_creation() {
|
||||
let system = MultipleSelvesSystem::new();
|
||||
assert_eq!(system.self_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_selves() {
|
||||
let mut system = MultipleSelvesSystem::new();
|
||||
|
||||
let id1 = system.add_self(
|
||||
"Protector",
|
||||
EmotionalTone {
|
||||
valence: 0.3,
|
||||
arousal: 0.7,
|
||||
dominance: 0.8,
|
||||
},
|
||||
);
|
||||
|
||||
let id2 = system.add_self(
|
||||
"Inner Child",
|
||||
EmotionalTone {
|
||||
valence: 0.8,
|
||||
arousal: 0.6,
|
||||
dominance: 0.3,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(system.self_count(), 2);
|
||||
assert_ne!(id1, id2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_coherence_measurement() {
|
||||
let mut system = MultipleSelvesSystem::new();
|
||||
|
||||
// Single self = high coherence
|
||||
system.add_self(
|
||||
"Core",
|
||||
EmotionalTone {
|
||||
valence: 0.5,
|
||||
arousal: 0.5,
|
||||
dominance: 0.5,
|
||||
},
|
||||
);
|
||||
|
||||
let coherence = system.measure_coherence();
|
||||
assert!(coherence >= 0.0 && coherence <= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_activation() {
|
||||
let mut system = MultipleSelvesSystem::new();
|
||||
|
||||
let id = system.add_self(
|
||||
"Test",
|
||||
EmotionalTone {
|
||||
valence: 0.5,
|
||||
arousal: 0.5,
|
||||
dominance: 0.5,
|
||||
},
|
||||
);
|
||||
|
||||
system.activate(id, 0.9);
|
||||
|
||||
let dominant = system.get_dominant();
|
||||
assert!(dominant.is_some());
|
||||
assert_eq!(dominant.unwrap().id, id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_conflict_and_resolution() {
|
||||
let mut system = MultipleSelvesSystem::new();
|
||||
|
||||
let id1 = system.add_self(
|
||||
"Self1",
|
||||
EmotionalTone {
|
||||
valence: 0.8,
|
||||
arousal: 0.5,
|
||||
dominance: 0.7,
|
||||
},
|
||||
);
|
||||
|
||||
let id2 = system.add_self(
|
||||
"Self2",
|
||||
EmotionalTone {
|
||||
valence: 0.2,
|
||||
arousal: 0.5,
|
||||
dominance: 0.3,
|
||||
},
|
||||
);
|
||||
|
||||
system.create_conflict(id1, id2);
|
||||
let initial_coherence = system.measure_coherence();
|
||||
|
||||
system.resolve_conflict(id1, id2);
|
||||
let final_coherence = system.measure_coherence();
|
||||
|
||||
// Coherence should improve after resolution
|
||||
assert!(final_coherence >= initial_coherence);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge() {
|
||||
let mut system = MultipleSelvesSystem::new();
|
||||
|
||||
let id1 = system.add_self(
|
||||
"Part1",
|
||||
EmotionalTone {
|
||||
valence: 0.6,
|
||||
arousal: 0.4,
|
||||
dominance: 0.5,
|
||||
},
|
||||
);
|
||||
|
||||
let id2 = system.add_self(
|
||||
"Part2",
|
||||
EmotionalTone {
|
||||
valence: 0.4,
|
||||
arousal: 0.6,
|
||||
dominance: 0.5,
|
||||
},
|
||||
);
|
||||
|
||||
assert_eq!(system.self_count(), 2);
|
||||
|
||||
let merged_id = system.merge(id1, id2);
|
||||
assert!(merged_id.is_some());
|
||||
assert_eq!(system.self_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_executive_function() {
|
||||
let mut exec = ExecutiveFunction::new(0.8);
|
||||
|
||||
let selves = vec![
|
||||
SubPersonality {
|
||||
id: Uuid::new_v4(),
|
||||
name: "Strong".to_string(),
|
||||
beliefs: Vec::new(),
|
||||
goals: Vec::new(),
|
||||
emotional_tone: EmotionalTone {
|
||||
valence: 0.5,
|
||||
arousal: 0.5,
|
||||
dominance: 0.9,
|
||||
},
|
||||
activation: 0.8,
|
||||
age: 10,
|
||||
relationships: HashMap::new(),
|
||||
},
|
||||
SubPersonality {
|
||||
id: Uuid::new_v4(),
|
||||
name: "Weak".to_string(),
|
||||
beliefs: Vec::new(),
|
||||
goals: Vec::new(),
|
||||
emotional_tone: EmotionalTone {
|
||||
valence: 0.5,
|
||||
arousal: 0.5,
|
||||
dominance: 0.1,
|
||||
},
|
||||
activation: 0.2,
|
||||
age: 5,
|
||||
relationships: HashMap::new(),
|
||||
},
|
||||
];
|
||||
|
||||
let winner = exec.arbitrate(&selves, selves[0].id, selves[1].id);
|
||||
assert!(winner.is_some());
|
||||
}
|
||||
}
|
||||
494
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/strange_loops.rs
vendored
Normal file
494
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/strange_loops.rs
vendored
Normal file
@@ -0,0 +1,494 @@
|
||||
//! # Strange Loops & Self-Reference (Hofstadter)
|
||||
//!
|
||||
//! Implementation of Gödel-Hofstadter style self-referential cognition where
|
||||
//! the system models itself modeling itself, creating tangled hierarchies.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Strange Loop**: A cyclical structure where moving through levels brings
|
||||
//! you back to the starting point (like Escher's staircases)
|
||||
//! - **Tangled Hierarchy**: Levels that should be separate become intertwined
|
||||
//! - **Self-Encoding**: System contains a representation of itself
|
||||
//!
|
||||
//! ## Mathematical Foundation
|
||||
//!
|
||||
//! Based on Gödel's incompleteness theorems and Hofstadter's "I Am a Strange Loop":
|
||||
//! - Gödel numbering for self-reference
|
||||
//! - Fixed-point combinators (Y-combinator style)
|
||||
//! - Quine-like self-replication patterns
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A strange loop implementing self-referential cognition
|
||||
#[derive(Debug)]
|
||||
pub struct StrangeLoop {
|
||||
/// Maximum recursion depth for self-modeling
|
||||
max_depth: usize,
|
||||
/// The self-model: a representation of this very structure
|
||||
self_model: Box<SelfModel>,
|
||||
/// Gödel number encoding of the system state
|
||||
godel_number: u64,
|
||||
/// Loop detection for tangled hierarchies
|
||||
visited_states: HashMap<u64, usize>,
|
||||
/// Current recursion level
|
||||
current_level: AtomicUsize,
|
||||
}
|
||||
|
||||
/// Self-model representing the system's view of itself
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SelfModel {
|
||||
/// Unique identifier
|
||||
pub id: Uuid,
|
||||
/// Model of capabilities
|
||||
pub capabilities: Vec<String>,
|
||||
/// Model of current state
|
||||
pub state_description: String,
|
||||
/// Nested self-model (model of the model)
|
||||
pub nested_model: Option<Box<SelfModel>>,
|
||||
/// Confidence in self-model accuracy (0-1)
|
||||
pub confidence: f64,
|
||||
/// Depth level in the hierarchy
|
||||
pub level: usize,
|
||||
}
|
||||
|
||||
/// Reference to self within the cognitive system
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SelfReference {
|
||||
/// What aspect is being referenced
|
||||
pub aspect: SelfAspect,
|
||||
/// Depth of reference (0 = direct, 1 = meta, 2 = meta-meta, etc.)
|
||||
pub depth: usize,
|
||||
/// Gödel encoding of the reference
|
||||
pub encoding: u64,
|
||||
}
|
||||
|
||||
/// Aspects of self that can be referenced
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SelfAspect {
|
||||
/// The entire system
|
||||
Whole,
|
||||
/// The reasoning process
|
||||
Reasoning,
|
||||
/// The self-model itself
|
||||
SelfModel,
|
||||
/// The reference mechanism
|
||||
ReferenceSystem,
|
||||
/// Memory of past states
|
||||
Memory,
|
||||
/// Goals and intentions
|
||||
Intentions,
|
||||
}
|
||||
|
||||
/// Tangled hierarchy of cognitive levels
|
||||
#[derive(Debug)]
|
||||
pub struct TangledHierarchy {
|
||||
/// Levels in the hierarchy
|
||||
levels: Vec<HierarchyLevel>,
|
||||
/// Cross-level connections (tangles)
|
||||
tangles: Vec<(usize, usize)>,
|
||||
/// Detected strange loops
|
||||
loops: Vec<Vec<usize>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HierarchyLevel {
|
||||
pub id: usize,
|
||||
pub name: String,
|
||||
pub content: Vec<CognitiveElement>,
|
||||
pub references_to: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CognitiveElement {
|
||||
pub id: Uuid,
|
||||
pub element_type: ElementType,
|
||||
pub self_reference_depth: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ElementType {
|
||||
Perception,
|
||||
Concept,
|
||||
Belief,
|
||||
MetaBelief, // Belief about beliefs
|
||||
MetaMetaBelief, // Belief about beliefs about beliefs
|
||||
SelfConcept, // Concept about self
|
||||
}
|
||||
|
||||
impl StrangeLoop {
|
||||
/// Create a new strange loop with specified maximum depth
|
||||
pub fn new(max_depth: usize) -> Self {
|
||||
let initial_model = SelfModel {
|
||||
id: Uuid::new_v4(),
|
||||
capabilities: vec![
|
||||
"self-modeling".to_string(),
|
||||
"meta-cognition".to_string(),
|
||||
"recursive-reflection".to_string(),
|
||||
],
|
||||
state_description: "Initial self-aware state".to_string(),
|
||||
nested_model: None,
|
||||
confidence: 0.5,
|
||||
level: 0,
|
||||
};
|
||||
|
||||
Self {
|
||||
max_depth,
|
||||
self_model: Box::new(initial_model),
|
||||
godel_number: 1,
|
||||
visited_states: HashMap::new(),
|
||||
current_level: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure the depth of self-referential loops
|
||||
pub fn measure_depth(&self) -> usize {
|
||||
self.count_nested_depth(&self.self_model)
|
||||
}
|
||||
|
||||
fn count_nested_depth(&self, model: &SelfModel) -> usize {
|
||||
match &model.nested_model {
|
||||
Some(nested) => 1 + self.count_nested_depth(nested),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Model the self, creating a new level of self-reference
|
||||
pub fn model_self(&mut self) -> &SelfModel {
|
||||
let current_depth = self.measure_depth();
|
||||
|
||||
if current_depth < self.max_depth {
|
||||
// Create a model of the current state
|
||||
let new_nested = SelfModel {
|
||||
id: Uuid::new_v4(),
|
||||
capabilities: self.self_model.capabilities.clone(),
|
||||
state_description: format!(
|
||||
"Meta-level {} observing level {}",
|
||||
current_depth + 1,
|
||||
current_depth
|
||||
),
|
||||
nested_model: self.self_model.nested_model.take(),
|
||||
confidence: self.self_model.confidence * 0.9, // Decreasing confidence
|
||||
level: current_depth + 1,
|
||||
};
|
||||
|
||||
self.self_model.nested_model = Some(Box::new(new_nested));
|
||||
self.update_godel_number();
|
||||
}
|
||||
|
||||
&self.self_model
|
||||
}
|
||||
|
||||
/// Reason about self-reasoning (meta-cognition)
|
||||
pub fn meta_reason(&mut self, thought: &str) -> MetaThought {
|
||||
let level = self.current_level.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
let meta_thought = MetaThought {
|
||||
original_thought: thought.to_string(),
|
||||
reasoning_about_thought: format!("I am thinking about the thought: '{}'", thought),
|
||||
reasoning_about_reasoning: format!(
|
||||
"I notice that I am analyzing my own thought process at level {}",
|
||||
level
|
||||
),
|
||||
infinite_regress_detected: level >= self.max_depth,
|
||||
godel_reference: self.compute_godel_reference(thought),
|
||||
};
|
||||
|
||||
self.current_level.store(0, Ordering::SeqCst);
|
||||
meta_thought
|
||||
}
|
||||
|
||||
/// Compute Gödel number for a string (simplified encoding)
|
||||
fn compute_godel_reference(&self, s: &str) -> u64 {
|
||||
// Simplified Gödel numbering using prime factorization concept
|
||||
let primes: [u64; 26] = [
|
||||
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
|
||||
89, 97, 101,
|
||||
];
|
||||
|
||||
let mut result: u64 = 1;
|
||||
for (i, c) in s.chars().take(20).enumerate() {
|
||||
let char_val = (c as u64) % 100;
|
||||
let prime = primes[i % primes.len()];
|
||||
result = result.wrapping_mul(prime.wrapping_pow(char_val as u32));
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
fn update_godel_number(&mut self) {
|
||||
// Update Gödel number based on current state
|
||||
let depth = self.measure_depth() as u64;
|
||||
self.godel_number = self
|
||||
.godel_number
|
||||
.wrapping_mul(2_u64.wrapping_pow(depth as u32 + 1));
|
||||
}
|
||||
|
||||
/// Create a self-reference to a specific aspect
|
||||
pub fn create_self_reference(&self, aspect: SelfAspect) -> SelfReference {
|
||||
let depth = match aspect {
|
||||
SelfAspect::Whole => 0,
|
||||
SelfAspect::Reasoning => 1,
|
||||
SelfAspect::SelfModel => 2,
|
||||
SelfAspect::ReferenceSystem => 3, // This references the reference system!
|
||||
SelfAspect::Memory => 1,
|
||||
SelfAspect::Intentions => 1,
|
||||
};
|
||||
|
||||
SelfReference {
|
||||
aspect: aspect.clone(),
|
||||
depth,
|
||||
encoding: self.encode_aspect(&aspect),
|
||||
}
|
||||
}
|
||||
|
||||
fn encode_aspect(&self, aspect: &SelfAspect) -> u64 {
|
||||
match aspect {
|
||||
SelfAspect::Whole => 1,
|
||||
SelfAspect::Reasoning => 2,
|
||||
SelfAspect::SelfModel => 3,
|
||||
SelfAspect::ReferenceSystem => 5,
|
||||
SelfAspect::Memory => 7,
|
||||
SelfAspect::Intentions => 11,
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect if we're in a strange loop
|
||||
pub fn detect_strange_loop(&mut self) -> Option<StrangeLoopDetection> {
|
||||
let current_state = self.godel_number;
|
||||
|
||||
if let Some(&previous_level) = self.visited_states.get(¤t_state) {
|
||||
let current_level = self.current_level.load(Ordering::SeqCst);
|
||||
return Some(StrangeLoopDetection {
|
||||
loop_start_level: previous_level,
|
||||
loop_end_level: current_level,
|
||||
loop_size: current_level.saturating_sub(previous_level),
|
||||
state_encoding: current_state,
|
||||
});
|
||||
}
|
||||
|
||||
self.visited_states
|
||||
.insert(current_state, self.current_level.load(Ordering::SeqCst));
|
||||
None
|
||||
}
|
||||
|
||||
/// Implement Y-combinator style fixed point (for self-application)
|
||||
pub fn fixed_point<F, T>(&self, f: F, initial: T, max_iterations: usize) -> T
|
||||
where
|
||||
F: Fn(&T) -> T,
|
||||
T: PartialEq + Clone,
|
||||
{
|
||||
let mut current = initial;
|
||||
for _ in 0..max_iterations {
|
||||
let next = f(¤t);
|
||||
if next == current {
|
||||
break; // Fixed point found
|
||||
}
|
||||
current = next;
|
||||
}
|
||||
current
|
||||
}
|
||||
|
||||
/// Get confidence in self-model at each level
|
||||
pub fn confidence_by_level(&self) -> Vec<(usize, f64)> {
|
||||
let mut confidences = Vec::new();
|
||||
let mut current: Option<&SelfModel> = Some(&self.self_model);
|
||||
|
||||
while let Some(model) = current {
|
||||
confidences.push((model.level, model.confidence));
|
||||
current = model.nested_model.as_deref();
|
||||
}
|
||||
|
||||
confidences
|
||||
}
|
||||
}
|
||||
|
||||
impl TangledHierarchy {
|
||||
/// Create a new tangled hierarchy
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
levels: Vec::new(),
|
||||
tangles: Vec::new(),
|
||||
loops: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a level to the hierarchy
|
||||
pub fn add_level(&mut self, name: &str) -> usize {
|
||||
let id = self.levels.len();
|
||||
self.levels.push(HierarchyLevel {
|
||||
id,
|
||||
name: name.to_string(),
|
||||
content: Vec::new(),
|
||||
references_to: Vec::new(),
|
||||
});
|
||||
id
|
||||
}
|
||||
|
||||
/// Create a tangle (cross-level reference)
|
||||
pub fn create_tangle(&mut self, from_level: usize, to_level: usize) {
|
||||
if from_level < self.levels.len() && to_level < self.levels.len() {
|
||||
self.tangles.push((from_level, to_level));
|
||||
self.levels[from_level].references_to.push(to_level);
|
||||
self.detect_loops();
|
||||
}
|
||||
}
|
||||
|
||||
/// Detect all strange loops in the hierarchy
|
||||
fn detect_loops(&mut self) {
|
||||
self.loops.clear();
|
||||
|
||||
for start in 0..self.levels.len() {
|
||||
let mut visited = vec![false; self.levels.len()];
|
||||
let mut path = Vec::new();
|
||||
self.dfs_find_loops(start, start, &mut visited, &mut path);
|
||||
}
|
||||
}
|
||||
|
||||
fn dfs_find_loops(
|
||||
&mut self,
|
||||
current: usize,
|
||||
target: usize,
|
||||
visited: &mut [bool],
|
||||
path: &mut Vec<usize>,
|
||||
) {
|
||||
path.push(current);
|
||||
|
||||
for &next in &self.levels[current].references_to.clone() {
|
||||
if next == target && path.len() > 1 {
|
||||
// Found a loop back to start
|
||||
self.loops.push(path.clone());
|
||||
} else if !visited[next] {
|
||||
visited[next] = true;
|
||||
self.dfs_find_loops(next, target, visited, path);
|
||||
visited[next] = false;
|
||||
}
|
||||
}
|
||||
|
||||
path.pop();
|
||||
}
|
||||
|
||||
/// Measure hierarchy tangle density
|
||||
pub fn tangle_density(&self) -> f64 {
|
||||
if self.levels.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let max_tangles = self.levels.len() * (self.levels.len() - 1);
|
||||
if max_tangles == 0 {
|
||||
return 0.0;
|
||||
}
|
||||
self.tangles.len() as f64 / max_tangles as f64
|
||||
}
|
||||
|
||||
/// Count strange loops
|
||||
pub fn strange_loop_count(&self) -> usize {
|
||||
self.loops.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TangledHierarchy {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of meta-cognition
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MetaThought {
|
||||
pub original_thought: String,
|
||||
pub reasoning_about_thought: String,
|
||||
pub reasoning_about_reasoning: String,
|
||||
pub infinite_regress_detected: bool,
|
||||
pub godel_reference: u64,
|
||||
}
|
||||
|
||||
/// Detection of a strange loop
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StrangeLoopDetection {
|
||||
pub loop_start_level: usize,
|
||||
pub loop_end_level: usize,
|
||||
pub loop_size: usize,
|
||||
pub state_encoding: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_strange_loop_creation() {
|
||||
let sl = StrangeLoop::new(5);
|
||||
assert_eq!(sl.measure_depth(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self_modeling_depth() {
|
||||
let mut sl = StrangeLoop::new(5);
|
||||
sl.model_self();
|
||||
assert_eq!(sl.measure_depth(), 1);
|
||||
sl.model_self();
|
||||
assert_eq!(sl.measure_depth(), 2);
|
||||
sl.model_self();
|
||||
assert_eq!(sl.measure_depth(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_meta_reasoning() {
|
||||
let mut sl = StrangeLoop::new(3);
|
||||
let meta = sl.meta_reason("I think therefore I am");
|
||||
assert!(!meta.infinite_regress_detected);
|
||||
// Godel reference may wrap to 0 with large primes, just check it's computed
|
||||
// The important thing is the meta-reasoning structure works
|
||||
assert!(!meta.original_thought.is_empty());
|
||||
assert!(!meta.reasoning_about_thought.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_self_reference() {
|
||||
let sl = StrangeLoop::new(5);
|
||||
let ref_whole = sl.create_self_reference(SelfAspect::Whole);
|
||||
let ref_meta = sl.create_self_reference(SelfAspect::ReferenceSystem);
|
||||
assert_eq!(ref_whole.depth, 0);
|
||||
assert_eq!(ref_meta.depth, 3); // Meta-reference is deeper
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tangled_hierarchy() {
|
||||
let mut th = TangledHierarchy::new();
|
||||
let l0 = th.add_level("Perception");
|
||||
let l1 = th.add_level("Concept");
|
||||
let l2 = th.add_level("Meta-Concept");
|
||||
|
||||
th.create_tangle(l0, l1);
|
||||
th.create_tangle(l1, l2);
|
||||
th.create_tangle(l2, l0); // Creates a loop!
|
||||
|
||||
// May detect multiple loops due to DFS traversal from each starting node
|
||||
assert!(th.strange_loop_count() >= 1);
|
||||
assert!(th.tangle_density() > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_confidence_decay() {
|
||||
let mut sl = StrangeLoop::new(10);
|
||||
for _ in 0..5 {
|
||||
sl.model_self();
|
||||
}
|
||||
|
||||
let confidences = sl.confidence_by_level();
|
||||
// Each level should have lower confidence than the previous
|
||||
for i in 1..confidences.len() {
|
||||
assert!(confidences[i].1 <= confidences[i - 1].1);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fixed_point() {
|
||||
let sl = StrangeLoop::new(5);
|
||||
|
||||
// f(x) = x/2 converges to 0
|
||||
let result = sl.fixed_point(|x: &f64| x / 2.0, 100.0, 1000);
|
||||
assert!(result < 0.001);
|
||||
}
|
||||
}
|
||||
529
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/temporal_qualia.rs
vendored
Normal file
529
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/temporal_qualia.rs
vendored
Normal file
@@ -0,0 +1,529 @@
|
||||
//! # Temporal Qualia
|
||||
//!
|
||||
//! Subjective experience of time dilation and compression in cognitive systems.
|
||||
//! Explores how information processing rate affects perceived time.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Time Dilation**: Subjective slowing of time during high information load
|
||||
//! - **Time Compression**: Subjective speeding up during routine/familiar tasks
|
||||
//! - **Temporal Binding**: Binding events into perceived "now"
|
||||
//! - **Time Crystals**: Periodic patterns in cognitive temporal space
|
||||
//!
|
||||
//! ## Theoretical Basis
|
||||
//!
|
||||
//! Inspired by:
|
||||
//! - Eagleman's research on temporal perception
|
||||
//! - Internal clock models (scalar timing theory)
|
||||
//! - Attention and time perception studies
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// System for experiencing and measuring subjective time
|
||||
#[derive(Debug)]
|
||||
pub struct TemporalQualia {
|
||||
/// Internal clock rate (ticks per objective time unit)
|
||||
clock_rate: f64,
|
||||
/// Base clock rate (reference)
|
||||
base_rate: f64,
|
||||
/// Attention level (affects time perception)
|
||||
attention: f64,
|
||||
/// Novelty level (affects time perception)
|
||||
novelty: f64,
|
||||
/// Time crystal patterns
|
||||
time_crystals: Vec<TimeCrystal>,
|
||||
/// Temporal binding window (ms equivalent)
|
||||
binding_window: f64,
|
||||
/// Experience buffer
|
||||
experience_buffer: VecDeque<TemporalEvent>,
|
||||
/// Subjective duration tracker
|
||||
subjective_duration: f64,
|
||||
/// Objective duration tracker
|
||||
objective_duration: f64,
|
||||
}
|
||||
|
||||
/// A pattern repeating in cognitive temporal space
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TimeCrystal {
|
||||
pub id: Uuid,
|
||||
/// Period of the crystal (cognitive time units)
|
||||
pub period: f64,
|
||||
/// Amplitude of oscillation
|
||||
pub amplitude: f64,
|
||||
/// Phase offset
|
||||
pub phase: f64,
|
||||
/// Pattern stability (0-1)
|
||||
pub stability: f64,
|
||||
/// Cognitive content repeated
|
||||
pub content_pattern: Vec<f64>,
|
||||
}
|
||||
|
||||
/// Subjective time perception interface
|
||||
#[derive(Debug)]
|
||||
pub struct SubjectiveTime {
|
||||
/// Current subjective moment
|
||||
now: f64,
|
||||
/// Duration of "now" (specious present)
|
||||
specious_present: f64,
|
||||
/// Past experiences (accessible memory)
|
||||
past: VecDeque<f64>,
|
||||
/// Future anticipation
|
||||
anticipated: Vec<f64>,
|
||||
/// Time perception mode
|
||||
mode: TimeMode,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum TimeMode {
|
||||
/// Normal flow of time
|
||||
Normal,
|
||||
/// Dilated (slow motion subjective time)
|
||||
Dilated,
|
||||
/// Compressed (fast-forward subjective time)
|
||||
Compressed,
|
||||
/// Flow state (time seems to disappear)
|
||||
Flow,
|
||||
/// Dissociated (disconnected from time)
|
||||
Dissociated,
|
||||
}
|
||||
|
||||
/// A temporal event to be experienced
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TemporalEvent {
|
||||
pub id: Uuid,
|
||||
/// Objective timestamp
|
||||
pub objective_time: f64,
|
||||
/// Subjective timestamp
|
||||
pub subjective_time: f64,
|
||||
/// Information content
|
||||
pub information: f64,
|
||||
/// Emotional arousal
|
||||
pub arousal: f64,
|
||||
/// Novelty of event
|
||||
pub novelty: f64,
|
||||
}
|
||||
|
||||
impl TemporalQualia {
|
||||
/// Create a new temporal qualia system
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
clock_rate: 1.0,
|
||||
base_rate: 1.0,
|
||||
attention: 0.5,
|
||||
novelty: 0.5,
|
||||
time_crystals: Vec::new(),
|
||||
binding_window: 100.0, // ~100ms binding window
|
||||
experience_buffer: VecDeque::with_capacity(1000),
|
||||
subjective_duration: 0.0,
|
||||
objective_duration: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure current time dilation factor
|
||||
pub fn measure_dilation(&self) -> f64 {
|
||||
// Dilation = subjective time / objective time
|
||||
// > 1 means time seems slower (more subjective time per objective unit)
|
||||
// < 1 means time seems faster
|
||||
if self.objective_duration > 0.0 {
|
||||
self.subjective_duration / self.objective_duration
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Process an experience and update temporal perception
|
||||
pub fn experience(&mut self, event: TemporalEvent) {
|
||||
// Update novelty-based time dilation
|
||||
// Novel events make time seem longer (more information to process)
|
||||
let dilation_factor = 1.0 + (event.novelty * 0.5) + (event.arousal * 0.3);
|
||||
|
||||
// Attention modulates time perception
|
||||
let attention_factor = 1.0 + (self.attention - 0.5) * 0.4;
|
||||
|
||||
// Update clock rate
|
||||
self.clock_rate = self.base_rate * dilation_factor * attention_factor;
|
||||
|
||||
// Track durations
|
||||
let obj_delta = 1.0; // Assume unit objective time per event
|
||||
let subj_delta = obj_delta * self.clock_rate;
|
||||
|
||||
self.objective_duration += obj_delta;
|
||||
self.subjective_duration += subj_delta;
|
||||
|
||||
// Update novelty (adapts over time)
|
||||
self.novelty = self.novelty * 0.9 + event.novelty * 0.1;
|
||||
|
||||
// Store experience
|
||||
self.experience_buffer.push_back(event);
|
||||
if self.experience_buffer.len() > 1000 {
|
||||
self.experience_buffer.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
/// Set attention level
|
||||
pub fn set_attention(&mut self, attention: f64) {
|
||||
self.attention = attention.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Enter a specific time mode
|
||||
pub fn enter_mode(&mut self, mode: TimeMode) {
|
||||
match mode {
|
||||
TimeMode::Normal => {
|
||||
self.clock_rate = self.base_rate;
|
||||
}
|
||||
TimeMode::Dilated => {
|
||||
self.clock_rate = self.base_rate * 2.0; // 2x subjective time
|
||||
}
|
||||
TimeMode::Compressed => {
|
||||
self.clock_rate = self.base_rate * 0.5; // 0.5x subjective time
|
||||
}
|
||||
TimeMode::Flow => {
|
||||
// In flow, subjective time seems to stop
|
||||
self.clock_rate = self.base_rate * 0.1;
|
||||
}
|
||||
TimeMode::Dissociated => {
|
||||
self.clock_rate = 0.0; // No subjective time passes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a time crystal pattern
|
||||
pub fn add_time_crystal(&mut self, period: f64, amplitude: f64, content: Vec<f64>) {
|
||||
self.time_crystals.push(TimeCrystal {
|
||||
id: Uuid::new_v4(),
|
||||
period,
|
||||
amplitude,
|
||||
phase: 0.0,
|
||||
stability: 0.5,
|
||||
content_pattern: content,
|
||||
});
|
||||
}
|
||||
|
||||
/// Get time crystal contribution at current time
|
||||
pub fn crystal_contribution(&self, time: f64) -> f64 {
|
||||
self.time_crystals
|
||||
.iter()
|
||||
.map(|crystal| {
|
||||
let phase = (time / crystal.period + crystal.phase) * std::f64::consts::TAU;
|
||||
crystal.amplitude * phase.sin() * crystal.stability
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
/// Estimate how much time has subjectively passed
|
||||
pub fn subjective_elapsed(&self) -> f64 {
|
||||
self.subjective_duration
|
||||
}
|
||||
|
||||
/// Get objective time elapsed
|
||||
pub fn objective_elapsed(&self) -> f64 {
|
||||
self.objective_duration
|
||||
}
|
||||
|
||||
/// Get current clock rate
|
||||
pub fn current_clock_rate(&self) -> f64 {
|
||||
self.clock_rate
|
||||
}
|
||||
|
||||
/// Bind events within temporal window
|
||||
pub fn temporal_binding(&self) -> Vec<Vec<&TemporalEvent>> {
|
||||
let mut bindings: Vec<Vec<&TemporalEvent>> = Vec::new();
|
||||
let mut current_binding: Vec<&TemporalEvent> = Vec::new();
|
||||
let mut window_start = 0.0;
|
||||
|
||||
for event in &self.experience_buffer {
|
||||
if event.objective_time - window_start <= self.binding_window {
|
||||
current_binding.push(event);
|
||||
} else {
|
||||
if !current_binding.is_empty() {
|
||||
bindings.push(current_binding);
|
||||
current_binding = Vec::new();
|
||||
}
|
||||
window_start = event.objective_time;
|
||||
current_binding.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
if !current_binding.is_empty() {
|
||||
bindings.push(current_binding);
|
||||
}
|
||||
|
||||
bindings
|
||||
}
|
||||
|
||||
/// Get temporal perception statistics
|
||||
pub fn statistics(&self) -> TemporalStatistics {
|
||||
let avg_novelty = if self.experience_buffer.is_empty() {
|
||||
0.0
|
||||
} else {
|
||||
self.experience_buffer
|
||||
.iter()
|
||||
.map(|e| e.novelty)
|
||||
.sum::<f64>()
|
||||
/ self.experience_buffer.len() as f64
|
||||
};
|
||||
|
||||
TemporalStatistics {
|
||||
dilation_factor: self.measure_dilation(),
|
||||
clock_rate: self.clock_rate,
|
||||
attention_level: self.attention,
|
||||
average_novelty: avg_novelty,
|
||||
crystal_count: self.time_crystals.len(),
|
||||
experiences_buffered: self.experience_buffer.len(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset temporal tracking
|
||||
pub fn reset(&mut self) {
|
||||
self.subjective_duration = 0.0;
|
||||
self.objective_duration = 0.0;
|
||||
self.clock_rate = self.base_rate;
|
||||
self.experience_buffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TemporalQualia {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl SubjectiveTime {
|
||||
/// Create a new subjective time interface
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
now: 0.0,
|
||||
specious_present: 3.0, // ~3 seconds specious present
|
||||
past: VecDeque::with_capacity(100),
|
||||
anticipated: Vec::new(),
|
||||
mode: TimeMode::Normal,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance subjective time
|
||||
pub fn tick(&mut self, delta: f64) {
|
||||
self.past.push_back(self.now);
|
||||
if self.past.len() > 100 {
|
||||
self.past.pop_front();
|
||||
}
|
||||
|
||||
self.now += delta;
|
||||
}
|
||||
|
||||
/// Get current subjective moment
|
||||
pub fn now(&self) -> f64 {
|
||||
self.now
|
||||
}
|
||||
|
||||
/// Get the specious present (experienced "now")
|
||||
pub fn specious_present_range(&self) -> (f64, f64) {
|
||||
let half = self.specious_present / 2.0;
|
||||
(self.now - half, self.now + half)
|
||||
}
|
||||
|
||||
/// Set anticipation for future moments
|
||||
pub fn anticipate(&mut self, future_moments: Vec<f64>) {
|
||||
self.anticipated = future_moments;
|
||||
}
|
||||
|
||||
/// Get accessible past
|
||||
pub fn accessible_past(&self) -> &VecDeque<f64> {
|
||||
&self.past
|
||||
}
|
||||
|
||||
/// Set time mode
|
||||
pub fn set_mode(&mut self, mode: TimeMode) {
|
||||
self.mode = mode;
|
||||
}
|
||||
|
||||
/// Get current mode
|
||||
pub fn mode(&self) -> &TimeMode {
|
||||
&self.mode
|
||||
}
|
||||
|
||||
/// Estimate duration between two moments
|
||||
pub fn estimate_duration(&self, start: f64, end: f64) -> f64 {
|
||||
let objective = end - start;
|
||||
|
||||
// Subjective duration affected by mode
|
||||
match self.mode {
|
||||
TimeMode::Normal => objective,
|
||||
TimeMode::Dilated => objective * 2.0,
|
||||
TimeMode::Compressed => objective * 0.5,
|
||||
TimeMode::Flow => objective * 0.1,
|
||||
TimeMode::Dissociated => 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SubjectiveTime {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TimeCrystal {
|
||||
/// Create a new time crystal
|
||||
pub fn new(period: f64, amplitude: f64) -> Self {
|
||||
Self {
|
||||
id: Uuid::new_v4(),
|
||||
period,
|
||||
amplitude,
|
||||
phase: 0.0,
|
||||
stability: 0.5,
|
||||
content_pattern: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get value at given time
|
||||
pub fn value_at(&self, time: f64) -> f64 {
|
||||
let phase = (time / self.period + self.phase) * std::f64::consts::TAU;
|
||||
self.amplitude * phase.sin()
|
||||
}
|
||||
|
||||
/// Update stability based on persistence
|
||||
pub fn reinforce(&mut self) {
|
||||
self.stability = (self.stability + 0.1).min(1.0);
|
||||
}
|
||||
|
||||
/// Decay stability
|
||||
pub fn decay(&mut self, factor: f64) {
|
||||
self.stability *= factor;
|
||||
}
|
||||
}
|
||||
|
||||
/// Statistics about temporal perception
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct TemporalStatistics {
|
||||
pub dilation_factor: f64,
|
||||
pub clock_rate: f64,
|
||||
pub attention_level: f64,
|
||||
pub average_novelty: f64,
|
||||
pub crystal_count: usize,
|
||||
pub experiences_buffered: usize,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_temporal_qualia_creation() {
|
||||
let tq = TemporalQualia::new();
|
||||
assert_eq!(tq.measure_dilation(), 1.0); // Initial dilation is 1.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_dilation_with_novelty() {
|
||||
let mut tq = TemporalQualia::new();
|
||||
|
||||
// Experience high novelty events
|
||||
for i in 0..10 {
|
||||
tq.experience(TemporalEvent {
|
||||
id: Uuid::new_v4(),
|
||||
objective_time: i as f64,
|
||||
subjective_time: 0.0,
|
||||
information: 0.5,
|
||||
arousal: 0.7,
|
||||
novelty: 0.9, // High novelty
|
||||
});
|
||||
}
|
||||
|
||||
// Time should seem dilated (more subjective time)
|
||||
assert!(tq.measure_dilation() > 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_compression_with_familiarity() {
|
||||
let mut tq = TemporalQualia::new();
|
||||
|
||||
// Experience low novelty events
|
||||
for i in 0..10 {
|
||||
tq.experience(TemporalEvent {
|
||||
id: Uuid::new_v4(),
|
||||
objective_time: i as f64,
|
||||
subjective_time: 0.0,
|
||||
information: 0.1,
|
||||
arousal: 0.1,
|
||||
novelty: 0.1, // Low novelty
|
||||
});
|
||||
}
|
||||
|
||||
// Time should feel slightly dilated still due to base processing
|
||||
let dilation = tq.measure_dilation();
|
||||
assert!(dilation >= 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_modes() {
|
||||
let mut tq = TemporalQualia::new();
|
||||
let base = tq.current_clock_rate();
|
||||
|
||||
tq.enter_mode(TimeMode::Dilated);
|
||||
assert!(tq.current_clock_rate() > base);
|
||||
|
||||
tq.enter_mode(TimeMode::Compressed);
|
||||
assert!(tq.current_clock_rate() < base);
|
||||
|
||||
tq.enter_mode(TimeMode::Flow);
|
||||
assert!(tq.current_clock_rate() < tq.base_rate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_time_crystal() {
|
||||
let crystal = TimeCrystal::new(10.0, 1.0);
|
||||
|
||||
// Value should oscillate
|
||||
let v1 = crystal.value_at(0.0);
|
||||
let v2 = crystal.value_at(2.5); // Quarter period
|
||||
let v3 = crystal.value_at(5.0); // Half period
|
||||
|
||||
assert!((v1 - 0.0).abs() < 0.01); // sin(0) = 0
|
||||
assert!(v2 > 0.9); // sin(π/2) ≈ 1
|
||||
assert!((v3 - 0.0).abs() < 0.01); // sin(π) ≈ 0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_subjective_time() {
|
||||
let mut st = SubjectiveTime::new();
|
||||
|
||||
st.tick(1.0);
|
||||
st.tick(1.0);
|
||||
st.tick(1.0);
|
||||
|
||||
assert_eq!(st.now(), 3.0);
|
||||
assert_eq!(st.accessible_past().len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_specious_present() {
|
||||
let st = SubjectiveTime::new();
|
||||
let (start, end) = st.specious_present_range();
|
||||
|
||||
assert!(end - start > 0.0); // Has duration
|
||||
assert_eq!(end - start, st.specious_present); // Equals specious present duration
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_temporal_statistics() {
|
||||
let mut tq = TemporalQualia::new();
|
||||
tq.add_time_crystal(5.0, 1.0, vec![0.1, 0.2]);
|
||||
|
||||
for i in 0..5 {
|
||||
tq.experience(TemporalEvent {
|
||||
id: Uuid::new_v4(),
|
||||
objective_time: i as f64,
|
||||
subjective_time: 0.0,
|
||||
information: 0.5,
|
||||
arousal: 0.5,
|
||||
novelty: 0.5,
|
||||
});
|
||||
}
|
||||
|
||||
let stats = tq.statistics();
|
||||
assert_eq!(stats.crystal_count, 1);
|
||||
assert_eq!(stats.experiences_buffered, 5);
|
||||
}
|
||||
}
|
||||
636
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/thermodynamics.rs
vendored
Normal file
636
vendor/ruvector/examples/exo-ai-2025/crates/exo-exotic/src/thermodynamics.rs
vendored
Normal file
@@ -0,0 +1,636 @@
|
||||
//! # Cognitive Thermodynamics
|
||||
//!
|
||||
//! Deep exploration of Landauer's principle and thermodynamic constraints
|
||||
//! on cognitive processing.
|
||||
//!
|
||||
//! ## Key Concepts
|
||||
//!
|
||||
//! - **Landauer's Principle**: Erasing 1 bit costs kT ln(2) energy
|
||||
//! - **Reversible Computation**: Computation without erasure costs no energy
|
||||
//! - **Cognitive Temperature**: Noise/randomness in cognitive processing
|
||||
//! - **Maxwell's Demon**: Information-to-work conversion
|
||||
//! - **Thought Entropy**: Disorder in cognitive states
|
||||
//!
|
||||
//! ## Theoretical Foundation
|
||||
//!
|
||||
//! Based on:
|
||||
//! - Landauer (1961) - Irreversibility and Heat Generation
|
||||
//! - Bennett - Reversible Computation
|
||||
//! - Szilard Engine - Information thermodynamics
|
||||
//! - Jarzynski Equality - Non-equilibrium thermodynamics
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::{HashMap, VecDeque};
|
||||
|
||||
/// Cognitive thermodynamics system
|
||||
#[derive(Debug)]
|
||||
pub struct CognitiveThermodynamics {
|
||||
/// Cognitive temperature (noise level)
|
||||
temperature: f64,
|
||||
/// Total entropy of the system
|
||||
entropy: ThoughtEntropy,
|
||||
/// Energy budget tracking
|
||||
energy: EnergyBudget,
|
||||
/// Maxwell's demon instance
|
||||
demon: MaxwellDemon,
|
||||
/// Phase state
|
||||
phase: CognitivePhase,
|
||||
/// History of thermodynamic events
|
||||
history: VecDeque<ThermodynamicEvent>,
|
||||
/// Boltzmann constant (normalized)
|
||||
k_b: f64,
|
||||
}
|
||||
|
||||
/// Entropy tracking for cognitive system
|
||||
#[derive(Debug)]
|
||||
pub struct ThoughtEntropy {
|
||||
/// Current entropy level
|
||||
current: f64,
|
||||
/// Entropy production rate
|
||||
production_rate: f64,
|
||||
/// Entropy capacity
|
||||
capacity: f64,
|
||||
/// Entropy components
|
||||
components: HashMap<String, f64>,
|
||||
}
|
||||
|
||||
/// Energy budget for cognitive operations
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnergyBudget {
|
||||
/// Available energy
|
||||
available: f64,
|
||||
/// Total energy consumed
|
||||
consumed: f64,
|
||||
/// Energy from erasure
|
||||
erasure_cost: f64,
|
||||
/// Energy recovered from reversible computation
|
||||
recovered: f64,
|
||||
}
|
||||
|
||||
/// Maxwell's Demon for cognitive sorting
|
||||
#[derive(Debug)]
|
||||
pub struct MaxwellDemon {
|
||||
/// Demon's memory (cost of operation)
|
||||
memory: Vec<bool>,
|
||||
/// Memory capacity
|
||||
capacity: usize,
|
||||
/// Work extracted
|
||||
work_extracted: f64,
|
||||
/// Information cost
|
||||
information_cost: f64,
|
||||
/// Operating state
|
||||
active: bool,
|
||||
}
|
||||
|
||||
/// Phase states of cognitive matter
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum CognitivePhase {
|
||||
/// Solid - highly ordered, low entropy
|
||||
Crystalline,
|
||||
/// Liquid - flowing thoughts, moderate entropy
|
||||
Fluid,
|
||||
/// Gas - chaotic, high entropy
|
||||
Gaseous,
|
||||
/// Critical point - phase transition
|
||||
Critical,
|
||||
/// Bose-Einstein condensate analog - unified consciousness
|
||||
Condensate,
|
||||
}
|
||||
|
||||
/// A thermodynamic event
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ThermodynamicEvent {
|
||||
pub event_type: EventType,
|
||||
pub entropy_change: f64,
|
||||
pub energy_change: f64,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||
pub enum EventType {
|
||||
Erasure,
|
||||
Computation,
|
||||
Measurement,
|
||||
PhaseTransition,
|
||||
DemonOperation,
|
||||
HeatDissipation,
|
||||
}
|
||||
|
||||
impl CognitiveThermodynamics {
|
||||
/// Create a new cognitive thermodynamics system
|
||||
pub fn new(temperature: f64) -> Self {
|
||||
Self {
|
||||
temperature: temperature.max(0.001), // Avoid division by zero
|
||||
entropy: ThoughtEntropy::new(100.0),
|
||||
energy: EnergyBudget::new(1000.0),
|
||||
demon: MaxwellDemon::new(100),
|
||||
phase: CognitivePhase::Fluid,
|
||||
history: VecDeque::with_capacity(1000),
|
||||
k_b: 1.0, // Normalized Boltzmann constant
|
||||
}
|
||||
}
|
||||
|
||||
/// Measure current cognitive temperature
|
||||
pub fn measure_temperature(&self) -> f64 {
|
||||
self.temperature
|
||||
}
|
||||
|
||||
/// Set cognitive temperature
|
||||
pub fn set_temperature(&mut self, temp: f64) {
|
||||
let old_temp = self.temperature;
|
||||
self.temperature = temp.max(0.001);
|
||||
|
||||
// Check for phase transition
|
||||
self.check_phase_transition(old_temp, self.temperature);
|
||||
}
|
||||
|
||||
fn check_phase_transition(&mut self, old: f64, new: f64) {
|
||||
// Critical temperatures for phase transitions
|
||||
const T_FREEZE: f64 = 100.0;
|
||||
const T_BOIL: f64 = 500.0;
|
||||
const T_CRITICAL: f64 = 1000.0;
|
||||
const T_CONDENSATE: f64 = 10.0;
|
||||
|
||||
let old_phase = self.phase.clone();
|
||||
|
||||
self.phase = if new < T_CONDENSATE {
|
||||
CognitivePhase::Condensate
|
||||
} else if new < T_FREEZE {
|
||||
CognitivePhase::Crystalline
|
||||
} else if new < T_BOIL {
|
||||
CognitivePhase::Fluid
|
||||
} else if new < T_CRITICAL {
|
||||
CognitivePhase::Gaseous
|
||||
} else {
|
||||
CognitivePhase::Critical
|
||||
};
|
||||
|
||||
if old_phase != self.phase {
|
||||
// Record phase transition
|
||||
self.record_event(ThermodynamicEvent {
|
||||
event_type: EventType::PhaseTransition,
|
||||
entropy_change: (new - old).abs() * 0.1,
|
||||
energy_change: -(new - old).abs() * self.k_b,
|
||||
timestamp: self.current_time(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute Landauer cost of erasing n bits
|
||||
pub fn landauer_cost(&self, bits: usize) -> f64 {
|
||||
// E = n * k_B * T * ln(2)
|
||||
bits as f64 * self.k_b * self.temperature * std::f64::consts::LN_2
|
||||
}
|
||||
|
||||
/// Erase information (irreversible)
|
||||
pub fn erase(&mut self, bits: usize) -> ErasureResult {
|
||||
let cost = self.landauer_cost(bits);
|
||||
|
||||
if self.energy.available < cost {
|
||||
return ErasureResult {
|
||||
success: false,
|
||||
bits_erased: 0,
|
||||
energy_cost: 0.0,
|
||||
entropy_increase: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
// Consume energy
|
||||
self.energy.available -= cost;
|
||||
self.energy.consumed += cost;
|
||||
self.energy.erasure_cost += cost;
|
||||
|
||||
// Increase entropy (heat dissipation)
|
||||
let entropy_increase = bits as f64 * std::f64::consts::LN_2;
|
||||
self.entropy.current += entropy_increase;
|
||||
self.entropy.production_rate = entropy_increase;
|
||||
|
||||
self.record_event(ThermodynamicEvent {
|
||||
event_type: EventType::Erasure,
|
||||
entropy_change: entropy_increase,
|
||||
energy_change: -cost,
|
||||
timestamp: self.current_time(),
|
||||
});
|
||||
|
||||
ErasureResult {
|
||||
success: true,
|
||||
bits_erased: bits,
|
||||
energy_cost: cost,
|
||||
entropy_increase,
|
||||
}
|
||||
}
|
||||
|
||||
/// Perform reversible computation
|
||||
pub fn reversible_compute<T>(
|
||||
&mut self,
|
||||
input: T,
|
||||
forward: impl Fn(T) -> T,
|
||||
_backward: impl Fn(T) -> T,
|
||||
) -> T {
|
||||
// Reversible computation has no erasure cost
|
||||
// Only the logical transformation happens
|
||||
|
||||
self.record_event(ThermodynamicEvent {
|
||||
event_type: EventType::Computation,
|
||||
entropy_change: 0.0, // Reversible = no entropy change
|
||||
energy_change: 0.0,
|
||||
timestamp: self.current_time(),
|
||||
});
|
||||
|
||||
forward(input)
|
||||
}
|
||||
|
||||
/// Perform measurement (gains information, increases entropy elsewhere)
|
||||
pub fn measure(&mut self, precision_bits: usize) -> MeasurementResult {
|
||||
// Measurement is fundamentally irreversible
|
||||
// Gains information but produces entropy
|
||||
|
||||
let information_gained = precision_bits as f64;
|
||||
let entropy_cost = precision_bits as f64 * std::f64::consts::LN_2;
|
||||
let energy_cost = self.landauer_cost(precision_bits);
|
||||
|
||||
self.entropy.current += entropy_cost;
|
||||
self.energy.available -= energy_cost;
|
||||
self.energy.consumed += energy_cost;
|
||||
|
||||
self.record_event(ThermodynamicEvent {
|
||||
event_type: EventType::Measurement,
|
||||
entropy_change: entropy_cost,
|
||||
energy_change: -energy_cost,
|
||||
timestamp: self.current_time(),
|
||||
});
|
||||
|
||||
MeasurementResult {
|
||||
information_gained,
|
||||
entropy_cost,
|
||||
energy_cost,
|
||||
}
|
||||
}
|
||||
|
||||
/// Run Maxwell's demon to extract work
|
||||
pub fn run_demon(&mut self, operations: usize) -> DemonResult {
|
||||
if !self.demon.active {
|
||||
return DemonResult {
|
||||
work_extracted: 0.0,
|
||||
memory_used: 0,
|
||||
erasure_cost: 0.0,
|
||||
net_work: 0.0,
|
||||
};
|
||||
}
|
||||
|
||||
let ops = operations.min(self.demon.capacity - self.demon.memory.len());
|
||||
if ops == 0 {
|
||||
// Demon must erase memory first
|
||||
let erase_cost = self.landauer_cost(self.demon.memory.len());
|
||||
self.demon.memory.clear();
|
||||
self.demon.information_cost += erase_cost;
|
||||
self.energy.available -= erase_cost;
|
||||
|
||||
return DemonResult {
|
||||
work_extracted: 0.0,
|
||||
memory_used: 0,
|
||||
erasure_cost: erase_cost,
|
||||
net_work: -erase_cost,
|
||||
};
|
||||
}
|
||||
|
||||
// Each operation records 1 bit and extracts k_B * T * ln(2) work
|
||||
let work_per_op = self.k_b * self.temperature * std::f64::consts::LN_2;
|
||||
let total_work = ops as f64 * work_per_op;
|
||||
|
||||
for _ in 0..ops {
|
||||
self.demon.memory.push(true);
|
||||
}
|
||||
self.demon.work_extracted += total_work;
|
||||
|
||||
self.record_event(ThermodynamicEvent {
|
||||
event_type: EventType::DemonOperation,
|
||||
entropy_change: -(ops as f64) * std::f64::consts::LN_2, // Local decrease
|
||||
energy_change: total_work,
|
||||
timestamp: self.current_time(),
|
||||
});
|
||||
|
||||
DemonResult {
|
||||
work_extracted: total_work,
|
||||
memory_used: ops,
|
||||
erasure_cost: 0.0,
|
||||
net_work: total_work,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current phase
|
||||
pub fn phase(&self) -> &CognitivePhase {
|
||||
&self.phase
|
||||
}
|
||||
|
||||
/// Get entropy
|
||||
pub fn entropy(&self) -> &ThoughtEntropy {
|
||||
&self.entropy
|
||||
}
|
||||
|
||||
/// Get energy budget
|
||||
pub fn energy(&self) -> &EnergyBudget {
|
||||
&self.energy
|
||||
}
|
||||
|
||||
/// Add energy to the system
|
||||
pub fn add_energy(&mut self, amount: f64) {
|
||||
self.energy.available += amount;
|
||||
}
|
||||
|
||||
/// Calculate free energy (available for work)
|
||||
pub fn free_energy(&self) -> f64 {
|
||||
// F = E - T*S
|
||||
self.energy.available - self.temperature * self.entropy.current
|
||||
}
|
||||
|
||||
/// Calculate efficiency
|
||||
pub fn efficiency(&self) -> f64 {
|
||||
if self.energy.consumed == 0.0 {
|
||||
return 1.0;
|
||||
}
|
||||
self.energy.recovered / self.energy.consumed
|
||||
}
|
||||
|
||||
/// Get Carnot efficiency limit
|
||||
pub fn carnot_limit(&self, cold_temp: f64) -> f64 {
|
||||
if self.temperature <= cold_temp {
|
||||
return 0.0;
|
||||
}
|
||||
1.0 - cold_temp / self.temperature
|
||||
}
|
||||
|
||||
fn record_event(&mut self, event: ThermodynamicEvent) {
|
||||
self.history.push_back(event);
|
||||
if self.history.len() > 1000 {
|
||||
self.history.pop_front();
|
||||
}
|
||||
}
|
||||
|
||||
fn current_time(&self) -> u64 {
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.map(|d| d.as_secs())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
/// Get thermodynamic statistics
|
||||
pub fn statistics(&self) -> ThermodynamicStatistics {
|
||||
ThermodynamicStatistics {
|
||||
temperature: self.temperature,
|
||||
entropy: self.entropy.current,
|
||||
free_energy: self.free_energy(),
|
||||
energy_available: self.energy.available,
|
||||
efficiency: self.efficiency(),
|
||||
phase: self.phase.clone(),
|
||||
demon_work: self.demon.work_extracted,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThoughtEntropy {
|
||||
/// Create new entropy tracker
|
||||
pub fn new(capacity: f64) -> Self {
|
||||
Self {
|
||||
current: 0.0,
|
||||
production_rate: 0.0,
|
||||
capacity,
|
||||
components: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current entropy
|
||||
pub fn current(&self) -> f64 {
|
||||
self.current
|
||||
}
|
||||
|
||||
/// Set entropy for a component
|
||||
pub fn set_component(&mut self, name: &str, entropy: f64) {
|
||||
self.components.insert(name.to_string(), entropy);
|
||||
self.current = self.components.values().sum();
|
||||
}
|
||||
|
||||
/// Get entropy headroom
|
||||
pub fn headroom(&self) -> f64 {
|
||||
(self.capacity - self.current).max(0.0)
|
||||
}
|
||||
|
||||
/// Is at maximum entropy?
|
||||
pub fn is_maximum(&self) -> bool {
|
||||
self.current >= self.capacity * 0.99
|
||||
}
|
||||
}
|
||||
|
||||
impl EnergyBudget {
|
||||
/// Create new energy budget
|
||||
pub fn new(initial: f64) -> Self {
|
||||
Self {
|
||||
available: initial,
|
||||
consumed: 0.0,
|
||||
erasure_cost: 0.0,
|
||||
recovered: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get available energy
|
||||
pub fn available(&self) -> f64 {
|
||||
self.available
|
||||
}
|
||||
|
||||
/// Get total consumed
|
||||
pub fn consumed(&self) -> f64 {
|
||||
self.consumed
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxwellDemon {
|
||||
/// Create new Maxwell's demon
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
Self {
|
||||
memory: Vec::with_capacity(capacity),
|
||||
capacity,
|
||||
work_extracted: 0.0,
|
||||
information_cost: 0.0,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Activate demon
|
||||
pub fn activate(&mut self) {
|
||||
self.active = true;
|
||||
}
|
||||
|
||||
/// Deactivate demon
|
||||
pub fn deactivate(&mut self) {
|
||||
self.active = false;
|
||||
}
|
||||
|
||||
/// Get work extracted
|
||||
pub fn work_extracted(&self) -> f64 {
|
||||
self.work_extracted
|
||||
}
|
||||
|
||||
/// Get net work (accounting for erasure)
|
||||
pub fn net_work(&self) -> f64 {
|
||||
self.work_extracted - self.information_cost
|
||||
}
|
||||
|
||||
/// Memory usage fraction
|
||||
pub fn memory_usage(&self) -> f64 {
|
||||
self.memory.len() as f64 / self.capacity as f64
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of erasure operation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ErasureResult {
|
||||
pub success: bool,
|
||||
pub bits_erased: usize,
|
||||
pub energy_cost: f64,
|
||||
pub entropy_increase: f64,
|
||||
}
|
||||
|
||||
/// Result of measurement
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MeasurementResult {
|
||||
pub information_gained: f64,
|
||||
pub entropy_cost: f64,
|
||||
pub energy_cost: f64,
|
||||
}
|
||||
|
||||
/// Result of demon operation
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DemonResult {
|
||||
pub work_extracted: f64,
|
||||
pub memory_used: usize,
|
||||
pub erasure_cost: f64,
|
||||
pub net_work: f64,
|
||||
}
|
||||
|
||||
/// Thermodynamic statistics
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ThermodynamicStatistics {
|
||||
pub temperature: f64,
|
||||
pub entropy: f64,
|
||||
pub free_energy: f64,
|
||||
pub energy_available: f64,
|
||||
pub efficiency: f64,
|
||||
pub phase: CognitivePhase,
|
||||
pub demon_work: f64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_thermodynamics_creation() {
|
||||
let thermo = CognitiveThermodynamics::new(300.0);
|
||||
assert_eq!(thermo.measure_temperature(), 300.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_landauer_cost() {
|
||||
let thermo = CognitiveThermodynamics::new(300.0);
|
||||
|
||||
let cost_1bit = thermo.landauer_cost(1);
|
||||
let cost_2bits = thermo.landauer_cost(2);
|
||||
|
||||
// Cost should scale linearly
|
||||
assert!((cost_2bits - 2.0 * cost_1bit).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_erasure() {
|
||||
let mut thermo = CognitiveThermodynamics::new(300.0);
|
||||
// Add enough energy for the erasure to succeed
|
||||
thermo.add_energy(10000.0);
|
||||
let initial_energy = thermo.energy().available();
|
||||
|
||||
let result = thermo.erase(10);
|
||||
|
||||
assert!(result.success);
|
||||
assert_eq!(result.bits_erased, 10);
|
||||
assert!(thermo.energy().available() < initial_energy);
|
||||
assert!(thermo.entropy().current() > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reversible_computation() {
|
||||
let mut thermo = CognitiveThermodynamics::new(300.0);
|
||||
|
||||
let input = 5;
|
||||
let output = thermo.reversible_compute(
|
||||
input,
|
||||
|x| x * 2, // forward
|
||||
|x| x / 2, // backward
|
||||
);
|
||||
|
||||
assert_eq!(output, 10);
|
||||
// Reversible computation shouldn't increase entropy significantly
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_phase_transitions() {
|
||||
let mut thermo = CognitiveThermodynamics::new(300.0);
|
||||
|
||||
// Start in Fluid phase
|
||||
assert_eq!(*thermo.phase(), CognitivePhase::Fluid);
|
||||
|
||||
// Cool down
|
||||
thermo.set_temperature(50.0);
|
||||
assert_eq!(*thermo.phase(), CognitivePhase::Crystalline);
|
||||
|
||||
// Heat up
|
||||
thermo.set_temperature(600.0);
|
||||
assert_eq!(*thermo.phase(), CognitivePhase::Gaseous);
|
||||
|
||||
// Extreme cooling
|
||||
thermo.set_temperature(5.0);
|
||||
assert_eq!(*thermo.phase(), CognitivePhase::Condensate);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_maxwell_demon() {
|
||||
let mut thermo = CognitiveThermodynamics::new(300.0);
|
||||
|
||||
let result = thermo.run_demon(10);
|
||||
|
||||
assert!(result.work_extracted > 0.0);
|
||||
assert_eq!(result.memory_used, 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_free_energy() {
|
||||
let thermo = CognitiveThermodynamics::new(300.0);
|
||||
let free = thermo.free_energy();
|
||||
|
||||
// Free energy should be positive initially
|
||||
assert!(free > 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_entropy_components() {
|
||||
let mut entropy = ThoughtEntropy::new(100.0);
|
||||
|
||||
entropy.set_component("perception", 10.0);
|
||||
entropy.set_component("memory", 15.0);
|
||||
|
||||
assert_eq!(entropy.current(), 25.0);
|
||||
assert!(!entropy.is_maximum());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_demon_memory_limit() {
|
||||
let mut thermo = CognitiveThermodynamics::new(300.0);
|
||||
|
||||
// Fill demon memory
|
||||
for _ in 0..10 {
|
||||
thermo.run_demon(10);
|
||||
}
|
||||
|
||||
// Demon should need to erase memory eventually
|
||||
let usage = thermo.demon.memory_usage();
|
||||
assert!(usage > 0.0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user