use crate::error::{Result, SynapseError};
#[derive(Debug, Clone)]
pub struct CalciumDynamics {
pub concentration: f64,
pub buffered: f64,
pub resting_concentration: f64,
pub tau_removal: f64,
pub spike_influx: f64,
pub buffer_capacity: f64,
pub buffer_kon: f64,
pub buffer_koff: f64,
pub buffer_total: f64,
pub volume: f64,
}
impl Default for CalciumDynamics {
fn default() -> Self {
Self {
concentration: 0.05, buffered: 0.0,
resting_concentration: 0.05,
tau_removal: 20.0, spike_influx: 0.5, buffer_capacity: 100.0, buffer_kon: 0.1, buffer_koff: 0.001, buffer_total: 100.0, volume: 1.0, }
}
}
impl CalciumDynamics {
pub fn new() -> Self {
Self::default()
}
pub fn presynaptic() -> Self {
Self {
resting_concentration: 0.05,
tau_removal: 15.0, spike_influx: 1.0, buffer_capacity: 200.0, ..Self::default()
}
}
pub fn postsynaptic() -> Self {
Self {
resting_concentration: 0.05,
tau_removal: 30.0, spike_influx: 0.3, buffer_capacity: 50.0, ..Self::default()
}
}
pub fn update(&mut self, influx: f64, dt: f64) -> Result<()> {
if influx < 0.0 {
return Err(SynapseError::InvalidConcentration(influx));
}
let buffer_free = self.buffer_total - self.buffered;
let d_buffered = self.buffer_kon * self.concentration * buffer_free
- self.buffer_koff * self.buffered;
self.buffered += d_buffered * dt;
self.buffered = self.buffered.clamp(0.0, self.buffer_total);
let removal = (self.concentration - self.resting_concentration) / self.tau_removal;
let d_concentration = influx - removal - d_buffered;
self.concentration += d_concentration * dt;
self.concentration = self.concentration.max(0.0);
Ok(())
}
pub fn spike(&mut self) {
self.concentration += self.spike_influx;
}
pub fn add_influx(&mut self, amount: f64) -> Result<()> {
if amount < 0.0 {
return Err(SynapseError::InvalidConcentration(amount));
}
self.concentration += amount;
Ok(())
}
pub fn get_concentration(&self) -> f64 {
self.concentration
}
pub fn reset(&mut self) {
self.concentration = self.resting_concentration;
self.buffered = 0.0;
}
}
#[derive(Debug, Clone)]
pub struct CalciumStore {
pub store_concentration: f64,
pub cytoplasmic_concentration: f64,
pub max_store_concentration: f64,
pub pump_rate: f64,
pub pump_km: f64,
pub ip3r_open_probability: f64,
pub ryr_open_probability: f64,
pub max_release_rate: f64,
pub ip3_concentration: f64,
pub cicr_threshold: f64,
}
impl Default for CalciumStore {
fn default() -> Self {
Self {
store_concentration: 400.0, cytoplasmic_concentration: 0.05,
max_store_concentration: 500.0,
pump_rate: 0.5,
pump_km: 0.3,
ip3r_open_probability: 0.0,
ryr_open_probability: 0.0,
max_release_rate: 10.0,
ip3_concentration: 0.0,
cicr_threshold: 0.3, }
}
}
impl CalciumStore {
pub fn new() -> Self {
Self::default()
}
pub fn update(&mut self, cytoplasmic_ca: f64, dt: f64) -> Result<f64> {
self.cytoplasmic_concentration = cytoplasmic_ca;
let pump_flux = self.pump_rate * cytoplasmic_ca / (cytoplasmic_ca + self.pump_km);
let ip3r_flux = self.ip3r_open_probability * self.max_release_rate
* (self.store_concentration / self.max_store_concentration);
self.update_ryr_probability();
let ryr_flux = self.ryr_open_probability * self.max_release_rate
* (self.store_concentration / self.max_store_concentration);
let net_flux = ip3r_flux + ryr_flux - pump_flux;
self.store_concentration -= net_flux * dt;
self.store_concentration = self.store_concentration.clamp(0.0, self.max_store_concentration);
Ok(net_flux)
}
fn update_ryr_probability(&mut self) {
let ca = self.cytoplasmic_concentration;
if ca > self.cicr_threshold {
let hill_coeff = 4.0;
let k_half = 0.5_f64; self.ryr_open_probability = ca.powf(hill_coeff) / (ca.powf(hill_coeff) + k_half.powf(hill_coeff));
} else {
self.ryr_open_probability = 0.0;
}
}
pub fn trigger_ip3_release(&mut self, ip3_level: f64) {
self.ip3_concentration = ip3_level;
let ip3_term = ip3_level / (ip3_level + 0.5); let ca_term = self.cytoplasmic_concentration / (self.cytoplasmic_concentration + 0.3);
self.ip3r_open_probability = ip3_term * ca_term * 0.8; }
pub fn is_cicr_active(&self) -> bool {
self.cytoplasmic_concentration > self.cicr_threshold && self.ryr_open_probability > 0.01
}
pub fn reset(&mut self) {
self.store_concentration = 400.0;
self.cytoplasmic_concentration = 0.05;
self.ip3r_open_probability = 0.0;
self.ryr_open_probability = 0.0;
self.ip3_concentration = 0.0;
}
}
pub struct CalciumDependent;
impl CalciumDependent {
pub fn plasticity_signal(calcium: f64, threshold_low: f64, threshold_high: f64) -> f64 {
if calcium < threshold_low {
0.0
} else if calcium < threshold_high {
-(calcium - threshold_low) / (threshold_high - threshold_low)
} else {
(calcium - threshold_high) / threshold_high
}
}
pub fn camkii_activation(calcium: f64) -> f64 {
let k_half = 0.7_f64; let hill = 3.0;
calcium.powf(hill) / (calcium.powf(hill) + k_half.powf(hill))
}
pub fn calcineurin_activation(calcium: f64) -> f64 {
let k_half = 0.3_f64; let hill = 2.0;
calcium.powf(hill) / (calcium.powf(hill) + k_half.powf(hill))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_calcium_dynamics_creation() {
let ca = CalciumDynamics::new();
assert_eq!(ca.concentration, 0.05);
assert_eq!(ca.resting_concentration, 0.05);
}
#[test]
fn test_calcium_spike() {
let mut ca = CalciumDynamics::new();
let initial = ca.concentration;
ca.spike();
assert!(ca.concentration > initial);
}
#[test]
fn test_calcium_decay() {
let mut ca = CalciumDynamics::new();
ca.concentration = 5.0;
for _ in 0..100 {
ca.update(0.0, 1.0).unwrap();
}
assert!(ca.concentration < 5.0);
assert!(ca.concentration > ca.resting_concentration);
}
#[test]
fn test_calcium_buffering() {
let mut ca = CalciumDynamics::new();
ca.concentration = 1.0;
for _ in 0..50 {
ca.update(0.0, 0.1).unwrap();
}
assert!(ca.buffered > 0.0);
}
#[test]
fn test_presynaptic_calcium() {
let ca_pre = CalciumDynamics::presynaptic();
assert!(ca_pre.spike_influx > 0.5);
assert!(ca_pre.buffer_capacity > 100.0);
}
#[test]
fn test_postsynaptic_calcium() {
let ca_post = CalciumDynamics::postsynaptic();
assert!(ca_post.spike_influx < 0.5);
}
#[test]
fn test_calcium_store() {
let store = CalciumStore::new();
assert!(store.store_concentration > 0.0);
assert_eq!(store.cytoplasmic_concentration, 0.05);
}
#[test]
fn test_cicr_activation() {
let mut store = CalciumStore::new();
store.update(0.1, 1.0).unwrap();
assert!(!store.is_cicr_active());
store.update(1.0, 1.0).unwrap();
assert!(store.ryr_open_probability > 0.0);
}
#[test]
fn test_ip3_release() {
let mut store = CalciumStore::new();
store.trigger_ip3_release(1.0);
assert!(store.ip3_concentration > 0.0);
assert!(store.ip3r_open_probability > 0.0);
}
#[test]
fn test_camkii_activation() {
let low_ca = CalciumDependent::camkii_activation(0.1);
let medium_ca = CalciumDependent::camkii_activation(0.7);
let high_ca = CalciumDependent::camkii_activation(2.0);
assert!(low_ca < medium_ca);
assert!(medium_ca < high_ca);
}
#[test]
fn test_plasticity_signal() {
let signal_low = CalciumDependent::plasticity_signal(0.2, 0.3, 0.8);
let signal_medium = CalciumDependent::plasticity_signal(0.5, 0.3, 0.8);
let signal_high = CalciumDependent::plasticity_signal(1.5, 0.3, 0.8);
assert_eq!(signal_low, 0.0); assert!(signal_medium < 0.0); assert!(signal_high > 0.0); }
}