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