ruvector-nervous-system 2.0.6

Bio-inspired neural system with spiking networks, BTSP learning, and EWC plasticity
Documentation
//! Single compartment model with membrane and calcium dynamics
//!
//! Implements a reduced compartment with:
//! - Membrane potential with exponential decay
//! - Calcium concentration with slower decay
//! - Threshold-based activation detection

/// Single compartment with membrane and calcium dynamics
#[derive(Debug, Clone)]
pub struct Compartment {
    /// Membrane potential (normalized 0.0-1.0)
    membrane: f32,

    /// Calcium concentration (normalized 0.0-1.0)
    calcium: f32,

    /// Membrane time constant (ms)
    tau_membrane: f32,

    /// Calcium time constant (ms)
    tau_calcium: f32,

    /// Resting potential
    resting: f32,
}

impl Compartment {
    /// Create a new compartment with default parameters
    ///
    /// Default values:
    /// - tau_membrane: 20ms (fast membrane dynamics)
    /// - tau_calcium: 100ms (slower calcium decay)
    /// - resting: 0.0 (normalized)
    pub fn new() -> Self {
        Self {
            membrane: 0.0,
            calcium: 0.0,
            tau_membrane: 20.0,
            tau_calcium: 100.0,
            resting: 0.0,
        }
    }

    /// Create a compartment with custom time constants
    pub fn with_time_constants(tau_membrane: f32, tau_calcium: f32) -> Self {
        Self {
            membrane: 0.0,
            calcium: 0.0,
            tau_membrane,
            tau_calcium,
            resting: 0.0,
        }
    }

    /// Update compartment state with input current
    ///
    /// Implements exponential decay for both membrane potential and calcium:
    /// - dV/dt = (I - V) / tau_membrane
    /// - dCa/dt = -Ca / tau_calcium
    ///
    /// # Arguments
    /// * `input_current` - Input current (normalized, positive depolarizes)
    /// * `dt` - Time step in milliseconds
    pub fn step(&mut self, input_current: f32, dt: f32) {
        // Membrane dynamics: exponential decay towards resting + input
        let membrane_decay = (self.resting - self.membrane) / self.tau_membrane;
        self.membrane += (membrane_decay + input_current) * dt;

        // Clamp membrane potential to [0.0, 1.0]
        self.membrane = self.membrane.clamp(0.0, 1.0);

        // Calcium dynamics: exponential decay
        let calcium_decay = -self.calcium / self.tau_calcium;
        self.calcium += calcium_decay * dt;

        // Calcium increases with strong depolarization
        if self.membrane > 0.5 {
            self.calcium += (self.membrane - 0.5) * 0.01 * dt;
        }

        // Clamp calcium to [0.0, 1.0]
        self.calcium = self.calcium.clamp(0.0, 1.0);
    }

    /// Check if compartment is active above threshold
    pub fn is_active(&self, threshold: f32) -> bool {
        self.membrane > threshold
    }

    /// Get current membrane potential
    pub fn membrane(&self) -> f32 {
        self.membrane
    }

    /// Get current calcium concentration
    pub fn calcium(&self) -> f32 {
        self.calcium
    }

    /// Reset compartment to resting state
    pub fn reset(&mut self) {
        self.membrane = self.resting;
        self.calcium = 0.0;
    }

    /// Inject a spike into the compartment
    pub fn inject_spike(&mut self, amplitude: f32) {
        self.membrane += amplitude;
        self.membrane = self.membrane.clamp(0.0, 1.0);
    }
}

impl Default for Compartment {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_compartment_creation() {
        let comp = Compartment::new();
        assert_eq!(comp.membrane(), 0.0);
        assert_eq!(comp.calcium(), 0.0);
    }

    #[test]
    fn test_compartment_step() {
        let mut comp = Compartment::new();

        // Apply positive current
        comp.step(0.1, 1.0);
        assert!(comp.membrane() > 0.0);
    }

    #[test]
    fn test_membrane_decay() {
        let mut comp = Compartment::new();

        // Inject spike
        comp.inject_spike(0.8);
        let initial = comp.membrane();

        // Let it decay
        for _ in 0..100 {
            comp.step(0.0, 1.0);
        }

        // Should decay towards resting
        assert!(comp.membrane() < initial);
    }

    #[test]
    fn test_calcium_accumulation() {
        let mut comp = Compartment::new();

        // Strong depolarization should increase calcium
        comp.inject_spike(0.9);

        for _ in 0..10 {
            comp.step(0.0, 1.0);
        }

        assert!(comp.calcium() > 0.0);
    }

    #[test]
    fn test_threshold_detection() {
        let mut comp = Compartment::new();
        assert!(!comp.is_active(0.5));

        comp.inject_spike(0.6);
        assert!(comp.is_active(0.5));
    }

    #[test]
    fn test_reset() {
        let mut comp = Compartment::new();
        comp.inject_spike(0.8);
        comp.step(0.0, 1.0);

        comp.reset();
        assert_eq!(comp.membrane(), 0.0);
        assert_eq!(comp.calcium(), 0.0);
    }
}