git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
190 lines
4.8 KiB
Rust
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);
|
|
}
|
|
}
|