OSCR

NMDA receptor kinetics drive distinct routes to chaotic firing in pyramidal neurons.

Code ↔ Paper

26 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 26 matches
  1. [1] § Materials and methods › Data analysis pipeline › Frequency band classification ↔ v_StimW_Beta_5_6_1D.py, lines 399–414 · score 0.96 · 7–33 ms, 33–77 ms, 77–125 ms, 125–250 ms, 250–2000 ms, 0.5–4 Hz
  2. [2] § Materials and methods › Data analysis pipeline › Frequency band classification ↔ v_StimW_Beta_5_6_1D_CaMKII_6_5_GABA_4_Complete_6.py, lines 516–531 · score 0.96 · 7–33 ms, 33–77 ms, 77–125 ms, 125–250 ms, 250–2000 ms, 0.5–4 Hz
  3. [3] § Materials and methods › Initial conditions robustness ↔ Sensitivity.py, lines 1–19 · score 0.91 · phase plane trajectories, mAMPA, mGABA, mNMDA, Steady state, synaptic gating
  4. [4] § Results › Differential excitatory and inhibitory neuron responses ↔ reviewer_response_simulations_final.py, lines 635–778 · score 0.82 · Conductance tuning, balance heatmaps, Glutamatergic scaling, inhibitory neuron, firing rate, GABAergic
  5. [5] § Results › Oscillatory band evolution and spectral consolidation ↔ v4_6_8_CaMKII_3.py, lines 90–144 · score 0.79 · neural oscillations, 0.5–4 Hz, 13–30 Hz, 8–13 Hz, 30–100 Hz, Frequency band
  6. [6] § Results › Oscillatory band evolution and spectral consolidation ↔ v4_6.py, lines 636–666 · score 0.78 · 0.5–4 Hz, 13–30 Hz, 8–13 Hz, 30–100 Hz, Frequency band, 4–8 Hz
  7. [7] § Results › Oscillatory band evolution and spectral consolidation ↔ v4_6_8_CaMKII_3.py, lines 90–144 · score 0.76 · 0.5–4 Hz, 13–30 Hz, 8–13 Hz, 30–100 Hz, Frequency band, 4–8 Hz
  8. [8] § Results › Oscillatory band evolution and spectral consolidation ↔ v4_6.py, lines 636–666 · score 0.74 · 0.5–4 Hz, 13–30 Hz, 8–13 Hz, 30–100 Hz, Frequency band, 4–8 Hz
  9. [9] § Materials and methods › Data analysis pipeline › Dynamical analysis measures › Maximum Lyapunov exponents ↔ v_StimW_Beta_5_6_1D.py, lines 141–175 · score 0.72 · nearest neighbor, KD tree, Lyapunov exponent, efficient, computationally
  10. [10] § Results ↔ v4_6_8_CaMKII_sub_21.py, lines 569–640 · score 0.70 · GABA mediated modulation, state transitions, Parameter space, plasticity region, E1, GABA frequencies
  11. [11] § Materials and methods › Data analysis pipeline › Bifurcation diagram construction ↔ gaba_bifurcation.py, lines 185–232 · score 0.67 · inter spike intervals, chaotic regions, branches, background, scatter, Bifurcation
  12. [12] § Materials and methods › Data analysis pipeline › Bifurcation diagram construction ↔ v_StimW_Beta_5_6_1D.py, lines 141–175 · score 0.65 · nearest neighbor, KD tree, Lyapunov exponent, simulations
  13. [13] § Results › Bifurcation analysis reveals period-doubling routes to chaos ↔ v4_6.py, lines 745–852 · score 0.63 · bifurcation diagram, frequency bands, Lyapunov exponent, chaotic regions, chaos, alpha
  14. [14] § Materials and methods › Data analysis pipeline › Dynamical analysis measures › Maximum Lyapunov exponents ↔ v4_6.py, lines 745–852 · score 0.61 · chaotic dynamics, Lyapunov exponent, bifurcation diagrams, chaos, ISI
  15. [15] § Materials and methods › Neuronal model overview ↔ v_StimW_Beta_5_6_1D_CaMKII_6_5_GABA_4_Complete_6.py, lines 286–331 · score 0.60 · isAHP, GABAergic, IKdr, IL, INaP, ICa
  16. [16] § Results › Differential excitatory and inhibitory neuron responses ↔ reviewer_response_simulations_final.py, lines 1–36 · score 0.60 · excitatory neuron, inhibitory neuron, GABAergic, sensitivity, glutamate, stimulation
  17. [17] § Materials and methods › Neuronal model overview ↔ Code1.py, lines 540–580 · score 0.59 · isAHP, GABAergic, IKdr, IL, INaP, ICa
  18. [18] § Results › GABA modulates CaMKII-mediated plasticity states ↔ v4_6_8_CaMKII_3.py, lines 456–521 · score 0.58 · phosphorylation states, dependent plasticity, GABA frequency, classified, CaMKII, space
  19. [19] § Results › GABAergic inhibition provides frequency-selective stabilization ↔ v4_6_8_CaMKII_3.py, lines 315–342 · score 0.58 · moderate inhibitory, low GABA frequencies, 20 ms, modulated, NMDA
  20. [20] § Results › Differential excitatory and inhibitory neuron responses ↔ reviewer_response_simulations_final.py, lines 781–819 · score 0.57 · fast spiking, inhibitory neurons, neuron responses, glutamate, GABA, NMDA
  21. [21] § Results › Model validation and basic neuronal response ↔ Code1.py, lines 582–656 · score 0.57 · spike detection, post transient, membrane potential, CaMKII, models, AMPA
  22. [22] § Results › Bifurcation analysis reveals period-doubling routes to chaos ↔ gaba_bifurcation.py, lines 131–183 · score 0.55 · bifurcation diagram, Lyapunov exponent, chaotic regions, doubled, scatter, intervals
  23. [23] § Results › Model validation and basic neuronal response ↔ v4_6_8_CaMKII_3.py, lines 736–779 · score 0.54 · transition zone, LTP thresholds, LTD, modulation, plasticity, calcium
  24. [24] § Materials and methods › Synaptic currents › NMDA receptors ↔ reviewer_response_simulations_final.py, lines 124–168 · score 0.53 · decay rate, NMDA receptor, intrinsic, neurons, synaptic
  25. [25] § Materials and methods › Data analysis pipeline › Statistical analysis and validation ↔ reviewer_response_simulations_final.py, lines 1–36 · score 0.53 · response curves, parameter sweeps, heatmaps, simulated
  26. [26] § Materials and methods › Data analysis pipeline › Bifurcation diagram construction ↔ gaba_bifurcation.py, lines 185–232 · score 0.50 · inter spike interval, chaotic regions, bifurcation, chaos

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,127 lines · 48 KB · MIT · 5 matches

  1. # -*- coding: utf-8 -*-
  2. """
  3. Excitatory/Inhibitory Neuron Simulations with Parameter Sensitivity
  4. ====================================================================
  5. Supplementary analyses:
  6. 1) Excitatory vs inhibitory neuron responses under different synaptic
  7. conditions (glutamate-only, GABA-only, combined)
  8. 2) β_NMDA parameter sweep across neuron types
  9. 3) Conductance tuning of GABAergic and glutamatergic stimulation
  10. 4) β_AMPA (AMPA receptor closing rate) sensitivity analysis
  11. Figures produced:
  12. Fig 1 — Synaptic input pulse profiles (glutamate, GABA, combined)
  13. Fig 2 — Three stimulation conditions: excitatory neuron
  14. Fig 3 — Excitatory vs inhibitory neuron comparison under combined input
  15. Fig 4 — β_NMDA parameter sweep: firing rate, CaMKII, E/I balance
  16. Fig 5 — Conductance tuning: dose-response curves + E/I balance heatmaps
  17. Fig 6 — Three stimulation conditions: inhibitory neuron
  18. Fig 7 — β_NMDA × neuron type × input condition interaction matrix
  19. Fig 8 — β_AMPA sensitivity: EPSC waveforms, firing rate, CaMKII,
  20. E/I ratio, and β_AMPA × β_NMDA interaction heatmaps
  21. Core simulation model: UNCHANGED from original manuscript.
  22. @author: Mehdi Borjkhani
  23. """
  24. import numpy as np
  25. import matplotlib.pyplot as plt
  26. import matplotlib.gridspec as gridspec
  27. from matplotlib.lines import Line2D
  28. import os
  29. import warnings
  30. warnings.filterwarnings('ignore')
  31. # == Publication-quality settings =============================================
  32. plt.rcParams.update({
  33. 'font.size': 11,
  34. 'axes.titlesize': 12,
  35. 'axes.labelsize': 11,
  36. 'xtick.labelsize': 9,
  37. 'ytick.labelsize': 9,
  38. 'legend.fontsize': 9,
  39. 'font.family': 'serif',
  40. 'font.serif': ['Times New Roman', 'DejaVu Serif', 'serif'],
  41. 'mathtext.fontset': 'dejavuserif',
  42. 'axes.linewidth': 1.0,
  43. 'axes.spines.top': False,
  44. 'axes.spines.right': False,
  45. 'lines.linewidth': 1.5,
  46. 'legend.frameon': False,
  47. 'axes.grid': False,
  48. 'savefig.dpi': 300,
  49. 'savefig.bbox': 'tight',
  50. })
  51. save_directory = os.path.join(os.getcwd(), "supplementary_figures")
  52. os.makedirs(save_directory, exist_ok=True)
  53. # =============================================================================
  54. # MODEL PARAMETERS (Same as original manuscript)
  55. # =============================================================================
  56. # Stimulation parameters
  57. FreqS = 10; StimW = 1000 / FreqS; App = 1; Puls = 5
  58. FreqS_GABA = 10; StimW_GABA = 1000 / FreqS_GABA; App_GABA = 1; Puls_GABA = 5
  59. # NMDA/AMPA/GABA synapse parameters
  60. alpha_nmda = 0.38
  61. alpha_ampa = 1.1; beta_ampa = 0.67; k_ampa = 1; g_ampa = 0.35
  62. k_nmda = 1
  63. g_gaba = 0.35; alpha_gaba = 5; beta_gaba = 0.18; k_gaba = 1
  64. # Simulation time
  65. Tmax = 8.0e3 # 8 seconds (allows CaMKII to equilibrate)
  66. deltaT = 0.05
  67. tn = int(Tmax / deltaT)
  68. t = np.arange(0, Tmax + deltaT, deltaT)
  69. # Reversal potentials
  70. vna = 55; vk = -90; vl = -70; vca = 120; E_gaba = -80
  71. # Conductance densities (excitatory pyramidal neuron defaults)
  72. gna_exc = 50; gnap_exc = 0.2; gl_exc = 0.1; gkdr_exc = 10
  73. gA_exc = 1; gM_exc = 0.5; gca_exc = 0.1; gc_exc = 5; gsAHP_exc = 2
  74. # Gating parameters
  75. theta_m, tau_m = -35, 8
  76. theta_h, tau_h = -50, -6
  77. theta_n, tau_n = -40, 12
  78. theta_a, tau_a = -55, 15
  79. theta_z, tau_z = -45, 4
  80. theta_p, tau_p = -40, 2
  81. theta_ht, theta_nt = -45, -30
  82. theta_b, tau_b = -85, -5
  83. theta_r, tau_r = -25, 8
  84. theta_c, tau_c = -35, 5
  85. # CaMKII parameters
  86. k_I = 0.001; nh1 = 3; vCaN = 2.0e-3; kM = 20
  87. kh1 = 4.0; kh2 = 0.7; vPKA = 0.45e-3; kpk = 0.0059
  88. ck1 = 0.5e-3; ck2 = 10e-3; ck3 = 1e-3; ck4 = 1e-6
  89. # --- Inhibitory interneuron parameter rationale ---
  90. # Fast-spiking (FS) interneurons differ from pyramidal neurons in:
  91. # - Higher g_Kdr: faster repolarisation -> narrower action potentials
  92. # (cf. Erisir et al., 1999, J Neurophysiol)
  93. # - Reduced g_M, g_sAHP: minimal spike-frequency adaptation
  94. # (cf. Bhatt et al., 2019, Front Cell Neurosci)
  95. # - Lower g_NaP: reduced persistent Na+ current
  96. # - Lower g_Ca, g_C: weaker Ca2+-dependent K+ coupling
  97. # - Slightly higher g_leak: lower input resistance
  98. # - Higher resting potential (~-65 mV vs -70 mV)
  99. # These adjustments produce the characteristic FS phenotype: high-frequency
  100. # non-adapting firing with narrow spikes.
  101. INHIBITORY_PARAMS = {
  102. 'gna': 55, 'gnap': 0.05, 'gl': 0.15, 'gkdr': 15,
  103. 'gA': 0.3, 'gM': 0.1, 'gca': 0.02, 'gc': 1.0, 'gsAHP': 0.2,
  104. 'v_init': -65,
  105. }
  106. # =============================================================================
  107. # CORE SIMULATION FUNCTION (parameterised -- UNCHANGED from original)
  108. # =============================================================================
  109. def run_neuron_simulation(beta_nmda, neuron_type='excitatory',
  110. enable_glutamate=True, enable_gaba=True,
  111. g_ampa_scale=1.0, g_gaba_scale=1.0,
  112. g_nmda_scale=1.0, beta_ampa_val=None):
  113. """
  114. Run a single neuron simulation under specified conditions.
  115. Parameters
  116. ----------
  117. beta_nmda : float
  118. NMDA receptor decay rate parameter.
  119. neuron_type : str
  120. 'excitatory' or 'inhibitory'. Adjusts intrinsic conductances.
  121. enable_glutamate : bool
  122. Enable glutamatergic (AMPA + NMDA) input.
  123. enable_gaba : bool
  124. Enable GABAergic input.
  125. g_ampa_scale, g_gaba_scale, g_nmda_scale : float
  126. Scaling factors for tuning synaptic conductances.
  127. beta_ampa_val : float or None
  128. AMPA receptor closing (unbinding) rate. If None, uses global beta_ampa.
  129. Returns
  130. -------
  131. dict : All recorded traces and metadata.
  132. """
  133. # -- Adjust intrinsic properties by neuron type --
  134. if neuron_type == 'inhibitory':
  135. gna = INHIBITORY_PARAMS['gna']
  136. gnap = INHIBITORY_PARAMS['gnap']
  137. gl = INHIBITORY_PARAMS['gl']
  138. gkdr = INHIBITORY_PARAMS['gkdr']
  139. gA = INHIBITORY_PARAMS['gA']
  140. gM = INHIBITORY_PARAMS['gM']
  141. gca = INHIBITORY_PARAMS['gca']
  142. gc = INHIBITORY_PARAMS['gc']
  143. gsAHP = INHIBITORY_PARAMS['gsAHP']
  144. v_init = INHIBITORY_PARAMS['v_init']
  145. else:
  146. gna = gna_exc; gnap = gnap_exc; gl = gl_exc; gkdr = gkdr_exc
  147. gA = gA_exc; gM = gM_exc; gca = gca_exc; gc = gc_exc; gsAHP = gsAHP_exc
  148. v_init = -70
  149. # Scale synaptic conductances
  150. g_ampa_eff = g_ampa * g_ampa_scale
  151. g_gaba_eff = g_gaba * g_gaba_scale
  152. beta_ampa_eff = beta_ampa_val if beta_ampa_val is not None else beta_ampa
  153. # -- Allocate arrays --
  154. temp = np.zeros(tn + 1)
  155. v = temp.copy(); v[0] = v_init
  156. h = temp.copy(); h[0] = 0.98
  157. n = temp.copy(); n[0] = 0.01
  158. b = temp.copy(); b[0] = 0
  159. z = temp.copy(); z[0] = 0.050
  160. r = temp.copy()
  161. c = temp.copy()
  162. q = temp.copy()
  163. Ca = temp.copy()
  164. m_ampa = temp.copy()
  165. m_nmda = temp.copy()
  166. g_VDpost = temp.copy()
  167. I_ampa = temp.copy()
  168. I_nmda = temp.copy()
  169. m_gaba = temp.copy()
  170. I_gaba = temp.copy()
  171. Iapp = np.zeros(tn)
  172. Iapp_GABA = np.zeros(tn)
  173. # CaMKII
  174. CaMKII_p = np.zeros(tn + 1)
  175. cam = np.zeros(tn + 1)
  176. ep = np.zeros(tn + 1); ep[0] = 1.0
  177. I_camkii = np.zeros(tn + 1)
  178. P0 = 1.0
  179. P1 = P2 = P3 = P4 = P5 = P6 = P7 = P8 = P9 = P10 = 0.0
  180. # -- Integration loop --
  181. for i in range(tn):
  182. # Stimulus pulses
  183. if enable_glutamate and (0 <= (t[i] % StimW) <= Puls):
  184. Iapp[i] = App
  185. if enable_gaba and (0 <= (t[i] % StimW_GABA) <= Puls_GABA):
  186. Iapp_GABA[i] = App_GABA
  187. # Gating steady-states
  188. m_inf = 1 / (1 + np.exp(-(v[i] - theta_m) / tau_m))
  189. h_inf = 1 / (1 + np.exp(-(v[i] - theta_h) / tau_h))
  190. n_inf = 1 / (1 + np.exp(-(v[i] - theta_n) / tau_n))
  191. a_inf = 1 / (1 + np.exp(-(v[i] - theta_a) / tau_a))
  192. b_inf = 1 / (1 + np.exp(-(v[i] - theta_b) / tau_b))
  193. z_inf = 1 / (1 + np.exp(-(v[i] - theta_z) / tau_z))
  194. p_inf = 1 / (1 + np.exp(-(v[i] - theta_p) / tau_p))
  195. r_inf = 1 / (1 + np.exp(-(v[i] - theta_r) / tau_r))
  196. tau_ht_mod = 0.1 + 0.75 / (1 + np.exp(-(v[i] - theta_ht) / (-6)))
  197. tau_nt_mod = 0.1 + 0.5 / (1 + np.exp(-(v[i] - theta_nt) / (-15)))
  198. d_inf = 1 / (1 + 6 / (Ca[i] + 1e-6))
  199. c_inf = 1 / (1 + np.exp(-(v[i] - theta_c) / tau_c))
  200. q_inf = 1 / (1 + (2 / (Ca[i] + 1e-6))**4)
  201. # Gating variable updates
  202. h[i+1] = h[i] + deltaT * ((h_inf - h[i]) / tau_ht_mod)
  203. n[i+1] = n[i] + deltaT * ((n_inf - n[i]) / tau_nt_mod)
  204. b[i+1] = b[i] + deltaT * ((b_inf - b[i]) / 15)
  205. z[i+1] = z[i] + deltaT * ((z_inf - z[i]) / 75)
  206. r[i+1] = r[i] + deltaT * ((r_inf - r[i]) / 1)
  207. c[i+1] = c[i] + deltaT * ((c_inf - c[i]) / 2)
  208. q[i+1] = q[i] + deltaT * ((q_inf - q[i]) / 450)
  209. # Ionic currents
  210. ina = gna * m_inf**3 * h[i] * (v[i] - vna)
  211. inap = gnap * p_inf * (v[i] - vna)
  212. ikdr = gkdr * n[i]**4 * (v[i] - vk)
  213. iA = gA * a_inf**3 * b[i] * (v[i] - vk)
  214. iM = gM * z[i] * (v[i] - vk)
  215. il = gl * (v[i] - vl)
  216. ica = gca * r[i]**2 * (v[i] - vca)
  217. ic = gc * d_inf * c[i] * (v[i] - vk)
  218. isAHP = gsAHP * q[i] * (v[i] - vk)
  219. # Synaptic currents
  220. G_syn = Iapp[i]
  221. m_ampa[i+1] = m_ampa[i] + deltaT * (alpha_ampa * G_syn * (1 - m_ampa[i]) - beta_ampa_eff * m_ampa[i])
  222. I_ampa[i] = k_ampa * g_ampa_eff * m_ampa[i] * (v[i] - 55)
  223. m_nmda[i+1] = m_nmda[i] + deltaT * (alpha_nmda * G_syn * (1 - m_nmda[i]) - beta_nmda * m_nmda[i])
  224. g_conpost = g_VDpost[i] + 1
  225. g_VDinfpost = 0.0007 * (v[i] - (-100))
  226. g_VDpost[i+1] = g_VDpost[i] + deltaT * ((g_VDinfpost - g_VDpost[i]) / 0.05)
  227. MG = 1 / (1 + (1.4 / 3.75) * np.exp(-0.062 * v[i]))
  228. I_nmda[i] = k_nmda * g_nmda_scale * g_conpost * MG * m_nmda[i] * (v[i] - 55)
  229. G_syn_gaba = Iapp_GABA[i]
  230. m_gaba[i+1] = m_gaba[i] + deltaT * (alpha_gaba * G_syn_gaba * (1 - m_gaba[i]) - beta_gaba * m_gaba[i])
  231. I_gaba[i] = k_gaba * g_gaba_eff * m_gaba[i] * (v[i] - E_gaba)
  232. # Calcium update
  233. Ca[i+1] = Ca[i] + deltaT * (-0.13 * ica - 0.0012 * I_ampa[i] - 0.012 * I_nmda[i] - Ca[i] / 13)
  234. # CaMKII
  235. Ca_uM = Ca[i] * 1
  236. num_cv1 = 10 * ck1 * P0 * (Ca_uM / kh1) ** (2 * nh1)
  237. den_cv1 = (1 + (Ca_uM / kh1) ** nh1) ** 2
  238. cv1 = num_cv1 / den_cv1
  239. cv2 = ck1 * (Ca_uM / kh1) ** nh1 / (1 + (Ca_uM / kh1) ** nh1)
  240. sum_k_Pk = P1 + 2*P2 + 3*P3 + 4*P4 + 5*P5 + 6*P6 + 7*P7 + 8*P8 + 9*P9 + 10*P10
  241. cv3 = ck2 * ep[i] / (kM + sum_k_Pk)
  242. P0 += deltaT * (-cv1 + cv3 * P1)
  243. P1 += deltaT * (cv1 - cv3*P1 - cv2*P1 + 2*cv3*P2)
  244. P2 += deltaT * (cv2*P1 - 2*cv3*P2 - 1.8*cv2*P2 + 3*cv3*P3)
  245. P3 += deltaT * (1.8*cv2*P2 - 3*cv3*P3 - 2.3*cv2*P3 + 4*cv3*P4)
  246. P4 += deltaT * (2.3*cv2*P3 - 4*cv3*P4 - 2.7*cv2*P4 + 5*cv3*P5)
  247. P5 += deltaT * (2.7*cv2*P4 - 5*cv3*P5 - 2.8*cv2*P5 + 6*cv3*P6)
  248. P6 += deltaT * (2.8*cv2*P5 - 6*cv3*P6 - 2.7*cv2*P6 + 7*cv3*P7)
  249. P7 += deltaT * (2.7*cv2*P6 - 7*cv3*P7 - 2.3*cv2*P7 + 8*cv3*P8)
  250. P8 += deltaT * (2.3*cv2*P7 - 8*cv3*P8 - 1.8*cv2*P8 + 9*cv3*P9)
  251. P9 += deltaT * (1.8*cv2*P8 - 9*cv3*P9 - cv2*P9 + 10*cv3*P10)
  252. P10 += deltaT * (cv2*P9 - 10*cv3*P10)
  253. cam[i+1] = P1+P2+P3+P4+P5+P6+P7+P8+P9+P10
  254. total_P = P0+P1+P2+P3+P4+P5+P6+P7+P8+P9+P10
  255. CaMKII_p[i+1] = cam[i+1] / total_P
  256. ep[i+1] = ep[i] + deltaT * (-ck3*I_camkii[i]*ep[i] + ck4*(ep[0]-ep[i]) + k_I*1.0)
  257. I_camkii[i+1] = I_camkii[i] + deltaT * (
  258. -ck3*I_camkii[i]*ep[i] + ck4*(ep[0]-ep[i]) +
  259. vPKA*(1.0/(kpk+1.0)) -
  260. (vCaN*I_camkii[i]*(Ca_uM/kh2)**3 / (1+(Ca_uM/kh2)**3))
  261. )
  262. I_syn = I_ampa[i] + I_nmda[i] + I_gaba[i]
  263. v[i+1] = v[i] + deltaT * (-(ina + inap + ikdr + iA + iM + il + ica + ic + isAHP + I_syn))
  264. # Spike detection
  265. spike_indices = np.where((v[:-1] < 0) & (v[1:] >= 0))[0]
  266. spike_times = t[spike_indices]
  267. firing_rate = len(spike_times) / (Tmax / 1000) if Tmax > 0 else 0
  268. return {
  269. 't': t, 'v': v, 'Ca': Ca, 'CaMKII_p': CaMKII_p,
  270. 'Iapp': Iapp, 'Iapp_GABA': Iapp_GABA,
  271. 'I_ampa': I_ampa, 'I_nmda': I_nmda, 'I_gaba': I_gaba,
  272. 'm_ampa': m_ampa, 'm_nmda': m_nmda, 'm_gaba': m_gaba,
  273. 'spike_times': spike_times, 'firing_rate': firing_rate,
  274. 'neuron_type': neuron_type, 'beta_nmda': beta_nmda,
  275. 'enable_glutamate': enable_glutamate, 'enable_gaba': enable_gaba,
  276. }
  277. # =============================================================================
  278. # ANALYSIS HELPERS
  279. # =============================================================================
  280. def count_spikes(v_trace, t_array, t_start=2000):
  281. """Count spikes after t_start ms to exclude transients."""
  282. idx_start = int(t_start / deltaT)
  283. v_seg = v_trace[idx_start:]
  284. spikes = np.where((v_seg[:-1] < 0) & (v_seg[1:] >= 0))[0]
  285. duration_s = (t_array[-1] - t_start) / 1000
  286. return len(spikes), len(spikes) / duration_s if duration_s > 0 else 0
  287. def compute_ei_ratio(res, t_start=2000):
  288. """
  289. Compute excitation/inhibition (E/I) current ratio in steady state.
  290. E/I ratio = |mean(I_AMPA + I_NMDA)| / |mean(I_GABA)|
  291. Returns np.inf if GABA current is zero.
  292. """
  293. idx_ss = int(t_start / deltaT)
  294. I_exc = np.abs(np.mean(res['I_ampa'][idx_ss:] + res['I_nmda'][idx_ss:]))
  295. I_inh = np.abs(np.mean(res['I_gaba'][idx_ss:]))
  296. if I_inh < 1e-12:
  297. return np.inf if I_exc > 1e-12 else 1.0
  298. return I_exc / I_inh
  299. def compute_mean_currents(res, t_start=2000):
  300. """Compute mean absolute synaptic currents in steady state."""
  301. idx_ss = int(t_start / deltaT)
  302. return {
  303. 'I_ampa': np.mean(np.abs(res['I_ampa'][idx_ss:])),
  304. 'I_nmda': np.mean(np.abs(res['I_nmda'][idx_ss:])),
  305. 'I_gaba': np.mean(np.abs(res['I_gaba'][idx_ss:])),
  306. 'I_exc_total': np.mean(np.abs(res['I_ampa'][idx_ss:] + res['I_nmda'][idx_ss:])),
  307. }
  308. def save_fig(fig, name):
  309. """Save figure in both PNG and PDF format."""
  310. fig.savefig(os.path.join(save_directory, f'{name}.png'), dpi=300)
  311. fig.savefig(os.path.join(save_directory, f'{name}.pdf'), dpi=300)
  312. plt.close(fig)
  313. # =============================================================================
  314. # FIGURE 1: PULSE PROFILES OF GABA AND GLUTAMATE
  315. # =============================================================================
  316. def figure1_pulse_profiles():
  317. """Show the synaptic input pulse profiles clearly."""
  318. print(" Figure 1: Pulse profiles ...")
  319. t_short = np.arange(0, 300 + deltaT, deltaT)
  320. glu_pulse = np.array([App if 0 <= (ti % StimW) <= Puls else 0 for ti in t_short])
  321. gaba_pulse = np.array([App_GABA if 0 <= (ti % StimW_GABA) <= Puls_GABA else 0 for ti in t_short])
  322. fig, axes = plt.subplots(3, 1, figsize=(7, 5.5), sharex=True)
  323. # Glutamate
  324. axes[0].fill_between(t_short, 0, glu_pulse, color='#2E86C1', alpha=0.35, step='mid')
  325. axes[0].step(t_short, glu_pulse, color='#2E86C1', linewidth=1.8, where='mid')
  326. axes[0].set_ylabel('Glutamate\nInput (a.u.)')
  327. axes[0].set_title('(A) Glutamatergic Pulse Profile', loc='left', fontweight='bold')
  328. axes[0].set_ylim(-0.1, 1.5)
  329. axes[0].text(0.75, 0.8, f'Freq = {FreqS} Hz\nWidth = {Puls} ms',
  330. transform=axes[0].transAxes, fontsize=9,
  331. bbox=dict(boxstyle='round', fc='white', alpha=0.8))
  332. # GABA
  333. axes[1].fill_between(t_short, 0, gaba_pulse, color='#E74C3C', alpha=0.35, step='mid')
  334. axes[1].step(t_short, gaba_pulse, color='#E74C3C', linewidth=1.8, where='mid')
  335. axes[1].set_ylabel('GABA\nInput (a.u.)')
  336. axes[1].set_title('(B) GABAergic Pulse Profile', loc='left', fontweight='bold')
  337. axes[1].set_ylim(-0.1, 1.5)
  338. axes[1].text(0.75, 0.8, f'Freq = {FreqS_GABA} Hz\nWidth = {Puls_GABA} ms',
  339. transform=axes[1].transAxes, fontsize=9,
  340. bbox=dict(boxstyle='round', fc='white', alpha=0.8))
  341. # Combined
  342. axes[2].fill_between(t_short, 0, glu_pulse, color='#2E86C1', alpha=0.3, step='mid', label='Glutamate')
  343. axes[2].fill_between(t_short, 0, gaba_pulse, color='#E74C3C', alpha=0.3, step='mid', label='GABA')
  344. axes[2].step(t_short, glu_pulse, color='#2E86C1', linewidth=1.5, where='mid')
  345. axes[2].step(t_short, gaba_pulse, color='#E74C3C', linewidth=1.5, where='mid', linestyle='--')
  346. axes[2].set_ylabel('Input (a.u.)')
  347. axes[2].set_xlabel('Time (ms)')
  348. axes[2].set_title('(C) Combined Input Profile', loc='left', fontweight='bold')
  349. axes[2].set_ylim(-0.1, 1.5)
  350. axes[2].legend(loc='upper right', fontsize=9)
  351. fig.tight_layout()
  352. save_fig(fig, 'Fig1_pulse_profiles')
  353. # =============================================================================
  354. # FIGURE 2: GLUTAMATE-ONLY vs GABA-ONLY vs COMBINED (Excitatory)
  355. # =============================================================================
  356. def figure2_three_conditions(beta_nmda_val=0.03):
  357. """
  358. Separate panels showing excitatory neuron under three conditions:
  359. (i) Glutamate only, (ii) GABA only, (iii) Combined.
  360. """
  361. print(" Figure 2: Three stimulation conditions (excitatory neuron) ...")
  362. res_glu = run_neuron_simulation(beta_nmda_val, 'excitatory', enable_glutamate=True, enable_gaba=False)
  363. res_gaba = run_neuron_simulation(beta_nmda_val, 'excitatory', enable_glutamate=False, enable_gaba=True)
  364. res_both = run_neuron_simulation(beta_nmda_val, 'excitatory', enable_glutamate=True, enable_gaba=True)
  365. t0, t1 = 7000, 7500
  366. i0, i1 = int(t0/deltaT), int(t1/deltaT)
  367. conditions = [
  368. (res_glu, 'Glutamate Only (AMPA + NMDA)', '#2E86C1'),
  369. (res_gaba, 'GABA Only', '#E74C3C'),
  370. (res_both, 'Combined (Glutamate + GABA)', '#2C3E50'),
  371. ]
  372. fig, axes = plt.subplots(6, 1, figsize=(10, 14), sharex=True)
  373. labels = 'ABCDEF'
  374. for j, (res, title, color) in enumerate(conditions):
  375. ax_v = axes[2*j]
  376. ax_c = axes[2*j + 1]
  377. tt = res['t'][i0:i1]
  378. # Membrane potential
  379. ax_v.plot(tt, res['v'][i0:i1], color=color, linewidth=1.2)
  380. ax_v.axhline(0, color='gray', ls='--', lw=0.7, alpha=0.5)
  381. n_spk, rate = count_spikes(res['v'], res['t'], t_start=t0)
  382. ax_v.set_ylabel('V (mV)')
  383. ax_v.set_title(f'({labels[2*j]}) {title} | Firing rate: {rate:.1f} Hz',
  384. loc='left', fontweight='bold', fontsize=10)
  385. ax_v.set_ylim(-95, 55)
  386. # Synaptic currents
  387. ax_c.plot(tt, res['I_ampa'][i0:i1], color='#27AE60', lw=1.2, label='$I_{AMPA}$')
  388. ax_c.plot(tt, res['I_nmda'][i0:i1], color='#8E44AD', lw=1.2, label='$I_{NMDA}$')
  389. ax_c.plot(tt, res['I_gaba'][i0:i1], color='#F39C12', lw=1.2, label='$I_{GABA}$')
  390. ax_c.set_ylabel('Current (nA)')
  391. ax_c.set_title(f'({labels[2*j+1]}) Synaptic Currents | {title}',
  392. loc='left', fontsize=10)
  393. ax_c.legend(loc='upper right', ncol=3, fontsize=8)
  394. axes[-1].set_xlabel('Time (ms)')
  395. fig.suptitle(f'Excitatory Neuron Responses Under Different Synaptic Conditions '
  396. r'($\beta_{NMDA}$' + f' = {beta_nmda_val})', fontsize=13, fontweight='bold', y=1.01)
  397. fig.tight_layout()
  398. save_fig(fig, 'Fig2_three_conditions_excitatory')
  399. return res_glu, res_gaba, res_both
  400. # =============================================================================
  401. # FIGURE 3: EXCITATORY vs INHIBITORY NEURON COMPARISON
  402. # =============================================================================
  403. def figure3_exc_vs_inh(beta_nmda_val=0.03):
  404. """
  405. Side-by-side comparison of excitatory and inhibitory neurons
  406. under combined GABA + Glutamate input.
  407. """
  408. print(" Figure 3: Excitatory vs Inhibitory neuron comparison ...")
  409. res_exc = run_neuron_simulation(beta_nmda_val, 'excitatory', True, True)
  410. res_inh = run_neuron_simulation(beta_nmda_val, 'inhibitory', True, True)
  411. t0, t1 = 7000, 7500
  412. i0, i1 = int(t0/deltaT), int(t1/deltaT)
  413. fig, axes = plt.subplots(4, 2, figsize=(12, 10), sharex='col')
  414. for col, (res, ntype, clr) in enumerate([
  415. (res_exc, 'Excitatory (Pyramidal)', '#2E86C1'),
  416. (res_inh, 'Inhibitory (Fast-Spiking)', '#E74C3C')]):
  417. tt = res['t'][i0:i1]
  418. _, rate = count_spikes(res['v'], res['t'], t_start=t0)
  419. axes[0, col].plot(tt, res['v'][i0:i1], color=clr, lw=1.2)
  420. axes[0, col].axhline(0, color='gray', ls='--', lw=0.6, alpha=0.5)
  421. axes[0, col].set_ylabel('V (mV)')
  422. axes[0, col].set_title(f'{ntype}\nFiring rate: {rate:.1f} Hz', fontweight='bold')
  423. axes[1, col].plot(tt, res['I_ampa'][i0:i1], '#27AE60', lw=1, label='AMPA')
  424. axes[1, col].plot(tt, res['I_nmda'][i0:i1], '#8E44AD', lw=1, label='NMDA')
  425. axes[1, col].plot(tt, res['I_gaba'][i0:i1], '#F39C12', lw=1, label='GABA')
  426. axes[1, col].set_ylabel('I$_{syn}$ (nA)')
  427. axes[1, col].legend(fontsize=7, ncol=3)
  428. axes[2, col].plot(tt, res['Ca'][i0:i1], color='#16A085', lw=1.2)
  429. axes[2, col].set_ylabel('[Ca$^{2+}$]$_i$ (a.u.)')
  430. axes[3, col].plot(tt, res['CaMKII_p'][i0:i1], color='black', lw=1.2)
  431. axes[3, col].set_ylabel('CaMKII$_p$')
  432. axes[3, col].set_xlabel('Time (ms)')
  433. fig.suptitle(f'Excitatory vs Inhibitory Neuron | Combined Input '
  434. r'($\beta_{NMDA}$' + f' = {beta_nmda_val})',
  435. fontsize=13, fontweight='bold')
  436. fig.tight_layout()
  437. save_fig(fig, 'Fig3_exc_vs_inh')
  438. # =============================================================================
  439. # FIGURE 4: NMDA beta PARAMETER SWEEP (ENHANCED)
  440. # =============================================================================
  441. def figure4_nmda_parameter_sweep():
  442. """
  443. Sweep beta_NMDA over a wider range and show:
  444. (A) Firing rate vs beta_NMDA for both neuron types
  445. (B) CaMKII phosphorylation vs beta_NMDA
  446. (C) E/I current ratio vs beta_NMDA
  447. (D-F) Representative voltage traces at low, mid, high beta_NMDA
  448. """
  449. print(" Figure 4: NMDA parameter sweep (enhanced) ...")
  450. # Wider range with finer resolution
  451. beta_vals = np.arange(0.005, 0.121, 0.005)
  452. n_beta = len(beta_vals)
  453. rates_exc = np.zeros(n_beta)
  454. rates_inh = np.zeros(n_beta)
  455. camkii_exc = np.zeros(n_beta)
  456. camkii_inh = np.zeros(n_beta)
  457. ei_ratio_exc = np.zeros(n_beta)
  458. ei_ratio_inh = np.zeros(n_beta)
  459. # Store full results at selected beta values for traces
  460. beta_trace_vals = [0.01, 0.04, 0.10]
  461. trace_results = {bv: {} for bv in beta_trace_vals}
  462. idx_ss = int(5000 / deltaT)
  463. for k, bv in enumerate(beta_vals):
  464. print(f" beta_NMDA = {bv:.3f} ({k+1}/{n_beta})")
  465. re = run_neuron_simulation(bv, 'excitatory', True, True)
  466. ri = run_neuron_simulation(bv, 'inhibitory', True, True)
  467. _, rates_exc[k] = count_spikes(re['v'], re['t'])
  468. _, rates_inh[k] = count_spikes(ri['v'], ri['t'])
  469. camkii_exc[k] = np.mean(re['CaMKII_p'][idx_ss:])
  470. camkii_inh[k] = np.mean(ri['CaMKII_p'][idx_ss:])
  471. ei_ratio_exc[k] = compute_ei_ratio(re)
  472. ei_ratio_inh[k] = compute_ei_ratio(ri)
  473. # Save traces at selected values
  474. for btv in beta_trace_vals:
  475. if abs(bv - btv) < 1e-6:
  476. trace_results[btv] = {'exc': re, 'inh': ri}
  477. # -- Create figure: 2 rows x 3 columns --
  478. fig = plt.figure(figsize=(14, 9))
  479. gs = gridspec.GridSpec(2, 3, hspace=0.45, wspace=0.35)
  480. clr_exc = '#2E86C1'
  481. clr_inh = '#E74C3C'
  482. # (A) Firing rate
  483. ax_a = fig.add_subplot(gs[0, 0])
  484. ax_a.plot(beta_vals, rates_exc, 'o-', color=clr_exc, lw=2, ms=4, label='Excitatory')
  485. ax_a.plot(beta_vals, rates_inh, 's--', color=clr_inh, lw=2, ms=4, label='Inhibitory')
  486. ax_a.set_xlabel(r'$\beta_{NMDA}$')
  487. ax_a.set_ylabel('Firing Rate (Hz)')
  488. ax_a.set_title(r'(A) Firing Rate vs $\beta_{NMDA}$', loc='left', fontweight='bold')
  489. ax_a.legend(fontsize=8)
  490. # Mark trace locations
  491. for btv in beta_trace_vals:
  492. ax_a.axvline(btv, color='gray', ls=':', lw=0.8, alpha=0.5)
  493. # (B) CaMKII
  494. ax_b = fig.add_subplot(gs[0, 1])
  495. ax_b.semilogy(beta_vals, camkii_exc, 'o-', color=clr_exc, lw=2, ms=4, label='Excitatory')
  496. ax_b.semilogy(beta_vals, camkii_inh, 's--', color=clr_inh, lw=2, ms=4, label='Inhibitory')
  497. ax_b.set_xlabel(r'$\beta_{NMDA}$')
  498. ax_b.set_ylabel('Mean CaMKII$_p$ (log scale)')
  499. ax_b.set_title('(B) CaMKII Phosphorylation', loc='left', fontweight='bold')
  500. ax_b.legend(fontsize=8)
  501. # (C) E/I ratio
  502. ax_c = fig.add_subplot(gs[0, 2])
  503. # Clip inf values for plotting
  504. ei_exc_plot = np.clip(ei_ratio_exc, 0, 50)
  505. ei_inh_plot = np.clip(ei_ratio_inh, 0, 50)
  506. ax_c.plot(beta_vals, ei_exc_plot, 'o-', color=clr_exc, lw=2, ms=4, label='Excitatory')
  507. ax_c.plot(beta_vals, ei_inh_plot, 's--', color=clr_inh, lw=2, ms=4, label='Inhibitory')
  508. ax_c.axhline(1.0, color='gray', ls='--', lw=1, alpha=0.6, label='E/I = 1')
  509. ax_c.set_xlabel(r'$\beta_{NMDA}$')
  510. ax_c.set_ylabel('E/I Current Ratio')
  511. ax_c.set_title('(C) Excitation/Inhibition Balance', loc='left', fontweight='bold')
  512. ax_c.legend(fontsize=8)
  513. # (D-F) Representative traces
  514. trace_labels = [r'(D) Low $\beta_{NMDA}$', r'(E) Mid $\beta_{NMDA}$', r'(F) High $\beta_{NMDA}$']
  515. t0_tr, t1_tr = 7000, 7400 # 400 ms window
  516. i0_tr = int(t0_tr / deltaT)
  517. i1_tr = int(t1_tr / deltaT)
  518. for j, btv in enumerate(beta_trace_vals):
  519. ax_tr = fig.add_subplot(gs[1, j])
  520. if btv in trace_results and trace_results[btv]:
  521. tt = trace_results[btv]['exc']['t'][i0_tr:i1_tr]
  522. ax_tr.plot(tt, trace_results[btv]['exc']['v'][i0_tr:i1_tr],
  523. color=clr_exc, lw=1.2, label='Exc')
  524. ax_tr.plot(tt, trace_results[btv]['inh']['v'][i0_tr:i1_tr],
  525. color=clr_inh, lw=1.2, alpha=0.8, label='Inh')
  526. ax_tr.axhline(0, color='gray', ls='--', lw=0.5, alpha=0.4)
  527. ax_tr.set_ylabel('V (mV)')
  528. ax_tr.set_xlabel('Time (ms)')
  529. ax_tr.set_title(f'{trace_labels[j]} = {btv}', loc='left', fontweight='bold')
  530. ax_tr.set_ylim(-95, 55)
  531. if j == 0:
  532. ax_tr.legend(fontsize=8)
  533. fig.suptitle(r'Effect of $\beta_{NMDA}$ on Excitatory and Inhibitory Neuron Responses',
  534. fontsize=14, fontweight='bold')
  535. fig.tight_layout(rect=[0, 0, 1, 0.96])
  536. save_fig(fig, 'Fig4_nmda_sweep_enhanced')
  537. # =============================================================================
  538. # FIGURE 5: CONDUCTANCE TUNING -- DOSE-RESPONSE + DUAL HEATMAPS
  539. # =============================================================================
  540. def figure5_conductance_tuning():
  541. """
  542. (A) GABA dose-response (glutamate fixed at 1x) for both cell types
  543. (B) Glutamate dose-response (GABA fixed at 1x) for both cell types
  544. (C) E-I balance heatmap: excitatory neuron
  545. (D) E-I balance heatmap: inhibitory neuron
  546. """
  547. print(" Figure 5: Conductance tuning (enhanced) ...")
  548. beta_val = 0.03
  549. clr_exc = '#2E86C1'
  550. clr_inh = '#E74C3C'
  551. # -- (A) GABA dose-response --
  552. gaba_scales_1d = np.array([0, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0])
  553. rates_gaba_exc = np.zeros(len(gaba_scales_1d))
  554. rates_gaba_inh = np.zeros(len(gaba_scales_1d))
  555. print(" GABA dose-response ...")
  556. for k, gs in enumerate(gaba_scales_1d):
  557. en_gab = gs > 0
  558. re = run_neuron_simulation(beta_val, 'excitatory', True, en_gab,
  559. g_gaba_scale=max(gs, 1e-6))
  560. ri = run_neuron_simulation(beta_val, 'inhibitory', True, en_gab,
  561. g_gaba_scale=max(gs, 1e-6))
  562. _, rates_gaba_exc[k] = count_spikes(re['v'], re['t'])
  563. _, rates_gaba_inh[k] = count_spikes(ri['v'], ri['t'])
  564. # -- (B) Glutamate dose-response --
  565. glu_scales_1d = np.array([0, 0.25, 0.5, 0.75, 1.0, 1.5, 2.0, 3.0, 4.0])
  566. rates_glu_exc = np.zeros(len(glu_scales_1d))
  567. rates_glu_inh = np.zeros(len(glu_scales_1d))
  568. print(" Glutamate dose-response ...")
  569. for k, gs in enumerate(glu_scales_1d):
  570. en_glu = gs > 0
  571. re = run_neuron_simulation(beta_val, 'excitatory', en_glu, True,
  572. g_ampa_scale=max(gs, 1e-6),
  573. g_nmda_scale=max(gs, 1e-6))
  574. ri = run_neuron_simulation(beta_val, 'inhibitory', en_glu, True,
  575. g_ampa_scale=max(gs, 1e-6),
  576. g_nmda_scale=max(gs, 1e-6))
  577. _, rates_glu_exc[k] = count_spikes(re['v'], re['t'])
  578. _, rates_glu_inh[k] = count_spikes(ri['v'], ri['t'])
  579. # -- (C, D) 2D heatmaps for both neuron types --
  580. gaba_scales_2d = np.array([0, 0.5, 1.0, 1.5, 2.0, 3.0])
  581. glu_scales_2d = np.array([0, 0.5, 1.0, 1.5, 2.0, 3.0])
  582. rate_map_exc = np.zeros((len(gaba_scales_2d), len(glu_scales_2d)))
  583. rate_map_inh = np.zeros((len(gaba_scales_2d), len(glu_scales_2d)))
  584. print(" 2D heatmaps ...")
  585. for gi, gs_gaba in enumerate(gaba_scales_2d):
  586. for gj, gs_glu in enumerate(glu_scales_2d):
  587. en_glu = gs_glu > 0
  588. en_gab = gs_gaba > 0
  589. re = run_neuron_simulation(
  590. beta_val, 'excitatory', en_glu, en_gab,
  591. g_ampa_scale=max(gs_glu, 1e-6),
  592. g_gaba_scale=max(gs_gaba, 1e-6),
  593. g_nmda_scale=max(gs_glu, 1e-6))
  594. ri = run_neuron_simulation(
  595. beta_val, 'inhibitory', en_glu, en_gab,
  596. g_ampa_scale=max(gs_glu, 1e-6),
  597. g_gaba_scale=max(gs_gaba, 1e-6),
  598. g_nmda_scale=max(gs_glu, 1e-6))
  599. _, rate_map_exc[gi, gj] = count_spikes(re['v'], re['t'])
  600. _, rate_map_inh[gi, gj] = count_spikes(ri['v'], ri['t'])
  601. # -- Plot --
  602. fig = plt.figure(figsize=(14, 10))
  603. gs_fig = gridspec.GridSpec(2, 2, hspace=0.38, wspace=0.3)
  604. # (A) GABA dose-response
  605. ax_a = fig.add_subplot(gs_fig[0, 0])
  606. ax_a.plot(gaba_scales_1d, rates_gaba_exc, 'o-', color=clr_exc, lw=2, ms=5, label='Excitatory')
  607. ax_a.plot(gaba_scales_1d, rates_gaba_inh, 's--', color=clr_inh, lw=2, ms=5, label='Inhibitory')
  608. ax_a.axvline(1.0, color='gray', ls=':', lw=0.8, alpha=0.6, label='Baseline')
  609. ax_a.set_xlabel('GABAergic Conductance Scale ($g_{GABA}$ / $g_{GABA,0}$)')
  610. ax_a.set_ylabel('Firing Rate (Hz)')
  611. ax_a.set_title('(A) GABA Dose-Response\n(Glutamate fixed at 1$\\times$)',
  612. loc='left', fontweight='bold')
  613. ax_a.legend(fontsize=8)
  614. # (B) Glutamate dose-response
  615. ax_b = fig.add_subplot(gs_fig[0, 1])
  616. ax_b.plot(glu_scales_1d, rates_glu_exc, 'o-', color=clr_exc, lw=2, ms=5, label='Excitatory')
  617. ax_b.plot(glu_scales_1d, rates_glu_inh, 's--', color=clr_inh, lw=2, ms=5, label='Inhibitory')
  618. ax_b.axvline(1.0, color='gray', ls=':', lw=0.8, alpha=0.6, label='Baseline')
  619. ax_b.set_xlabel('Glutamatergic Conductance Scale ($g_{AMPA/NMDA}$ / $g_0$)')
  620. ax_b.set_ylabel('Firing Rate (Hz)')
  621. ax_b.set_title('(B) Glutamate Dose-Response\n(GABA fixed at 1$\\times$)',
  622. loc='left', fontweight='bold')
  623. ax_b.legend(fontsize=8)
  624. # (C) Heatmap -- excitatory
  625. ax_c = fig.add_subplot(gs_fig[1, 0])
  626. vmax = max(rate_map_exc.max(), rate_map_inh.max())
  627. im_c = ax_c.imshow(rate_map_exc, origin='lower', aspect='auto',
  628. extent=[glu_scales_2d[0]-0.25, glu_scales_2d[-1]+0.25,
  629. gaba_scales_2d[0]-0.25, gaba_scales_2d[-1]+0.25],
  630. cmap='RdYlBu_r', interpolation='bilinear',
  631. vmin=0, vmax=vmax)
  632. for gi, gs_gaba in enumerate(gaba_scales_2d):
  633. for gj, gs_glu in enumerate(glu_scales_2d):
  634. val = rate_map_exc[gi, gj]
  635. ax_c.text(gs_glu, gs_gaba, f'{val:.0f}',
  636. ha='center', va='center', fontsize=7,
  637. color='white' if val > vmax*0.5 else 'black')
  638. fig.colorbar(im_c, ax=ax_c, label='Firing Rate (Hz)', shrink=0.85)
  639. ax_c.set_xlabel('Glutamatergic Scale')
  640. ax_c.set_ylabel('GABAergic Scale')
  641. ax_c.set_title('(C) E-I Balance | Excitatory Neuron', loc='left', fontweight='bold')
  642. ax_c.set_xticks(glu_scales_2d)
  643. ax_c.set_yticks(gaba_scales_2d)
  644. # (D) Heatmap -- inhibitory
  645. ax_d = fig.add_subplot(gs_fig[1, 1])
  646. im_d = ax_d.imshow(rate_map_inh, origin='lower', aspect='auto',
  647. extent=[glu_scales_2d[0]-0.25, glu_scales_2d[-1]+0.25,
  648. gaba_scales_2d[0]-0.25, gaba_scales_2d[-1]+0.25],
  649. cmap='RdYlBu_r', interpolation='bilinear',
  650. vmin=0, vmax=vmax)
  651. for gi, gs_gaba in enumerate(gaba_scales_2d):
  652. for gj, gs_glu in enumerate(glu_scales_2d):
  653. val = rate_map_inh[gi, gj]
  654. ax_d.text(gs_glu, gs_gaba, f'{val:.0f}',
  655. ha='center', va='center', fontsize=7,
  656. color='white' if val > vmax*0.5 else 'black')
  657. fig.colorbar(im_d, ax=ax_d, label='Firing Rate (Hz)', shrink=0.85)
  658. ax_d.set_xlabel('Glutamatergic Scale')
  659. ax_d.set_ylabel('GABAergic Scale')
  660. ax_d.set_title('(D) E-I Balance | Inhibitory Neuron', loc='left', fontweight='bold')
  661. ax_d.set_xticks(glu_scales_2d)
  662. ax_d.set_yticks(gaba_scales_2d)
  663. fig.suptitle(r'Tuning GABAergic and Glutamatergic Stimulation ($\beta_{NMDA}$' + f' = {beta_val})',
  664. fontsize=14, fontweight='bold')
  665. fig.tight_layout(rect=[0, 0, 1, 0.96])
  666. save_fig(fig, 'Fig5_conductance_tuning_enhanced')
  667. # =============================================================================
  668. # FIGURE 6: THREE CONDITIONS FOR INHIBITORY NEURON
  669. # =============================================================================
  670. def figure6_three_conditions_inhibitory(beta_nmda_val=0.03):
  671. """Same three-condition comparison but for the inhibitory neuron."""
  672. print(" Figure 6: Three conditions (inhibitory neuron) ...")
  673. res_glu = run_neuron_simulation(beta_nmda_val, 'inhibitory', True, False)
  674. res_gaba = run_neuron_simulation(beta_nmda_val, 'inhibitory', False, True)
  675. res_both = run_neuron_simulation(beta_nmda_val, 'inhibitory', True, True)
  676. t0, t1 = 7000, 7500
  677. i0, i1 = int(t0/deltaT), int(t1/deltaT)
  678. conditions = [
  679. (res_glu, 'Glutamate Only', '#2E86C1'),
  680. (res_gaba, 'GABA Only', '#E74C3C'),
  681. (res_both, 'Combined', '#2C3E50'),
  682. ]
  683. fig, axes = plt.subplots(3, 1, figsize=(10, 7), sharex=True)
  684. for j, (res, title, color) in enumerate(conditions):
  685. tt = res['t'][i0:i1]
  686. ax = axes[j]
  687. ax.plot(tt, res['v'][i0:i1], color=color, lw=1.2)
  688. ax.axhline(0, color='gray', ls='--', lw=0.6, alpha=0.5)
  689. _, rate = count_spikes(res['v'], res['t'], t_start=t0)
  690. ax.set_ylabel('V (mV)')
  691. ax.set_title(f'({chr(65+j)}) Inhibitory Neuron | {title} | '
  692. f'Rate: {rate:.1f} Hz', loc='left', fontweight='bold', fontsize=10)
  693. ax.set_ylim(-95, 55)
  694. axes[-1].set_xlabel('Time (ms)')
  695. fig.suptitle(f'Inhibitory (Fast-Spiking) Neuron Responses '
  696. r'($\beta_{NMDA}$' + f' = {beta_nmda_val})',
  697. fontsize=13, fontweight='bold')
  698. fig.tight_layout()
  699. save_fig(fig, 'Fig6_three_conditions_inhibitory')
  700. # =============================================================================
  701. # FIGURE 7: beta_NMDA x NEURON TYPE x INPUT CONDITION INTERACTION
  702. # =============================================================================
  703. def figure7_interaction_summary():
  704. """
  705. Comprehensive summary: how beta_NMDA modulates firing rate under each
  706. input condition (Glu only, GABA only, Combined) for both neuron types.
  707. """
  708. print(" Figure 7: Interaction summary ...")
  709. beta_vals = np.arange(0.005, 0.101, 0.01)
  710. n_beta = len(beta_vals)
  711. conditions = [
  712. ('Glutamate Only', True, False),
  713. ('GABA Only', False, True),
  714. ('Combined', True, True),
  715. ]
  716. neuron_types = [
  717. ('Excitatory', 'excitatory', '#2E86C1'),
  718. ('Inhibitory', 'inhibitory', '#E74C3C'),
  719. ]
  720. # results[condition_name][neuron_label] = array of firing rates
  721. results = {}
  722. camkii_results = {}
  723. idx_ss = int(5000 / deltaT)
  724. for cond_name, en_glu, en_gab in conditions:
  725. results[cond_name] = {}
  726. camkii_results[cond_name] = {}
  727. for n_label, n_type, _ in neuron_types:
  728. rates = np.zeros(n_beta)
  729. camkii = np.zeros(n_beta)
  730. for k, bv in enumerate(beta_vals):
  731. res = run_neuron_simulation(bv, n_type, en_glu, en_gab)
  732. _, rates[k] = count_spikes(res['v'], res['t'])
  733. camkii[k] = np.mean(res['CaMKII_p'][idx_ss:])
  734. results[cond_name][n_label] = rates
  735. camkii_results[cond_name][n_label] = camkii
  736. # -- Plot: 2 rows x 3 columns --
  737. fig, axes = plt.subplots(2, 3, figsize=(14, 8), sharey='row')
  738. panel_labels = iter('ABCDEF')
  739. for j, (cond_name, _, _) in enumerate(conditions):
  740. ax_rate = axes[0, j]
  741. ax_cam = axes[1, j]
  742. lbl = next(panel_labels)
  743. lbl2 = next(panel_labels)
  744. for n_label, n_type, clr in neuron_types:
  745. ax_rate.plot(beta_vals, results[cond_name][n_label],
  746. 'o-' if n_label == 'Excitatory' else 's--',
  747. color=clr, lw=2, ms=4, label=n_label)
  748. # Guard against zero/negative values for log scale
  749. # Clamp floor at 1e-10 (values below are numerical zero)
  750. cam_data = camkii_results[cond_name][n_label]
  751. cam_data_safe = np.clip(cam_data, 1e-10, None)
  752. ax_cam.semilogy(beta_vals, cam_data_safe,
  753. 'o-' if n_label == 'Excitatory' else 's--',
  754. color=clr, lw=2, ms=4, label=n_label)
  755. ax_rate.set_title(f'({lbl}) {cond_name}', loc='left', fontweight='bold')
  756. ax_rate.set_xlabel(r'$\beta_{NMDA}$')
  757. if j == 0:
  758. ax_rate.set_ylabel('Firing Rate (Hz)')
  759. ax_rate.legend(fontsize=8)
  760. ax_cam.set_title(f'({lbl2}) {cond_name}', loc='left', fontweight='bold')
  761. ax_cam.set_xlabel(r'$\beta_{NMDA}$')
  762. if j == 0:
  763. ax_cam.set_ylabel('Mean CaMKII$_p$ (log)')
  764. ax_cam.set_ylim(bottom=1e-10) # floor: values below are numerical zero
  765. fig.suptitle(r'$\beta_{NMDA}$ $\times$ Neuron Type $\times$ Input Condition Interaction',
  766. fontsize=14, fontweight='bold')
  767. fig.tight_layout(rect=[0, 0, 1, 0.96])
  768. save_fig(fig, 'Fig7_interaction_summary')
  769. # =============================================================================
  770. # FIGURE 8: AMPA RECEPTOR CLOSING RATE (β_AMPA) SENSITIVITY ANALYSIS
  771. # =============================================================================
  772. def figure8_ampa_closing_rate_sensitivity():
  773. """
  774. AMPA receptor closing rate (β_AMPA) sensitivity analysis.
  775. β_AMPA (AMPA unbinding rate) controls AMPA EPSC decay kinetics.
  776. Physiological range: ~0.2–2.0 ms⁻¹ (decay τ ≈ 0.5–5 ms).
  777. Default value: 0.67 ms⁻¹ (τ ≈ 1.5 ms).
  778. Panels:
  779. (A) AMPA EPSC waveforms at selected β_AMPA values
  780. (B) Firing rate vs β_AMPA — excitatory & inhibitory neurons
  781. (C) Mean CaMKII phosphorylation vs β_AMPA — both neuron types
  782. (D) E/I current ratio vs β_AMPA — both neuron types
  783. (E) 2D heatmap: β_AMPA × β_NMDA interaction (firing rate, excitatory)
  784. (F) 2D heatmap: β_AMPA × β_NMDA interaction (CaMKII, excitatory)
  785. """
  786. print(" Figure 8: β_AMPA (AMPA closing rate) sensitivity ...")
  787. # --- β_AMPA values (finer resolution around 0.3–0.7 transition) ---
  788. beta_ampa_1d = np.array([0.1, 0.2, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6,
  789. 0.67, 0.8, 1.0, 1.5, 2.0, 3.0])
  790. beta_nmda_fixed = 0.03 # fixed for 1D sweeps
  791. clr_exc = '#2E86C1'
  792. clr_inh = '#E74C3C'
  793. idx_ss = int(5000 / deltaT) # steady-state index
  794. # ── 1D sweeps ──────────────────────────────────────────────────────
  795. fr_exc = np.zeros(len(beta_ampa_1d))
  796. fr_inh = np.zeros(len(beta_ampa_1d))
  797. camk_exc = np.zeros(len(beta_ampa_1d))
  798. camk_inh = np.zeros(len(beta_ampa_1d))
  799. ei_exc = np.zeros(len(beta_ampa_1d))
  800. ei_inh = np.zeros(len(beta_ampa_1d))
  801. print(" 1D β_AMPA sweep ...")
  802. for k, ba in enumerate(beta_ampa_1d):
  803. re = run_neuron_simulation(beta_nmda_fixed, 'excitatory',
  804. beta_ampa_val=ba)
  805. ri = run_neuron_simulation(beta_nmda_fixed, 'inhibitory',
  806. beta_ampa_val=ba)
  807. _, fr_exc[k] = count_spikes(re['v'], re['t'])
  808. _, fr_inh[k] = count_spikes(ri['v'], ri['t'])
  809. camk_exc[k] = np.mean(re['CaMKII_p'][idx_ss:])
  810. camk_inh[k] = np.mean(ri['CaMKII_p'][idx_ss:])
  811. ei_exc[k] = compute_ei_ratio(re)
  812. ei_inh[k] = compute_ei_ratio(ri)
  813. if k % 3 == 0:
  814. print(f" β_AMPA = {ba:.2f}: FR(exc) = {fr_exc[k]:.1f} Hz, "
  815. f"CaMKII = {camk_exc[k]:.2e}")
  816. # ── 2D heatmap: β_AMPA × β_NMDA ──────────────────────────────────
  817. beta_ampa_2d = np.array([0.2, 0.4, 0.67, 1.0, 1.5, 2.0])
  818. beta_nmda_2d = np.array([0.005, 0.01, 0.02, 0.03, 0.05, 0.08, 0.12])
  819. rate_map = np.zeros((len(beta_ampa_2d), len(beta_nmda_2d)))
  820. camk_map = np.zeros((len(beta_ampa_2d), len(beta_nmda_2d)))
  821. print(" 2D β_AMPA × β_NMDA heatmap ...")
  822. for ai, ba in enumerate(beta_ampa_2d):
  823. for nj, bn in enumerate(beta_nmda_2d):
  824. re = run_neuron_simulation(bn, 'excitatory', beta_ampa_val=ba)
  825. _, rate_map[ai, nj] = count_spikes(re['v'], re['t'])
  826. camk_map[ai, nj] = np.mean(re['CaMKII_p'][idx_ss:])
  827. # ── AMPA EPSC waveforms (analytical, for illustration) ────────────
  828. # Single-pulse AMPA gating: m_AMPA(t) = (α/(α+β)) * exp(-β*t) for t > pulse
  829. t_epsc = np.linspace(0, 30, 600) # 30 ms window
  830. pulse_dur = 5.0 # ms
  831. epsc_betas = [0.2, 0.67, 1.5, 3.0]
  832. epsc_colors = ['#2C3E50', '#2E86C1', '#E67E22', '#E74C3C']
  833. # ── Plot ───────────────────────────────────────────────────────────
  834. fig = plt.figure(figsize=(18, 11))
  835. gs_fig = gridspec.GridSpec(2, 3, hspace=0.38, wspace=0.32)
  836. # (A) AMPA EPSC waveforms
  837. ax_a = fig.add_subplot(gs_fig[0, 0])
  838. for ba_val, col in zip(epsc_betas, epsc_colors):
  839. # Simulate single-pulse AMPA gating
  840. dt_ep = 0.05
  841. tn_ep = int(30 / dt_ep)
  842. t_ep = np.arange(tn_ep + 1) * dt_ep
  843. m_ep = np.zeros(tn_ep + 1)
  844. for i_ep in range(tn_ep):
  845. G_ep = 1.0 if t_ep[i_ep] <= pulse_dur else 0.0
  846. m_ep[i_ep + 1] = m_ep[i_ep] + dt_ep * (
  847. alpha_ampa * G_ep * (1 - m_ep[i_ep]) - ba_val * m_ep[i_ep])
  848. # Normalise to peak for shape comparison
  849. if m_ep.max() > 0:
  850. m_norm = m_ep / m_ep.max()
  851. else:
  852. m_norm = m_ep
  853. tau_decay = 1.0 / ba_val
  854. ax_a.plot(t_ep, m_norm, color=col, lw=2,
  855. label=f'β = {ba_val:.2f} (τ ≈ {tau_decay:.1f} ms)')
  856. ax_a.set_xlabel('Time (ms)')
  857. ax_a.set_ylabel('Normalised AMPA conductance')
  858. ax_a.set_title('(A) AMPA EPSC Waveforms\n(single pulse, normalised)',
  859. loc='left', fontweight='bold')
  860. ax_a.legend(fontsize=8, title='$β_{AMPA}$', title_fontsize=9)
  861. # (B) Firing rate vs β_AMPA
  862. ax_b = fig.add_subplot(gs_fig[0, 1])
  863. ax_b.plot(beta_ampa_1d, fr_exc, 'o-', color=clr_exc, lw=2, ms=6,
  864. label='Excitatory')
  865. ax_b.plot(beta_ampa_1d, fr_inh, 's--', color=clr_inh, lw=2, ms=6,
  866. label='Inhibitory')
  867. ax_b.axvline(0.67, color='gray', ls=':', lw=1, alpha=0.6, label='Default (0.67)')
  868. ax_b.set_xlabel(r'$\beta_{AMPA}$ (ms$^{-1}$)')
  869. ax_b.set_ylabel('Firing Rate (Hz)')
  870. ax_b.set_title(f'(B) Firing Rate vs $β_{{AMPA}}$\n'
  871. f'($β_{{NMDA}}$ = {beta_nmda_fixed})',
  872. loc='left', fontweight='bold')
  873. ax_b.legend(fontsize=8)
  874. # (C) CaMKII vs β_AMPA
  875. ax_c = fig.add_subplot(gs_fig[0, 2])
  876. ax_c.semilogy(beta_ampa_1d, camk_exc, 'o-', color=clr_exc, lw=2, ms=6,
  877. label='Excitatory')
  878. ax_c.semilogy(beta_ampa_1d, camk_inh, 's--', color=clr_inh, lw=2, ms=6,
  879. label='Inhibitory')
  880. ax_c.axvline(0.67, color='gray', ls=':', lw=1, alpha=0.6, label='Default')
  881. ax_c.set_xlabel(r'$\beta_{AMPA}$ (ms$^{-1}$)')
  882. ax_c.set_ylabel('Mean CaMKII$_p$ (log scale)')
  883. ax_c.set_title(f'(C) CaMKII Phosphorylation vs $β_{{AMPA}}$',
  884. loc='left', fontweight='bold')
  885. ax_c.legend(fontsize=8)
  886. # (D) E/I ratio vs β_AMPA
  887. ax_d = fig.add_subplot(gs_fig[1, 0])
  888. ax_d.plot(beta_ampa_1d, ei_exc, 'o-', color=clr_exc, lw=2, ms=6,
  889. label='Excitatory')
  890. ax_d.plot(beta_ampa_1d, ei_inh, 's--', color=clr_inh, lw=2, ms=6,
  891. label='Inhibitory')
  892. ax_d.axvline(0.67, color='gray', ls=':', lw=1, alpha=0.6, label='Default')
  893. ax_d.axhline(1.0, color='black', ls=':', lw=0.8, alpha=0.4)
  894. ax_d.set_xlabel(r'$\beta_{AMPA}$ (ms$^{-1}$)')
  895. ax_d.set_ylabel('E/I Current Ratio')
  896. ax_d.set_title('(D) Excitation–Inhibition Balance vs $β_{AMPA}$',
  897. loc='left', fontweight='bold')
  898. ax_d.legend(fontsize=8)
  899. # (E) 2D heatmap: β_AMPA × β_NMDA → Firing Rate
  900. ax_e = fig.add_subplot(gs_fig[1, 1])
  901. im_e = ax_e.imshow(rate_map, origin='lower', aspect='auto',
  902. extent=[beta_nmda_2d[0], beta_nmda_2d[-1],
  903. beta_ampa_2d[0], beta_ampa_2d[-1]],
  904. cmap='viridis', interpolation='bilinear')
  905. for ai, ba in enumerate(beta_ampa_2d):
  906. for nj, bn in enumerate(beta_nmda_2d):
  907. val = rate_map[ai, nj]
  908. ax_e.text(bn, ba, f'{val:.0f}', ha='center', va='center',
  909. fontsize=6, color='white' if val > rate_map.max()*0.5 else 'black')
  910. fig.colorbar(im_e, ax=ax_e, label='Firing Rate (Hz)', shrink=0.85)
  911. ax_e.set_xlabel(r'$\beta_{NMDA}$')
  912. ax_e.set_ylabel(r'$\beta_{AMPA}$ (ms$^{-1}$)')
  913. ax_e.set_title('(E) $β_{AMPA}$ × $β_{NMDA}$ → FR\n(Excitatory)',
  914. loc='left', fontweight='bold')
  915. # Mark default
  916. ax_e.plot(0.03, 0.67, 'r*', markersize=12, zorder=5)
  917. # (F) 2D heatmap: β_AMPA × β_NMDA → CaMKII
  918. ax_f = fig.add_subplot(gs_fig[1, 2])
  919. # Use log scale for CaMKII
  920. camk_log = np.log10(camk_map + 1e-30)
  921. im_f = ax_f.imshow(camk_log, origin='lower', aspect='auto',
  922. extent=[beta_nmda_2d[0], beta_nmda_2d[-1],
  923. beta_ampa_2d[0], beta_ampa_2d[-1]],
  924. cmap='magma', interpolation='bilinear')
  925. for ai, ba in enumerate(beta_ampa_2d):
  926. for nj, bn in enumerate(beta_nmda_2d):
  927. val = camk_map[ai, nj]
  928. ax_f.text(bn, ba, f'{val:.0e}', ha='center', va='center',
  929. fontsize=5, color='white')
  930. fig.colorbar(im_f, ax=ax_f, label='log₁₀(CaMKII$_p$)', shrink=0.85)
  931. ax_f.set_xlabel(r'$\beta_{NMDA}$')
  932. ax_f.set_ylabel(r'$\beta_{AMPA}$ (ms$^{-1}$)')
  933. ax_f.set_title('(F) $β_{AMPA}$ × $β_{NMDA}$ → CaMKII\n(Excitatory)',
  934. loc='left', fontweight='bold')
  935. ax_f.plot(0.03, 0.67, 'r*', markersize=12, zorder=5)
  936. fig.suptitle(
  937. r'AMPA Receptor Closing Rate ($\beta_{AMPA}$) Sensitivity Analysis',
  938. fontsize=14, fontweight='bold')
  939. fig.tight_layout(rect=[0, 0, 1, 0.96])
  940. save_fig(fig, 'Fig8_beta_AMPA_sensitivity')
  941. # ── Print summary ──────────────────────────────────────────────────
  942. print("\n ── β_AMPA Sensitivity Summary ──")
  943. print(f" Range tested: {beta_ampa_1d[0]:.2f} – {beta_ampa_1d[-1]:.2f} ms⁻¹")
  944. print(f" Default value: 0.67 ms⁻¹ (τ_decay ≈ 1.5 ms)")
  945. fr_range = fr_exc.max() - fr_exc.min()
  946. fr_pct = 100 * fr_range / fr_exc[beta_ampa_1d == 0.67][0] if 0.67 in beta_ampa_1d else 0
  947. print(f" Excitatory FR range: {fr_exc.min():.1f} – {fr_exc.max():.1f} Hz "
  948. f"(Δ = {fr_pct:.1f}%)")
  949. print(f" Inhibitory FR range: {fr_inh.min():.1f} – {fr_inh.max():.1f} Hz")
  950. camk_ratio = camk_exc.max() / (camk_exc.min() + 1e-30)
  951. print(f" CaMKII range (exc): {camk_exc.min():.2e} – {camk_exc.max():.2e} "
  952. f"({camk_ratio:.1f}× variation)")
  953. # =============================================================================
  954. # RUN ALL FIGURES
  955. # =============================================================================
  956. if __name__ == '__main__':
  957. print("=" * 60)
  958. print("Generating Supplementary Figures")
  959. print("=" * 60)
  960. figure1_pulse_profiles()
  961. figure2_three_conditions()
  962. figure3_exc_vs_inh()
  963. figure4_nmda_parameter_sweep()
  964. figure5_conductance_tuning()
  965. figure6_three_conditions_inhibitory()
  966. figure7_interaction_summary()
  967. figure8_ampa_closing_rate_sensitivity()
  968. print("\n" + "=" * 60)
  969. print(f"All figures saved to: {save_directory}")
  970. print("=" * 60)

