1pub 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
46pub 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
63pub type SimTime = f64;
65
66#[derive(Debug, Clone, Copy, PartialEq)]
68pub struct Spike {
69 pub neuron_id: usize,
71 pub time: SimTime,
73}
74
75pub type Vector = Vec<f64>;
77
78#[derive(Debug, Clone)]
80pub struct SNNMinCutConfig {
81 pub dt: f64,
83 pub num_neurons: usize,
85 pub enable_attractors: bool,
87 pub enable_strange_loop: bool,
89 pub enable_causal_discovery: bool,
91 pub enable_time_crystal: bool,
93 pub enable_morphogenetic: bool,
95 pub enable_optimizer: bool,
97}
98
99impl Default for SNNMinCutConfig {
100 fn default() -> Self {
101 Self {
102 dt: 1.0, 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#[derive(Debug, Clone)]
116pub struct SpikeComputeResult {
117 pub spikes: Vec<Spike>,
119 pub energy: f64,
121 pub duration: Duration,
123 pub mincut_value: Option<f64>,
125}
126
127pub trait SpikeToGraph {
129 fn spikes_to_weights(&self, spikes: &[Spike], graph: &mut DynamicGraph);
131
132 fn graph_to_spike_rates(&self, graph: &DynamicGraph) -> Vec<f64>;
134}
135
136pub trait GraphToSpike {
138 fn weight_to_current(&self, weight: Weight) -> f64;
140
141 fn degree_to_threshold(&self, degree: usize) -> f64;
143}
144
145#[derive(Debug, Clone, Default)]
147pub struct DefaultSpikeGraphTransducer {
148 pub weight_factor: f64,
150 pub current_factor: f64,
152 pub threshold_scale: f64,
154}
155
156impl DefaultSpikeGraphTransducer {
157 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 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 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 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 let degree = graph.degree(*v);
206 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 if degree == 0 {
234 return 1.0;
235 }
236 1.0 + self.threshold_scale * (degree as f64).ln()
237 }
238}
239
240const MAX_SYNCHRONY_SPIKES: usize = 10_000;
242
243pub fn compute_synchrony(spikes: &[Spike], window_ms: f64) -> f64 {
248 if spikes.len() < 2 {
249 return 0.0;
250 }
251
252 let spikes = if spikes.len() > MAX_SYNCHRONY_SPIKES {
254 &spikes[..MAX_SYNCHRONY_SPIKES]
255 } else {
256 spikes
257 };
258
259 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 let mut coincidences = 0usize;
269 let mut window_start = 0;
270
271 for i in 0..sorted.len() {
272 while window_start < i && sorted[i].time - sorted[window_start].time > window_ms {
274 window_start += 1;
275 }
276
277 for j in window_start..i {
279 if sorted[i].neuron_id != sorted[j].neuron_id {
280 coincidences += 1;
281 }
282 }
283 }
284
285 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 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
307pub 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 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 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}