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}