#[derive(Debug, Clone)]
pub struct SpikeEvent {
pub neuron_id: usize,
pub timestamp: f64,
pub amplitude: f64,
pub spatial_coords: Vec<f64>,
}
impl SpikeEvent {
pub fn new(neuron_id: usize, timestamp: f64, amplitude: f64, spatial_coords: Vec<f64>) -> Self {
Self {
neuron_id,
timestamp,
amplitude,
spatial_coords,
}
}
pub fn neuron_id(&self) -> usize {
self.neuron_id
}
pub fn timestamp(&self) -> f64 {
self.timestamp
}
pub fn amplitude(&self) -> f64 {
self.amplitude
}
pub fn spatial_coords(&self) -> &[f64] {
&self.spatial_coords
}
pub fn spatial_dims(&self) -> usize {
self.spatial_coords.len()
}
pub fn temporal_distance(&self, other: &SpikeEvent) -> f64 {
(self.timestamp - other.timestamp).abs()
}
pub fn spatial_distance(&self, other: &SpikeEvent) -> Option<f64> {
if self.spatial_coords.len() != other.spatial_coords.len() {
return None;
}
let distance = self
.spatial_coords
.iter()
.zip(other.spatial_coords.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
Some(distance)
}
pub fn is_before(&self, other: &SpikeEvent) -> bool {
self.timestamp < other.timestamp
}
pub fn is_after(&self, other: &SpikeEvent) -> bool {
self.timestamp > other.timestamp
}
pub fn same_neuron(&self, other: &SpikeEvent) -> bool {
self.neuron_id == other.neuron_id
}
}
impl PartialEq for SpikeEvent {
fn eq(&self, other: &Self) -> bool {
self.neuron_id == other.neuron_id
&& (self.timestamp - other.timestamp).abs() < 1e-9
&& (self.amplitude - other.amplitude).abs() < 1e-9
&& self.spatial_coords == other.spatial_coords
}
}
impl PartialOrd for SpikeEvent {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.timestamp.partial_cmp(&other.timestamp)
}
}
#[derive(Debug, Clone)]
pub struct SpikeSequence {
events: Vec<SpikeEvent>,
}
impl SpikeSequence {
pub fn new() -> Self {
Self { events: Vec::new() }
}
pub fn from_events(events: Vec<SpikeEvent>) -> Self {
Self { events }
}
pub fn add_event(&mut self, event: SpikeEvent) {
self.events.push(event);
}
pub fn events(&self) -> &[SpikeEvent] {
&self.events
}
pub fn len(&self) -> usize {
self.events.len()
}
pub fn is_empty(&self) -> bool {
self.events.is_empty()
}
pub fn sort_by_time(&mut self) {
self.events.sort_by(|a, b| {
a.timestamp
.partial_cmp(&b.timestamp)
.expect("Operation failed")
});
}
pub fn events_in_window(&self, start_time: f64, end_time: f64) -> Vec<&SpikeEvent> {
self.events
.iter()
.filter(|event| event.timestamp >= start_time && event.timestamp <= end_time)
.collect()
}
pub fn events_from_neuron(&self, neuron_id: usize) -> Vec<&SpikeEvent> {
self.events
.iter()
.filter(|event| event.neuron_id == neuron_id)
.collect()
}
pub fn firing_rate(&self, neuron_id: usize, time_window: f64) -> f64 {
let neuron_events = self.events_from_neuron(neuron_id);
if neuron_events.is_empty() || time_window <= 0.0 {
return 0.0;
}
neuron_events.len() as f64 / time_window
}
pub fn time_span(&self) -> Option<(f64, f64)> {
if self.events.is_empty() {
return None;
}
let times: Vec<f64> = self.events.iter().map(|event| event.timestamp).collect();
let min_time = times.iter().fold(f64::INFINITY, |a, &b| a.min(b));
let max_time = times.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
Some((min_time, max_time))
}
}
impl Default for SpikeSequence {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_spike_event_creation() {
let spike = SpikeEvent::new(42, 1000.5, 1.0, vec![0.5, 0.3, 0.8]);
assert_eq!(spike.neuron_id(), 42);
assert_eq!(spike.timestamp(), 1000.5);
assert_eq!(spike.amplitude(), 1.0);
assert_eq!(spike.spatial_coords(), &[0.5, 0.3, 0.8]);
assert_eq!(spike.spatial_dims(), 3);
}
#[test]
fn test_temporal_distance() {
let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
assert_eq!(spike1.temporal_distance(&spike2), 5.0);
assert_eq!(spike2.temporal_distance(&spike1), 5.0);
}
#[test]
fn test_spatial_distance() {
let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0, 0.0]);
let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![3.0, 4.0]);
assert_eq!(spike1.spatial_distance(&spike2), Some(5.0));
let spike3 = SpikeEvent::new(3, 110.0, 1.0, vec![1.0]);
assert_eq!(spike1.spatial_distance(&spike3), None);
}
#[test]
fn test_spike_ordering() {
let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
assert!(spike1.is_before(&spike2));
assert!(spike2.is_after(&spike1));
assert!(!spike1.is_after(&spike2));
assert!(!spike2.is_before(&spike1));
}
#[test]
fn test_spike_sequence() {
let mut sequence = SpikeSequence::new();
assert!(sequence.is_empty());
let spike1 = SpikeEvent::new(1, 100.0, 1.0, vec![0.0]);
let spike2 = SpikeEvent::new(2, 105.0, 1.0, vec![1.0]);
sequence.add_event(spike1);
sequence.add_event(spike2);
assert_eq!(sequence.len(), 2);
assert!(!sequence.is_empty());
let time_span = sequence.time_span().expect("Operation failed");
assert_eq!(time_span.0, 100.0);
assert_eq!(time_span.1, 105.0);
}
#[test]
fn test_events_in_window() {
let mut sequence = SpikeSequence::new();
sequence.add_event(SpikeEvent::new(1, 95.0, 1.0, vec![0.0]));
sequence.add_event(SpikeEvent::new(2, 100.0, 1.0, vec![1.0]));
sequence.add_event(SpikeEvent::new(3, 105.0, 1.0, vec![2.0]));
sequence.add_event(SpikeEvent::new(4, 110.0, 1.0, vec![3.0]));
let windowed_events = sequence.events_in_window(100.0, 105.0);
assert_eq!(windowed_events.len(), 2);
assert_eq!(windowed_events[0].neuron_id, 2);
assert_eq!(windowed_events[1].neuron_id, 3);
}
#[test]
fn test_firing_rate() {
let mut sequence = SpikeSequence::new();
sequence.add_event(SpikeEvent::new(1, 100.0, 1.0, vec![0.0]));
sequence.add_event(SpikeEvent::new(1, 105.0, 1.0, vec![0.0]));
sequence.add_event(SpikeEvent::new(1, 110.0, 1.0, vec![0.0]));
sequence.add_event(SpikeEvent::new(2, 107.0, 1.0, vec![1.0]));
assert_eq!(sequence.firing_rate(1, 10.0), 0.3);
assert_eq!(sequence.firing_rate(2, 10.0), 0.1);
assert_eq!(sequence.firing_rate(99, 10.0), 0.0);
}
}