synapse_models/
vesicle.rs1use crate::error::{Result, SynapseError};
11
12#[derive(Debug, Clone)]
23pub struct VesiclePool {
24 pub available_fraction: f64,
26
27 pub utilization: f64,
29
30 pub baseline_utilization: f64,
32
33 pub tau_recovery: f64,
35
36 pub tau_facilitation: f64,
38
39 pub total_vesicles: usize,
41
42 pub docked_vesicles: usize,
44
45 pub reserve_pool: usize,
47
48 pub recycling_pool: usize,
50
51 pub calcium_cooperativity: f64,
53
54 pub calcium_half_max: f64,
56}
57
58impl Default for VesiclePool {
59 fn default() -> Self {
60 Self {
61 available_fraction: 1.0,
62 utilization: 0.5,
63 baseline_utilization: 0.5,
64 tau_recovery: 800.0, tau_facilitation: 1000.0, total_vesicles: 100,
67 docked_vesicles: 10,
68 reserve_pool: 80,
69 recycling_pool: 0,
70 calcium_cooperativity: 4.0, calcium_half_max: 1.0, }
73 }
74}
75
76impl VesiclePool {
77 pub fn new() -> Self {
79 Self::default()
80 }
81
82 pub fn with_params(baseline_u: f64, tau_rec: f64, tau_facil: f64) -> Result<Self> {
89 if !(0.0..=1.0).contains(&baseline_u) {
90 return Err(SynapseError::InvalidProbability(baseline_u));
91 }
92 if tau_rec <= 0.0 {
93 return Err(SynapseError::InvalidTimeConstant(tau_rec));
94 }
95 if tau_facil <= 0.0 {
96 return Err(SynapseError::InvalidTimeConstant(tau_facil));
97 }
98
99 Ok(Self {
100 baseline_utilization: baseline_u,
101 utilization: baseline_u,
102 tau_recovery: tau_rec,
103 tau_facilitation: tau_facil,
104 ..Self::default()
105 })
106 }
107
108 pub fn depressing() -> Self {
112 Self {
113 baseline_utilization: 0.6,
114 utilization: 0.6,
115 tau_recovery: 130.0,
116 tau_facilitation: 530.0,
117 ..Self::default()
118 }
119 }
120
121 pub fn facilitating() -> Self {
125 Self {
126 baseline_utilization: 0.15,
127 utilization: 0.15,
128 tau_recovery: 670.0,
129 tau_facilitation: 17.0,
130 ..Self::default()
131 }
132 }
133
134 pub fn update(&mut self, dt: f64) -> Result<()> {
141 let dx = (1.0 - self.available_fraction) / self.tau_recovery;
143 self.available_fraction += dx * dt;
144 self.available_fraction = self.available_fraction.clamp(0.0, 1.0);
145
146 let du = (self.baseline_utilization - self.utilization) / self.tau_facilitation;
148 self.utilization += du * dt;
149 self.utilization = self.utilization.clamp(0.0, 1.0);
150
151 let total = self.total_vesicles as f64;
153 self.docked_vesicles = (self.available_fraction * total * 0.1) as usize;
154 self.reserve_pool = ((1.0 - self.available_fraction) * total * 0.8) as usize;
155 self.recycling_pool = self.total_vesicles - self.docked_vesicles - self.reserve_pool;
156
157 Ok(())
158 }
159
160 pub fn calcium_release_probability(&self, calcium_concentration: f64) -> f64 {
167 let ca_n = calcium_concentration.powf(self.calcium_cooperativity);
168 let k_n = self.calcium_half_max.powf(self.calcium_cooperativity);
169 ca_n / (ca_n + k_n)
170 }
171
172 pub fn release(&mut self, calcium_concentration: f64) -> Result<usize> {
182 if calcium_concentration < 0.0 {
183 return Err(SynapseError::InvalidConcentration(calcium_concentration));
184 }
185
186 let ca_prob = self.calcium_release_probability(calcium_concentration);
188
189 let release_prob = self.utilization * ca_prob;
191
192 let vesicles_released = (self.available_fraction * release_prob * self.total_vesicles as f64) as usize;
194
195 self.available_fraction *= 1.0 - self.utilization;
197 self.available_fraction = self.available_fraction.clamp(0.0, 1.0);
198
199 self.utilization += self.baseline_utilization * (1.0 - self.utilization);
201 self.utilization = self.utilization.clamp(0.0, 1.0);
202
203 if vesicles_released <= self.docked_vesicles {
205 self.docked_vesicles -= vesicles_released;
206 self.recycling_pool += vesicles_released;
207 } else {
208 self.recycling_pool += self.docked_vesicles;
209 self.docked_vesicles = 0;
210 }
211
212 Ok(vesicles_released)
213 }
214
215 pub fn release_probability(&self) -> f64 {
217 self.available_fraction * self.utilization
218 }
219
220 pub fn reset(&mut self) {
222 self.available_fraction = 1.0;
223 self.utilization = self.baseline_utilization;
224 self.docked_vesicles = (self.total_vesicles as f64 * 0.1) as usize;
225 self.reserve_pool = (self.total_vesicles as f64 * 0.8) as usize;
226 self.recycling_pool = 0;
227 }
228}
229
230#[derive(Debug, Clone)]
234pub struct QuantalRelease {
235 pub n_sites: usize,
237
238 pub release_probability: f64,
240
241 pub quantal_size: f64,
243
244 pub quantal_variance: f64,
246}
247
248impl QuantalRelease {
249 pub fn new(n_sites: usize, release_probability: f64, quantal_size: f64) -> Result<Self> {
251 if !(0.0..=1.0).contains(&release_probability) {
252 return Err(SynapseError::InvalidProbability(release_probability));
253 }
254
255 Ok(Self {
256 n_sites,
257 release_probability,
258 quantal_size,
259 quantal_variance: quantal_size * 0.3, })
261 }
262
263 pub fn expected_release(&self) -> f64 {
265 self.n_sites as f64 * self.release_probability
266 }
267
268 pub fn release_variance(&self) -> f64 {
270 self.n_sites as f64 * self.release_probability * (1.0 - self.release_probability)
271 }
272
273 pub fn expected_amplitude(&self) -> f64 {
275 self.expected_release() * self.quantal_size
276 }
277
278 pub fn coefficient_of_variation(&self) -> f64 {
280 if self.expected_release() == 0.0 {
281 return f64::INFINITY;
282 }
283
284 let release_cv = (self.release_variance() / self.expected_release().powi(2)).sqrt();
285 let quantal_cv = self.quantal_variance / self.quantal_size;
286
287 (release_cv.powi(2) + quantal_cv.powi(2)).sqrt()
288 }
289}
290
291#[derive(Debug, Clone)]
295pub struct MultiVesicularRelease {
296 pub release_probabilities: Vec<f64>,
298
299 pub max_vesicles_per_site: usize,
301}
302
303impl MultiVesicularRelease {
304 pub fn poisson(mean_vesicles: f64, max_vesicles: usize) -> Self {
310 let mut probs = Vec::with_capacity(max_vesicles + 1);
311
312 for k in 0..=max_vesicles {
314 let p = (mean_vesicles.powi(k as i32) * (-mean_vesicles).exp())
315 / Self::factorial(k) as f64;
316 probs.push(p);
317 }
318
319 let sum: f64 = probs.iter().sum();
321 probs.iter_mut().for_each(|p| *p /= sum);
322
323 Self {
324 release_probabilities: probs,
325 max_vesicles_per_site: max_vesicles,
326 }
327 }
328
329 fn factorial(n: usize) -> usize {
331 (1..=n).product()
332 }
333
334 pub fn mean_release(&self) -> f64 {
336 self.release_probabilities
337 .iter()
338 .enumerate()
339 .map(|(k, &p)| k as f64 * p)
340 .sum()
341 }
342
343 pub fn probability(&self, k: usize) -> f64 {
345 if k <= self.max_vesicles_per_site {
346 self.release_probabilities[k]
347 } else {
348 0.0
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
358 fn test_vesicle_pool_creation() {
359 let pool = VesiclePool::new();
360 assert_eq!(pool.available_fraction, 1.0);
361 assert_eq!(pool.utilization, 0.5);
362 }
363
364 #[test]
365 fn test_depressing_synapse() {
366 let mut pool = VesiclePool::depressing();
367 let initial_prob = pool.release_probability();
368
369 pool.release(10.0).unwrap(); let prob_after_first = pool.release_probability();
372
373 assert!(prob_after_first < initial_prob);
375
376 pool.release(10.0).unwrap();
378 let prob_after_second = pool.release_probability();
379
380 assert!(prob_after_second < prob_after_first);
382 }
383
384 #[test]
385 fn test_facilitating_synapse() {
386 let mut pool = VesiclePool::facilitating();
387 let initial_u = pool.utilization;
388
389 pool.release(10.0).unwrap();
391
392 assert!(pool.utilization > initial_u);
394 }
395
396 #[test]
397 fn test_vesicle_pool_recovery() {
398 let mut pool = VesiclePool::new();
399
400 pool.release(10.0).unwrap();
402 let depleted_fraction = pool.available_fraction;
403
404 for _ in 0..100 {
406 pool.update(10.0).unwrap(); }
408
409 assert!(pool.available_fraction > depleted_fraction);
410 }
411
412 #[test]
413 fn test_calcium_release_probability() {
414 let pool = VesiclePool::new();
415
416 let low_ca = pool.calcium_release_probability(0.1);
417 let medium_ca = pool.calcium_release_probability(1.0);
418 let high_ca = pool.calcium_release_probability(10.0);
419
420 assert!(low_ca < medium_ca);
421 assert!(medium_ca < high_ca);
422 assert!(high_ca > 0.9); }
424
425 #[test]
426 fn test_quantal_release() {
427 let qr = QuantalRelease::new(10, 0.5, 20.0).unwrap();
428
429 assert_eq!(qr.expected_release(), 5.0); assert_eq!(qr.expected_amplitude(), 100.0); assert!(qr.coefficient_of_variation() > 0.0);
432 }
433
434 #[test]
435 fn test_multi_vesicular_release() {
436 let mvr = MultiVesicularRelease::poisson(2.0, 10);
437
438 let sum: f64 = mvr.release_probabilities.iter().sum();
440 assert!((sum - 1.0).abs() < 1e-6);
441
442 assert!((mvr.mean_release() - 2.0).abs() < 0.1);
444 }
445
446 #[test]
447 fn test_vesicle_pool_reset() {
448 let mut pool = VesiclePool::new();
449 pool.release(10.0).unwrap();
450
451 pool.reset();
452 assert_eq!(pool.available_fraction, 1.0);
453 assert_eq!(pool.utilization, pool.baseline_utilization);
454 }
455}