Files
wifi-densepose/crates/ruvector-nervous-system/src/dendrite/compartment.rs
ruv d803bfe2b1 Squashed 'vendor/ruvector/' content from commit b64c2172
git-subtree-dir: vendor/ruvector
git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
2026-02-28 14:39:40 -05:00

190 lines
4.8 KiB
Rust

//! 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);
}
}