ruvector-nervous-system 2.0.6

Bio-inspired neural system with spiking networks, BTSP learning, and EWC plasticity
Documentation
//! Dendritic plateau potential for behavioral timescale synaptic plasticity
//!
//! Implements a plateau potential that:
//! - Activates when NMDA threshold is reached
//! - Lasts 100-500ms (behavioral timescale)
//! - Provides temporal credit assignment signal for BTSP

/// Dendritic plateau potential
#[derive(Debug, Clone)]
pub struct PlateauPotential {
    /// Duration of plateau in milliseconds
    duration_ms: f32,

    /// Time remaining in current plateau (ms)
    time_remaining: f32,

    /// Amplitude of plateau (0.0-1.0)
    amplitude: f32,

    /// Whether plateau is currently active
    active: bool,
}

impl PlateauPotential {
    /// Create a new plateau potential with specified duration
    ///
    /// # Arguments
    /// * `duration_ms` - Duration of plateau in milliseconds (typically 100-500ms)
    pub fn new(duration_ms: f32) -> Self {
        Self {
            duration_ms,
            time_remaining: 0.0,
            amplitude: 0.0,
            active: false,
        }
    }

    /// Trigger the plateau potential
    ///
    /// Initiates a plateau with full amplitude and resets the timer
    pub fn trigger(&mut self) {
        self.active = true;
        self.time_remaining = self.duration_ms;
        self.amplitude = 1.0;
    }

    /// Update plateau state
    ///
    /// Decrements timer and updates amplitude. Deactivates when time expires.
    ///
    /// # Arguments
    /// * `dt` - Time step in milliseconds
    pub fn update(&mut self, dt: f32) {
        if !self.active {
            return;
        }

        self.time_remaining -= dt;

        if self.time_remaining <= 0.0 {
            // Plateau expired
            self.active = false;
            self.amplitude = 0.0;
            self.time_remaining = 0.0;
        } else {
            // Maintain amplitude during plateau
            // Could implement decay here if needed
            self.amplitude = 1.0;
        }
    }

    /// Check if plateau is currently active
    pub fn is_active(&self) -> bool {
        self.active
    }

    /// Get current amplitude (0.0-1.0)
    pub fn amplitude(&self) -> f32 {
        self.amplitude
    }

    /// Get time remaining in plateau (ms)
    pub fn time_remaining(&self) -> f32 {
        self.time_remaining
    }

    /// Reset plateau to inactive state
    pub fn reset(&mut self) {
        self.active = false;
        self.amplitude = 0.0;
        self.time_remaining = 0.0;
    }
}

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

    #[test]
    fn test_plateau_creation() {
        let plateau = PlateauPotential::new(200.0);
        assert!(!plateau.is_active());
        assert_eq!(plateau.amplitude(), 0.0);
    }

    #[test]
    fn test_plateau_trigger() {
        let mut plateau = PlateauPotential::new(200.0);

        plateau.trigger();

        assert!(plateau.is_active());
        assert_eq!(plateau.amplitude(), 1.0);
        assert_eq!(plateau.time_remaining(), 200.0);
    }

    #[test]
    fn test_plateau_duration() {
        let mut plateau = PlateauPotential::new(100.0);

        plateau.trigger();

        // Update 50ms
        plateau.update(50.0);
        assert!(plateau.is_active());
        assert_eq!(plateau.time_remaining(), 50.0);

        // Update another 60ms - should expire
        plateau.update(60.0);
        assert!(!plateau.is_active());
        assert_eq!(plateau.amplitude(), 0.0);
    }

    #[test]
    fn test_plateau_maintains_amplitude() {
        let mut plateau = PlateauPotential::new(200.0);

        plateau.trigger();

        // Amplitude should remain at 1.0 during active period
        for _ in 0..10 {
            plateau.update(10.0);
            if plateau.is_active() {
                assert_eq!(plateau.amplitude(), 1.0);
            }
        }
    }

    #[test]
    fn test_plateau_reset() {
        let mut plateau = PlateauPotential::new(200.0);

        plateau.trigger();
        plateau.update(50.0);

        plateau.reset();

        assert!(!plateau.is_active());
        assert_eq!(plateau.amplitude(), 0.0);
        assert_eq!(plateau.time_remaining(), 0.0);
    }

    #[test]
    fn test_update_inactive_plateau() {
        let mut plateau = PlateauPotential::new(200.0);

        // Should do nothing when inactive
        plateau.update(10.0);

        assert!(!plateau.is_active());
        assert_eq!(plateau.amplitude(), 0.0);
    }
}