ruvector-mincut 2.0.6

World's first subpolynomial dynamic min-cut: self-healing networks, AI optimization, real-time graph analysis
Documentation
//! # SNN Integration for Dynamic MinCut
//!
//! Deep integration of Spiking Neural Networks with subpolynomial-time
//! dynamic minimum cut algorithms.
//!
//! ## Architecture Overview
//!
//! This module implements a six-layer integration architecture:
//!
//! 1. **Temporal Attractors**: SNN energy landscapes for graph optimization
//! 2. **Strange Loop**: Self-modifying meta-cognitive protocols
//! 3. **Causal Discovery**: Spike-timing based causal inference
//! 4. **Time Crystal CPG**: Central pattern generators for coordination
//! 5. **Morphogenetic Networks**: Bio-inspired self-organizing growth
//! 6. **Neural Optimizer**: Reinforcement learning on graph structures
//!
//! ## Triple Isomorphism
//!
//! The integration exploits the deep structural correspondence:
//!
//! | Graph Theory | Dynamical Systems | Neuromorphic |
//! |--------------|-------------------|--------------|
//! | MinCut value | Lyapunov exponent | Spike synchrony |
//! | Edge contraction | Phase space flow | Synaptic plasticity |
//! | Attractor basin | Stable manifold | Memory consolidation |
//!
//! ## Performance Targets
//!
//! | Metric | Current (CPU) | Unified (Neuromorphic) | Improvement |
//! |--------|---------------|------------------------|-------------|
//! | MinCut (1K nodes) | 50 μs | ~5 μs | 10x |
//! | Search (1M vectors) | 400 μs | ~40 μs | 10x |
//! | Energy per query | ~10 mJ | ~10 μJ | 1000x |

pub mod attractor;
pub mod causal;
pub mod cognitive_engine;
pub mod morphogenetic;
pub mod network;
pub mod neuron;
pub mod optimizer;
pub mod strange_loop;
pub mod synapse;
pub mod time_crystal;

// Re-exports
pub use attractor::{AttractorConfig, AttractorDynamics, EnergyLandscape};
pub use causal::{CausalConfig, CausalDiscoverySNN, CausalGraph, CausalRelation};
pub use cognitive_engine::{CognitiveMinCutEngine, EngineConfig, EngineMetrics, OperationMode};
pub use morphogenetic::{GrowthRules, MorphConfig, MorphogeneticSNN, TuringPattern};
pub use network::{LayerConfig, NetworkConfig, SpikingNetwork};
pub use neuron::{LIFNeuron, NeuronConfig, NeuronState, SpikeTrain};
pub use optimizer::{
    NeuralGraphOptimizer, OptimizationResult, OptimizerConfig, PolicySNN, ValueNetwork,
};
pub use strange_loop::{MetaAction, MetaCognitiveMinCut, MetaLevel, StrangeLoopConfig};
pub use synapse::{STDPConfig, Synapse, SynapseMatrix};
pub use time_crystal::{CPGConfig, OscillatorNeuron, PhaseTopology, TimeCrystalCPG};

use crate::graph::{DynamicGraph, EdgeId, VertexId, Weight};
use std::time::{Duration, Instant};

/// Simulation time in milliseconds
pub type SimTime = f64;

/// Spike event with timestamp
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Spike {
    /// Neuron ID that fired
    pub neuron_id: usize,
    /// Time of spike in simulation time
    pub time: SimTime,
}

/// Vector type for neural computations
pub type Vector = Vec<f64>;

/// Configuration for the unified SNN-MinCut system
#[derive(Debug, Clone)]
pub struct SNNMinCutConfig {
    /// Time step for simulation (ms)
    pub dt: f64,
    /// Number of neurons (typically matches graph vertices)
    pub num_neurons: usize,
    /// Enable attractor dynamics
    pub enable_attractors: bool,
    /// Enable strange loop self-modification
    pub enable_strange_loop: bool,
    /// Enable causal discovery
    pub enable_causal_discovery: bool,
    /// Enable time crystal coordination
    pub enable_time_crystal: bool,
    /// Enable morphogenetic growth
    pub enable_morphogenetic: bool,
    /// Enable neural optimization
    pub enable_optimizer: bool,
}

