use super::*;
fn compressed_config(algorithm: EventCompressionAlgorithm) -> EventDrivenConfig<f64> {
EventDrivenConfig {
event_compression: true,
compression_algorithm: algorithm,
rate_limits: HashMap::new(),
temporal_correlation: false,
..Default::default()
}
}
fn plain_config() -> EventDrivenConfig<f64> {
EventDrivenConfig {
event_compression: false,
compression_algorithm: EventCompressionAlgorithm::None,
rate_limits: HashMap::new(),
temporal_correlation: false,
..Default::default()
}
}
fn optimizer(config: EventDrivenConfig<f64>) -> EventDrivenOptimizer<f64> {
EventDrivenOptimizer::new(
config,
STDPConfig::default(),
MembraneDynamicsConfig::default(),
8,
)
}
fn stimulus(
source: usize,
value: f64,
energy: f64,
priority: EventPriority,
) -> NeuromorphicEvent<f64> {
NeuromorphicEvent {
event_type: EventType::ExternalStimulus,
timestamp: 1.5,
source_neuron: source,
target_neuron: None,
value,
energy_cost: energy,
priority,
}
}
fn similar_spikes(count: usize) -> Vec<NeuromorphicEvent<f64>> {
(0..count)
.map(|i| NeuromorphicEvent {
event_type: EventType::Spike,
timestamp: 10.0 + i as f64 * 0.5,
source_neuron: 2 + i % 3,
target_neuron: Some(5),
value: 1.0,
energy_cost: 0.05,
priority: EventPriority::Normal,
})
.collect()
}
#[test]
fn enqueue_stores_compressed_bytes_instead_of_events() {
let mut opt = optimizer(compressed_config(EventCompressionAlgorithm::DeltaEncoding));
for event in similar_spikes(8) {
opt.enqueue_event(event).expect("enqueue");
}
assert!(
opt.event_queue.is_empty(),
"F57 wiring regression: events were stored uncompressed in the \
in-memory priority queue even though event_compression is enabled"
);
assert_eq!(
opt.get_queue_size(),
8,
"queued events must still be counted"
);
assert!(
opt.compressed_queue_bytes() > 0,
"F57 wiring regression: nothing was actually stored in compressed form"
);
}
#[test]
fn compressed_queue_round_trips_events_through_live_processing() {
let events = vec![
stimulus(0, 0.25, 0.05, EventPriority::Normal),
stimulus(1, -0.5, 0.125, EventPriority::High),
stimulus(0, 1.0, 0.25, EventPriority::Low),
NeuromorphicEvent {
event_type: EventType::TimerEvent,
timestamp: 7.25,
source_neuron: 3,
target_neuron: Some(4),
value: 0.0,
energy_cost: 0.0,
priority: EventPriority::Critical,
},
];
for algorithm in [
EventCompressionAlgorithm::None,
EventCompressionAlgorithm::DeltaEncoding,
EventCompressionAlgorithm::SparseEncoding,
] {
let mut plain = optimizer(plain_config());
let mut compressed = optimizer(compressed_config(algorithm));
for event in &events {
plain.enqueue_event(event.clone()).expect("plain enqueue");
compressed
.enqueue_event(event.clone())
.expect("compressed enqueue");
}
let plain_processed = plain.process_events().expect("plain process");
let compressed_processed = compressed.process_events().expect("compressed process");
assert_eq!(
plain_processed, compressed_processed,
"{algorithm:?}: compressed path processed a different number of events"
);
assert_eq!(
plain.get_system_state().membrane_potentials,
compressed.get_system_state().membrane_potentials,
"{algorithm:?}: F57 wiring regression — events decoded from the \
compressed queue did not reproduce the uncompressed membrane state"
);
assert_eq!(
plain.get_system_state().current_time,
compressed.get_system_state().current_time,
"{algorithm:?}: TimerEvent timestamp did not survive the round trip"
);
assert_eq!(
plain.get_metrics().energy_consumption,
compressed.get_metrics().energy_consumption,
"{algorithm:?}: per-event energy cost did not survive the round trip"
);
assert_eq!(
compressed.get_queue_size(),
0,
"{algorithm:?}: compressed frames were left behind after processing"
);
assert_eq!(
compressed.compressed_queue_bytes(),
0,
"{algorithm:?}: compressed byte accounting leaked after draining"
);
}
}
#[test]
fn delta_compression_shrinks_the_live_queue() {
let mut opt = optimizer(compressed_config(EventCompressionAlgorithm::DeltaEncoding));
for event in similar_spikes(32) {
opt.enqueue_event(event).expect("enqueue");
}
let (raw, compressed) = opt.compression_statistics();
assert!(raw > 0, "no raw byte measurement was recorded");
assert!(
compressed < raw,
"compressed live queue did not shrink the stream: raw={raw}, compressed={compressed}"
);
let ratio = opt.compression_ratio().expect("ratio after compressing");
assert!(
ratio < 1.0,
"compression ratio must be below 1.0 for a similar-event stream, got {ratio}"
);
assert!(
opt.compressed_queue_bytes() <= compressed,
"stored bytes cannot exceed the cumulative compressed total"
);
}
#[test]
fn compressed_queue_preserves_priority_then_fifo_order() {
let mut opt = optimizer(compressed_config(EventCompressionAlgorithm::DeltaEncoding));
let plan = [
(EventPriority::Low, 0usize),
(EventPriority::High, 1),
(EventPriority::Normal, 2),
(EventPriority::High, 3),
(EventPriority::Critical, 4),
(EventPriority::Low, 5),
];
for (priority, source) in plan {
opt.enqueue_event(stimulus(source, 0.5, 0.0, priority))
.expect("enqueue");
}
let mut order = Vec::new();
while let Some(event) = opt.pop_next_event().expect("pop") {
order.push((event.priority, event.source_neuron));
}
assert_eq!(
order,
vec![
(EventPriority::Critical, 4),
(EventPriority::High, 1),
(EventPriority::High, 3),
(EventPriority::Normal, 2),
(EventPriority::Low, 0),
(EventPriority::Low, 5),
],
"compressed queue lost priority-then-FIFO scheduling order"
);
}
#[test]
fn queue_capacity_limit_counts_compressed_frames() {
let mut config = compressed_config(EventCompressionAlgorithm::DeltaEncoding);
config.max_queue_size = 4;
let mut opt = optimizer(config);
for (i, event) in similar_spikes(4).into_iter().enumerate() {
opt.enqueue_event(event)
.unwrap_or_else(|e| panic!("enqueue {i} within capacity failed: {e}"));
}
assert_eq!(opt.get_queue_size(), 4);
assert!(
opt.enqueue_event(stimulus(1, 0.5, 0.0, EventPriority::Normal))
.is_err(),
"queue capacity check ignored the compressed frames already queued"
);
}
#[test]
fn clearing_the_compressed_queue_resets_codec_baselines() {
let mut opt = optimizer(compressed_config(EventCompressionAlgorithm::DeltaEncoding));
for event in similar_spikes(3) {
opt.enqueue_event(event).expect("enqueue burst");
}
opt.clear_event_queue();
assert_eq!(opt.get_queue_size(), 0);
assert_eq!(opt.compressed_queue_bytes(), 0);
let follow_up = vec![
stimulus(0, 0.75, 0.05, EventPriority::Normal),
stimulus(1, -0.25, 0.125, EventPriority::Normal),
];
let mut plain = optimizer(plain_config());
for event in &follow_up {
opt.enqueue_event(event.clone()).expect("enqueue follow-up");
plain.enqueue_event(event.clone()).expect("plain enqueue");
}
opt.process_events().expect("process compressed");
plain.process_events().expect("process plain");
assert_eq!(
opt.get_system_state().membrane_potentials,
plain.get_system_state().membrane_potentials,
"codec baselines were not reset by clear_event_queue: events after a \
clear decoded to different values than the uncompressed path"
);
}
#[test]
fn measured_event_rate_is_finite_for_a_fast_batch() {
let mut opt = optimizer(plain_config());
for event in similar_spikes(4) {
opt.enqueue_event(event).expect("enqueue");
}
let processed = opt.process_events().expect("process");
assert_eq!(processed, 4);
let rate = opt
.last_measured_event_rate()
.expect("a measurable interval must have been recorded");
assert!(
rate.is_finite(),
"event processing rate must be finite, got {rate}"
);
assert!(
rate > 0.0,
"positive event count must yield a positive rate"
);
assert!(
opt.current_adaptation_factor().is_finite(),
"adaptation factor must stay finite"
);
assert!(
opt.adaptive_batch_size() >= 1,
"adaptive batch size must never be zero"
);
}