1use crate::error::{DatasetsError, Result};
8use crate::utils::Dataset;
9use scirs2_core::ndarray::{s, Array1, Array2, Array3};
10use scirs2_core::random::prelude::*;
11use scirs2_core::random::rand_distributions::Uniform;
12use statrs::statistics::Statistics;
13use std::time::{Duration, Instant};
14
15#[derive(Debug, Clone)]
17pub struct NeuromorphicProcessor {
18 network_config: NetworkTopology,
20 plasticity_config: SynapticPlasticity,
22 stdp_enabled: bool,
24 membrane_decay: f64,
26 spike_threshold: f64,
28 learning_rate: f64,
30}
31
32#[derive(Debug, Clone)]
34pub struct NetworkTopology {
35 pub input_neurons: usize,
37 pub hidden_neurons: usize,
39 pub output_neurons: usize,
41 pub connection_probability: f64,
43 pub recurrent_connections: bool,
45}
46
47#[derive(Debug, Clone)]
49pub struct SynapticPlasticity {
50 pub hebbian_strength: f64,
52 pub anti_hebbian_strength: f64,
54 pub weight_decay: f64,
56 pub max_weight: f64,
58 pub min_weight: f64,
60}
61
62#[derive(Debug, Clone)]
64#[allow(dead_code)]
65struct NeuronState {
66 membrane_potential: f64,
68 last_spike_time: Option<Instant>,
70 refractory_time: Duration,
72 adaptive_threshold: f64,
74}
75
76#[derive(Debug, Clone)]
78#[allow(dead_code)]
79struct Synapse {
80 weight: f64,
82 pre_neuron: usize,
84 post_neuron: usize,
86 delay: Duration,
88 spike_trace: f64,
90}
91
92#[derive(Debug, Clone)]
94pub struct NeuromorphicTransform {
95 pub spike_patterns: Array3<f64>, pub connectivity_matrix: Array2<f64>,
99 pub learning_trajectory: Vec<f64>,
101 pub emergent_features: Array2<f64>,
103}
104
105impl Default for NetworkTopology {
106 fn default() -> Self {
107 Self {
108 input_neurons: 100,
109 hidden_neurons: 256,
110 output_neurons: 10,
111 connection_probability: 0.15,
112 recurrent_connections: true,
113 }
114 }
115}
116
117impl Default for SynapticPlasticity {
118 fn default() -> Self {
119 Self {
120 hebbian_strength: 0.01,
121 anti_hebbian_strength: 0.005,
122 weight_decay: 0.001,
123 max_weight: 1.0,
124 min_weight: -1.0,
125 }
126 }
127}
128
129impl Default for NeuromorphicProcessor {
130 fn default() -> Self {
131 Self {
132 network_config: NetworkTopology::default(),
133 plasticity_config: SynapticPlasticity::default(),
134 stdp_enabled: true,
135 membrane_decay: 0.95,
136 spike_threshold: 1.0,
137 learning_rate: 0.001,
138 }
139 }
140}
141
142impl NeuromorphicProcessor {
143 pub fn new(network_config: NetworkTopology, plasticity_config: SynapticPlasticity) -> Self {
145 Self {
146 network_config,
147 plasticity_config,
148 stdp_enabled: true,
149 membrane_decay: 0.95,
150 spike_threshold: 1.0,
151 learning_rate: 0.001,
152 }
153 }
154
155 pub fn with_stdp(mut self, enabled: bool) -> Self {
157 self.stdp_enabled = enabled;
158 self
159 }
160
161 pub fn with_membrane_dynamics(mut self, decay: f64, threshold: f64) -> Self {
163 self.membrane_decay = decay;
164 self.spike_threshold = threshold;
165 self
166 }
167
168 pub fn transform_dataset(
170 &self,
171 dataset: &Dataset,
172 simulation_time: Duration,
173 random_seed: Option<u64>,
174 ) -> Result<NeuromorphicTransform> {
175 let data = &dataset.data;
176 let n_samples = data.nrows();
177 let n_features = data.ncols();
178
179 if n_samples == 0 || n_features == 0 {
180 return Err(DatasetsError::InvalidFormat(
181 "Dataset must have samples and features".to_string(),
182 ));
183 }
184
185 let mut rng = match random_seed {
186 Some(_seed) => StdRng::seed_from_u64(_seed),
187 None => StdRng::from_rng(&mut thread_rng()),
188 };
189
190 let mut network = self.initialize_network(&mut rng)?;
192
193 let time_steps = (simulation_time.as_millis() as usize) / 10; let mut spike_patterns =
196 Array3::zeros((time_steps, self.network_config.hidden_neurons, n_samples));
197 let mut learning_trajectory = Vec::with_capacity(n_samples);
198
199 for sample_idx in 0..n_samples {
200 let sample = data.row(sample_idx);
201 let (sample_spikes, learning_score) =
202 self.process_sample_neuromorphic(&sample, &mut network, time_steps, &mut rng)?;
203
204 for time_idx in 0..time_steps {
206 for neuron_idx in 0..self.network_config.hidden_neurons {
207 spike_patterns[[time_idx, neuron_idx, sample_idx]] =
208 sample_spikes[[time_idx, neuron_idx]];
209 }
210 }
211
212 learning_trajectory.push(learning_score);
213 }
214
215 let connectivity_matrix = self.extract_connectivity_matrix(&network)?;
217
218 let emergent_features = self.extract_emergent_features(&spike_patterns)?;
220
221 Ok(NeuromorphicTransform {
222 spike_patterns,
223 connectivity_matrix,
224 learning_trajectory,
225 emergent_features,
226 })
227 }
228
229 pub fn generate_bioinspired_dataset(
231 &self,
232 n_samples: usize,
233 n_features: usize,
234 adaptation_cycles: usize,
235 random_seed: Option<u64>,
236 ) -> Result<Dataset> {
237 let mut rng = match random_seed {
238 Some(_seed) => StdRng::seed_from_u64(_seed),
239 None => StdRng::from_rng(&mut thread_rng()),
240 };
241
242 let mut network = self.initialize_network(&mut rng)?;
244
245 let mut data = Array2::zeros((n_samples, n_features));
246 let mut targets = Array1::zeros(n_samples);
247
248 for sample_idx in 0..n_samples {
250 let neural_features = self.generate_neural_features(n_features, &network, &mut rng)?;
252
253 let target = self.competitive_learning_assignment(&neural_features, &mut rng)?;
255
256 for feature_idx in 0..n_features {
258 data[[sample_idx, feature_idx]] = neural_features[feature_idx];
259 }
260 targets[sample_idx] = target;
261
262 if sample_idx % adaptation_cycles == 0 {
264 self.adapt_network_hebbian(&mut network, &neural_features)?;
265 }
266 }
267
268 Ok(Dataset::new(data, Some(targets)))
269 }
270
271 pub fn process_temporal_sequence(
273 &self,
274 sequence_data: &Array3<f64>, stdp_learning: bool,
276 random_seed: Option<u64>,
277 ) -> Result<NeuromorphicTransform> {
278 let (time_steps, n_samples, n_features) = sequence_data.dim();
279
280 if time_steps == 0 || n_samples == 0 || n_features == 0 {
281 return Err(DatasetsError::InvalidFormat(
282 "Sequence _data must have time, samples, and features".to_string(),
283 ));
284 }
285
286 let mut rng = match random_seed {
287 Some(_seed) => StdRng::seed_from_u64(_seed),
288 None => StdRng::from_rng(&mut thread_rng()),
289 };
290
291 let mut network = self.initialize_network(&mut rng)?;
292 let mut spike_patterns =
293 Array3::zeros((time_steps, self.network_config.hidden_neurons, n_samples));
294 let mut learning_trajectory = Vec::with_capacity(time_steps);
295
296 for time_idx in 0..time_steps {
298 let mut time_step_learning = 0.0;
299
300 for sample_idx in 0..n_samples {
301 let current_input = sequence_data.slice(s![time_idx, sample_idx, ..]);
302 let current_input_array = current_input.to_owned();
303
304 let spike_response = self.temporal_spike_processing(
306 ¤t_input_array,
307 &mut network,
308 time_idx,
309 &mut rng,
310 )?;
311
312 for neuron_idx in 0..self.network_config.hidden_neurons {
314 spike_patterns[[time_idx, neuron_idx, sample_idx]] = spike_response[neuron_idx];
315 }
316
317 if stdp_learning && self.stdp_enabled {
319 let learning_change = self.apply_stdp_learning(&mut network, time_idx)?;
320 time_step_learning += learning_change;
321 }
322 }
323
324 learning_trajectory.push(time_step_learning / n_samples as f64);
325 }
326
327 let connectivity_matrix = self.extract_connectivity_matrix(&network)?;
328 let emergent_features = self.extract_emergent_features(&spike_patterns)?;
329
330 Ok(NeuromorphicTransform {
331 spike_patterns,
332 connectivity_matrix,
333 learning_trajectory,
334 emergent_features,
335 })
336 }
337
338 #[allow(clippy::needless_range_loop)]
341 fn initialize_network(&self, rng: &mut StdRng) -> Result<Vec<Vec<Synapse>>> {
342 let total_neurons = self.network_config.input_neurons
343 + self.network_config.hidden_neurons
344 + self.network_config.output_neurons;
345
346 let mut network = vec![Vec::new(); total_neurons];
347
348 for pre_idx in 0..self.network_config.input_neurons {
350 for post_idx in self.network_config.input_neurons
351 ..(self.network_config.input_neurons + self.network_config.hidden_neurons)
352 {
353 if rng.random::<f64>() < self.network_config.connection_probability {
354 let weight =
355 (rng.random::<f64>() - 0.5) * 2.0 * self.plasticity_config.max_weight;
356 let delay = Duration::from_millis(rng.sample(Uniform::new(1, 5).unwrap()));
357
358 network[pre_idx].push(Synapse {
359 weight,
360 pre_neuron: pre_idx,
361 post_neuron: post_idx,
362 delay,
363 spike_trace: 0.0,
364 });
365 }
366 }
367 }
368
369 if self.network_config.recurrent_connections {
371 self.add_recurrent_connections(&mut network, rng)?;
372 }
373
374 Ok(network)
375 }
376
377 fn process_sample_neuromorphic(
378 &self,
379 sample: &scirs2_core::ndarray::ArrayView1<f64>,
380 network: &mut [Vec<Synapse>],
381 time_steps: usize,
382 _rng: &mut StdRng,
383 ) -> Result<(Array2<f64>, f64)> {
384 let n_neurons = self.network_config.hidden_neurons;
385 let mut spike_pattern = Array2::zeros((time_steps, n_neurons));
386 let mut neuron_states = vec![
387 NeuronState {
388 membrane_potential: 0.0,
389 last_spike_time: None,
390 refractory_time: Duration::ZERO,
391 adaptive_threshold: self.spike_threshold,
392 };
393 n_neurons
394 ];
395
396 let mut learning_score = 0.0;
397
398 for time_idx in 0..time_steps {
400 self.apply_input_stimulus(sample, &mut neuron_states, time_idx)?;
402
403 for neuron_idx in 0..n_neurons {
405 neuron_states[neuron_idx].membrane_potential *= self.membrane_decay;
407
408 if neuron_states[neuron_idx].membrane_potential
410 > neuron_states[neuron_idx].adaptive_threshold
411 {
412 spike_pattern[[time_idx, neuron_idx]] = 1.0;
413 neuron_states[neuron_idx].membrane_potential = 0.0; neuron_states[neuron_idx].last_spike_time = Some(Instant::now());
415
416 neuron_states[neuron_idx].adaptive_threshold *= 1.05;
418
419 learning_score += 0.1; }
421
422 neuron_states[neuron_idx].adaptive_threshold =
424 (neuron_states[neuron_idx].adaptive_threshold * 0.99).max(self.spike_threshold);
425 }
426
427 self.propagate_spikes(network, &spike_pattern, &mut neuron_states, time_idx)?;
429 }
430
431 Ok((spike_pattern, learning_score / time_steps as f64))
432 }
433
434 fn apply_input_stimulus(
435 &self,
436 sample: &scirs2_core::ndarray::ArrayView1<f64>,
437 neuron_states: &mut [NeuronState],
438 _time_idx: usize,
439 ) -> Result<()> {
440 for (feature_idx, &feature_value) in sample.iter().enumerate() {
442 if feature_idx < self.network_config.input_neurons {
443 let spike_probability = (feature_value.abs().tanh() + 1.0) / 2.0;
445 let spike_current = if thread_rng().random::<f64>() < spike_probability {
446 0.5 * feature_value.signum()
447 } else {
448 0.0
449 };
450
451 if feature_idx < neuron_states.len() {
453 neuron_states[feature_idx].membrane_potential += spike_current;
454 }
455 }
456 }
457
458 Ok(())
459 }
460
461 fn propagate_spikes(
462 &self,
463 network: &mut [Vec<Synapse>],
464 spike_pattern: &Array2<f64>,
465 neuron_states: &mut [NeuronState],
466 time_idx: usize,
467 ) -> Result<()> {
468 for (pre_neuron_idx, synapses) in network.iter().enumerate() {
470 if pre_neuron_idx < spike_pattern.ncols() {
471 let spike_strength = spike_pattern[[time_idx, pre_neuron_idx]];
472
473 if spike_strength > 0.0 {
474 for synapse in synapses {
475 let post_neuron_idx = synapse.post_neuron;
476 if post_neuron_idx < neuron_states.len() {
477 let synaptic_current = spike_strength * synapse.weight;
479 neuron_states[post_neuron_idx].membrane_potential += synaptic_current;
480 }
481 }
482 }
483 }
484 }
485
486 Ok(())
487 }
488
489 fn extract_connectivity_matrix(&self, network: &[Vec<Synapse>]) -> Result<Array2<f64>> {
490 let n_neurons = self.network_config.hidden_neurons;
491 let mut connectivity = Array2::zeros((n_neurons, n_neurons));
492
493 for (pre_idx, synapses) in network.iter().enumerate() {
494 for synapse in synapses {
495 if pre_idx < n_neurons && synapse.post_neuron < n_neurons {
496 connectivity[[pre_idx, synapse.post_neuron]] = synapse.weight;
497 }
498 }
499 }
500
501 Ok(connectivity)
502 }
503
504 fn extract_emergent_features(&self, spike_patterns: &Array3<f64>) -> Result<Array2<f64>> {
505 let (time_steps, n_neurons, n_samples) = spike_patterns.dim();
506 let mut features = Array2::zeros((n_samples, n_neurons));
507
508 for sample_idx in 0..n_samples {
510 for neuron_idx in 0..n_neurons {
511 let neuron_spikes = spike_patterns.slice(s![.., neuron_idx, sample_idx]);
512
513 let spike_rate = neuron_spikes.sum() / time_steps as f64;
515 let spike_variance = neuron_spikes.variance();
516
517 features[[sample_idx, neuron_idx]] = spike_rate + 0.1 * spike_variance;
519 }
520 }
521
522 Ok(features)
523 }
524
525 fn generate_neural_features(
526 &self,
527 n_features: usize,
528 network: &[Vec<Synapse>],
529 rng: &mut StdRng,
530 ) -> Result<Array1<f64>> {
531 let mut features = Array1::zeros(n_features);
532
533 for feature_idx in 0..n_features {
535 let mut feature_value = rng.random::<f64>() - 0.5;
536
537 if feature_idx < network.len() {
539 let synaptic_influence: f64 = network[feature_idx]
540 .iter()
541 .map(|synapse| synapse.weight)
542 .sum::<f64>()
543 / network[feature_idx].len().max(1) as f64;
544
545 feature_value += 0.3 * synaptic_influence;
546 }
547
548 features[feature_idx] = feature_value.tanh(); }
550
551 Ok(features)
552 }
553
554 fn competitive_learning_assignment(
555 &self,
556 features: &Array1<f64>,
557 rng: &mut StdRng,
558 ) -> Result<f64> {
559 let max_feature_idx = features
561 .iter()
562 .enumerate()
563 .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
564 .map(|(idx, _)| idx)
565 .unwrap_or(0);
566
567 let noise = rng.random::<f64>() * 0.1 - 0.05;
569 Ok(max_feature_idx as f64 + noise)
570 }
571
572 fn adapt_network_hebbian(
573 &self,
574 network: &mut [Vec<Synapse>],
575 features: &Array1<f64>,
576 ) -> Result<()> {
577 for (pre_idx, synapses) in network.iter_mut().enumerate() {
579 if pre_idx < features.len() {
580 let pre_activity = features[pre_idx];
581
582 for synapse in synapses {
583 if synapse.post_neuron < features.len() {
584 let post_activity = features[synapse.post_neuron];
585
586 let hebbian_change =
588 self.plasticity_config.hebbian_strength * pre_activity * post_activity;
589
590 let decay_change = -self.plasticity_config.weight_decay * synapse.weight;
592
593 synapse.weight += hebbian_change + decay_change;
595 synapse.weight = synapse.weight.clamp(
596 self.plasticity_config.min_weight,
597 self.plasticity_config.max_weight,
598 );
599 }
600 }
601 }
602 }
603
604 Ok(())
605 }
606
607 fn temporal_spike_processing(
608 &self,
609 input: &Array1<f64>,
610 _network: &mut [Vec<Synapse>],
611 time_idx: usize,
612 _rng: &mut StdRng,
613 ) -> Result<Array1<f64>> {
614 let n_neurons = self.network_config.hidden_neurons;
615 let mut spike_response = Array1::zeros(n_neurons);
616
617 for (neuron_idx, &input_val) in input.iter().enumerate().take(n_neurons) {
619 let temporal_factor = 1.0 - (time_idx as f64 / 100.0).min(0.9);
621 let spike_probability = (input_val.abs() * temporal_factor).tanh();
622
623 spike_response[neuron_idx] = if spike_probability > 0.5 { 1.0 } else { 0.0 };
624 }
625
626 Ok(spike_response)
627 }
628
629 fn apply_stdp_learning(&self, network: &mut [Vec<Synapse>], time_idx: usize) -> Result<f64> {
630 let mut total_learning_change = 0.0;
631
632 for synapses in network.iter_mut() {
634 for synapse in synapses {
635 let time_factor = 1.0 / (1.0 + time_idx as f64 * 0.01);
637 let stdp_change = self.learning_rate * time_factor * synapse.spike_trace;
638
639 synapse.weight += stdp_change;
640 synapse.weight = synapse.weight.clamp(
641 self.plasticity_config.min_weight,
642 self.plasticity_config.max_weight,
643 );
644
645 synapse.spike_trace *= 0.95;
647
648 total_learning_change += stdp_change.abs();
649 }
650 }
651
652 Ok(total_learning_change)
653 }
654
655 #[allow(clippy::needless_range_loop)]
656 fn add_recurrent_connections(
657 &self,
658 network: &mut [Vec<Synapse>],
659 rng: &mut StdRng,
660 ) -> Result<()> {
661 let start_hidden = self.network_config.input_neurons;
662 let end_hidden = start_hidden + self.network_config.hidden_neurons;
663
664 for pre_idx in start_hidden..end_hidden {
666 for post_idx in start_hidden..end_hidden {
667 if pre_idx != post_idx
668 && rng.random::<f64>() < self.network_config.connection_probability * 0.5
669 {
670 let weight =
671 (rng.random::<f64>() - 0.5) * self.plasticity_config.max_weight * 0.5;
672 let delay = Duration::from_millis(rng.sample(Uniform::new(2, 10).unwrap()));
673
674 network[pre_idx].push(Synapse {
675 weight,
676 pre_neuron: pre_idx,
677 post_neuron: post_idx,
678 delay,
679 spike_trace: 0.0,
680 });
681 }
682 }
683 }
684
685 Ok(())
686 }
687}
688
689#[allow(dead_code)]
691pub fn create_neuromorphic_processor() -> NeuromorphicProcessor {
692 NeuromorphicProcessor::default()
693}
694
695#[allow(dead_code)]
697pub fn create_neuromorphic_processor_with_topology(
698 input_neurons: usize,
699 hidden_neurons: usize,
700 output_neurons: usize,
701) -> NeuromorphicProcessor {
702 let topology = NetworkTopology {
703 input_neurons,
704 hidden_neurons,
705 output_neurons,
706 connection_probability: 0.15,
707 recurrent_connections: true,
708 };
709
710 NeuromorphicProcessor::new(topology, SynapticPlasticity::default())
711}
712
713#[cfg(test)]
714mod tests {
715 use super::*;
716 use scirs2_core::ndarray::Array2;
717 use scirs2_core::random::Uniform;
718
719 #[test]
720 fn test_neuromorphic_dataset_transformation() {
721 let data = Array2::from_shape_vec((10, 4), (0..40).map(|x| x as f64).collect()).unwrap();
722 let targets = Array1::from((0..10).map(|x| (x % 2) as f64).collect::<Vec<_>>());
723 let dataset = Dataset::new(data, Some(targets));
724
725 let processor = NeuromorphicProcessor::default();
726 let transform = processor
727 .transform_dataset(&dataset, Duration::from_millis(100), Some(42))
728 .unwrap();
729
730 assert_eq!(transform.spike_patterns.dim().0, 10); assert_eq!(transform.spike_patterns.dim().1, 256); assert_eq!(transform.spike_patterns.dim().2, 10); assert_eq!(transform.connectivity_matrix.dim(), (256, 256));
734 assert_eq!(transform.learning_trajectory.len(), 10);
735 assert_eq!(transform.emergent_features.dim(), (10, 256));
736 }
737
738 #[test]
739 fn test_bioinspired_dataset_generation() {
740 let processor = NeuromorphicProcessor::default();
741 let dataset = processor
742 .generate_bioinspired_dataset(50, 5, 10, Some(42))
743 .unwrap();
744
745 assert_eq!(dataset.n_samples(), 50);
746 assert_eq!(dataset.n_features(), 5);
747 assert!(dataset.has_target());
748 }
749
750 #[test]
751 fn test_temporal_sequence_processing() {
752 let processor = NeuromorphicProcessor::default();
753 let sequence = Array3::from_shape_fn((5, 10, 4), |(t, s, f)| {
754 (t as f64 + s as f64 + f as f64) * 0.1
755 });
756
757 let result = processor
758 .process_temporal_sequence(&sequence, true, Some(42))
759 .unwrap();
760
761 assert_eq!(result.spike_patterns.dim(), (5, 256, 10)); assert_eq!(result.learning_trajectory.len(), 5);
763 }
764
765 #[test]
766 fn test_network_topology_configuration() {
767 let topology = NetworkTopology {
768 input_neurons: 50,
769 hidden_neurons: 128,
770 output_neurons: 5,
771 connection_probability: 0.2,
772 recurrent_connections: false,
773 };
774
775 let plasticity = SynapticPlasticity {
776 hebbian_strength: 0.02,
777 anti_hebbian_strength: 0.01,
778 weight_decay: 0.0005,
779 max_weight: 2.0,
780 min_weight: -2.0,
781 };
782
783 let processor = NeuromorphicProcessor::new(topology.clone(), plasticity.clone());
784 assert_eq!(processor.network_config.input_neurons, 50);
785 assert_eq!(processor.network_config.hidden_neurons, 128);
786 assert_eq!(processor.plasticity_config.hebbian_strength, 0.02);
787 }
788
789 #[test]
790 fn test_stdp_configuration() {
791 let processor = NeuromorphicProcessor::default()
792 .with_stdp(false)
793 .with_membrane_dynamics(0.9, 1.5);
794
795 assert!(!processor.stdp_enabled);
796 assert_eq!(processor.membrane_decay, 0.9);
797 assert_eq!(processor.spike_threshold, 1.5);
798 }
799}