impl Default for SNNMinCutConfig {
    fn default() -> Self {
        Self {
            dt: 1.0, // 1ms timestep
            num_neurons: 1000,
            enable_attractors: true,
            enable_strange_loop: true,
            enable_causal_discovery: true,
            enable_time_crystal: true,
            enable_morphogenetic: true,
            enable_optimizer: true,
        }
    }
}

/// Result of a spike-driven computation
#[derive(Debug, Clone)]
pub struct SpikeComputeResult {
    /// Spikes generated during computation
    pub spikes: Vec<Spike>,
    /// Energy consumed (in arbitrary units)
    pub energy: f64,
    /// Duration of computation
    pub duration: Duration,
    /// MinCut value discovered/optimized
    pub mincut_value: Option<f64>,
}

/// Trait for spike-to-graph transduction
pub trait SpikeToGraph {
    /// Convert spike train to edge weight modulation
    fn spikes_to_weights(&self, spikes: &[Spike], graph: &mut DynamicGraph);

    /// Encode graph state as spike rates
    fn graph_to_spike_rates(&self, graph: &DynamicGraph) -> Vec<f64>;
}

/// Trait for graph-to-spike transduction
pub trait GraphToSpike {
    /// Convert edge weight to spike input current
    fn weight_to_current(&self, weight: Weight) -> f64;

    /// Convert vertex degree to spike threshold
    fn degree_to_threshold(&self, degree: usize) -> f64;
}

/// Default implementation of spike-to-graph transduction
#[derive(Debug, Clone, Default)]
pub struct DefaultSpikeGraphTransducer {
    /// Weight modulation factor
    pub weight_factor: f64,
    /// Current conversion factor
    pub current_factor: f64,
    /// Threshold scaling
    pub threshold_scale: f64,
}

impl DefaultSpikeGraphTransducer {
    /// Create a new transducer with default parameters
    pub fn new() -> Self {
        Self {
            weight_factor: 0.01,
            current_factor: 10.0,
            threshold_scale: 0.5,
        }
    }
}

impl SpikeToGraph for DefaultSpikeGraphTransducer {
    fn spikes_to_weights(&self, spikes: &[Spike], graph: &mut DynamicGraph) {
        // Group spikes by time window
        let mut spike_counts: std::collections::HashMap<usize, usize> =
            std::collections::HashMap::new();

        for spike in spikes {
            *spike_counts.entry(spike.neuron_id).or_insert(0) += 1;
        }

        // High spike correlation → strengthen edge
        for edge in graph.edges() {
            let src_spikes = spike_counts
                .get(&(edge.source as usize))
                .copied()
                .unwrap_or(0);
            let tgt_spikes = spike_counts
                .get(&(edge.target as usize))
                .copied()
                .unwrap_or(0);

            // Hebbian-like weight update
            let correlation = (src_spikes * tgt_spikes) as f64;
            let delta_w = self.weight_factor * correlation;

            if delta_w > 0.0 {
                let new_weight = edge.weight + delta_w;
                let _ = graph.update_edge_weight(edge.source, edge.target, new_weight);
            }
        }
    }

    fn graph_to_spike_rates(&self, graph: &DynamicGraph) -> Vec<f64> {
        let vertices = graph.vertices();
        let mut rates = vec![0.0; vertices.len()];

        for (i, v) in vertices.iter().enumerate() {
            // Higher degree → higher rate
            let degree = graph.degree(*v);
            // Total incident weight → rate modulation
            let weight_sum: f64 = graph
                .neighbors(*v)
                .iter()
                .filter_map(|(_, eid)| {
                    graph
                        .edges()
                        .iter()
                        .find(|e| e.id == *eid)
                        .map(|e| e.weight)
                })
                .sum();

            rates[i] = (degree as f64 + weight_sum) * 0.01;
        }

        rates
    }
}

