synapse_models/
calcium.rs1use crate::error::{Result, SynapseError};
11
12#[derive(Debug, Clone)]
21pub struct CalciumDynamics {
22 pub concentration: f64,
24
25 pub buffered: f64,
27
28 pub resting_concentration: f64,
30
31 pub tau_removal: f64,
33
34 pub spike_influx: f64,
36
37 pub buffer_capacity: f64,
39
40 pub buffer_kon: f64,
42
43 pub buffer_koff: f64,
45
46 pub buffer_total: f64,
48
49 pub volume: f64,
51}
52
53impl Default for CalciumDynamics {
54 fn default() -> Self {
55 Self {
56 concentration: 0.05, buffered: 0.0,
58 resting_concentration: 0.05,
59 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, }
67 }
68}
69
70impl CalciumDynamics {
71 pub fn new() -> Self {
73 Self::default()
74 }
75
76 pub fn presynaptic() -> Self {
80 Self {
81 resting_concentration: 0.05,
82 tau_removal: 15.0, spike_influx: 1.0, buffer_capacity: 200.0, ..Self::default()
86 }
87 }
88
89 pub fn postsynaptic() -> Self {
93 Self {
94 resting_concentration: 0.05,
95 tau_removal: 30.0, spike_influx: 0.3, buffer_capacity: 50.0, ..Self::default()
99 }
100 }
101
102 pub fn update(&mut self, influx: f64, dt: f64) -> Result<()> {
110 if influx < 0.0 {
111 return Err(SynapseError::InvalidConcentration(influx));
112 }
113
114 let buffer_free = self.buffer_total - self.buffered;
116
117 let d_buffered = self.buffer_kon * self.concentration * buffer_free
120 - self.buffer_koff * self.buffered;
121 self.buffered += d_buffered * dt;
122 self.buffered = self.buffered.clamp(0.0, self.buffer_total);
123
124 let removal = (self.concentration - self.resting_concentration) / self.tau_removal;
127 let d_concentration = influx - removal - d_buffered;
128
129 self.concentration += d_concentration * dt;
130 self.concentration = self.concentration.max(0.0);
131
132 Ok(())
133 }
134
135 pub fn spike(&mut self) {
137 self.concentration += self.spike_influx;
138 }
139
140 pub fn add_influx(&mut self, amount: f64) -> Result<()> {
145 if amount < 0.0 {
146 return Err(SynapseError::InvalidConcentration(amount));
147 }
148 self.concentration += amount;
149 Ok(())
150 }
151
152 pub fn get_concentration(&self) -> f64 {
154 self.concentration
155 }
156
157 pub fn reset(&mut self) {
159 self.concentration = self.resting_concentration;
160 self.buffered = 0.0;
161 }
162}
163
164#[derive(Debug, Clone)]
168pub struct CalciumStore {
169 pub store_concentration: f64,
171
172 pub cytoplasmic_concentration: f64,
174
175 pub max_store_concentration: f64,
177
178 pub pump_rate: f64,
180
181 pub pump_km: f64,
183
184 pub ip3r_open_probability: f64,
186
187 pub ryr_open_probability: f64,
189
190 pub max_release_rate: f64,
192
193 pub ip3_concentration: f64,
195
196 pub cicr_threshold: f64,
198}
199
200impl Default for CalciumStore {
201 fn default() -> Self {
202 Self {
203 store_concentration: 400.0, cytoplasmic_concentration: 0.05,
205 max_store_concentration: 500.0,
206 pump_rate: 0.5,
207 pump_km: 0.3,
208 ip3r_open_probability: 0.0,
209 ryr_open_probability: 0.0,
210 max_release_rate: 10.0,
211 ip3_concentration: 0.0,
212 cicr_threshold: 0.3, }
214 }
215}
216
217impl CalciumStore {
218 pub fn new() -> Self {
220 Self::default()
221 }
222
223 pub fn update(&mut self, cytoplasmic_ca: f64, dt: f64) -> Result<f64> {
232 self.cytoplasmic_concentration = cytoplasmic_ca;
233
234 let pump_flux = self.pump_rate * cytoplasmic_ca / (cytoplasmic_ca + self.pump_km);
236
237 let ip3r_flux = self.ip3r_open_probability * self.max_release_rate
239 * (self.store_concentration / self.max_store_concentration);
240
241 self.update_ryr_probability();
243 let ryr_flux = self.ryr_open_probability * self.max_release_rate
244 * (self.store_concentration / self.max_store_concentration);
245
246 let net_flux = ip3r_flux + ryr_flux - pump_flux;
248
249 self.store_concentration -= net_flux * dt;
251 self.store_concentration = self.store_concentration.clamp(0.0, self.max_store_concentration);
252
253 Ok(net_flux)
254 }
255
256 fn update_ryr_probability(&mut self) {
260 let ca = self.cytoplasmic_concentration;
262 if ca > self.cicr_threshold {
263 let hill_coeff = 4.0;
264 let k_half = 0.5_f64; self.ryr_open_probability = ca.powf(hill_coeff) / (ca.powf(hill_coeff) + k_half.powf(hill_coeff));
266 } else {
267 self.ryr_open_probability = 0.0;
268 }
269 }
270
271 pub fn trigger_ip3_release(&mut self, ip3_level: f64) {
276 self.ip3_concentration = ip3_level;
277
278 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; }
284
285 pub fn is_cicr_active(&self) -> bool {
287 self.cytoplasmic_concentration > self.cicr_threshold && self.ryr_open_probability > 0.01
288 }
289
290 pub fn reset(&mut self) {
292 self.store_concentration = 400.0;
293 self.cytoplasmic_concentration = 0.05;
294 self.ip3r_open_probability = 0.0;
295 self.ryr_open_probability = 0.0;
296 self.ip3_concentration = 0.0;
297 }
298}
299
300pub struct CalciumDependent;
304
305impl CalciumDependent {
306 pub fn plasticity_signal(calcium: f64, threshold_low: f64, threshold_high: f64) -> f64 {
315 if calcium < threshold_low {
316 0.0
317 } else if calcium < threshold_high {
318 -(calcium - threshold_low) / (threshold_high - threshold_low)
320 } else {
321 (calcium - threshold_high) / threshold_high
323 }
324 }
325
326 pub fn camkii_activation(calcium: f64) -> f64 {
333 let k_half = 0.7_f64; let hill = 3.0;
335 calcium.powf(hill) / (calcium.powf(hill) + k_half.powf(hill))
336 }
337
338 pub fn calcineurin_activation(calcium: f64) -> f64 {
345 let k_half = 0.3_f64; let hill = 2.0;
347 calcium.powf(hill) / (calcium.powf(hill) + k_half.powf(hill))
348 }
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
356 fn test_calcium_dynamics_creation() {
357 let ca = CalciumDynamics::new();
358 assert_eq!(ca.concentration, 0.05);
359 assert_eq!(ca.resting_concentration, 0.05);
360 }
361
362 #[test]
363 fn test_calcium_spike() {
364 let mut ca = CalciumDynamics::new();
365 let initial = ca.concentration;
366
367 ca.spike();
368 assert!(ca.concentration > initial);
369 }
370
371 #[test]
372 fn test_calcium_decay() {
373 let mut ca = CalciumDynamics::new();
374 ca.concentration = 5.0; for _ in 0..100 {
378 ca.update(0.0, 1.0).unwrap();
379 }
380
381 assert!(ca.concentration < 5.0);
383 assert!(ca.concentration > ca.resting_concentration);
384 }
385
386 #[test]
387 fn test_calcium_buffering() {
388 let mut ca = CalciumDynamics::new();
389 ca.concentration = 1.0;
390
391 for _ in 0..50 {
393 ca.update(0.0, 0.1).unwrap();
394 }
395
396 assert!(ca.buffered > 0.0);
398 }
399
400 #[test]
401 fn test_presynaptic_calcium() {
402 let ca_pre = CalciumDynamics::presynaptic();
403 assert!(ca_pre.spike_influx > 0.5);
404 assert!(ca_pre.buffer_capacity > 100.0);
405 }
406
407 #[test]
408 fn test_postsynaptic_calcium() {
409 let ca_post = CalciumDynamics::postsynaptic();
410 assert!(ca_post.spike_influx < 0.5);
411 }
412
413 #[test]
414 fn test_calcium_store() {
415 let store = CalciumStore::new();
416 assert!(store.store_concentration > 0.0);
417 assert_eq!(store.cytoplasmic_concentration, 0.05);
418 }
419
420 #[test]
421 fn test_cicr_activation() {
422 let mut store = CalciumStore::new();
423
424 store.update(0.1, 1.0).unwrap();
426 assert!(!store.is_cicr_active());
427
428 store.update(1.0, 1.0).unwrap();
430 assert!(store.ryr_open_probability > 0.0);
431 }
432
433 #[test]
434 fn test_ip3_release() {
435 let mut store = CalciumStore::new();
436 store.trigger_ip3_release(1.0);
437
438 assert!(store.ip3_concentration > 0.0);
439 assert!(store.ip3r_open_probability > 0.0);
440 }
441
442 #[test]
443 fn test_camkii_activation() {
444 let low_ca = CalciumDependent::camkii_activation(0.1);
445 let medium_ca = CalciumDependent::camkii_activation(0.7);
446 let high_ca = CalciumDependent::camkii_activation(2.0);
447
448 assert!(low_ca < medium_ca);
449 assert!(medium_ca < high_ca);
450 }
451
452 #[test]
453 fn test_plasticity_signal() {
454 let signal_low = CalciumDependent::plasticity_signal(0.2, 0.3, 0.8);
455 let signal_medium = CalciumDependent::plasticity_signal(0.5, 0.3, 0.8);
456 let signal_high = CalciumDependent::plasticity_signal(1.5, 0.3, 0.8);
457
458 assert_eq!(signal_low, 0.0); assert!(signal_medium < 0.0); assert!(signal_high > 0.0); }
462}