reviewer_response_simulations_final.py at commit 006879e, under MIT · at the source

Overview

Authors: Mehdi Borjkhani1,2, Hadi Borjkhani3, Morteza A Sharif4, Fariba Bahrami5, Mahyar Janahmadi6
  1. International Centre for Translational Eye Research (ICTER), Institute of Physical Chemistry, Polish Academy of Sciences, Warsaw, Poland
  2. Institute of Physical Chemistry, Polish Academy of Sciences, Warsaw, Poland
  3. School of Engineering – Energy and Information, HTW Berlin – University of Applied Sciences, Berlin, Germany
  4. Optics and Laser Engineering Group, Faculty of Electrical Engineering, Urmia University of Technology, Urmia, Iran
  5. CIPCE, Motor Control and Computational Neuroscience Laboratory, School of ECE, College of Engineering, University of Tehran, Tehran, Iran
  6. Neuroscience Research Center and Department of Physiology, Medical School, Shahid Beheshti University of Medical Sciences, Tehran, Iran
Journal: Frontiers in computational neuroscience, volume 20, article 1753444
Dates: received 24 November 2025; accepted 21 April 2026; published online 3 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.3389/fncom.2026.1753444 · PMID 42318008 · PMCID PMC13272316 · OpenAlex W4414141469
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: none (in silico) (organism), other condition (population), cellular / molecular (subfield)
Methods: Smoothing, state filtering, decompositions, Connectivity, Single-unit activity, calcium imaging
Keywords: addiction, chaos theory, computational neuroscience, information theory, neuronal dynamics, NMDA receptors, synaptic plasticity, visual processing
Topic: Neural dynamics and brain function (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Citations: not cited yet (Europe PMC); 83 references in the paper

Abstract

Introduction: Neuronal firing patterns emerge from complex interactions between intrinsic membrane properties and synaptic receptor dynamics. N-methyl-D-aspartate (NMDA) receptors critically shape calcium influx and synaptic plasticity through their voltage-dependent Mg2+ block and prolonged activation kinetics, yet how their closing kinetics interact with glutamatergic drive and GABAergic modulation to control neuronal dynamics and information processing remains incompletely understood.

Methods: We developed a Hodgkin–Huxley-type computational model incorporating NMDA, AMPA, and GABA receptor kinetics to investigate how the NMDA receptor closing rate βNMDA and glutamatergic stimulation frequency control neuronal dynamics. We performed a systematic analysis of over 2.9 million inter-spike intervals across a large multi-parameter sweep of NMDA kinetics, glutamatergic stimulation frequency, and GABAergic modulation. Dynamical behavior was characterized using entropy–Lyapunov correlation analysis and frequency-dependent bifurcation analysis, and CaMKII phosphorylation was quantified to link kinetic regimes to downstream plasticity signaling.

Results: The analysis revealed two mechanistically distinct pathways to firing irregularity. Pathway 1 (rapid-deactivation irregularity) emerged under relatively fast NMDA deactivation combined with specific input-frequency conditions, producing deterministic chaos with compromised information encoding. Pathway 2 (prolonged-activation irregularity) resulted from slow NMDA deactivation under weak drive, creating irregularity through sustained receptor activation and calcium influx. An optimal kinetic window emerged at βNMDA = 0.042 ms−1, maximizing information transfer (0.275 bits) while maintaining stable dynamics. Entropy–Lyapunov correlation analysis confirmed deterministic chaos, and frequency-dependent bifurcation analysis demonstrated progressive narrowing and displacement of chaotic windows across the analyzed βNMDA range as stimulation frequency increased. GABAergic inhibition provided frequency-selective stabilization, expanding the stable parameter space by 34.2% while preserving gamma oscillations. CaMKII phosphorylation analysis revealed that prolonged NMDA activation maintained elevated phosphorylation levels, creating conditions for pathological long-term potentiation.

Discussion: These findings establish NMDA receptor kinetics as fundamental controllers of cortical excitability and information processing. The dual-pathway framework provides mechanistic insights into addiction-related memory formation, where prolonged NMDA activation enables pathological plasticity, and into visual processing disorders, where altered kinetics disrupt retinal function and cortical oscillatory balance. The identification of optimal kinetic windows and frequency-selective GABA modulation suggests therapeutic strategies based on kinetically specific interventions for neuropsychiatric disorders involving NMDA dysfunction.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 26 matches between paragraphs and lines of code.

borjkhani/Bifurcation_NMDA_FCN

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 006879efec965df60df84b7d103bc00b714a8983, 27 February 2026
Languages: Python (9)
Size: 11 files, 9 scripts
Software Heritage: not archived
Found in: “Reproducibility statement”
Holds: README, license file
Not found: CITATION.cff, environment file, tests, continuous integration, documentation
Tools: Matplotlib (9 files), NumPy (9 files), SciPy (5 files), seaborn (5 files), pandas (2 files), scikit-learn (2 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
11 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 9 scripts, each with its path and the digest of its content;
  • 26 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data availability statement

The datasets presented in this study can be found in online repositories. The names of the repository/repositories and accession number(s) can be found below: https://github.com/borjkhani/Bifurcation_NMDA_FCN.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Authors: added Mehdi Borjkhani (0000-0003-0469-0902); Hadi Borjkhani (0000-0001-5495-3964); removed Mehdi Borjkhani; Hadi Borjkhani
  • Funding: added European Commission; Fundacja na rzecz Nauki Polskiej

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, pages, dates, 5 authors, 8 keywords, 82 references.

Cite

This paper

Borjkhani, M., Borjkhani, H., Sharif, M. A., Bahrami, F., & Janahmadi, M. (2026). NMDA receptor kinetics drive distinct routes to chaotic firing in pyramidal neurons. Frontiers in computational neuroscience, 20, 1753444. https://doi.org/10.3389/fncom.2026.1753444

BibTeX

@article{borjkhani2026nmda,
author = {Borjkhani, Mehdi and Borjkhani, Hadi and Sharif, Morteza A and Bahrami, Fariba and Janahmadi, Mahyar},
title = {{NMDA receptor kinetics drive distinct routes to chaotic firing in pyramidal neurons}},
journal = {Frontiers in computational neuroscience},
year = {2026},
month = jun,
volume = {20},
pages = {1753444},
publisher = {Frontiers Media SA},
issn = {1662-5188},
doi = {10.3389/fncom.2026.1753444},
url = {https://doi.org/10.3389/fncom.2026.1753444},
pmid = {42318008},
pmcid = {PMC13272316}
}

RIS

TY - JOUR
AU - Borjkhani, Mehdi
AU - Borjkhani, Hadi
AU - Sharif, Morteza A
AU - Bahrami, Fariba
AU - Janahmadi, Mahyar
TI - NMDA receptor kinetics drive distinct routes to chaotic firing in pyramidal neurons
T2 - Frontiers in computational neuroscience
J2 - Front Comput Neurosci
PY - 2026
DA - 2026/06/03
VL - 20
SP - 1753444
SN - 1662-5188
PB - Frontiers Media SA
DO - 10.3389/fncom.2026.1753444
UR - https://doi.org/10.3389/fncom.2026.1753444
LA - en
ER -

CSL-JSON

{
"id": "10.3389/fncom.2026.1753444",
"type": "article-journal",
"title": "NMDA receptor kinetics drive distinct routes to chaotic firing in pyramidal neurons",
"container-title": "Frontiers in computational neuroscience",
"author": [
{
"family": "Borjkhani",
"given": "Mehdi"
},
{
"family": "Borjkhani",
"given": "Hadi"
},
{
"family": "Sharif",
"given": "Morteza A"
},
{
"family": "Bahrami",
"given": "Fariba"
},
{
"family": "Janahmadi",
"given": "Mahyar"
}
],
"container-title-short": "Front Comput Neurosci",
"volume": "20",
"page": "1753444",
"DOI": "10.3389/fncom.2026.1753444",
"PMID": "42318008",
"PMCID": "PMC13272316",
"ISSN": "1662-5188",
"publisher": "Frontiers Media SA",
"URL": "https://doi.org/10.3389/fncom.2026.1753444",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
3
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s41598-026-42120-y [code]
Qualitative EEG abnormalities in ASD reflect inhibition-dominated brain dynamics.
Journal: Scientific reports
In common: seaborn, pandas, SciPy, 2 other tools, 3 references
[2] doi:10.1016/j.patter.2026.101619 [code]
Sampling bias corrections for discrete and Gaussian partial information decompositions.
Journal: Patterns (New York, N.Y.)
In common: seaborn, scikit-learn, pandas, 3 other tools, 2 references
[3] doi:10.7554/elife.105482 [code]
Principles of gamma synchrony predict figure-ground perception in texture stimuli.
Journal: eLife
In common: seaborn, scikit-learn, pandas, 3 other tools, 2 references
[4] doi:10.1162/imag.a.1252 [code]
Does the brain's E:I balance really shape long-range temporal correlations? Lessons learned from 3T MRI.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: seaborn, scikit-learn, pandas, 3 other tools, 2 references
[5] doi:10.1371/journal.pcbi.1014304 [code]
Linking reduced prefrontal microcircuit inhibition in schizophrenia to EEG biomarkers in silico.
Journal: PLoS computational biology
In common: scikit-learn, pandas, SciPy, 2 other tools, 2 references
[6] doi:10.1371/journal.pcbi.1014391 [code]
Multi-stable oscillations in cortical networks with two classes of inhibition.
Journal: PLoS computational biology
In common: seaborn, scikit-learn, pandas, 3 other tools, none (in silico), 1 reference
[7] doi:10.1371/journal.pcbi.1014571 [code]
SynAPSeg: A novel dataset and image analysis framework for deep learning-based synapse detection and quantification.
Journal: PLoS computational biology
In common: seaborn, scikit-learn, pandas, 3 other tools, 1 reference
[8] doi:10.1038/s41467-026-74227-1 [code]
Age-related changes in behavioural and neural variability in a decision-making task.
Journal: Nature communications
In common: seaborn, scikit-learn, pandas, 3 other tools, 1 reference
[9] doi:10.1038/s41586-026-10331-y [code]
Active dissociation of intracortical spiking and high gamma activity.
Journal: Nature
In common: scikit-learn, pandas, SciPy, 2 other tools, 2 references
[10] doi:10.1016/j.isci.2026.115488 [code]
An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs.
Journal: iScience
In common: seaborn, scikit-learn, pandas, 3 other tools, cellular / molecular, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.