synapse_models/
lib.rs

1//! # Synapse Models Library
2//!
3//! A comprehensive Rust library for modeling synaptic dynamics in computational neuroscience.
4//!
5//! ## Overview
6//!
7//! This library provides detailed biophysical models of synaptic transmission including:
8//!
9//! - **Neurotransmitter dynamics**: Multiple neurotransmitter types (glutamate, GABA, dopamine, etc.)
10//!   with realistic release and clearance kinetics
11//! - **Receptor models**: Detailed kinetic models for ionotropic (AMPA, NMDA, GABA-A) and
12//!   metabotropic (GABA-B, mGluR) receptors
13//! - **Vesicle pool dynamics**: Tsodyks-Markram model for short-term depression and facilitation
14//! - **Calcium dynamics**: Pre- and postsynaptic calcium with buffering, stores, and CICR
15//! - **Plasticity rules**: STDP, BCM, Oja's rule, Hebbian learning, homeostatic plasticity
16//! - **Network models**: Support for chemical synapses, gap junctions, ephaptic coupling, and neuromodulation
17//!
18//! ## Features
19//!
20//! - Biologically accurate parameters based on experimental data
21//! - Efficient numerical integration using exponential Euler methods
22//! - Thread-safe design for parallel simulations
23//! - Comprehensive test coverage
24//! - Extensive documentation with neuroscience background
25//!
26//! ## Example: Basic Synaptic Transmission
27//!
28//! ```rust
29//! use synapse_models::synapse::Synapse;
30//!
31//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
32//! // Create an excitatory synapse
33//! let mut synapse = Synapse::excitatory(1.0, 1.0)?;
34//!
35//! // Presynaptic spike at t=0
36//! synapse.presynaptic_spike(0.0)?;
37//!
38//! // Simulate for 10 ms
39//! for t in 0..100 {
40//!     let time = t as f64 * 0.1;
41//!     synapse.update(time, -65.0, 0.1)?;
42//!
43//!     // Get postsynaptic current
44//!     let current = synapse.current(-65.0);
45//! }
46//! # Ok(())
47//! # }
48//! ```
49//!
50//! ## Example: STDP Learning
51//!
52//! ```rust
53//! use synapse_models::synapse::Synapse;
54//!
55//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
56//! let mut synapse = Synapse::excitatory(0.5, 1.0)?;
57//!
58//! let initial_weight = synapse.weight;
59//!
60//! // Pre before post -> potentiation
61//! synapse.presynaptic_spike(0.0)?;
62//! synapse.postsynaptic_spike(10.0)?;
63//!
64//! assert!(synapse.weight > initial_weight);
65//! # Ok(())
66//! # }
67//! ```
68//!
69//! ## Example: Network Simulation
70//!
71//! ```rust
72//! use synapse_models::network::SynapticNetwork;
73//! use synapse_models::synapse::Synapse;
74//!
75//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
76//! // Create a network with 10 neurons
77//! let mut network = SynapticNetwork::new(10);
78//!
79//! // Add excitatory connections
80//! for i in 0..9 {
81//!     let syn = Synapse::excitatory(1.0, 1.0)?;
82//!     network.add_connection(i, i + 1, syn)?;
83//! }
84//!
85//! // Spike from first neuron
86//! network.spike(0)?;
87//!
88//! // Update network
89//! let voltages = vec![-65.0; 10];
90//! network.update(&voltages, 0.1)?;
91//! # Ok(())
92//! # }
93//! ```
94//!
95//! ## Biophysical Background
96//!
97//! ### Synaptic Transmission
98//!
99//! Synaptic transmission involves multiple steps:
100//! 1. **Action potential arrival** triggers voltage-gated calcium channels
101//! 2. **Calcium influx** causes vesicle fusion and neurotransmitter release
102//! 3. **Neurotransmitter diffusion** across the synaptic cleft (~20 nm)
103//! 4. **Receptor binding** opens ion channels or activates second messengers
104//! 5. **Postsynaptic current** flows, changing membrane potential
105//! 6. **Neurotransmitter clearance** by reuptake or degradation
106//!
107//! ### Short-Term Plasticity
108//!
109//! Short-term plasticity operates on timescales of milliseconds to seconds:
110//! - **Depression**: Depletion of readily releasable vesicle pool
111//! - **Facilitation**: Residual calcium enhances release probability
112//! - Modeled by Tsodyks-Markram equations
113//!
114//! ### Long-Term Plasticity
115//!
116//! Long-term plasticity underlies learning and memory:
117//! - **STDP**: Spike timing-dependent modification (±20-40 ms window)
118//! - **LTP/LTD**: Long-term potentiation/depression
119//! - Requires postsynaptic calcium elevation
120//! - CaMKII activation → LTP, calcineurin activation → LTD
121//!
122//! ## Mathematical Models
123//!
124//! ### Receptor Kinetics
125//!
126//! First-order binding scheme:
127//! ```text
128//! dR/dt = α[NT](1-R) - βR
129//! ```
130//! where R is open probability, [NT] is neurotransmitter concentration,
131//! α is binding rate, β is unbinding rate.
132//!
133//! ### NMDA Voltage Dependence
134//!
135//! Mg²⁺ block (Jahr & Stevens, 1990):
136//! ```text
137//! B(V) = 1 / (1 + [Mg²⁺]/3.57 * exp(-0.062*V))
138//! ```
139//!
140//! ### Tsodyks-Markram Model
141//!
142//! ```text
143//! dx/dt = (1-x)/τ_rec - U*x*δ(t-t_spike)
144//! du/dt = (U₀-u)/τ_facil + U₀(1-u)δ(t-t_spike)
145//! ```
146//!
147//! ### STDP Window
148//!
149//! ```text
150//! Δw = A₊ exp(-Δt/τ₊)     for Δt > 0 (potentiation)
151//! Δw = -A₋ exp(Δt/τ₋)     for Δt < 0 (depression)
152//! ```
153//!
154//! ## Performance Considerations
155//!
156//! - Uses exponential Euler integration for numerical stability
157//! - Sparse network representation for efficiency
158//! - Minimal allocations in update loops
159//! - Thread-safe for parallel neuron updates
160//!
161//! ## References
162//!
163//! - Tsodyks & Markram (1997). The neural code between neocortical pyramidal neurons depends on neurotransmitter release probability.
164//! - Bi & Poo (1998). Synaptic modifications in cultured hippocampal neurons: dependence on spike timing, synaptic strength, and postsynaptic cell type.
165//! - Jahr & Stevens (1990). Voltage dependence of NMDA-activated macroscopic conductances predicted by single-channel kinetics.
166//! - Bienenstock et al. (1982). Theory for the development of neuron selectivity: orientation specificity and binocular interaction in visual cortex.
167
168pub mod calcium;
169pub mod error;
170pub mod network;
171pub mod neurotransmitter;
172pub mod plasticity;
173pub mod receptor;
174pub mod synapse;
175pub mod vesicle;
176
177// Re-export commonly used types
178pub use error::{Result, SynapseError};
179pub use synapse::{Synapse, SynapseBuilder, SynapseType};
180pub use network::{SynapticNetwork, NetworkStats};
181
182/// Library version.
183pub const VERSION: &str = env!("CARGO_PKG_VERSION");
184
185#[cfg(test)]
186mod integration_tests {
187    use super::*;
188    use synapse::Synapse;
189    use network::SynapticNetwork;
190
191    #[test]
192    fn test_complete_synaptic_transmission() {
193        let mut syn = Synapse::excitatory(1.0, 1.0).unwrap();
194
195        // Initial state
196        assert_eq!(syn.conductance(), 0.0);
197
198        // Presynaptic spike
199        syn.presynaptic_spike(0.0).unwrap();
200
201        // Simulate transmission - need enough time for delay + receptor activation
202        let mut max_conductance = 0.0_f64;
203        let mut max_nt = 0.0_f64;
204        for t in 0..100 {
205            syn.update(t as f64 * 0.1, -65.0, 0.1).unwrap();
206            max_conductance = max_conductance.max(syn.conductance());
207            max_nt = max_nt.max(syn.neurotransmitter.get_concentration());
208        }
209
210        // Should have produced postsynaptic response at some point
211        assert!(max_conductance > 0.0 || max_nt > 0.0,
212                "max_conductance: {}, max_nt: {}", max_conductance, max_nt);
213    }
214
215    #[test]
216    fn test_stdp_learning_window() {
217        // Test potentiation
218        let mut syn_pot = Synapse::excitatory(0.5, 1.0).unwrap();
219        let w0 = syn_pot.weight;
220        syn_pot.presynaptic_spike(0.0).unwrap();
221        syn_pot.postsynaptic_spike(10.0).unwrap();
222        assert!(syn_pot.weight > w0, "Pre before post should potentiate");
223
224        // Test depression
225        let mut syn_dep = Synapse::excitatory(0.5, 1.0).unwrap();
226        let w0 = syn_dep.weight;
227        syn_dep.postsynaptic_spike(0.0).unwrap();
228        syn_dep.presynaptic_spike(10.0).unwrap();
229        assert!(syn_dep.weight < w0, "Post before pre should depress");
230    }
231
232    #[test]
233    fn test_short_term_plasticity() {
234        let mut syn = Synapse::depressing_excitatory(1.0, 1.0).unwrap();
235
236        let initial_prob = syn.vesicle_pool.release_probability();
237
238        // Rapid spikes cause depression
239        for i in 0..5 {
240            syn.presynaptic_spike(i as f64 * 10.0).unwrap();
241            for _ in 0..10 {
242                syn.update((i as f64 + 0.1) * 10.0, -65.0, 1.0).unwrap();
243            }
244        }
245
246        assert!(syn.vesicle_pool.release_probability() < initial_prob);
247    }
248
249    #[test]
250    fn test_network_connectivity() {
251        let mut net = SynapticNetwork::new(5);
252
253        // Create feedforward network
254        for i in 0..4 {
255            let syn = Synapse::excitatory(1.0, 1.0).unwrap();
256            net.add_connection(i, i + 1, syn).unwrap();
257        }
258
259        let stats = net.connectivity_stats();
260        assert_eq!(stats.n_connections, 4);
261        assert_eq!(stats.n_neurons, 5);
262    }
263
264    #[test]
265    fn test_excitatory_inhibitory_balance() {
266        let mut net = SynapticNetwork::new(3);
267
268        // Excitatory connection
269        let exc = Synapse::excitatory(1.0, 1.0).unwrap();
270        net.add_connection(0, 2, exc).unwrap();
271
272        // Inhibitory connection
273        let inh = Synapse::inhibitory(1.0, 1.0).unwrap();
274        net.add_connection(1, 2, inh).unwrap();
275
276        // Both spike
277        net.spike(0).unwrap();
278        net.spike(1).unwrap();
279
280        // Update
281        let voltages = vec![-65.0; 3];
282        for _ in 0..30 {
283            net.update(&voltages, 0.1).unwrap();
284        }
285
286        // Should have both excitatory and inhibitory currents
287        let inputs = net.get_inputs(2);
288        assert_eq!(inputs.len(), 2);
289    }
290
291    #[test]
292    fn test_calcium_dynamics() {
293        let mut syn = Synapse::excitatory(1.0, 1.0).unwrap();
294
295        let initial_ca = syn.postsynaptic_calcium.get_concentration();
296
297        // Spike causes calcium elevation
298        syn.postsynaptic_spike(0.0).unwrap();
299
300        assert!(syn.postsynaptic_calcium.get_concentration() > initial_ca);
301
302        // Calcium should decay
303        for _ in 0..100 {
304            syn.update(1.0, -65.0, 0.1).unwrap();
305        }
306
307        // Should return toward baseline (but may not reach due to other dynamics)
308        let final_ca = syn.postsynaptic_calcium.get_concentration();
309        assert!(final_ca <= syn.postsynaptic_calcium.get_concentration());
310    }
311
312    #[test]
313    fn test_nmda_voltage_dependence() {
314        use receptor::NMDAReceptor;
315
316        let nmda = NMDAReceptor::new();
317
318        let block_neg = nmda.mg_block(-70.0);
319        let block_zero = nmda.mg_block(0.0);
320        let block_pos = nmda.mg_block(40.0);
321
322        // Block should decrease with depolarization
323        assert!(block_neg < block_zero);
324        assert!(block_zero < block_pos);
325    }
326
327    #[test]
328    fn test_receptor_kinetics() {
329        use receptor::{AMPAReceptor, ReceptorDynamics};
330
331        let mut ampa = AMPAReceptor::new();
332
333        // Apply neurotransmitter
334        for _ in 0..50 {
335            ampa.update(1.0, -65.0, 0.1).unwrap();
336        }
337
338        let peak = ampa.get_conductance();
339        assert!(peak > 0.0);
340
341        // Remove neurotransmitter
342        for _ in 0..100 {
343            ampa.update(0.0, -65.0, 0.1).unwrap();
344        }
345
346        // Should decay
347        assert!(ampa.get_conductance() < peak);
348    }
349}