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