Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
This commit is contained in:
189
crates/ruvector-nervous-system/src/dendrite/compartment.rs
Normal file
189
crates/ruvector-nervous-system/src/dendrite/compartment.rs
Normal file
@@ -0,0 +1,189 @@
|
||||
//! 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user