use crate::error::{Result, SynapseError};
#[derive(Debug, Clone)]
pub struct VesiclePool {
pub available_fraction: f64,
pub utilization: f64,
pub baseline_utilization: f64,
pub tau_recovery: f64,
pub tau_facilitation: f64,
pub total_vesicles: usize,
pub docked_vesicles: usize,
pub reserve_pool: usize,
pub recycling_pool: usize,
pub calcium_cooperativity: f64,
pub calcium_half_max: f64,
}
impl Default for VesiclePool {
fn default() -> Self {
Self {
available_fraction: 1.0,
utilization: 0.5,
baseline_utilization: 0.5,
tau_recovery: 800.0, tau_facilitation: 1000.0, total_vesicles: 100,
docked_vesicles: 10,
reserve_pool: 80,
recycling_pool: 0,
calcium_cooperativity: 4.0, calcium_half_max: 1.0, }
}
}
impl VesiclePool {
pub fn new() -> Self {
Self::default()
}
pub fn with_params(baseline_u: f64, tau_rec: f64, tau_facil: f64) -> Result<Self> {
if !(0.0..=1.0).contains(&baseline_u) {
return Err(SynapseError::InvalidProbability(baseline_u));
}
if tau_rec <= 0.0 {
return Err(SynapseError::InvalidTimeConstant(tau_rec));
}
if tau_facil <= 0.0 {
return Err(SynapseError::InvalidTimeConstant(tau_facil));
}
Ok(Self {
baseline_utilization: baseline_u,
utilization: baseline_u,
tau_recovery: tau_rec,
tau_facilitation: tau_facil,
..Self::default()
})
}
pub fn depressing() -> Self {
Self {
baseline_utilization: 0.6,
utilization: 0.6,
tau_recovery: 130.0,
tau_facilitation: 530.0,
..Self::default()
}
}
pub fn facilitating() -> Self {
Self {
baseline_utilization: 0.15,
utilization: 0.15,
tau_recovery: 670.0,
tau_facilitation: 17.0,
..Self::default()
}
}
pub fn update(&mut self, dt: f64) -> Result<()> {
let dx = (1.0 - self.available_fraction) / self.tau_recovery;
self.available_fraction += dx * dt;
self.available_fraction = self.available_fraction.clamp(0.0, 1.0);
let du = (self.baseline_utilization - self.utilization) / self.tau_facilitation;
self.utilization += du * dt;
self.utilization = self.utilization.clamp(0.0, 1.0);
let total = self.total_vesicles as f64;
self.docked_vesicles = (self.available_fraction * total * 0.1) as usize;
self.reserve_pool = ((1.0 - self.available_fraction) * total * 0.8) as usize;
self.recycling_pool = self.total_vesicles - self.docked_vesicles - self.reserve_pool;
Ok(())
}
pub fn calcium_release_probability(&self, calcium_concentration: f64) -> f64 {
let ca_n = calcium_concentration.powf(self.calcium_cooperativity);
let k_n = self.calcium_half_max.powf(self.calcium_cooperativity);
ca_n / (ca_n + k_n)
}
pub fn release(&mut self, calcium_concentration: f64) -> Result<usize> {
if calcium_concentration < 0.0 {
return Err(SynapseError::InvalidConcentration(calcium_concentration));
}
let ca_prob = self.calcium_release_probability(calcium_concentration);
let release_prob = self.utilization * ca_prob;
let vesicles_released = (self.available_fraction * release_prob * self.total_vesicles as f64) as usize;
self.available_fraction *= 1.0 - self.utilization;
self.available_fraction = self.available_fraction.clamp(0.0, 1.0);
self.utilization += self.baseline_utilization * (1.0 - self.utilization);
self.utilization = self.utilization.clamp(0.0, 1.0);
if vesicles_released <= self.docked_vesicles {
self.docked_vesicles -= vesicles_released;
self.recycling_pool += vesicles_released;
} else {
self.recycling_pool += self.docked_vesicles;
self.docked_vesicles = 0;
}
Ok(vesicles_released)
}
pub fn release_probability(&self) -> f64 {
self.available_fraction * self.utilization
}
pub fn reset(&mut self) {
self.available_fraction = 1.0;
self.utilization = self.baseline_utilization;
self.docked_vesicles = (self.total_vesicles as f64 * 0.1) as usize;
self.reserve_pool = (self.total_vesicles as f64 * 0.8) as usize;
self.recycling_pool = 0;
}
}
#[derive(Debug, Clone)]
pub struct QuantalRelease {
pub n_sites: usize,
pub release_probability: f64,
pub quantal_size: f64,
pub quantal_variance: f64,
}
impl QuantalRelease {
pub fn new(n_sites: usize, release_probability: f64, quantal_size: f64) -> Result<Self> {
if !(0.0..=1.0).contains(&release_probability) {
return Err(SynapseError::InvalidProbability(release_probability));
}
Ok(Self {
n_sites,
release_probability,
quantal_size,
quantal_variance: quantal_size * 0.3, })
}
pub fn expected_release(&self) -> f64 {
self.n_sites as f64 * self.release_probability
}
pub fn release_variance(&self) -> f64 {
self.n_sites as f64 * self.release_probability * (1.0 - self.release_probability)
}
pub fn expected_amplitude(&self) -> f64 {
self.expected_release() * self.quantal_size
}
pub fn coefficient_of_variation(&self) -> f64 {
if self.expected_release() == 0.0 {
return f64::INFINITY;
}
let release_cv = (self.release_variance() / self.expected_release().powi(2)).sqrt();
let quantal_cv = self.quantal_variance / self.quantal_size;
(release_cv.powi(2) + quantal_cv.powi(2)).sqrt()
}
}
#[derive(Debug, Clone)]
pub struct MultiVesicularRelease {
pub release_probabilities: Vec<f64>,
pub max_vesicles_per_site: usize,
}
impl MultiVesicularRelease {
pub fn poisson(mean_vesicles: f64, max_vesicles: usize) -> Self {
let mut probs = Vec::with_capacity(max_vesicles + 1);
for k in 0..=max_vesicles {
let p = (mean_vesicles.powi(k as i32) * (-mean_vesicles).exp())
/ Self::factorial(k) as f64;
probs.push(p);
}
let sum: f64 = probs.iter().sum();
probs.iter_mut().for_each(|p| *p /= sum);
Self {
release_probabilities: probs,
max_vesicles_per_site: max_vesicles,
}
}
fn factorial(n: usize) -> usize {
(1..=n).product()
}
pub fn mean_release(&self) -> f64 {
self.release_probabilities
.iter()
.enumerate()
.map(|(k, &p)| k as f64 * p)
.sum()
}
pub fn probability(&self, k: usize) -> f64 {
if k <= self.max_vesicles_per_site {
self.release_probabilities[k]
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vesicle_pool_creation() {
let pool = VesiclePool::new();
assert_eq!(pool.available_fraction, 1.0);
assert_eq!(pool.utilization, 0.5);
}
#[test]
fn test_depressing_synapse() {
let mut pool = VesiclePool::depressing();
let initial_prob = pool.release_probability();
pool.release(10.0).unwrap(); let prob_after_first = pool.release_probability();
assert!(prob_after_first < initial_prob);
pool.release(10.0).unwrap();
let prob_after_second = pool.release_probability();
assert!(prob_after_second < prob_after_first);
}
#[test]
fn test_facilitating_synapse() {
let mut pool = VesiclePool::facilitating();
let initial_u = pool.utilization;
pool.release(10.0).unwrap();
assert!(pool.utilization > initial_u);
}
#[test]
fn test_vesicle_pool_recovery() {
let mut pool = VesiclePool::new();
pool.release(10.0).unwrap();
let depleted_fraction = pool.available_fraction;
for _ in 0..100 {
pool.update(10.0).unwrap(); }
assert!(pool.available_fraction > depleted_fraction);
}
#[test]
fn test_calcium_release_probability() {
let pool = VesiclePool::new();
let low_ca = pool.calcium_release_probability(0.1);
let medium_ca = pool.calcium_release_probability(1.0);
let high_ca = pool.calcium_release_probability(10.0);
assert!(low_ca < medium_ca);
assert!(medium_ca < high_ca);
assert!(high_ca > 0.9); }
#[test]
fn test_quantal_release() {
let qr = QuantalRelease::new(10, 0.5, 20.0).unwrap();
assert_eq!(qr.expected_release(), 5.0); assert_eq!(qr.expected_amplitude(), 100.0); assert!(qr.coefficient_of_variation() > 0.0);
}
#[test]
fn test_multi_vesicular_release() {
let mvr = MultiVesicularRelease::poisson(2.0, 10);
let sum: f64 = mvr.release_probabilities.iter().sum();
assert!((sum - 1.0).abs() < 1e-6);
assert!((mvr.mean_release() - 2.0).abs() < 0.1);
}
#[test]
fn test_vesicle_pool_reset() {
let mut pool = VesiclePool::new();
pool.release(10.0).unwrap();
pool.reset();
assert_eq!(pool.available_fraction, 1.0);
assert_eq!(pool.utilization, pool.baseline_utilization);
}
}