ruvector_mincut/snn/
mod.rs

1//! # SNN Integration for Dynamic MinCut
2//!
3//! Deep integration of Spiking Neural Networks with subpolynomial-time
4//! dynamic minimum cut algorithms.
5//!
6//! ## Architecture Overview
7//!
8//! This module implements a six-layer integration architecture:
9//!
10//! 1. **Temporal Attractors**: SNN energy landscapes for graph optimization
11//! 2. **Strange Loop**: Self-modifying meta-cognitive protocols
12//! 3. **Causal Discovery**: Spike-timing based causal inference
13//! 4. **Time Crystal CPG**: Central pattern generators for coordination
14//! 5. **Morphogenetic Networks**: Bio-inspired self-organizing growth
15//! 6. **Neural Optimizer**: Reinforcement learning on graph structures
16//!
17//! ## Triple Isomorphism
18//!
19//! The integration exploits the deep structural correspondence:
20//!
21//! | Graph Theory | Dynamical Systems | Neuromorphic |
22//! |--------------|-------------------|--------------|
23//! | MinCut value | Lyapunov exponent | Spike synchrony |
24//! | Edge contraction | Phase space flow | Synaptic plasticity |
25//! | Attractor basin | Stable manifold | Memory consolidation |
26//!
27//! ## Performance Targets
28//!
29//! | Metric | Current (CPU) | Unified (Neuromorphic) | Improvement |
30//! |--------|---------------|------------------------|-------------|
31//! | MinCut (1K nodes) | 50 μs | ~5 μs | 10x |
32//! | Search (1M vectors) | 400 μs | ~40 μs | 10x |
33//! | Energy per query | ~10 mJ | ~10 μJ | 1000x |
34
35pub mod neuron;
36pub mod synapse;
37pub mod network;
38pub mod attractor;
39pub mod strange_loop;
40pub mod causal;
41pub mod time_crystal;
42pub mod morphogenetic;
43pub mod optimizer;
44pub mod cognitive_engine;
45
46// Re-exports
47pub use neuron::{LIFNeuron, NeuronState, NeuronConfig, SpikeTrain};
48pub use synapse::{Synapse, STDPConfig, SynapseMatrix};
49pub use network::{SpikingNetwork, NetworkConfig, LayerConfig};
50pub use attractor::{AttractorDynamics, EnergyLandscape, AttractorConfig};
51pub use strange_loop::{MetaCognitiveMinCut, MetaAction, MetaLevel, StrangeLoopConfig};
52pub use causal::{CausalDiscoverySNN, CausalGraph, CausalRelation, CausalConfig};
53pub use time_crystal::{TimeCrystalCPG, OscillatorNeuron, PhaseTopology, CPGConfig};
54pub use morphogenetic::{MorphogeneticSNN, GrowthRules, TuringPattern, MorphConfig};
55pub use optimizer::{NeuralGraphOptimizer, PolicySNN, ValueNetwork, OptimizerConfig, OptimizationResult};
56pub use cognitive_engine::{CognitiveMinCutEngine, EngineConfig, EngineMetrics, OperationMode};
57
58use crate::graph::{DynamicGraph, VertexId, EdgeId, Weight};
59use std::time::{Duration, Instant};
60
61/// Simulation time in milliseconds
62pub type SimTime = f64;
63
64/// Spike event with timestamp
65#[derive(Debug, Clone, Copy, PartialEq)]
66pub struct Spike {
67    /// Neuron ID that fired
68    pub neuron_id: usize,
69    /// Time of spike in simulation time
70    pub time: SimTime,
71}
72
73/// Vector type for neural computations
74pub type Vector = Vec<f64>;
75
76/// Configuration for the unified SNN-MinCut system
77#[derive(Debug, Clone)]
78pub struct SNNMinCutConfig {
79    /// Time step for simulation (ms)
80    pub dt: f64,
81    /// Number of neurons (typically matches graph vertices)
82    pub num_neurons: usize,
83    /// Enable attractor dynamics
84    pub enable_attractors: bool,
85    /// Enable strange loop self-modification
86    pub enable_strange_loop: bool,
87    /// Enable causal discovery
88    pub enable_causal_discovery: bool,
89    /// Enable time crystal coordination
90    pub enable_time_crystal: bool,
91    /// Enable morphogenetic growth
92    pub enable_morphogenetic: bool,
93    /// Enable neural optimization
94    pub enable_optimizer: bool,
95}
96
97impl Default for SNNMinCutConfig {
98    fn default() -> Self {
99        Self {
100            dt: 1.0, // 1ms timestep
101            num_neurons: 1000,
102            enable_attractors: true,
103            enable_strange_loop: true,
104            enable_causal_discovery: true,
105            enable_time_crystal: true,
106            enable_morphogenetic: true,
107            enable_optimizer: true,
108        }
109    }
110}
111
112/// Result of a spike-driven computation
113#[derive(Debug, Clone)]
114pub struct SpikeComputeResult {
115    /// Spikes generated during computation
116    pub spikes: Vec<Spike>,
117    /// Energy consumed (in arbitrary units)
118    pub energy: f64,
119    /// Duration of computation
120    pub duration: Duration,
121    /// MinCut value discovered/optimized
122    pub mincut_value: Option<f64>,
123}
124
125/// Trait for spike-to-graph transduction
126pub trait SpikeToGraph {
127    /// Convert spike train to edge weight modulation
128    fn spikes_to_weights(&self, spikes: &[Spike], graph: &mut DynamicGraph);
129
130    /// Encode graph state as spike rates
131    fn graph_to_spike_rates(&self, graph: &DynamicGraph) -> Vec<f64>;
132}
133
134/// Trait for graph-to-spike transduction
135pub trait GraphToSpike {
136    /// Convert edge weight to spike input current
137    fn weight_to_current(&self, weight: Weight) -> f64;
138
139    /// Convert vertex degree to spike threshold
140    fn degree_to_threshold(&self, degree: usize) -> f64;
141}
142
143/// Default implementation of spike-to-graph transduction
144#[derive(Debug, Clone, Default)]
145pub struct DefaultSpikeGraphTransducer {
146    /// Weight modulation factor
147    pub weight_factor: f64,
148    /// Current conversion factor
149    pub current_factor: f64,
150    /// Threshold scaling
151    pub threshold_scale: f64,
152}
153
154impl DefaultSpikeGraphTransducer {
155    /// Create a new transducer with default parameters
156    pub fn new() -> Self {
157        Self {
158            weight_factor: 0.01,
159            current_factor: 10.0,
160            threshold_scale: 0.5,
161        }
162    }
163}
164
165impl SpikeToGraph for DefaultSpikeGraphTransducer {
166    fn spikes_to_weights(&self, spikes: &[Spike], graph: &mut DynamicGraph) {
167        // Group spikes by time window
168        let mut spike_counts: std::collections::HashMap<usize, usize> =
169            std::collections::HashMap::new();
170
171        for spike in spikes {
172            *spike_counts.entry(spike.neuron_id).or_insert(0) += 1;
173        }
174
175        // High spike correlation → strengthen edge
176        for edge in graph.edges() {
177            let src_spikes = spike_counts.get(&(edge.source as usize)).copied().unwrap_or(0);
178            let tgt_spikes = spike_counts.get(&(edge.target as usize)).copied().unwrap_or(0);
179
180            // Hebbian-like weight update
181            let correlation = (src_spikes * tgt_spikes) as f64;
182            let delta_w = self.weight_factor * correlation;
183
184            if delta_w > 0.0 {
185                let new_weight = edge.weight + delta_w;
186                let _ = graph.update_edge_weight(edge.source, edge.target, new_weight);
187            }
188        }
189    }
190
191    fn graph_to_spike_rates(&self, graph: &DynamicGraph) -> Vec<f64> {
192        let vertices = graph.vertices();
193        let mut rates = vec![0.0; vertices.len()];
194
195        for (i, v) in vertices.iter().enumerate() {
196            // Higher degree → higher rate
197            let degree = graph.degree(*v);
198            // Total incident weight → rate modulation
199            let weight_sum: f64 = graph.neighbors(*v)
200                .iter()
201                .filter_map(|(_, eid)| {
202                    graph.edges().iter()
203                        .find(|e| e.id == *eid)
204                        .map(|e| e.weight)
205                })
206                .sum();
207
208            rates[i] = (degree as f64 + weight_sum) * 0.01;
209        }
210
211        rates
212    }
213}
214
215impl GraphToSpike for DefaultSpikeGraphTransducer {
216    fn weight_to_current(&self, weight: Weight) -> f64 {
217        self.current_factor * weight
218    }
219
220    fn degree_to_threshold(&self, degree: usize) -> f64 {
221        // Handle degree=0 to avoid ln(0) = -inf
222        if degree == 0 {
223            return 1.0;
224        }
225        1.0 + self.threshold_scale * (degree as f64).ln()
226    }
227}
228
229/// Maximum spikes to process for synchrony (DoS protection)
230const MAX_SYNCHRONY_SPIKES: usize = 10_000;
231
232/// Synchrony measurement for spike trains using efficient O(n log n) algorithm
233///
234/// Uses time-binning approach instead of O(n²) pairwise comparison.
235/// For large spike trains, uses sampling to maintain O(n log n) complexity.
236pub fn compute_synchrony(spikes: &[Spike], window_ms: f64) -> f64 {
237    if spikes.len() < 2 {
238        return 0.0;
239    }
240
241    // Limit input size to prevent DoS
242    let spikes = if spikes.len() > MAX_SYNCHRONY_SPIKES {
243        &spikes[..MAX_SYNCHRONY_SPIKES]
244    } else {
245        spikes
246    };
247
248    // Sort by time for efficient windowed counting
249    let mut sorted: Vec<_> = spikes.to_vec();
250    sorted.sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap_or(std::cmp::Ordering::Equal));
251
252    // Use sliding window approach: O(n log n) due to sort
253    let mut coincidences = 0usize;
254    let mut window_start = 0;
255
256    for i in 0..sorted.len() {
257        // Move window start forward
258        while window_start < i && sorted[i].time - sorted[window_start].time > window_ms {
259            window_start += 1;
260        }
261
262        // Count coincident pairs in window (from window_start to i-1)
263        for j in window_start..i {
264            if sorted[i].neuron_id != sorted[j].neuron_id {
265                coincidences += 1;
266            }
267        }
268    }
269
270    // Total inter-neuron pairs (excluding same-neuron pairs)
271    let n = sorted.len();
272    let mut neuron_counts: std::collections::HashMap<usize, usize> = std::collections::HashMap::new();
273    for spike in &sorted {
274        *neuron_counts.entry(spike.neuron_id).or_insert(0) += 1;
275    }
276
277    // Total inter-neuron pairs = all pairs - same-neuron pairs
278    let total_inter_pairs: usize = {
279        let total = n * (n - 1) / 2;
280        let intra: usize = neuron_counts.values().map(|&c| c * (c - 1) / 2).sum();
281        total - intra
282    };
283
284    if total_inter_pairs == 0 {
285        0.0
286    } else {
287        coincidences as f64 / total_inter_pairs as f64
288    }
289}
290
291/// Lyapunov-like energy function combining mincut and synchrony
292pub fn compute_energy(mincut: f64, synchrony: f64) -> f64 {
293    -mincut - synchrony
294}
295
296#[cfg(test)]
297mod tests {
298    use super::*;
299
300    #[test]
301    fn test_default_config() {
302        let config = SNNMinCutConfig::default();
303        assert_eq!(config.dt, 1.0);
304        assert!(config.enable_attractors);
305    }
306
307    #[test]
308    fn test_synchrony_computation() {
309        let spikes = vec![
310            Spike { neuron_id: 0, time: 0.0 },
311            Spike { neuron_id: 1, time: 0.5 },
312            Spike { neuron_id: 2, time: 10.0 },
313        ];
314
315        let sync_narrow = compute_synchrony(&spikes, 1.0);
316        let sync_wide = compute_synchrony(&spikes, 20.0);
317
318        // Wider window should capture more coincidences
319        assert!(sync_wide >= sync_narrow);
320    }
321
322    #[test]
323    fn test_energy_function() {
324        let energy = compute_energy(10.0, 0.5);
325        assert!(energy < 0.0);
326
327        // Higher mincut and synchrony → lower (more negative) energy
328        let energy2 = compute_energy(20.0, 0.8);
329        assert!(energy2 < energy);
330    }
331
332    #[test]
333    fn test_spike_train() {
334        let spike = Spike { neuron_id: 42, time: 100.5 };
335        assert_eq!(spike.neuron_id, 42);
336        assert!((spike.time - 100.5).abs() < 1e-10);
337    }
338}