1use super::{
8 to_generic_or, EventPriority, MembraneDynamicsConfig, NeuromorphicEvent, NeuromorphicMetrics,
9 STDPConfig,
10};
11use crate::error::{OptimError, Result};
12use scirs2_core::ndarray::{Array1, Array2};
13use scirs2_core::numeric::Float;
14use std::cmp::Reverse;
15use std::collections::{BTreeMap, BinaryHeap, HashMap, HashSet, VecDeque};
16use std::fmt::Debug;
17use std::time::{Duration, Instant};
18
19fn write_uvarint(buf: &mut Vec<u8>, mut value: u64) {
26 loop {
27 let mut byte = (value & 0x7f) as u8;
28 value >>= 7;
29 if value != 0 {
30 byte |= 0x80;
31 }
32 buf.push(byte);
33 if value == 0 {
34 break;
35 }
36 }
37}
38
39fn read_uvarint(buf: &[u8], pos: &mut usize) -> Option<u64> {
40 let mut result: u64 = 0;
41 let mut shift: u32 = 0;
42 loop {
43 let byte = *buf.get(*pos)?;
44 *pos += 1;
45 result |= ((byte & 0x7f) as u64) << shift;
46 if byte & 0x80 == 0 {
47 break;
48 }
49 shift += 7;
50 if shift >= 64 {
51 return None;
52 }
53 }
54 Some(result)
55}
56
57fn zigzag_encode(value: i64) -> u64 {
58 ((value << 1) ^ (value >> 63)) as u64
59}
60
61fn zigzag_decode(value: u64) -> i64 {
62 ((value >> 1) as i64) ^ -((value & 1) as i64)
63}
64
65fn write_ivarint(buf: &mut Vec<u8>, value: i64) {
66 write_uvarint(buf, zigzag_encode(value));
67}
68
69fn read_ivarint(buf: &[u8], pos: &mut usize) -> Option<i64> {
70 read_uvarint(buf, pos).map(zigzag_decode)
71}
72
73const EVENT_FIXED_POINT_SCALE: f64 = 1000.0;
78
79fn decode_event_type(byte: u8) -> Result<EventType> {
80 match byte {
81 0 => Ok(EventType::Spike),
82 1 => Ok(EventType::WeightUpdate),
83 2 => Ok(EventType::ThresholdCrossing),
84 3 => Ok(EventType::PlasticityEvent),
85 4 => Ok(EventType::ExternalStimulus),
86 5 => Ok(EventType::TimerEvent),
87 6 => Ok(EventType::ErrorEvent),
88 7 => Ok(EventType::HomeostaticEvent),
89 8 => Ok(EventType::SynchronizationEvent),
90 9 => Ok(EventType::EnergyEvent),
91 other => Err(OptimError::InvalidConfig(format!(
92 "unknown encoded EventType discriminant: {other}"
93 ))),
94 }
95}
96
97fn decode_priority(byte: u8) -> Result<EventPriority> {
98 match byte {
99 0 => Ok(EventPriority::Low),
100 1 => Ok(EventPriority::Normal),
101 2 => Ok(EventPriority::High),
102 3 => Ok(EventPriority::Critical),
103 4 => Ok(EventPriority::RealTime),
104 other => Err(OptimError::InvalidConfig(format!(
105 "unknown encoded EventPriority discriminant: {other}"
106 ))),
107 }
108}
109
110fn truncated_bytes_err() -> crate::error::OptimError {
111 OptimError::InvalidConfig("truncated compressed event bytes".to_string())
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
116pub enum EventType {
117 Spike,
119
120 WeightUpdate,
122
123 ThresholdCrossing,
125
126 PlasticityEvent,
128
129 ExternalStimulus,
131
132 TimerEvent,
134
135 ErrorEvent,
137
138 HomeostaticEvent,
140
141 SynchronizationEvent,
143
144 EnergyEvent,
146}
147
148#[derive(Debug, Clone)]
150pub struct EventDrivenConfig<T: Float + Debug + Send + Sync + 'static> {
151 pub max_queue_size: usize,
153
154 pub processing_timeout: T,
156
157 pub priority_scheduling: bool,
159
160 pub event_threshold: T,
162
163 pub event_batching: bool,
165
166 pub batch_size: usize,
168
169 pub temporal_correlation: bool,
171
172 pub correlation_window: T,
174
175 pub adaptive_handling: bool,
177
178 pub rate_limits: HashMap<EventType, T>,
180
181 pub event_compression: bool,
183
184 pub compression_algorithm: EventCompressionAlgorithm,
186
187 pub distributed_processing: bool,
189
190 pub load_balancing: LoadBalancingStrategy,
192}
193
194#[derive(Debug, Clone, Copy)]
196pub enum EventCompressionAlgorithm {
197 None,
199
200 DeltaEncoding,
202
203 HuffmanEncoding,
205
206 RunLengthEncoding,
208
209 SparseEncoding,
211
212 PredictiveEncoding,
214}
215
216#[derive(Debug, Clone, Copy)]
218pub enum LoadBalancingStrategy {
219 RoundRobin,
221
222 TypeBased,
224
225 LoadAware,
227
228 LocalityAware,
230
231 Dynamic,
233}
234
235impl<T: Float + Debug + Send + Sync + 'static> Default for EventDrivenConfig<T> {
236 fn default() -> Self {
237 let mut rate_limits = HashMap::new();
238 rate_limits.insert(
239 EventType::Spike,
240 T::from(1000.0).unwrap_or_else(|| T::zero()),
241 );
242 rate_limits.insert(
243 EventType::WeightUpdate,
244 T::from(100.0).unwrap_or_else(|| T::zero()),
245 );
246 rate_limits.insert(
247 EventType::PlasticityEvent,
248 T::from(50.0).unwrap_or_else(|| T::zero()),
249 );
250
251 Self {
252 max_queue_size: 10000,
253 processing_timeout: T::from(1.0).unwrap_or_else(|| T::zero()),
254 priority_scheduling: true,
255 event_threshold: T::from(0.001).unwrap_or_else(|| T::zero()),
256 event_batching: true,
257 batch_size: 32,
258 temporal_correlation: true,
259 correlation_window: T::from(10.0).unwrap_or_else(|| T::zero()),
260 adaptive_handling: true,
261 rate_limits,
262 event_compression: false,
263 compression_algorithm: EventCompressionAlgorithm::None,
264 distributed_processing: false,
265 load_balancing: LoadBalancingStrategy::RoundRobin,
266 }
267 }
268}
269
270#[derive(Debug, Clone)]
272struct PriorityEventEntry<T: Float + Debug + Send + Sync + 'static> {
273 event: NeuromorphicEvent<T>,
274 insertion_time: Instant,
275}
276
277impl<T: Float + Debug + Send + Sync + 'static> PartialEq for PriorityEventEntry<T> {
278 fn eq(&self, other: &Self) -> bool {
279 self.event.priority == other.event.priority
280 }
281}
282
283impl<T: Float + Debug + Send + Sync + 'static> Eq for PriorityEventEntry<T> {}
284
285impl<T: Float + Debug + Send + Sync + 'static> PartialOrd for PriorityEventEntry<T> {
286 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
287 Some(self.cmp(other))
288 }
289}
290
291impl<T: Float + Debug + Send + Sync + 'static> Ord for PriorityEventEntry<T> {
292 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
293 self.event
302 .priority
303 .cmp(&other.event.priority)
304 .then_with(|| Reverse(self.insertion_time).cmp(&Reverse(other.insertion_time)))
305 }
306}
307
308pub struct EventDrivenOptimizer<T: Float + Debug + Send + Sync + 'static> {
310 config: EventDrivenConfig<T>,
312
313 stdp_config: STDPConfig<T>,
315
316 membrane_config: MembraneDynamicsConfig<T>,
318
319 event_queue: BinaryHeap<PriorityEventEntry<T>>,
321
322 event_stats: HashMap<EventType, EventStatistics<T>>,
324
325 system_state: SystemState<T>,
327
328 event_handlers: HashMap<EventType, Box<dyn EventHandler<T>>>,
330
331 correlation_tracker: TemporalCorrelationTracker<T>,
333
334 rate_limiter: EventRateLimiter<T>,
336
337 metrics: NeuromorphicMetrics<T>,
339
340 distributed_coordinator: Option<DistributedEventCoordinator<T>>,
342
343 compression_engine: EventCompressionEngine<T>,
348
349 compressed_chains: BTreeMap<EventPriority, CompressedEventChain<T>>,
354
355 compression_raw_bytes: usize,
358
359 compression_compressed_bytes: usize,
362
363 adaptive_handler: AdaptiveEventHandler<T>,
365}
366
367#[derive(Debug, Clone)]
369pub struct EventStatistics<T: Float + Debug + Send + Sync + 'static> {
370 pub total_processed: usize,
372
373 pub avg_processing_time: T,
375
376 pub event_rate: T,
378
379 pub avg_queue_wait_time: T,
381
382 pub error_count: usize,
384
385 pub last_update: Instant,
387}
388
389#[derive(Debug, Clone)]
391pub struct SystemState<T: Float + Debug + Send + Sync + 'static> {
392 pub membrane_potentials: Array1<T>,
394
395 pub synaptic_weights: Array2<T>,
397
398 pub last_spike_times: Array1<T>,
400
401 pub refractory_until: Array1<T>,
403
404 pub current_time: T,
406
407 pub active_neurons: HashSet<usize>,
409
410 pub pending_updates: HashMap<(usize, usize), T>,
412}
413
414trait EventHandler<T: Float + Debug + Send + Sync + 'static>: Send + Sync {
416 fn handle_event(
417 &mut self,
418 event: &NeuromorphicEvent<T>,
419 state: &mut SystemState<T>,
420 ) -> Result<()>;
421}
422
423struct SpikeEventHandler<T: Float + Debug + Send + Sync + 'static> {
425 stdp_config: STDPConfig<T>,
426 membrane_config: MembraneDynamicsConfig<T>,
427}
428
429impl<T: Float + Debug + Send + Sync + 'static> EventHandler<T> for SpikeEventHandler<T> {
430 fn handle_event(
431 &mut self,
432 event: &NeuromorphicEvent<T>,
433 state: &mut SystemState<T>,
434 ) -> Result<()> {
435 let neuron_id = event.source_neuron;
436
437 if neuron_id < state.membrane_potentials.len() {
439 state.membrane_potentials[neuron_id] = self.membrane_config.reset_potential;
441
442 state.refractory_until[neuron_id] =
444 state.current_time + self.membrane_config.refractory_period;
445
446 state.last_spike_times[neuron_id] = state.current_time;
448
449 state.active_neurons.insert(neuron_id);
451
452 self.trigger_stdp_updates(neuron_id, state)?;
454 }
455
456 Ok(())
457 }
458}
459
460impl<T: Float + Debug + Send + Sync + 'static> SpikeEventHandler<T> {
461 fn trigger_stdp_updates(&self, post_neuron: usize, state: &mut SystemState<T>) -> Result<()> {
462 let long_ago = to_generic_or(-1000.0, T::zero());
463 let now = state.current_time;
464
465 for other_neuron in 0..state.last_spike_times.len() {
466 if other_neuron == post_neuron {
467 continue;
468 }
469 let other_spike_time = state.last_spike_times[other_neuron];
470 if other_spike_time <= long_ago {
471 continue; }
473
474 let dt_ltp = now - other_spike_time;
478 let ltp = self.compute_stdp_weight_change(dt_ltp);
479 Self::accumulate_pending(state, (other_neuron, post_neuron), ltp);
480
481 let dt_ltd = other_spike_time - now;
491 let ltd = self.compute_stdp_weight_change(dt_ltd);
492 Self::accumulate_pending(state, (post_neuron, other_neuron), ltd);
493 }
494
495 Ok(())
496 }
497
498 fn accumulate_pending(state: &mut SystemState<T>, key: (usize, usize), delta: T) {
503 let entry = state.pending_updates.entry(key).or_insert_with(T::zero);
504 *entry = *entry + delta;
505 }
506
507 fn compute_stdp_weight_change(&self, dt: T) -> T {
508 if dt > T::zero() {
509 let exp_arg = -dt / self.stdp_config.tau_pot;
511 self.stdp_config.learning_rate_pot * exp_arg.exp()
512 } else {
513 let exp_arg = dt / self.stdp_config.tau_dep;
515 -self.stdp_config.learning_rate_dep * exp_arg.exp()
516 }
517 }
518}
519
520struct WeightUpdateEventHandler<T: Float + Debug + Send + Sync + 'static> {
522 stdp_config: STDPConfig<T>,
523}
524
525impl<T: Float + Debug + Send + Sync + 'static> EventHandler<T> for WeightUpdateEventHandler<T> {
526 fn handle_event(
527 &mut self,
528 event: &NeuromorphicEvent<T>,
529 state: &mut SystemState<T>,
530 ) -> Result<()> {
531 let source = event.source_neuron;
532
533 if let Some(target) = event.target_neuron {
534 if source < state.synaptic_weights.nrows() && target < state.synaptic_weights.ncols() {
535 let current_weight = state.synaptic_weights[[source, target]];
537 let new_weight = (current_weight + event.value)
538 .max(self.stdp_config.weight_min)
539 .min(self.stdp_config.weight_max);
540
541 state.synaptic_weights[[source, target]] = new_weight;
542 }
543 }
544
545 Ok(())
546 }
547}
548
549struct TemporalCorrelationTracker<T: Float + Debug + Send + Sync + 'static> {
551 correlation_window: T,
552 event_history: VecDeque<(T, EventType, usize)>,
553 correlation_patterns: HashMap<(EventType, EventType), T>,
554}
555
556impl<T: Float + Debug + Send + Sync + 'static + std::ops::AddAssign> TemporalCorrelationTracker<T> {
557 fn new(correlation_window: T) -> Self {
558 Self {
559 correlation_window,
560 event_history: VecDeque::new(),
561 correlation_patterns: HashMap::new(),
562 }
563 }
564
565 fn add_event(&mut self, time: T, event_type: EventType, neuron_id: usize) {
566 self.event_history.push_back((time, event_type, neuron_id));
568
569 while let Some(&(old_time, _, _)) = self.event_history.front() {
571 if time - old_time > self.correlation_window {
572 self.event_history.pop_front();
573 } else {
574 break;
575 }
576 }
577
578 self.update_correlations(time, event_type);
580 }
581
582 fn update_correlations(&mut self, current_time: T, current_event: EventType) {
583 for &(event_time, event_type_, _) in &self.event_history {
584 if current_time - event_time <= self.correlation_window {
585 let correlation_key = (event_type_, current_event);
586 let time_diff = current_time - event_time;
587 let correlation_strength = (-time_diff / self.correlation_window).exp();
588
589 *self
590 .correlation_patterns
591 .entry(correlation_key)
592 .or_insert(T::zero()) += correlation_strength;
593 }
594 }
595 }
596
597 pub(crate) fn get_correlation(&self, event1: EventType, event2: EventType) -> T {
599 self.correlation_patterns
600 .get(&(event1, event2))
601 .copied()
602 .unwrap_or(T::zero())
603 }
604}
605
606struct EventRateLimiter<T: Float + Debug + Send + Sync + 'static> {
608 rate_limits: HashMap<EventType, T>,
609 event_counts: HashMap<EventType, usize>,
610 last_reset: Instant,
611 reset_interval: Duration,
612}
613
614impl<T: Float + Debug + Send + Sync + 'static> EventRateLimiter<T> {
615 fn new(rate_limits: HashMap<EventType, T>) -> Self {
616 Self {
617 rate_limits,
618 event_counts: HashMap::new(),
619 last_reset: Instant::now(),
620 reset_interval: Duration::from_secs(1),
621 }
622 }
623
624 fn can_process(&mut self, event_type: EventType) -> bool {
625 if self.last_reset.elapsed() >= self.reset_interval {
627 self.event_counts.clear();
628 self.last_reset = Instant::now();
629 }
630
631 if let Some(&limit) = self.rate_limits.get(&event_type) {
632 let current_count = self.event_counts.get(&event_type).copied().unwrap_or(0);
633 if T::from(current_count).unwrap_or_else(|| T::zero()) < limit {
634 *self.event_counts.entry(event_type).or_insert(0) += 1;
635 true
636 } else {
637 false
638 }
639 } else {
640 true
641 }
642 }
643}
644
645struct EventCompressionEngine<T: Float + Debug + Send + Sync + 'static> {
647 algorithm: EventCompressionAlgorithm,
648 compression_buffer: Vec<u8>,
649 decompression_buffer: Vec<u8>,
650 last_event_fields: Option<(i64, i64, Option<i64>, i64, i64)>,
654 _phantom: std::marker::PhantomData<T>,
655}
656
657fn field_to_fixed<T: Float>(value: T) -> i64 {
661 let scaled = value.to_f64().unwrap_or(0.0) * EVENT_FIXED_POINT_SCALE;
662 if !scaled.is_finite() {
663 0
664 } else {
665 scaled.clamp(i64::MIN as f64, i64::MAX as f64) as i64
666 }
667}
668
669fn fixed_to_field<T: Float>(fixed: i64) -> T {
670 to_generic_or(fixed as f64 / EVENT_FIXED_POINT_SCALE, T::zero())
671}
672
673impl<T: Float + Debug + Send + Sync + 'static> EventCompressionEngine<T> {
674 fn new(algorithm: EventCompressionAlgorithm) -> Self {
675 Self {
676 algorithm,
677 compression_buffer: Vec::new(),
678 decompression_buffer: Vec::new(),
679 last_event_fields: None,
680 _phantom: std::marker::PhantomData,
681 }
682 }
683
684 fn compress_event(&mut self, event: &NeuromorphicEvent<T>) -> Result<Vec<u8>> {
685 let compressed = match self.algorithm {
686 EventCompressionAlgorithm::None => {
687 self.serialize_event(event)
689 }
690 EventCompressionAlgorithm::DeltaEncoding => self.delta_encode_event(event),
691 EventCompressionAlgorithm::SparseEncoding => self.sparse_encode_event(event),
692 _ => {
693 self.serialize_event(event)
695 }
696 }?;
697 self.compression_buffer.clear();
698 self.compression_buffer.extend_from_slice(&compressed);
699 Ok(compressed)
700 }
701
702 fn decompress_event(&mut self, bytes: &[u8]) -> Result<NeuromorphicEvent<T>> {
708 self.decompression_buffer.clear();
709 self.decompression_buffer.extend_from_slice(bytes);
710 match self.algorithm {
711 EventCompressionAlgorithm::DeltaEncoding => self.delta_decode_event(bytes),
712 EventCompressionAlgorithm::SparseEncoding => self.sparse_decode_event(bytes),
713 _ => self.deserialize_event(bytes),
714 }
715 }
716
717 fn serialize_event(&self, event: &NeuromorphicEvent<T>) -> Result<Vec<u8>> {
723 let mut data = Vec::new();
724 data.push(event.event_type as u8);
725 data.push(event.priority as u8);
726 write_uvarint(&mut data, event.source_neuron as u64);
727 match event.target_neuron {
728 Some(target) => {
729 data.push(1);
730 write_uvarint(&mut data, target as u64);
731 }
732 None => data.push(0),
733 }
734 write_ivarint(&mut data, field_to_fixed(event.timestamp));
735 write_ivarint(&mut data, field_to_fixed(event.value));
736 write_ivarint(&mut data, field_to_fixed(event.energy_cost));
737 Ok(data)
738 }
739
740 fn deserialize_event(&self, bytes: &[u8]) -> Result<NeuromorphicEvent<T>> {
741 let mut pos = 0usize;
742 let event_type = decode_event_type(*bytes.first().ok_or_else(truncated_bytes_err)?)?;
743 pos += 1;
744 let priority = decode_priority(*bytes.get(pos).ok_or_else(truncated_bytes_err)?)?;
745 pos += 1;
746 let source_neuron = read_uvarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)? as usize;
747 let has_target = *bytes.get(pos).ok_or_else(truncated_bytes_err)?;
748 pos += 1;
749 let target_neuron = if has_target == 1 {
750 Some(read_uvarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)? as usize)
751 } else {
752 None
753 };
754 let timestamp =
755 fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?);
756 let value = fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?);
757 let energy_cost =
758 fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?);
759 Ok(NeuromorphicEvent {
760 event_type,
761 timestamp,
762 source_neuron,
763 target_neuron,
764 value,
765 energy_cost,
766 priority,
767 })
768 }
769
770 fn delta_encode_event(&mut self, event: &NeuromorphicEvent<T>) -> Result<Vec<u8>> {
779 let ts = field_to_fixed(event.timestamp);
780 let src = event.source_neuron as i64;
781 let tgt = event.target_neuron.map(|t| t as i64);
782 let val = field_to_fixed(event.value);
783 let energy = field_to_fixed(event.energy_cost);
784
785 let (base_ts, base_src, base_tgt, base_val, base_energy) =
786 self.last_event_fields.unwrap_or((0, 0, None, 0, 0));
787
788 let mut data = Vec::new();
789 data.push(event.event_type as u8);
790 data.push(event.priority as u8);
791 write_ivarint(&mut data, src - base_src);
792 match (tgt, base_tgt) {
793 (Some(t), Some(b)) => {
794 data.push(1);
795 write_ivarint(&mut data, t - b);
796 }
797 (Some(t), None) => {
798 data.push(2); write_ivarint(&mut data, t);
800 }
801 (None, _) => data.push(0),
802 }
803 write_ivarint(&mut data, ts - base_ts);
804 write_ivarint(&mut data, val - base_val);
805 write_ivarint(&mut data, energy - base_energy);
806
807 self.last_event_fields = Some((ts, src, tgt, val, energy));
808 Ok(data)
809 }
810
811 fn delta_decode_event(&mut self, bytes: &[u8]) -> Result<NeuromorphicEvent<T>> {
812 let mut pos = 0usize;
813 let event_type = decode_event_type(*bytes.first().ok_or_else(truncated_bytes_err)?)?;
814 pos += 1;
815 let priority = decode_priority(*bytes.get(pos).ok_or_else(truncated_bytes_err)?)?;
816 pos += 1;
817
818 let (base_ts, base_src, base_tgt, base_val, base_energy) =
819 self.last_event_fields.unwrap_or((0, 0, None, 0, 0));
820
821 let src = base_src + read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?;
822 let target_tag = *bytes.get(pos).ok_or_else(truncated_bytes_err)?;
823 pos += 1;
824 let tgt = match target_tag {
825 0 => None,
826 1 => {
827 let base = base_tgt.ok_or_else(|| {
828 OptimError::InvalidConfig(
829 "delta-encoded target references a missing baseline".to_string(),
830 )
831 })?;
832 Some(base + read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?)
833 }
834 2 => Some(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?),
835 other => {
836 return Err(OptimError::InvalidConfig(format!(
837 "invalid delta-encoded target tag: {other}"
838 )))
839 }
840 };
841 let ts = base_ts + read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?;
842 let val = base_val + read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?;
843 let energy = base_energy + read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?;
844
845 self.last_event_fields = Some((ts, src, tgt, val, energy));
846
847 if src < 0 {
848 return Err(OptimError::InvalidConfig(
849 "delta-decoded source_neuron underflowed".to_string(),
850 ));
851 }
852 Ok(NeuromorphicEvent {
853 event_type,
854 timestamp: fixed_to_field(ts),
855 source_neuron: src as usize,
856 target_neuron: match tgt {
857 Some(t) if t >= 0 => Some(t as usize),
858 Some(_) => {
859 return Err(OptimError::InvalidConfig(
860 "delta-decoded target_neuron underflowed".to_string(),
861 ))
862 }
863 None => None,
864 },
865 value: fixed_to_field(val),
866 energy_cost: fixed_to_field(energy),
867 priority,
868 })
869 }
870
871 fn sparse_encode_event(&mut self, event: &NeuromorphicEvent<T>) -> Result<Vec<u8>> {
877 let has_target = event.target_neuron.is_some();
878 let has_value = event.value != T::zero();
879 let has_energy = event.energy_cost != T::zero();
880
881 let mut mask = 0u8;
882 if has_target {
883 mask |= 0b001;
884 }
885 if has_value {
886 mask |= 0b010;
887 }
888 if has_energy {
889 mask |= 0b100;
890 }
891
892 let mut data = Vec::new();
893 data.push(event.event_type as u8);
894 data.push(event.priority as u8);
895 data.push(mask);
896 write_uvarint(&mut data, event.source_neuron as u64);
897 write_ivarint(&mut data, field_to_fixed(event.timestamp));
898 if let Some(target) = event.target_neuron {
899 write_uvarint(&mut data, target as u64);
900 }
901 if has_value {
902 write_ivarint(&mut data, field_to_fixed(event.value));
903 }
904 if has_energy {
905 write_ivarint(&mut data, field_to_fixed(event.energy_cost));
906 }
907 Ok(data)
908 }
909
910 fn sparse_decode_event(&mut self, bytes: &[u8]) -> Result<NeuromorphicEvent<T>> {
911 let mut pos = 0usize;
912 let event_type = decode_event_type(*bytes.first().ok_or_else(truncated_bytes_err)?)?;
913 pos += 1;
914 let priority = decode_priority(*bytes.get(pos).ok_or_else(truncated_bytes_err)?)?;
915 pos += 1;
916 let mask = *bytes.get(pos).ok_or_else(truncated_bytes_err)?;
917 pos += 1;
918 let source_neuron = read_uvarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)? as usize;
919 let timestamp =
920 fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?);
921 let target_neuron = if mask & 0b001 != 0 {
922 Some(read_uvarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)? as usize)
923 } else {
924 None
925 };
926 let value = if mask & 0b010 != 0 {
927 fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?)
928 } else {
929 T::zero()
930 };
931 let energy_cost = if mask & 0b100 != 0 {
932 fixed_to_field(read_ivarint(bytes, &mut pos).ok_or_else(truncated_bytes_err)?)
933 } else {
934 T::zero()
935 };
936 Ok(NeuromorphicEvent {
937 event_type,
938 timestamp,
939 source_neuron,
940 target_neuron,
941 value,
942 energy_cost,
943 priority,
944 })
945 }
946}
947
948struct CompressedEventChain<T: Float + Debug + Send + Sync + 'static> {
963 encoder: EventCompressionEngine<T>,
965 decoder: EventCompressionEngine<T>,
967 frames: VecDeque<Vec<u8>>,
969 stored_bytes: usize,
971}
972
973impl<T: Float + Debug + Send + Sync + 'static> CompressedEventChain<T> {
974 fn new(algorithm: EventCompressionAlgorithm) -> Self {
975 Self {
976 encoder: EventCompressionEngine::new(algorithm),
977 decoder: EventCompressionEngine::new(algorithm),
978 frames: VecDeque::new(),
979 stored_bytes: 0,
980 }
981 }
982
983 fn push(&mut self, event: &NeuromorphicEvent<T>) -> Result<usize> {
986 let frame = self.encoder.compress_event(event)?;
987 let frame_len = frame.len();
988 self.stored_bytes += frame_len;
989 self.frames.push_back(frame);
990 Ok(frame_len)
991 }
992
993 fn pop(&mut self) -> Result<Option<NeuromorphicEvent<T>>> {
995 match self.frames.pop_front() {
996 Some(frame) => {
997 self.stored_bytes = self.stored_bytes.saturating_sub(frame.len());
998 Ok(Some(self.decoder.decompress_event(&frame)?))
999 }
1000 None => Ok(None),
1001 }
1002 }
1003
1004 fn len(&self) -> usize {
1005 self.frames.len()
1006 }
1007
1008 fn is_empty(&self) -> bool {
1009 self.frames.is_empty()
1010 }
1011
1012 fn clear(&mut self) {
1016 self.frames.clear();
1017 self.stored_bytes = 0;
1018 self.encoder.last_event_fields = None;
1019 self.decoder.last_event_fields = None;
1020 }
1021}
1022
1023struct AdaptiveEventHandler<T: Float + Debug + Send + Sync + 'static> {
1025 performance_history: VecDeque<T>,
1026 current_strategy: AdaptationStrategy,
1027}
1028
1029#[derive(Debug, Clone, Copy)]
1030enum AdaptationStrategy {
1031 Conservative,
1032 Balanced,
1033 Aggressive,
1034}
1035
1036impl<T: Float + Debug + Send + Sync + 'static + std::iter::Sum> AdaptiveEventHandler<T> {
1037 fn new() -> Self {
1038 Self {
1039 performance_history: VecDeque::new(),
1040 current_strategy: AdaptationStrategy::Balanced,
1041 }
1042 }
1043
1044 fn adapt_processing(&mut self, current_performance: T) {
1045 self.performance_history.push_back(current_performance);
1046
1047 if self.performance_history.len() > 100 {
1048 self.performance_history.pop_front();
1049 }
1050
1051 if self.performance_history.len() >= 10 {
1052 let recent_avg = self
1053 .performance_history
1054 .iter()
1055 .rev()
1056 .take(10)
1057 .cloned()
1058 .sum::<T>()
1059 / T::from(10).unwrap_or_else(|| T::zero());
1060 let older_avg = if self.performance_history.len() >= 20 {
1061 self.performance_history
1062 .iter()
1063 .rev()
1064 .skip(10)
1065 .take(10)
1066 .cloned()
1067 .sum::<T>()
1068 / T::from(10).unwrap_or_else(|| T::zero())
1069 } else {
1070 recent_avg
1071 };
1072
1073 let performance_change = recent_avg - older_avg;
1074
1075 self.current_strategy =
1076 if performance_change > T::from(0.1).unwrap_or_else(|| T::zero()) {
1077 AdaptationStrategy::Aggressive
1078 } else if performance_change < T::from(-0.1).unwrap_or_else(|| T::zero()) {
1079 AdaptationStrategy::Conservative
1080 } else {
1081 AdaptationStrategy::Balanced
1082 };
1083 }
1084 }
1085
1086 fn get_adaptation_factor(&self) -> T {
1087 match self.current_strategy {
1088 AdaptationStrategy::Conservative => T::from(0.5).unwrap_or_else(|| T::zero()),
1089 AdaptationStrategy::Balanced => T::one(),
1090 AdaptationStrategy::Aggressive => T::from(1.5).unwrap_or_else(|| T::zero()),
1091 }
1092 }
1093}
1094
1095struct DistributedEventCoordinator<T: Float + Debug + Send + Sync + 'static> {
1097 load_balancing: LoadBalancingStrategy,
1098 worker_loads: HashMap<usize, T>,
1099 current_worker: usize,
1100 total_workers: usize,
1101}
1102
1103impl<T: Float + Debug + Send + Sync + 'static> DistributedEventCoordinator<T> {
1104 fn new(strategy: LoadBalancingStrategy, num_workers: usize) -> Self {
1105 Self {
1106 load_balancing: strategy,
1107 worker_loads: HashMap::new(),
1108 current_worker: 0,
1109 total_workers: num_workers,
1110 }
1111 }
1112
1113 fn assign_worker(&mut self, event: &NeuromorphicEvent<T>) -> usize {
1114 match self.load_balancing {
1115 LoadBalancingStrategy::RoundRobin => {
1116 let worker = self.current_worker;
1117 self.current_worker = (self.current_worker + 1) % self.total_workers;
1118 worker
1119 }
1120 LoadBalancingStrategy::TypeBased => {
1121 (event.event_type as usize) % self.total_workers
1123 }
1124 LoadBalancingStrategy::LoadAware => {
1125 self.worker_loads
1127 .iter()
1128 .min_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
1129 .map(|(&worker_id, _)| worker_id)
1130 .unwrap_or(0)
1131 }
1132 _ => 0,
1133 }
1134 }
1135
1136 fn update_worker_load(&mut self, worker_id: usize, load: T) {
1137 self.worker_loads.insert(worker_id, load);
1138 }
1139
1140 fn worker_load(&self, worker_id: usize) -> Option<T> {
1142 self.worker_loads.get(&worker_id).copied()
1143 }
1144
1145 fn loads(&self) -> Vec<(usize, T)> {
1147 let mut loads: Vec<(usize, T)> = self
1148 .worker_loads
1149 .iter()
1150 .map(|(&worker, &load)| (worker, load))
1151 .collect();
1152 loads.sort_by_key(|(worker, _)| *worker);
1153 loads
1154 }
1155}
1156
1157impl<
1158 T: Float
1159 + Debug
1160 + Send
1161 + Sync
1162 + 'static
1163 + std::iter::Sum
1164 + scirs2_core::ndarray::ScalarOperand
1165 + std::ops::AddAssign,
1166 > EventDrivenOptimizer<T>
1167{
1168 pub fn new(
1170 config: EventDrivenConfig<T>,
1171 stdp_config: STDPConfig<T>,
1172 membrane_config: MembraneDynamicsConfig<T>,
1173 num_neurons: usize,
1174 ) -> Self {
1175 let mut optimizer = Self {
1176 config: config.clone(),
1177 stdp_config: stdp_config.clone(),
1178 membrane_config: membrane_config.clone(),
1179 event_queue: BinaryHeap::new(),
1180 event_stats: HashMap::new(),
1181 system_state: SystemState {
1182 membrane_potentials: Array1::from_elem(
1183 num_neurons,
1184 membrane_config.resting_potential,
1185 ),
1186 synaptic_weights: Array2::ones((num_neurons, num_neurons))
1187 * T::from(0.1).unwrap_or_else(|| T::zero()),
1188 last_spike_times: Array1::from_elem(
1189 num_neurons,
1190 T::from(-1000.0).unwrap_or_else(|| T::zero()),
1191 ),
1192 refractory_until: Array1::zeros(num_neurons),
1193 current_time: T::zero(),
1194 active_neurons: HashSet::new(),
1195 pending_updates: HashMap::new(),
1196 },
1197 event_handlers: HashMap::new(),
1198 correlation_tracker: TemporalCorrelationTracker::new(config.correlation_window),
1199 rate_limiter: EventRateLimiter::new(config.rate_limits.clone()),
1200 metrics: NeuromorphicMetrics::default(),
1201 distributed_coordinator: if config.distributed_processing {
1202 Some(DistributedEventCoordinator::new(config.load_balancing, 4))
1203 } else {
1204 None
1205 },
1206 compression_engine: EventCompressionEngine::new(config.compression_algorithm),
1207 compressed_chains: BTreeMap::new(),
1208 compression_raw_bytes: 0,
1209 compression_compressed_bytes: 0,
1210 adaptive_handler: AdaptiveEventHandler::new(),
1211 };
1212
1213 optimizer.register_default_handlers();
1215
1216 optimizer
1217 }
1218
1219 fn register_default_handlers(&mut self) {
1221 let spike_handler = Box::new(SpikeEventHandler {
1222 stdp_config: self.stdp_config.clone(),
1223 membrane_config: self.membrane_config.clone(),
1224 });
1225
1226 let weight_handler = Box::new(WeightUpdateEventHandler {
1227 stdp_config: self.stdp_config.clone(),
1228 });
1229
1230 self.event_handlers.insert(EventType::Spike, spike_handler);
1231 self.event_handlers
1232 .insert(EventType::WeightUpdate, weight_handler);
1233 }
1234
1235 pub fn enqueue_event(&mut self, event: NeuromorphicEvent<T>) -> Result<()> {
1243 if !self.rate_limiter.can_process(event.event_type) {
1245 return Err(OptimError::InvalidConfig("Rate limit exceeded".to_string()));
1246 }
1247
1248 if self.get_queue_size() >= self.config.max_queue_size {
1251 return Err(OptimError::InvalidConfig("Event queue full".to_string()));
1252 }
1253
1254 let timestamp = event.timestamp;
1256 let event_type = event.event_type;
1257 let source_neuron = event.source_neuron;
1258
1259 if self.config.event_compression {
1260 let raw_bytes = self.compression_engine.serialize_event(&event)?.len();
1261 let algorithm = self.config.compression_algorithm;
1262 let chain = self
1263 .compressed_chains
1264 .entry(event.priority)
1265 .or_insert_with(|| CompressedEventChain::new(algorithm));
1266 let compressed_bytes = chain.push(&event)?;
1267 self.compression_raw_bytes += raw_bytes;
1268 self.compression_compressed_bytes += compressed_bytes;
1269 } else {
1270 self.event_queue.push(PriorityEventEntry {
1271 event,
1272 insertion_time: Instant::now(),
1273 });
1274 }
1275
1276 if self.config.temporal_correlation {
1278 self.correlation_tracker
1279 .add_event(timestamp, event_type, source_neuron);
1280 }
1281
1282 Ok(())
1283 }
1284
1285 fn pop_next_event(&mut self) -> Result<Option<NeuromorphicEvent<T>>> {
1294 let highest = self
1298 .compressed_chains
1299 .iter()
1300 .rev()
1301 .find(|(_, chain)| !chain.is_empty())
1302 .map(|(&priority, _)| priority);
1303 if let Some(priority) = highest {
1304 if let Some(chain) = self.compressed_chains.get_mut(&priority) {
1305 return chain.pop();
1306 }
1307 }
1308 Ok(self.event_queue.pop().map(|entry| entry.event))
1309 }
1310
1311 fn has_pending_events(&self) -> bool {
1313 !self.event_queue.is_empty()
1314 || self
1315 .compressed_chains
1316 .values()
1317 .any(|chain| !chain.is_empty())
1318 }
1319
1320 pub fn process_events(&mut self) -> Result<usize> {
1322 let mut processed_count = 0;
1323 let start_time = Instant::now();
1324 let timeout =
1325 Duration::from_millis(self.config.processing_timeout.to_u64().unwrap_or(1000));
1326
1327 while self.has_pending_events() && start_time.elapsed() < timeout {
1328 if self.config.event_batching {
1329 let batch_size = self.adaptive_batch_size().min(self.get_queue_size());
1330 let batch_processed = self.process_event_batch(batch_size)?;
1331 if batch_processed == 0 {
1332 break;
1333 }
1334 processed_count += batch_processed;
1335 } else if let Some(event) = self.pop_next_event()? {
1336 self.assign_event_to_worker(&event);
1337 self.process_single_event(&event)?;
1338 processed_count += 1;
1339 } else {
1340 break;
1341 }
1342 }
1343
1344 self.apply_pending_updates()?;
1346
1347 let elapsed_secs = start_time.elapsed().as_secs_f64();
1353 if elapsed_secs > 0.0 {
1354 let processing_rate = to_generic_or(processed_count as f64 / elapsed_secs, T::zero());
1355 self.adaptive_handler.adapt_processing(processing_rate);
1356 }
1357
1358 Ok(processed_count)
1359 }
1360
1361 fn process_event_batch(&mut self, batch_size: usize) -> Result<usize> {
1363 let mut batch_events = Vec::with_capacity(batch_size);
1364
1365 for _ in 0..batch_size {
1367 match self.pop_next_event()? {
1368 Some(event) => batch_events.push(event),
1369 None => break,
1370 }
1371 }
1372
1373 for event in &batch_events {
1375 self.assign_event_to_worker(event);
1376 self.process_single_event(event)?;
1377 }
1378
1379 Ok(batch_events.len())
1380 }
1381
1382 fn process_single_event(&mut self, event: &NeuromorphicEvent<T>) -> Result<()> {
1384 let start_time = Instant::now();
1385
1386 if let Some(handler) = self.event_handlers.get_mut(&event.event_type) {
1388 handler.handle_event(event, &mut self.system_state)?;
1389 } else {
1390 self.default_event_handling(event)?;
1392 }
1393
1394 let processing_time = start_time.elapsed().as_nanos() as f64 / 1_000_000.0;
1396 self.update_event_statistics(
1397 event.event_type,
1398 T::from(processing_time).unwrap_or_else(|| T::zero()),
1399 );
1400
1401 self.metrics.energy_consumption += event.energy_cost;
1403
1404 Ok(())
1405 }
1406
1407 fn default_event_handling(&mut self, event: &NeuromorphicEvent<T>) -> Result<()> {
1409 match event.event_type {
1410 EventType::ExternalStimulus
1411 if event.source_neuron < self.system_state.membrane_potentials.len() => {
1413 self.system_state.membrane_potentials[event.source_neuron] += event.value;
1414 }
1415 EventType::TimerEvent => {
1416 self.system_state.current_time = event.timestamp;
1418 }
1419 _ => {
1420 }
1422 }
1423
1424 Ok(())
1425 }
1426
1427 fn apply_pending_updates(&mut self) -> Result<()> {
1429 for ((pre, post), weight_change) in self.system_state.pending_updates.drain() {
1430 if pre < self.system_state.synaptic_weights.nrows()
1431 && post < self.system_state.synaptic_weights.ncols()
1432 {
1433 let current_weight = self.system_state.synaptic_weights[[pre, post]];
1434 let new_weight = (current_weight + weight_change)
1435 .max(self.stdp_config.weight_min)
1436 .min(self.stdp_config.weight_max);
1437
1438 self.system_state.synaptic_weights[[pre, post]] = new_weight;
1439 }
1440 }
1441
1442 Ok(())
1443 }
1444
1445 fn update_event_statistics(&mut self, event_type: EventType, processing_time: T) {
1447 let stats = self
1448 .event_stats
1449 .entry(event_type)
1450 .or_insert_with(|| EventStatistics {
1451 total_processed: 0,
1452 avg_processing_time: T::zero(),
1453 event_rate: T::zero(),
1454 avg_queue_wait_time: T::zero(),
1455 error_count: 0,
1456 last_update: Instant::now(),
1457 });
1458
1459 stats.total_processed += 1;
1460
1461 let alpha = T::from(0.1).unwrap_or_else(|| T::zero());
1463 stats.avg_processing_time =
1464 stats.avg_processing_time * (T::one() - alpha) + processing_time * alpha;
1465
1466 let time_since_last = stats.last_update.elapsed().as_secs_f64();
1468 if time_since_last > 0.0 {
1469 let current_rate = T::one() / T::from(time_since_last).unwrap_or_else(|| T::zero());
1470 stats.event_rate = stats.event_rate * (T::one() - alpha) + current_rate * alpha;
1471 }
1472
1473 stats.last_update = Instant::now();
1474 }
1475
1476 pub fn get_event_statistics(&self) -> &HashMap<EventType, EventStatistics<T>> {
1478 &self.event_stats
1479 }
1480
1481 pub fn get_system_state(&self) -> &SystemState<T> {
1483 &self.system_state
1484 }
1485
1486 pub fn get_metrics(&self) -> &NeuromorphicMetrics<T> {
1488 &self.metrics
1489 }
1490
1491 pub fn clear_event_queue(&mut self) {
1493 self.event_queue.clear();
1494 for chain in self.compressed_chains.values_mut() {
1495 chain.clear();
1496 }
1497 }
1498
1499 pub fn get_queue_size(&self) -> usize {
1502 self.event_queue.len()
1503 + self
1504 .compressed_chains
1505 .values()
1506 .map(|chain| chain.len())
1507 .sum::<usize>()
1508 }
1509
1510 pub fn compressed_queue_bytes(&self) -> usize {
1514 self.compressed_chains
1515 .values()
1516 .map(|chain| chain.stored_bytes)
1517 .sum()
1518 }
1519
1520 pub fn compression_statistics(&self) -> (usize, usize) {
1526 (
1527 self.compression_raw_bytes,
1528 self.compression_compressed_bytes,
1529 )
1530 }
1531
1532 pub fn compression_ratio(&self) -> Option<f64> {
1535 if self.compression_raw_bytes == 0 {
1536 None
1537 } else {
1538 Some(self.compression_compressed_bytes as f64 / self.compression_raw_bytes as f64)
1539 }
1540 }
1541
1542 pub fn last_measured_event_rate(&self) -> Option<T> {
1549 self.adaptive_handler.performance_history.back().copied()
1550 }
1551
1552 pub fn current_adaptation_factor(&self) -> T {
1556 self.adaptive_handler.get_adaptation_factor()
1557 }
1558
1559 pub fn adaptive_batch_size(&self) -> usize {
1564 let factor = self.current_adaptation_factor().to_f64().unwrap_or(1.0);
1565 let scaled = (self.config.batch_size as f64 * factor).round();
1566 if scaled.is_finite() && scaled >= 1.0 {
1567 scaled as usize
1568 } else {
1569 self.config.batch_size.max(1)
1570 }
1571 }
1572
1573 fn assign_event_to_worker(&mut self, event: &NeuromorphicEvent<T>) {
1580 let Some(coordinator) = self.distributed_coordinator.as_mut() else {
1581 return;
1582 };
1583 let worker = coordinator.assign_worker(event);
1584 let current = coordinator.worker_load(worker).unwrap_or_else(T::zero);
1585 coordinator.update_worker_load(worker, current + T::one());
1586 }
1587
1588 pub fn event_correlation(&self, first: EventType, second: EventType) -> T {
1594 self.correlation_tracker.get_correlation(first, second)
1595 }
1596
1597 pub fn worker_loads(&self) -> Option<Vec<(usize, T)>> {
1600 self.distributed_coordinator
1601 .as_ref()
1602 .map(|coordinator| coordinator.loads())
1603 }
1604
1605 pub fn enable_distributed_processing(&mut self, num_workers: usize) {
1607 self.distributed_coordinator = Some(DistributedEventCoordinator::new(
1608 self.config.load_balancing,
1609 num_workers,
1610 ));
1611 self.config.distributed_processing = true;
1612 }
1613
1614 pub fn disable_distributed_processing(&mut self) {
1616 self.distributed_coordinator = None;
1617 self.config.distributed_processing = false;
1618 }
1619}
1620
1621impl<T: Float + Debug + Send + Sync + 'static> Default for EventStatistics<T> {
1622 fn default() -> Self {
1623 Self {
1624 total_processed: 0,
1625 avg_processing_time: T::zero(),
1626 event_rate: T::zero(),
1627 avg_queue_wait_time: T::zero(),
1628 error_count: 0,
1629 last_update: Instant::now(),
1630 }
1631 }
1632}
1633
1634#[cfg(test)]
1637#[path = "event_driven_compression_tests.rs"]
1638mod compression_wiring_tests;
1639
1640#[cfg(test)]
1641mod f57_compression_tests {
1642 use super::*;
1643
1644 fn sample_events() -> Vec<NeuromorphicEvent<f64>> {
1645 vec![
1646 NeuromorphicEvent {
1647 event_type: EventType::Spike,
1648 timestamp: 12.5,
1649 source_neuron: 3,
1650 target_neuron: Some(7),
1651 value: 1.0,
1652 energy_cost: 0.05,
1653 priority: EventPriority::High,
1654 },
1655 NeuromorphicEvent {
1656 event_type: EventType::WeightUpdate,
1657 timestamp: 12.6,
1658 source_neuron: 4,
1659 target_neuron: Some(8),
1660 value: -0.25,
1661 energy_cost: 0.02,
1662 priority: EventPriority::Normal,
1663 },
1664 NeuromorphicEvent {
1665 event_type: EventType::TimerEvent,
1666 timestamp: 100.0,
1667 source_neuron: 0,
1668 target_neuron: None,
1669 value: 0.0,
1670 energy_cost: 0.0,
1671 priority: EventPriority::Low,
1672 },
1673 NeuromorphicEvent {
1674 event_type: EventType::EnergyEvent,
1675 timestamp: 0.0,
1676 source_neuron: 42,
1677 target_neuron: Some(1),
1678 value: 999.75,
1679 energy_cost: -3.5,
1680 priority: EventPriority::RealTime,
1681 },
1682 ]
1683 }
1684
1685 fn assert_events_close(a: &NeuromorphicEvent<f64>, b: &NeuromorphicEvent<f64>) {
1686 assert_eq!(a.event_type, b.event_type);
1687 assert_eq!(a.priority, b.priority);
1688 assert_eq!(a.source_neuron, b.source_neuron);
1689 assert_eq!(a.target_neuron, b.target_neuron);
1690 assert!(
1691 (a.timestamp - b.timestamp).abs() < 1e-3,
1692 "timestamp mismatch: {} vs {}",
1693 a.timestamp,
1694 b.timestamp
1695 );
1696 assert!(
1697 (a.value - b.value).abs() < 1e-3,
1698 "value mismatch: {} vs {}",
1699 a.value,
1700 b.value
1701 );
1702 assert!(
1703 (a.energy_cost - b.energy_cost).abs() < 1e-3,
1704 "energy_cost mismatch: {} vs {}",
1705 a.energy_cost,
1706 b.energy_cost
1707 );
1708 }
1709
1710 #[test]
1714 fn none_algorithm_round_trips_all_fields() {
1715 let mut engine = EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::None);
1716 for event in sample_events() {
1717 let bytes = engine.compress_event(&event).expect("compress");
1718 let decoded = engine.decompress_event(&bytes).expect("decompress");
1719 assert_events_close(&event, &decoded);
1720 }
1721 }
1722
1723 #[test]
1727 fn delta_encoding_round_trips_event_stream() {
1728 let mut encoder =
1729 EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::DeltaEncoding);
1730 let mut decoder =
1731 EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::DeltaEncoding);
1732
1733 for event in sample_events() {
1734 let bytes = encoder.compress_event(&event).expect("compress");
1735 let decoded = decoder.decompress_event(&bytes).expect("decompress");
1736 assert_events_close(&event, &decoded);
1737 }
1738 }
1739
1740 #[test]
1745 fn delta_encoding_is_smaller_for_similar_events() {
1746 let mut none_engine = EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::None);
1747 let mut delta_engine =
1748 EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::DeltaEncoding);
1749
1750 let mut none_total = 0usize;
1751 let mut delta_total = 0usize;
1752 for i in 0..20 {
1753 let event = NeuromorphicEvent {
1754 event_type: EventType::Spike,
1755 timestamp: 1000.0 + i as f64 * 0.1,
1756 source_neuron: 50 + (i % 3),
1757 target_neuron: Some(100 + (i % 2)),
1758 value: 0.5,
1759 energy_cost: 0.01,
1760 priority: EventPriority::Normal,
1761 };
1762 none_total += none_engine.compress_event(&event).expect("compress").len();
1763 delta_total += delta_engine.compress_event(&event).expect("compress").len();
1764 }
1765 assert!(
1766 delta_total < none_total,
1767 "delta encoding was not smaller for a similar-event stream: \
1768 delta={delta_total} bytes, none={none_total} bytes"
1769 );
1770 }
1771
1772 #[test]
1776 fn sparse_encoding_round_trips_default_and_full_events() {
1777 let mut engine =
1778 EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::SparseEncoding);
1779 for event in sample_events() {
1780 let bytes = engine.compress_event(&event).expect("compress");
1781 let decoded = engine.decompress_event(&bytes).expect("decompress");
1782 assert_events_close(&event, &decoded);
1783 }
1784 }
1785
1786 #[test]
1790 fn decoding_truncated_bytes_is_an_error_not_a_panic() {
1791 let mut engine = EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::None);
1792 let event = sample_events().remove(0);
1793 let bytes = engine.compress_event(&event).expect("compress");
1794
1795 for len in 0..bytes.len() {
1796 let mut fresh_engine =
1797 EventCompressionEngine::<f64>::new(EventCompressionAlgorithm::None);
1798 let result = fresh_engine.decompress_event(&bytes[..len]);
1799 assert!(
1800 result.is_err(),
1801 "truncating to {len} bytes should be a decode error, got {result:?}"
1802 );
1803 }
1804 }
1805}