impl GraphToSpike for DefaultSpikeGraphTransducer {
    fn weight_to_current(&self, weight: Weight) -> f64 {
        self.current_factor * weight
    }

    fn degree_to_threshold(&self, degree: usize) -> f64 {
        // Handle degree=0 to avoid ln(0) = -inf
        if degree == 0 {
            return 1.0;
        }
        1.0 + self.threshold_scale * (degree as f64).ln()
    }
}

/// Maximum spikes to process for synchrony (DoS protection)
const MAX_SYNCHRONY_SPIKES: usize = 10_000;

/// Synchrony measurement for spike trains using efficient O(n log n) algorithm
///
/// Uses time-binning approach instead of O(n²) pairwise comparison.
/// For large spike trains, uses sampling to maintain O(n log n) complexity.
pub fn compute_synchrony(spikes: &[Spike], window_ms: f64) -> f64 {
    if spikes.len() < 2 {
        return 0.0;
    }

    // Limit input size to prevent DoS
    let spikes = if spikes.len() > MAX_SYNCHRONY_SPIKES {
        &spikes[..MAX_SYNCHRONY_SPIKES]
    } else {
        spikes
    };

    // Sort by time for efficient windowed counting
    let mut sorted: Vec<_> = spikes.to_vec();
    sorted.sort_by(|a, b| {
        a.time
            .partial_cmp(&b.time)
            .unwrap_or(std::cmp::Ordering::Equal)
    });

    // Use sliding window approach: O(n log n) due to sort
    let mut coincidences = 0usize;
    let mut window_start = 0;

    for i in 0..sorted.len() {
        // Move window start forward
        while window_start < i && sorted[i].time - sorted[window_start].time > window_ms {
            window_start += 1;
        }

        // Count coincident pairs in window (from window_start to i-1)
        for j in window_start..i {
            if sorted[i].neuron_id != sorted[j].neuron_id {
                coincidences += 1;
            }
        }
    }

    // Total inter-neuron pairs (excluding same-neuron pairs)
    let n = sorted.len();
    let mut neuron_counts: std::collections::HashMap<usize, usize> =
        std::collections::HashMap::new();
    for spike in &sorted {
        *neuron_counts.entry(spike.neuron_id).or_insert(0) += 1;
    }

    // Total inter-neuron pairs = all pairs - same-neuron pairs
    let total_inter_pairs: usize = {
        let total = n * (n - 1) / 2;
        let intra: usize = neuron_counts.values().map(|&c| c * (c - 1) / 2).sum();
        total - intra
    };

    if total_inter_pairs == 0 {
        0.0
    } else {
        coincidences as f64 / total_inter_pairs as f64
    }
}

/// Lyapunov-like energy function combining mincut and synchrony
pub fn compute_energy(mincut: f64, synchrony: f64) -> f64 {
    -mincut - synchrony
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_default_config() {
        let config = SNNMinCutConfig::default();
        assert_eq!(config.dt, 1.0);
        assert!(config.enable_attractors);
    }

    #[test]
    fn test_synchrony_computation() {
        let spikes = vec![
            Spike {
                neuron_id: 0,
                time: 0.0,
            },
            Spike {
                neuron_id: 1,
                time: 0.5,
            },
            Spike {
                neuron_id: 2,
                time: 10.0,
            },
        ];

        let sync_narrow = compute_synchrony(&spikes, 1.0);
        let sync_wide = compute_synchrony(&spikes, 20.0);

        // Wider window should capture more coincidences
        assert!(sync_wide >= sync_narrow);
    }

    #[test]
    fn test_energy_function() {
        let energy = compute_energy(10.0, 0.5);
        assert!(energy < 0.0);

        // Higher mincut and synchrony → lower (more negative) energy
        let energy2 = compute_energy(20.0, 0.8);
        assert!(energy2 < energy);
    }

    #[test]
    fn test_spike_train() {
        let spike = Spike {
            neuron_id: 42,
            time: 100.5,
        };
        assert_eq!(spike.neuron_id, 42);
        assert!((spike.time - 100.5).abs() < 1e-10);
    }
}