scirs2_datasets/
neuromorphic_data_processor.rs

1//! Neuromorphic Data Processing Engine
2//!
3//! This module provides bio-inspired neuromorphic computing capabilities for
4//! advanced dataset processing, featuring spiking neural networks, synaptic
5//! plasticity, and brain-inspired learning algorithms.
6
7use 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/// Neuromorphic data processor using spiking neural networks
16#[derive(Debug, Clone)]
17pub struct NeuromorphicProcessor {
18    /// Network topology configuration
19    network_config: NetworkTopology,
20    /// Synaptic plasticity parameters
21    plasticity_config: SynapticPlasticity,
22    /// Spike timing dependent plasticity (STDP) enabled
23    stdp_enabled: bool,
24    /// Membrane potential decay rate
25    membrane_decay: f64,
26    /// Spike threshold voltage
27    spike_threshold: f64,
28    /// Learning rate for synaptic updates
29    learning_rate: f64,
30}
31
32/// Network topology configuration for neuromorphic processing
33#[derive(Debug, Clone)]
34pub struct NetworkTopology {
35    /// Number of input neurons
36    pub input_neurons: usize,
37    /// Number of hidden layer neurons
38    pub hidden_neurons: usize,
39    /// Number of output neurons
40    pub output_neurons: usize,
41    /// Connection probability between layers
42    pub connection_probability: f64,
43    /// Enable recurrent connections
44    pub recurrent_connections: bool,
45}
46
47/// Synaptic plasticity configuration
48#[derive(Debug, Clone)]
49pub struct SynapticPlasticity {
50    /// Hebbian learning strength
51    pub hebbian_strength: f64,
52    /// Anti-Hebbian learning strength  
53    pub anti_hebbian_strength: f64,
54    /// Synaptic weight decay rate
55    pub weight_decay: f64,
56    /// Maximum synaptic weight
57    pub max_weight: f64,
58    /// Minimum synaptic weight
59    pub min_weight: f64,
60}
61
62/// Spiking neuron state
63#[derive(Debug, Clone)]
64#[allow(dead_code)]
65struct NeuronState {
66    /// Current membrane potential
67    membrane_potential: f64,
68    /// Last spike time
69    last_spike_time: Option<Instant>,
70    /// Refractory period remaining
71    refractory_time: Duration,
72    /// Adaptive threshold
73    adaptive_threshold: f64,
74}
75
76/// Synaptic connection with STDP
77#[derive(Debug, Clone)]
78#[allow(dead_code)]
79struct Synapse {
80    /// Synaptic weight
81    weight: f64,
82    /// Pre-synaptic neuron index
83    pre_neuron: usize,
84    /// Post-synaptic neuron index
85    post_neuron: usize,
86    /// Synaptic delay
87    delay: Duration,
88    /// Spike trace for STDP
89    spike_trace: f64,
90}
91
92/// Neuromorphic dataset transformation results
93#[derive(Debug, Clone)]
94pub struct NeuromorphicTransform {
95    /// Transformed feature patterns
96    pub spike_patterns: Array3<f64>, // (time, neurons, features)
97    /// Synaptic connectivity matrix
98    pub connectivity_matrix: Array2<f64>,
99    /// Learning trajectory over time
100    pub learning_trajectory: Vec<f64>,
101    /// Emergent feature representations
102    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    /// Create a new neuromorphic processor
144    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    /// Configure spike timing dependent plasticity
156    pub fn with_stdp(mut self, enabled: bool) -> Self {
157        self.stdp_enabled = enabled;
158        self
159    }
160
161    /// Set membrane dynamics parameters
162    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    /// Transform dataset using neuromorphic processing
169    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        // Initialize neuromorphic network
191        let mut network = self.initialize_network(&mut rng)?;
192
193        // Process each sample through the spiking network
194        let time_steps = (simulation_time.as_millis() as usize) / 10; // 10ms resolution
195        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            // Store spike patterns for this sample
205            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        // Extract connectivity matrix
216        let connectivity_matrix = self.extract_connectivity_matrix(&network)?;
217
218        // Generate emergent feature representations
219        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    /// Generate neuromorphic-enhanced dataset using bio-inspired processes
230    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        // Initialize adaptive neural network
243        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        // Generate _samples through neuromorphic adaptation
249        for sample_idx in 0..n_samples {
250            // Neural network driven feature generation
251            let neural_features = self.generate_neural_features(n_features, &network, &mut rng)?;
252
253            // Bio-inspired target assignment using competitive learning
254            let target = self.competitive_learning_assignment(&neural_features, &mut rng)?;
255
256            // Store generated sample
257            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            // Adapt network based on generated sample (Hebbian plasticity)
263            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    /// Process temporal sequences using spike timing
272    pub fn process_temporal_sequence(
273        &self,
274        sequence_data: &Array3<f64>, // (time, samples, features)
275        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        // Process temporal sequence with spike timing dependent plasticity
297        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                // Convert to spike trains and process
305                let spike_response = self.temporal_spike_processing(
306                    &current_input_array,
307                    &mut network,
308                    time_idx,
309                    &mut rng,
310                )?;
311
312                // Store spike responses
313                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                // Apply STDP _learning if enabled
318                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    // Private helper methods for neuromorphic processing
339
340    #[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        // Create synaptic connections based on topology
349        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        // Add recurrent connections if enabled
370        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        // Simulate network dynamics over time
399        for time_idx in 0..time_steps {
400            // Apply input stimulus
401            self.apply_input_stimulus(sample, &mut neuron_states, time_idx)?;
402
403            // Update neuron dynamics
404            for neuron_idx in 0..n_neurons {
405                // Membrane potential decay
406                neuron_states[neuron_idx].membrane_potential *= self.membrane_decay;
407
408                // Check for spike generation
409                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; // Reset
414                    neuron_states[neuron_idx].last_spike_time = Some(Instant::now());
415
416                    // Adaptive threshold increase
417                    neuron_states[neuron_idx].adaptive_threshold *= 1.05;
418
419                    learning_score += 0.1; // Reward spiking activity
420                }
421
422                // Threshold decay
423                neuron_states[neuron_idx].adaptive_threshold =
424                    (neuron_states[neuron_idx].adaptive_threshold * 0.99).max(self.spike_threshold);
425            }
426
427            // Synaptic transmission
428            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        // Convert input features to spike trains using rate encoding
441        for (feature_idx, &feature_value) in sample.iter().enumerate() {
442            if feature_idx < self.network_config.input_neurons {
443                // Rate encoding: higher values = higher spike probability
444                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                // Apply to corresponding hidden neurons
452                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        // Propagate spikes through synaptic connections
469        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                            // Apply synaptic current with delay consideration
478                            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        // Extract temporal spike statistics as emergent features
509        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                // Compute spike rate and temporal _patterns
514                let spike_rate = neuron_spikes.sum() / time_steps as f64;
515                let spike_variance = neuron_spikes.variance();
516
517                // Combine metrics for emergent feature
518                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        // Use network weights to influence feature generation
534        for feature_idx in 0..n_features {
535            let mut feature_value = rng.random::<f64>() - 0.5;
536
537            // Neural network influence
538            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(); // Bounded activation
549        }
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        // Winner-take-all competitive learning for target assignment
560        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        // Add some noise for variability
568        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        // Apply Hebbian learning: "neurons that fire together, wire together"
578        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                        // Hebbian update: Δw = η * pre * post
587                        let hebbian_change =
588                            self.plasticity_config.hebbian_strength * pre_activity * post_activity;
589
590                        // Weight decay
591                        let decay_change = -self.plasticity_config.weight_decay * synapse.weight;
592
593                        // Update weight with bounds checking
594                        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        // Convert temporal input to spike response
618        for (neuron_idx, &input_val) in input.iter().enumerate().take(n_neurons) {
619            // Temporal encoding with time dependency
620            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        // Spike Timing Dependent Plasticity (STDP)
633        for synapses in network.iter_mut() {
634            for synapse in synapses {
635                // Simplified STDP: recent activity strengthens connections
636                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                // Decay spike trace
646                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        // Add recurrent connections within hidden layer
665        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/// Convenience function to create neuromorphic processor with default settings
690#[allow(dead_code)]
691pub fn create_neuromorphic_processor() -> NeuromorphicProcessor {
692    NeuromorphicProcessor::default()
693}
694
695/// Convenience function to create neuromorphic processor with custom topology
696#[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); // 100ms / 10ms = 10 time steps
731        assert_eq!(transform.spike_patterns.dim().1, 256); // Default hidden neurons
732        assert_eq!(transform.spike_patterns.dim().2, 10); // 10 samples
733        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)); // time, neurons, samples
762        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}