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