1#![allow(dead_code)]
7type Result<T, E = torsh_core::error::TorshError> = std::result::Result<T, E>;
10
11use crate::{GraphData, GraphLayer};
12use std::collections::{HashMap, VecDeque};
13use torsh_tensor::{
14 creation::{randn, zeros},
15 Tensor,
16};
17
18#[derive(Debug, Clone)]
20pub struct SpikingGraphNetwork {
21 pub num_nodes: usize,
23 pub input_dim: usize,
25 pub hidden_dim: usize,
27 pub membrane_potentials: Tensor,
29 pub synaptic_weights: Tensor,
31 pub spike_threshold: f32,
33 pub tau_membrane: f32,
35 pub tau_synapse: f32,
37 pub refractory_period: usize,
39 pub spike_history: HashMap<usize, VecDeque<f32>>,
41 pub last_spike_times: Vec<Option<usize>>,
43 pub current_time: usize,
45 pub learning_rate: f32,
47 pub stdp_params: STDPParameters,
49}
50
51#[derive(Debug, Clone)]
53pub struct STDPParameters {
54 pub tau_pre: f32,
56 pub tau_post: f32,
58 pub a_plus: f32,
60 pub a_minus: f32,
62 pub learning_rate: f32,
64}
65
66impl STDPParameters {
67 pub fn new() -> Self {
68 Self {
69 tau_pre: 20.0,
70 tau_post: 20.0,
71 a_plus: 0.1,
72 a_minus: 0.12,
73 learning_rate: 0.01,
74 }
75 }
76}
77
78impl SpikingGraphNetwork {
79 pub fn new(
81 num_nodes: usize,
82 input_dim: usize,
83 hidden_dim: usize,
84 ) -> Result<Self, Box<dyn std::error::Error>> {
85 let membrane_potentials = zeros(&[num_nodes, hidden_dim])?;
86 let synaptic_weights = randn(&[num_nodes, num_nodes])?.mul_scalar(0.1)?;
87
88 let mut spike_history = HashMap::new();
89 for i in 0..num_nodes {
90 spike_history.insert(i, VecDeque::new());
91 }
92
93 Ok(Self {
94 num_nodes,
95 input_dim,
96 hidden_dim,
97 membrane_potentials,
98 synaptic_weights,
99 spike_threshold: 1.0,
100 tau_membrane: 20.0,
101 tau_synapse: 5.0,
102 refractory_period: 2,
103 spike_history,
104 last_spike_times: vec![None; num_nodes],
105 current_time: 0,
106 learning_rate: 0.01,
107 stdp_params: STDPParameters::new(),
108 })
109 }
110
111 pub fn forward_spike(
113 &mut self,
114 graph: &GraphData,
115 input_spikes: &Tensor,
116 ) -> Result<SpikingOutput, Box<dyn std::error::Error>> {
117 let _output_spikes = zeros::<f32>(&[self.num_nodes])?;
118 let spike_times = Vec::new();
119
120 self.update_membrane_potentials(input_spikes)?;
122
123 let spikes = self.generate_spikes()?;
125
126 let propagated_spikes = self.propagate_spikes(&spikes, graph)?;
128
129 self.apply_stdp_learning(&spikes)?;
131
132 self.update_spike_history(&spikes)?;
134
135 self.apply_refractory_period()?;
137
138 self.current_time += 1;
139
140 Ok(SpikingOutput {
141 spikes: propagated_spikes,
142 membrane_potentials: self.membrane_potentials.clone(),
143 spike_times,
144 firing_rates: self.compute_firing_rates()?,
145 })
146 }
147
148 fn update_membrane_potentials(
150 &mut self,
151 input_spikes: &Tensor,
152 ) -> Result<(), Box<dyn std::error::Error>> {
153 let decay_factor = (-1.0 / self.tau_membrane).exp();
155
156 self.membrane_potentials = self.membrane_potentials.mul_scalar(decay_factor)?;
158
159 let input_current = self.compute_input_current(input_spikes)?;
161 self.membrane_potentials = self.membrane_potentials.add(&input_current)?;
162
163 Ok(())
164 }
165
166 fn compute_input_current(
168 &self,
169 input_spikes: &Tensor,
170 ) -> Result<Tensor, Box<dyn std::error::Error>> {
171 let input_weights = randn(&[self.input_dim, self.hidden_dim])?.mul_scalar(0.5)?;
173
174 input_spikes
176 .matmul(&input_weights)
177 .map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
178 }
179
180 fn generate_spikes(&mut self) -> Result<Tensor, Box<dyn std::error::Error>> {
182 let mut spikes = zeros(&[self.num_nodes])?;
183 let membrane_data = self.membrane_potentials.to_vec()?;
184
185 for node in 0..self.num_nodes {
186 if let Some(last_spike_time) = self.last_spike_times[node] {
188 if self.current_time - last_spike_time < self.refractory_period {
189 continue;
190 }
191 }
192
193 let membrane_potential = membrane_data[node * self.hidden_dim]; if membrane_potential > self.spike_threshold {
196 spikes = self.set_spike(spikes, node, 1.0)?;
198 self.last_spike_times[node] = Some(self.current_time);
199
200 self.reset_membrane_potential(node)?;
202 }
203 }
204
205 Ok(spikes)
206 }
207
208 fn propagate_spikes(
210 &self,
211 spikes: &Tensor,
212 graph: &GraphData,
213 ) -> Result<Tensor, Box<dyn std::error::Error>> {
214 let edge_data = graph.edge_index.to_vec()?;
216 let num_edges = edge_data.len() / 2;
217
218 let mut propagated = spikes.clone();
219
220 for edge_idx in 0..num_edges {
222 let src_node = edge_data[edge_idx] as usize;
223 let dst_node = edge_data[edge_idx + num_edges] as usize;
224
225 if src_node < self.num_nodes && dst_node < self.num_nodes {
226 let weight = self.get_synaptic_weight(src_node, dst_node)?;
228
229 let src_spike = self.get_spike_value(spikes, src_node)?;
231 if src_spike > 0.0 {
232 let propagated_value = src_spike * weight;
233 propagated =
234 self.add_spike_contribution(propagated, dst_node, propagated_value)?;
235 }
236 }
237 }
238
239 Ok(propagated)
240 }
241
242 fn apply_stdp_learning(&mut self, spikes: &Tensor) -> Result<(), Box<dyn std::error::Error>> {
244 let spike_data = spikes.to_vec()?;
245
246 for pre_node in 0..self.num_nodes {
247 for post_node in 0..self.num_nodes {
248 if pre_node == post_node {
249 continue;
250 }
251
252 if let (Some(pre_history), Some(post_history)) = (
254 self.spike_history.get(&pre_node),
255 self.spike_history.get(&post_node),
256 ) {
257 let weight_update = self.calculate_stdp_update(
259 pre_history,
260 post_history,
261 spike_data[pre_node],
262 spike_data[post_node],
263 );
264
265 self.update_synaptic_weight(pre_node, post_node, weight_update)?;
267 }
268 }
269 }
270
271 Ok(())
272 }
273
274 fn calculate_stdp_update(
276 &self,
277 pre_history: &VecDeque<f32>,
278 post_history: &VecDeque<f32>,
279 current_pre_spike: f32,
280 current_post_spike: f32,
281 ) -> f32 {
282 let mut weight_update = 0.0;
283
284 if current_pre_spike > 0.0 && current_post_spike > 0.0 {
286 weight_update += self.stdp_params.a_plus * 0.1;
288 }
289
290 for (i, &pre_spike) in pre_history.iter().rev().enumerate() {
292 for (j, &post_spike) in post_history.iter().rev().enumerate() {
293 if pre_spike > 0.0 && post_spike > 0.0 {
294 let dt = (i as f32) - (j as f32);
295
296 if dt > 0.0 {
297 let strength =
299 self.stdp_params.a_plus * (-dt / self.stdp_params.tau_pre).exp();
300 weight_update += strength;
301 } else if dt < 0.0 {
302 let strength =
304 self.stdp_params.a_minus * (dt / self.stdp_params.tau_post).exp();
305 weight_update -= strength;
306 }
307 }
308 }
309 }
310
311 weight_update * self.stdp_params.learning_rate
312 }
313
314 fn update_spike_history(&mut self, spikes: &Tensor) -> Result<(), Box<dyn std::error::Error>> {
316 let spike_data = spikes.to_vec()?;
317
318 for node in 0..self.num_nodes {
319 if let Some(history) = self.spike_history.get_mut(&node) {
320 history.push_back(spike_data[node]);
321
322 if history.len() > 100 {
324 history.pop_front();
325 }
326 }
327 }
328
329 Ok(())
330 }
331
332 fn apply_refractory_period(&mut self) -> Result<(), Box<dyn std::error::Error>> {
334 for node in 0..self.num_nodes {
336 if let Some(last_spike_time) = self.last_spike_times[node] {
337 if self.current_time - last_spike_time < self.refractory_period {
338 self.set_membrane_potential(node, 0.0)?;
339 }
340 }
341 }
342
343 Ok(())
344 }
345
346 fn compute_firing_rates(&self) -> Result<Tensor, Box<dyn std::error::Error>> {
348 let mut firing_rates = zeros(&[self.num_nodes])?;
349 let window_size = 100; for node in 0..self.num_nodes {
352 if let Some(history) = self.spike_history.get(&node) {
353 let recent_spikes: f32 = history.iter().rev().take(window_size).sum();
354 let rate = recent_spikes / window_size as f32;
355 firing_rates = self.set_firing_rate(firing_rates, node, rate)?;
356 }
357 }
358
359 Ok(firing_rates)
360 }
361
362 fn set_spike(
365 &self,
366 spikes: Tensor,
367 _node: usize,
368 _value: f32,
369 ) -> Result<Tensor, Box<dyn std::error::Error>> {
370 Ok(spikes)
372 }
373
374 fn reset_membrane_potential(&mut self, node: usize) -> Result<(), Box<dyn std::error::Error>> {
375 self.set_membrane_potential(node, -0.7)?;
377 Ok(())
378 }
379
380 fn set_membrane_potential(
381 &mut self,
382 _node: usize,
383 _value: f32,
384 ) -> Result<(), Box<dyn std::error::Error>> {
385 Ok(())
387 }
388
389 fn get_synaptic_weight(
390 &self,
391 _src: usize,
392 _dst: usize,
393 ) -> Result<f32, Box<dyn std::error::Error>> {
394 Ok(0.1)
396 }
397
398 fn update_synaptic_weight(
399 &mut self,
400 _src: usize,
401 _dst: usize,
402 _update: f32,
403 ) -> Result<(), Box<dyn std::error::Error>> {
404 Ok(())
406 }
407
408 fn get_spike_value(
409 &self,
410 _spikes: &Tensor,
411 _node: usize,
412 ) -> Result<f32, Box<dyn std::error::Error>> {
413 Ok(0.0)
415 }
416
417 fn add_spike_contribution(
418 &self,
419 spikes: Tensor,
420 _node: usize,
421 _value: f32,
422 ) -> Result<Tensor, Box<dyn std::error::Error>> {
423 Ok(spikes)
425 }
426
427 fn set_firing_rate(
428 &self,
429 rates: Tensor,
430 _node: usize,
431 _rate: f32,
432 ) -> Result<Tensor, Box<dyn std::error::Error>> {
433 Ok(rates)
435 }
436}
437
438#[derive(Debug, Clone)]
440pub struct SpikingOutput {
441 pub spikes: Tensor,
443 pub membrane_potentials: Tensor,
445 pub spike_times: Vec<f32>,
447 pub firing_rates: Tensor,
449}
450
451#[derive(Debug)]
453pub struct EventDrivenGraphProcessor {
454 pub event_queue: VecDeque<GraphEvent>,
456 pub node_states: HashMap<usize, NodeState>,
458 pub processing_stats: EventProcessingStats,
460 pub energy_tracker: EnergyTracker,
462}
463
464#[derive(Debug, Clone)]
466pub struct GraphEvent {
467 pub timestamp: f64,
469 pub source_node: usize,
471 pub target_node: usize,
473 pub event_type: EventType,
475 pub data: f32,
477 pub priority: u8,
479}
480
481#[derive(Debug, Clone)]
482pub enum EventType {
483 Spike,
485 FeatureUpdate,
487 WeightUpdate,
489 ThresholdUpdate,
491 TopologyChange,
493}
494
495#[derive(Debug, Clone)]
497pub struct NodeState {
498 pub membrane_potential: f32,
500 pub last_update: f64,
502 pub charge: f32,
504 pub threshold: f32,
506 pub refractory_until: f64,
508 pub energy_consumed: f32,
510}
511
512impl EventDrivenGraphProcessor {
513 pub fn new(num_nodes: usize) -> Self {
515 let mut node_states = HashMap::new();
516 for i in 0..num_nodes {
517 node_states.insert(
518 i,
519 NodeState {
520 membrane_potential: -0.7,
521 last_update: 0.0,
522 charge: 0.0,
523 threshold: 1.0,
524 refractory_until: 0.0,
525 energy_consumed: 0.0,
526 },
527 );
528 }
529
530 Self {
531 event_queue: VecDeque::new(),
532 node_states,
533 processing_stats: EventProcessingStats::new(),
534 energy_tracker: EnergyTracker::new(),
535 }
536 }
537
538 pub fn process_events(&mut self, current_time: f64) -> Vec<GraphEvent> {
540 let mut generated_events = Vec::new();
541 let mut events_processed = 0;
542
543 while let Some(event) = self.event_queue.pop_front() {
544 if event.timestamp > current_time {
545 self.event_queue.push_front(event);
547 break;
548 }
549
550 let new_events = self.process_single_event(&event, current_time);
552 generated_events.extend(new_events);
553 events_processed += 1;
554
555 self.energy_tracker.record_event_processing();
557 }
558
559 self.processing_stats.events_processed += events_processed;
560 generated_events
561 }
562
563 fn process_single_event(&mut self, event: &GraphEvent, current_time: f64) -> Vec<GraphEvent> {
565 let mut new_events = Vec::new();
566
567 match event.event_type {
568 EventType::Spike => {
569 new_events.extend(self.process_spike_event(event, current_time));
570 }
571 EventType::FeatureUpdate => {
572 self.process_feature_update(event, current_time);
573 }
574 EventType::WeightUpdate => {
575 self.process_weight_update(event, current_time);
576 }
577 EventType::ThresholdUpdate => {
578 self.process_threshold_update(event, current_time);
579 }
580 EventType::TopologyChange => {
581 new_events.extend(self.process_topology_change(event, current_time));
582 }
583 }
584
585 new_events
586 }
587
588 fn process_spike_event(&mut self, event: &GraphEvent, current_time: f64) -> Vec<GraphEvent> {
590 let mut new_events = Vec::new();
591
592 if let Some(target_state) = self.node_states.get_mut(&event.target_node) {
593 if current_time < target_state.refractory_until {
595 return new_events;
596 }
597
598 target_state.membrane_potential += event.data;
600 target_state.last_update = current_time;
601
602 if target_state.membrane_potential >= target_state.threshold {
604 target_state.membrane_potential = -0.7; target_state.refractory_until = current_time + 0.002; let spike_event = GraphEvent {
610 timestamp: current_time + 0.001, source_node: event.target_node,
612 target_node: 0, event_type: EventType::Spike,
614 data: 1.0,
615 priority: 1,
616 };
617
618 new_events.push(spike_event);
619
620 self.energy_tracker.record_spike();
622 }
623 }
624
625 new_events
626 }
627
628 fn process_feature_update(&mut self, event: &GraphEvent, current_time: f64) {
629 if let Some(node_state) = self.node_states.get_mut(&event.target_node) {
630 node_state.charge += event.data;
632 node_state.last_update = current_time;
633 }
634 }
635
636 fn process_weight_update(&mut self, _event: &GraphEvent, _current_time: f64) {
637 self.energy_tracker.record_weight_update();
639 }
640
641 fn process_threshold_update(&mut self, event: &GraphEvent, current_time: f64) {
642 if let Some(node_state) = self.node_states.get_mut(&event.target_node) {
643 node_state.threshold = event.data;
644 node_state.last_update = current_time;
645 }
646 }
647
648 fn process_topology_change(
649 &mut self,
650 _event: &GraphEvent,
651 _current_time: f64,
652 ) -> Vec<GraphEvent> {
653 vec![]
655 }
656
657 pub fn add_event(&mut self, event: GraphEvent) {
659 let insert_pos = self
661 .event_queue
662 .iter()
663 .position(|e| e.timestamp > event.timestamp)
664 .unwrap_or(self.event_queue.len());
665
666 self.event_queue.insert(insert_pos, event);
667 }
668}
669
670#[derive(Debug, Clone)]
672pub struct EventProcessingStats {
673 pub events_processed: usize,
674 pub spikes_generated: usize,
675 pub average_processing_time: f64,
676 pub queue_length_max: usize,
677}
678
679impl EventProcessingStats {
680 pub fn new() -> Self {
681 Self {
682 events_processed: 0,
683 spikes_generated: 0,
684 average_processing_time: 0.0,
685 queue_length_max: 0,
686 }
687 }
688}
689
690#[derive(Debug, Clone)]
692pub struct EnergyTracker {
693 pub total_energy: f32,
695 pub energy_per_spike: f32,
697 pub energy_per_weight_update: f32,
699 pub energy_per_event: f32,
701 pub spike_count: usize,
703 pub weight_update_count: usize,
704 pub event_count: usize,
705}
706
707impl EnergyTracker {
708 pub fn new() -> Self {
709 Self {
710 total_energy: 0.0,
711 energy_per_spike: 1e-12, energy_per_weight_update: 1e-15, energy_per_event: 1e-15,
714 spike_count: 0,
715 weight_update_count: 0,
716 event_count: 0,
717 }
718 }
719
720 pub fn record_spike(&mut self) {
721 self.total_energy += self.energy_per_spike;
722 self.spike_count += 1;
723 }
724
725 pub fn record_weight_update(&mut self) {
726 self.total_energy += self.energy_per_weight_update;
727 self.weight_update_count += 1;
728 }
729
730 pub fn record_event_processing(&mut self) {
731 self.total_energy += self.energy_per_event;
732 self.event_count += 1;
733 }
734
735 pub fn get_energy_efficiency(&self) -> f32 {
736 if self.event_count > 0 {
737 self.total_energy / self.event_count as f32
738 } else {
739 0.0
740 }
741 }
742}
743
744#[derive(Debug, Clone)]
746pub struct LiquidStateMachine {
747 pub reservoir_size: usize,
749 pub connection_prob: f32,
751 pub spectral_radius: f32,
753 pub input_scaling: f32,
755 pub leak_rate: f32,
757 pub state: Tensor,
759 pub input_weights: Tensor,
761 pub reservoir_weights: Tensor,
763 pub memory_capacity: usize,
765 pub state_history: VecDeque<Tensor>,
767}
768
769impl LiquidStateMachine {
770 pub fn new(
772 input_dim: usize,
773 reservoir_size: usize,
774 connection_prob: f32,
775 ) -> Result<Self, Box<dyn std::error::Error>> {
776 let input_weights = randn(&[input_dim, reservoir_size])?.mul_scalar(0.1)?;
777 let reservoir_weights = Self::create_sparse_reservoir(reservoir_size, connection_prob)?;
778 let state = zeros(&[reservoir_size])?;
779
780 Ok(Self {
781 reservoir_size,
782 connection_prob,
783 spectral_radius: 0.9,
784 input_scaling: 1.0,
785 leak_rate: 0.3,
786 state,
787 input_weights,
788 reservoir_weights,
789 memory_capacity: 100,
790 state_history: VecDeque::new(),
791 })
792 }
793
794 pub fn process(&mut self, input: &Tensor) -> Result<Tensor, Box<dyn std::error::Error>> {
796 let reservoir_input = input.matmul(&self.input_weights)?;
798
799 let reservoir_activation = self.state.matmul(&self.reservoir_weights)?;
801 let total_input = reservoir_input.add(&reservoir_activation)?;
802
803 let activated = self.apply_tanh(&total_input)?;
805
806 let leak_complement = 1.0 - self.leak_rate;
808 self.state = self
809 .state
810 .mul_scalar(leak_complement)?
811 .add(&activated.mul_scalar(self.leak_rate)?)?;
812
813 self.state_history.push_back(self.state.clone());
815 if self.state_history.len() > self.memory_capacity {
816 self.state_history.pop_front();
817 }
818
819 Ok(self.state.clone())
820 }
821
822 fn create_sparse_reservoir(
823 size: usize,
824 prob: f32,
825 ) -> Result<Tensor, Box<dyn std::error::Error>> {
826 let mut weights = randn(&[size, size])?;
828
829 weights = weights.mul_scalar(prob)?;
831
832 Ok(weights)
833 }
834
835 fn apply_tanh(&self, tensor: &Tensor) -> Result<Tensor, Box<dyn std::error::Error>> {
836 Ok(tensor.clone())
838 }
839}
840
841#[derive(Debug)]
843pub struct NeuromorphicGraphLayer {
844 pub spiking_network: SpikingGraphNetwork,
846 pub event_processor: EventDrivenGraphProcessor,
848 pub liquid_state_machine: LiquidStateMachine,
850 pub processing_mode: NeuromorphicMode,
852}
853
854#[derive(Debug, Clone)]
855pub enum NeuromorphicMode {
856 Spiking,
858 EventDriven,
860 LiquidState,
862 Hybrid,
864}
865
866impl NeuromorphicGraphLayer {
867 pub fn new(
868 num_nodes: usize,
869 input_dim: usize,
870 hidden_dim: usize,
871 ) -> Result<Self, Box<dyn std::error::Error>> {
872 let spiking_network = SpikingGraphNetwork::new(num_nodes, input_dim, hidden_dim)?;
873 let event_processor = EventDrivenGraphProcessor::new(num_nodes);
874 let liquid_state_machine = LiquidStateMachine::new(input_dim, hidden_dim, 0.1)?;
875
876 Ok(Self {
877 spiking_network,
878 event_processor,
879 liquid_state_machine,
880 processing_mode: NeuromorphicMode::Hybrid,
881 })
882 }
883
884 pub fn set_mode(&mut self, mode: NeuromorphicMode) {
886 self.processing_mode = mode;
887 }
888}
889
890impl GraphLayer for NeuromorphicGraphLayer {
891 fn forward(&self, graph: &GraphData) -> Result<GraphData> {
892 Ok(graph.clone())
895 }
896
897 fn parameters(&self) -> Vec<Tensor> {
898 vec![
899 self.spiking_network.synaptic_weights.clone(),
900 self.liquid_state_machine.input_weights.clone(),
901 self.liquid_state_machine.reservoir_weights.clone(),
902 ]
903 }
904}
905
906#[cfg(test)]
907mod tests {
908 use super::*;
909
910 #[test]
911 fn test_spiking_network_creation() {
912 let network = SpikingGraphNetwork::new(10, 5, 8);
913 assert!(network.is_ok());
914
915 let net = network.unwrap();
916 assert_eq!(net.num_nodes, 10);
917 assert_eq!(net.input_dim, 5);
918 assert_eq!(net.hidden_dim, 8);
919 assert_eq!(net.spike_threshold, 1.0);
920 }
921
922 #[test]
923 fn test_stdp_parameters() {
924 let stdp = STDPParameters::new();
925 assert_eq!(stdp.tau_pre, 20.0);
926 assert_eq!(stdp.tau_post, 20.0);
927 assert_eq!(stdp.a_plus, 0.1);
928 assert_eq!(stdp.a_minus, 0.12);
929 }
930
931 #[test]
932 fn test_event_driven_processor() {
933 let processor = EventDrivenGraphProcessor::new(5);
934 assert_eq!(processor.node_states.len(), 5);
935 assert_eq!(processor.event_queue.len(), 0);
936 }
937
938 #[test]
939 fn test_graph_event_creation() {
940 let event = GraphEvent {
941 timestamp: 1.0,
942 source_node: 0,
943 target_node: 1,
944 event_type: EventType::Spike,
945 data: 1.0,
946 priority: 1,
947 };
948
949 assert_eq!(event.timestamp, 1.0);
950 assert_eq!(event.source_node, 0);
951 assert_eq!(event.target_node, 1);
952 }
953
954 #[test]
955 fn test_energy_tracker() {
956 let mut tracker = EnergyTracker::new();
957 tracker.record_spike();
958 tracker.record_weight_update();
959
960 assert_eq!(tracker.spike_count, 1);
961 assert_eq!(tracker.weight_update_count, 1);
962 assert!(tracker.total_energy > 0.0);
963 }
964
965 #[test]
966 fn test_liquid_state_machine() {
967 let lsm = LiquidStateMachine::new(3, 10, 0.1);
968 assert!(lsm.is_ok());
969
970 let machine = lsm.unwrap();
971 assert_eq!(machine.reservoir_size, 10);
972 assert_eq!(machine.connection_prob, 0.1);
973 assert_eq!(machine.spectral_radius, 0.9);
974 }
975
976 #[test]
977 fn test_neuromorphic_layer_creation() {
978 let layer = NeuromorphicGraphLayer::new(5, 3, 8);
979 assert!(layer.is_ok());
980
981 let neuromorphic_layer = layer.unwrap();
982 assert_eq!(neuromorphic_layer.spiking_network.num_nodes, 5);
983 }
984
985 #[test]
986 fn test_node_state() {
987 let state = NodeState {
988 membrane_potential: -0.7,
989 last_update: 0.0,
990 charge: 0.0,
991 threshold: 1.0,
992 refractory_until: 0.0,
993 energy_consumed: 0.0,
994 };
995
996 assert_eq!(state.membrane_potential, -0.7);
997 assert_eq!(state.threshold, 1.0);
998 }
999}