493 lines
20 KiB
Python
493 lines
20 KiB
Python
|
|
import numpy as np
|
||
|
|
import matplotlib as plt
|
||
|
|
import matplotlib.pyplot as plt # <-- this is essential
|
||
|
|
plt.rcdefaults() # Restore default rc parameters
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# TRIPARTITE SYNAPSE MODEL
|
||
|
|
# ============================================================================
|
||
|
|
# This code simulates a single synapse with presynapse, postsynapse, and astrocyte.
|
||
|
|
# Timescales range from milliseconds (AP, Ca2+ influx) to minutes (vesicle replenishment,
|
||
|
|
# astrocyte Ca2+ waves, synaptic weight changes).
|
||
|
|
#
|
||
|
|
# Units:
|
||
|
|
# - Time: seconds (s) or milliseconds (ms) as noted. Internal time step is in seconds.
|
||
|
|
# - Concentrations: micromolar (µM)
|
||
|
|
# - Vesicle pools: number of vesicles (can be fractional)
|
||
|
|
# - Conductances: nanosiemens (nS)
|
||
|
|
# - Membrane potential: millivolts (mV)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
# Presynapse Class
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
class Presynapse:
|
||
|
|
"""
|
||
|
|
Models the presynaptic terminal.
|
||
|
|
State variables:
|
||
|
|
Cf (float): fast calcium concentration (µM)
|
||
|
|
Cs (float): slow calcium concentration (µM)
|
||
|
|
R (float): vesicles in Readily Releasable Pool (RRP)
|
||
|
|
P (float): vesicles in Reserve Pool (RP)
|
||
|
|
N_VGCC (int): number of functional VGCCs (can be modulated by astrocyte)
|
||
|
|
release_prob (float): probability of vesicle fusion per AP (computed)
|
||
|
|
NT_released (float): amount of neurotransmitter ejected (arbitrary units)
|
||
|
|
"""
|
||
|
|
def __init__(self, params):
|
||
|
|
self.p = params # store parameters
|
||
|
|
# Initial states
|
||
|
|
self.Cf = 0.0
|
||
|
|
self.Cs = 0.0
|
||
|
|
self.R = self.p['R_max'] # start with full RRP
|
||
|
|
self.P = self.p['P_max'] # start with full RP
|
||
|
|
self.N_VGCC = self.p['N_VGCC0'] # baseline VGCC number
|
||
|
|
self.release_prob = 0.0
|
||
|
|
self.NT_released = 0.0
|
||
|
|
|
||
|
|
def ap_event(self):
|
||
|
|
"""
|
||
|
|
Called when an action potential arrives.
|
||
|
|
Increases calcium (fast and slow components) based on available VGCCs.
|
||
|
|
Triggers vesicle release and updates pools.
|
||
|
|
"""
|
||
|
|
# 1. Calcium influx (instantaneous jump)
|
||
|
|
self.Cf += self.p['alpha'] * self.N_VGCC
|
||
|
|
self.Cs += self.p['beta'] * self.N_VGCC # small slow component
|
||
|
|
|
||
|
|
# 2. Compute release probability (Hill function of total calcium)
|
||
|
|
C_tot = self.Cf + self.Cs
|
||
|
|
self.release_prob = self.p['p_max'] * (C_tot**self.p['n_Hill']) / \
|
||
|
|
(C_tot**self.p['n_Hill'] + self.p['Kd']**self.p['n_Hill'])
|
||
|
|
|
||
|
|
# 3. Vesicles released from RRP
|
||
|
|
released = self.R * self.release_prob
|
||
|
|
self.NT_released = self.p['gamma'] * released
|
||
|
|
self.R -= released
|
||
|
|
|
||
|
|
# Ensure pools don't go negative (shouldn't happen if dt small)
|
||
|
|
self.R = max(self.R, 0)
|
||
|
|
|
||
|
|
def step(self, dt):
|
||
|
|
"""
|
||
|
|
Update presynaptic states between APs.
|
||
|
|
- Calcium decay (fast and slow)
|
||
|
|
- Vesicle mobilization (RP -> RRP) depending on calcium trace
|
||
|
|
- RP replenishment from an infinite source
|
||
|
|
"""
|
||
|
|
# Total calcium for trace classification
|
||
|
|
C_tot = self.Cf + self.Cs
|
||
|
|
|
||
|
|
# 1. Calcium decay (first order)
|
||
|
|
self.Cf -= dt * (self.Cf / self.p['tau_f'])
|
||
|
|
self.Cs -= dt * (self.Cs / self.p['tau_s'])
|
||
|
|
|
||
|
|
# 2. Vesicle mobilization rate based on calcium trace
|
||
|
|
if C_tot < self.p['theta_low']:
|
||
|
|
k_mob = self.p['k_slow']
|
||
|
|
elif C_tot < self.p['theta_high']:
|
||
|
|
k_mob = self.p['k_med']
|
||
|
|
else:
|
||
|
|
k_mob = self.p['k_fast']
|
||
|
|
|
||
|
|
# Move vesicles from RP to RRP
|
||
|
|
move = k_mob * self.P * dt
|
||
|
|
self.R += move
|
||
|
|
self.P -= move
|
||
|
|
|
||
|
|
# 3. RP replenishment (slow refilling)
|
||
|
|
replenish = self.p['k_rep'] * (self.p['P_max'] - self.P) * dt
|
||
|
|
self.P += replenish
|
||
|
|
|
||
|
|
# Optional: keep pools within bounds
|
||
|
|
self.R = min(self.R, self.p['R_max'])
|
||
|
|
self.P = min(self.P, self.p['P_max'])
|
||
|
|
self.P = max(self.P, 0)
|
||
|
|
|
||
|
|
def set_vgcc_modulation(self, factor):
|
||
|
|
"""
|
||
|
|
Allow astrocyte to modulate VGCC availability.
|
||
|
|
factor: multiplier (0..1) applied to baseline N_VGCC.
|
||
|
|
"""
|
||
|
|
self.N_VGCC = self.p['N_VGCC0'] * factor
|
||
|
|
|
||
|
|
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
# Postsynapse Class
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
class Postsynapse:
|
||
|
|
"""
|
||
|
|
Models the postsynaptic density and spine.
|
||
|
|
State variables:
|
||
|
|
G (float): cleft glutamate concentration (a.u.)
|
||
|
|
gA (float): AMPA conductance (nS)
|
||
|
|
gN (float): NMDA conductance (nS)
|
||
|
|
Vm (float): membrane potential (mV)
|
||
|
|
C_post (float): postsynaptic calcium (µM)
|
||
|
|
w (float): synaptic weight (dimensionless, modulates AMPAR conductance)
|
||
|
|
"""
|
||
|
|
def __init__(self, params):
|
||
|
|
self.p = params
|
||
|
|
self.G = 0.0
|
||
|
|
self.gA = 0.0
|
||
|
|
self.gN = 0.0
|
||
|
|
self.Vm = self.p['E_rest']
|
||
|
|
self.C_post = self.p['C_post_rest']
|
||
|
|
self.w = 1.0 # initial weight
|
||
|
|
|
||
|
|
def receive_nt(self, NT_released):
|
||
|
|
"""
|
||
|
|
Neurotransmitter released into cleft. Instantaneous rise,
|
||
|
|
then decay handled in step().
|
||
|
|
"""
|
||
|
|
self.G += NT_released # instantaneous jump
|
||
|
|
|
||
|
|
def step(self, dt, astrocyte_dserine_factor=1.0):
|
||
|
|
"""
|
||
|
|
Update postsynaptic states.
|
||
|
|
- Glutamate clearance
|
||
|
|
- AMPA/NMDA conductance kinetics
|
||
|
|
- Membrane potential (Euler)
|
||
|
|
- Postsynaptic calcium dynamics
|
||
|
|
- Synaptic weight plasticity (slow)
|
||
|
|
"""
|
||
|
|
# 1. Glutamate clearance (first order)
|
||
|
|
self.G -= dt * (self.G / self.p['tau_glu'])
|
||
|
|
|
||
|
|
# 2. AMPA receptor kinetics
|
||
|
|
# Target conductance = max conductance * (G/(G+K)) * weight
|
||
|
|
target_A = self.p['gA_max'] * (self.G / (self.G + self.p['K_glu'])) * self.w
|
||
|
|
self.gA += dt * ((target_A - self.gA) / self.p['tau_AMPA'])
|
||
|
|
|
||
|
|
# 3. NMDA receptor kinetics (with voltage-dependent Mg block)
|
||
|
|
Mg_block = 1.0 / (1.0 + (self.p['Mg'] / 3.57) * np.exp(-0.062 * self.Vm))
|
||
|
|
# astrocyte D-serine enhances NMDA conductance (multiplying max)
|
||
|
|
target_N = self.p['gN_max'] * astrocyte_dserine_factor * \
|
||
|
|
(self.G / (self.G + self.p['K_glu'])) * Mg_block
|
||
|
|
self.gN += dt * ((target_N - self.gN) / self.p['tau_NMDA'])
|
||
|
|
|
||
|
|
# 4. Membrane potential (current balance)
|
||
|
|
I_syn = self.gA * (self.Vm - self.p['E_AMPA']) + self.gN * (self.Vm - self.p['E_NMDA'])
|
||
|
|
I_leak = self.p['gL'] * (self.Vm - self.p['E_rest'])
|
||
|
|
dVm = (-I_leak - I_syn) / self.p['Cm']
|
||
|
|
self.Vm += dt * dVm
|
||
|
|
|
||
|
|
# 5. Postsynaptic calcium influx (via NMDA and VGCCs)
|
||
|
|
# Simplified: calcium current proportional to NMDA conductance and voltage driving force
|
||
|
|
I_Ca_NMDA = self.p['eta_N'] * self.gN * (self.Vm - self.p['E_Ca'])
|
||
|
|
# VGCC contribution (activated by depolarisation)
|
||
|
|
vgcc_act = 1.0 / (1.0 + np.exp(-(self.Vm - self.p['V_half'])/self.p['k_vgcc']))
|
||
|
|
I_Ca_VGCC = self.p['eta_V'] * vgcc_act * (self.Vm - self.p['E_Ca'])
|
||
|
|
self.C_post += dt * (I_Ca_NMDA + I_Ca_VGCC - (self.C_post - self.p['C_post_rest'])/self.p['tau_Ca_post'])
|
||
|
|
|
||
|
|
# 6. Synaptic weight plasticity (slow, calcium-driven)
|
||
|
|
# LTP and LTD thresholds based on calcium control hypothesis
|
||
|
|
if self.C_post > self.p['theta_LTP']:
|
||
|
|
dw = self.p['gamma_LTP'] * (1.0 - self.w) * dt
|
||
|
|
elif self.C_post > self.p['theta_LTD']:
|
||
|
|
dw = -self.p['gamma_LTD'] * self.w * dt
|
||
|
|
else:
|
||
|
|
dw = 0.0
|
||
|
|
self.w += dw
|
||
|
|
self.w = np.clip(self.w, 0.0, 2.0) # keep within bounds
|
||
|
|
|
||
|
|
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
# Astrocyte Class
|
||
|
|
# ----------------------------------------------------------------------------
|
||
|
|
class Astrocyte:
|
||
|
|
"""
|
||
|
|
Models an astrocytic process enwrapping the synapse.
|
||
|
|
State variables:
|
||
|
|
I (float): IP3 concentration (µM)
|
||
|
|
A_Ca (float): astrocyte calcium (µM)
|
||
|
|
S (float): gliotransmitter (ATP) concentration (a.u.)
|
||
|
|
uptake_efficiency (float): modulates glutamate clearance (not used directly)
|
||
|
|
"""
|
||
|
|
def __init__(self, params):
|
||
|
|
self.p = params
|
||
|
|
self.I = 0.0
|
||
|
|
self.A_Ca = self.p['A_Ca_rest']
|
||
|
|
self.S = 0.0
|
||
|
|
self.uptake = 1.0 # can be modulated
|
||
|
|
|
||
|
|
def sense_glutamate(self, G):
|
||
|
|
"""
|
||
|
|
Glutamate spillover activates mGluRs, producing IP3.
|
||
|
|
IP3 production is proportional to low-pass filtered glutamate.
|
||
|
|
"""
|
||
|
|
# Low-pass filter of G (simple exponential moving average)
|
||
|
|
# We'll use a separate state for filtered G for simplicity.
|
||
|
|
# For now, we directly update IP3 based on G with a delay.
|
||
|
|
# Here we use a simplified approach: IP3 production rate depends on G.
|
||
|
|
pass
|
||
|
|
|
||
|
|
def step(self, dt, G):
|
||
|
|
"""
|
||
|
|
Update astrocyte states.
|
||
|
|
- IP3 dynamics (production from mGluR activation, decay)
|
||
|
|
- Calcium release from internal stores (IP3-dependent)
|
||
|
|
- Gliotransmitter release when calcium high
|
||
|
|
"""
|
||
|
|
# 1. IP3 production (driven by glutamate spillover) and decay
|
||
|
|
# Use a Hill function for mGluR activation
|
||
|
|
mGluR_act = (G**self.p['n_mGluR']) / (G**self.p['n_mGluR'] + self.p['K_mGluR']**self.p['n_mGluR'])
|
||
|
|
prod = self.p['beta_IP3'] * mGluR_act
|
||
|
|
self.I += dt * (prod - self.I / self.p['tau_IP3'])
|
||
|
|
|
||
|
|
# 2. Astrocyte calcium (simplified Li-Rinzel type)
|
||
|
|
# IP3 opens ER channels; calcium-induced calcium release not explicitly modeled
|
||
|
|
# We use a simple equation: increase when IP3 high, decay otherwise.
|
||
|
|
# More detailed: dA_Ca/dt = r_IP3 * (I^n/(I^n+K_I^n)) * (A_store - A_Ca) - A_Ca/tau_A_Ca
|
||
|
|
# For simplicity, use a direct dependence:
|
||
|
|
target = self.p['A_max'] * (self.I**self.p['n_IP3']) / (self.I**self.p['n_IP3'] + self.p['K_IP3']**self.p['n_IP3'])
|
||
|
|
self.A_Ca += dt * ((target - self.A_Ca) / self.p['tau_A_rise'] - (self.A_Ca - self.p['A_Ca_rest'])/self.p['tau_A_decay'])
|
||
|
|
|
||
|
|
# 3. Gliotransmitter release (ATP) when calcium above threshold
|
||
|
|
if self.A_Ca > self.p['theta_S']:
|
||
|
|
self.S = self.p['S_max'] # instantaneous release, can be made graded
|
||
|
|
else:
|
||
|
|
self.S = 0.0
|
||
|
|
# Gliotransmitter decays
|
||
|
|
self.S *= np.exp(-dt / self.p['tau_S'])
|
||
|
|
|
||
|
|
def get_vgcc_modulation(self):
|
||
|
|
"""
|
||
|
|
Compute factor to modulate presynaptic VGCCs (e.g., via adenosine).
|
||
|
|
"""
|
||
|
|
# ATP released by astrocyte is converted to adenosine, which activates A1 receptors.
|
||
|
|
# We model it as a simple inhibition: factor = 1 - delta * S/(S+K_S)
|
||
|
|
factor = 1.0 - self.p['delta_A1'] * (self.S / (self.S + self.p['K_A1']))
|
||
|
|
return max(factor, 0.0)
|
||
|
|
|
||
|
|
def get_dserine_modulation(self):
|
||
|
|
"""
|
||
|
|
D-serine enhances NMDA receptor function.
|
||
|
|
Return a factor >1 when astrocyte calcium is high.
|
||
|
|
"""
|
||
|
|
# Simple relation: factor = 1 + dserine_max * (A_Ca/(A_Ca+K_D))
|
||
|
|
factor = 1.0 + self.p['dserine_max'] * (self.A_Ca / (self.A_Ca + self.p['K_D']))
|
||
|
|
return factor
|
||
|
|
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Parameter Sets (with timescales and biological references)
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
# All times are in seconds unless noted.
|
||
|
|
|
||
|
|
presyn_params = {
|
||
|
|
# Calcium dynamics
|
||
|
|
'alpha': 0.5, # fast Ca influx per VGCC (µM)
|
||
|
|
'beta': 0.02, # slow Ca influx per VGCC (µM)
|
||
|
|
'tau_f': 0.020, # fast Ca decay time constant (20 ms)
|
||
|
|
'tau_s': 1.0, # slow Ca decay time constant (1 s)
|
||
|
|
# VGCC
|
||
|
|
'N_VGCC0': 10, # baseline number of functional VGCCs
|
||
|
|
# Release
|
||
|
|
'p_max': 0.8, # max release probability
|
||
|
|
'Kd': 2.0, # Ca half-activation (µM)
|
||
|
|
'n_Hill': 4, # Hill coefficient (cooperativity)
|
||
|
|
'gamma': 1.0, # conversion factor from vesicles to NT units
|
||
|
|
# Vesicle pools
|
||
|
|
'R_max': 50, # max RRP size (vesicles)
|
||
|
|
'P_max': 200, # max RP size (vesicles)
|
||
|
|
'k_fast': 2.0, # fast mobilization rate (s^-1) under high Ca
|
||
|
|
'k_med': 0.5, # medium mobilization rate (s^-1)
|
||
|
|
'k_slow': 0.1, # slow mobilization rate (s^-1) under low Ca
|
||
|
|
'k_rep': 0.03, # RP replenishment rate (s^-1) (time constant ~33 s)
|
||
|
|
# Calcium trace thresholds (µM)
|
||
|
|
'theta_low': 1.0,
|
||
|
|
'theta_high': 3.0,
|
||
|
|
}
|
||
|
|
|
||
|
|
postsyn_params = {
|
||
|
|
# Glutamate dynamics
|
||
|
|
'tau_glu': 0.002, # glutamate decay in cleft (2 ms)
|
||
|
|
'K_glu': 1.0, # glutamate half-activation for receptors (a.u.)
|
||
|
|
# AMPA receptors
|
||
|
|
'gA_max': 5.0, # max AMPA conductance (nS)
|
||
|
|
'tau_AMPA': 0.002, # AMPA rise/decay time (2 ms, simplified)
|
||
|
|
'E_AMPA': 0.0, # reversal potential (mV)
|
||
|
|
# NMDA receptors
|
||
|
|
'gN_max': 2.0, # max NMDA conductance (nS)
|
||
|
|
'tau_NMDA': 0.050, # NMDA decay time (50 ms)
|
||
|
|
'E_NMDA': 0.0, # reversal potential (mV)
|
||
|
|
'Mg': 1.0, # extracellular Mg concentration (mM)
|
||
|
|
# Membrane parameters
|
||
|
|
'Cm': 1.0, # membrane capacitance (µF/cm^2, scaled)
|
||
|
|
'gL': 0.1, # leak conductance (nS)
|
||
|
|
'E_rest': -70.0, # resting potential (mV)
|
||
|
|
'E_Ca': 120.0, # calcium reversal potential (mV)
|
||
|
|
# Postsynaptic calcium
|
||
|
|
'eta_N': 0.02, # NMDA calcium influx factor (µM/ms/nA? simplified)
|
||
|
|
'eta_V': 0.01, # VGCC calcium influx factor
|
||
|
|
'V_half': -20.0, # half-activation for VGCC (mV)
|
||
|
|
'k_vgcc': 5.0, # slope factor for VGCC activation
|
||
|
|
'tau_Ca_post': 0.050, # calcium decay time (50 ms)
|
||
|
|
'C_post_rest': 0.05, # resting calcium (µM)
|
||
|
|
# Plasticity thresholds
|
||
|
|
'theta_LTD': 0.5, # LTD threshold (µM)
|
||
|
|
'theta_LTP': 1.5, # LTP threshold (µM)
|
||
|
|
'gamma_LTD': 0.001, # LTD rate (s^-1)
|
||
|
|
'gamma_LTP': 0.002, # LTP rate (s^-1)
|
||
|
|
}
|
||
|
|
|
||
|
|
astro_params = {
|
||
|
|
# IP3 dynamics
|
||
|
|
'beta_IP3': 0.1, # IP3 production rate per glutamate (µM/s)
|
||
|
|
'tau_IP3': 2.0, # IP3 decay time constant (2 s)
|
||
|
|
'K_mGluR': 0.5, # glutamate half-activation for mGluR (a.u.)
|
||
|
|
'n_mGluR': 2, # cooperativity for mGluR
|
||
|
|
# Astrocyte calcium
|
||
|
|
'A_max': 2.0, # max calcium release (µM)
|
||
|
|
'K_IP3': 0.5, # IP3 half-activation (µM)
|
||
|
|
'n_IP3': 2, # cooperativity for IP3
|
||
|
|
'tau_A_rise': 1.0, # rise time for Ca release (1 s)
|
||
|
|
'tau_A_decay': 5.0, # decay time for Ca (5 s)
|
||
|
|
'A_Ca_rest': 0.1, # resting calcium (µM)
|
||
|
|
# Gliotransmitter release
|
||
|
|
'theta_S': 0.8, # Ca threshold for release (µM)
|
||
|
|
'S_max': 1.0, # max gliotransmitter concentration (a.u.)
|
||
|
|
'tau_S': 1.0, # gliotransmitter decay time (1 s)
|
||
|
|
# Feedback parameters
|
||
|
|
'delta_A1': 0.5, # maximum inhibition of VGCCs (fraction)
|
||
|
|
'K_A1': 0.3, # half-saturation for adenosine inhibition (a.u.)
|
||
|
|
'dserine_max': 0.5, # maximum fractional increase in NMDA conductance
|
||
|
|
'K_D': 0.5, # half-saturation for D-serine modulation (µM)
|
||
|
|
}
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Simulation Setup
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
# Create instances
|
||
|
|
pre = Presynapse(presyn_params)
|
||
|
|
post = Postsynapse(postsyn_params)
|
||
|
|
astro = Astrocyte(astro_params)
|
||
|
|
|
||
|
|
# Simulation parameters
|
||
|
|
dt = 0.0001 # time step = 0.1 ms
|
||
|
|
T_total = 30.0 # simulate 30 seconds
|
||
|
|
time = np.arange(0, T_total, dt)
|
||
|
|
n_steps = len(time)
|
||
|
|
|
||
|
|
# Storage for plotting
|
||
|
|
store = {
|
||
|
|
't': [],
|
||
|
|
'Cf': [], 'Cs': [], 'R': [], 'P': [],
|
||
|
|
'G': [], 'gA': [], 'gN': [], 'Vm': [], 'C_post': [], 'w': [],
|
||
|
|
'I': [], 'A_Ca': [], 'S': [],
|
||
|
|
'AP': []
|
||
|
|
}
|
||
|
|
|
||
|
|
# Define spike times (example: two bursts)
|
||
|
|
spike_times = []
|
||
|
|
# 5 spikes at 20 Hz starting at 1.0 s
|
||
|
|
spike_times.extend(np.linspace(1.0, 1.2, 5, endpoint=False))
|
||
|
|
# 10 spikes at 50 Hz starting at 5.0 s
|
||
|
|
spike_times.extend(np.linspace(5.0, 5.18, 10, endpoint=False))
|
||
|
|
# 3 spikes at 10 Hz starting at 15.0 s
|
||
|
|
spike_times.extend(np.linspace(15.0, 15.2, 3, endpoint=False))
|
||
|
|
spike_times.sort()
|
||
|
|
spike_index = 0
|
||
|
|
next_spike = spike_times[spike_index] if spike_times else np.inf
|
||
|
|
|
||
|
|
# Main simulation loop
|
||
|
|
for i, t in enumerate(time):
|
||
|
|
# Check for AP
|
||
|
|
AP = 0
|
||
|
|
if t >= next_spike:
|
||
|
|
AP = 1
|
||
|
|
spike_index += 1
|
||
|
|
next_spike = spike_times[spike_index] if spike_index < len(spike_times) else np.inf
|
||
|
|
|
||
|
|
# --- Presynapse ---
|
||
|
|
if AP:
|
||
|
|
pre.ap_event()
|
||
|
|
pre.step(dt)
|
||
|
|
|
||
|
|
# --- Astrocyte modulation (feedback) ---
|
||
|
|
# Astrocyte senses glutamate from cleft (post.G)
|
||
|
|
astro.step(dt, post.G)
|
||
|
|
vgcc_factor = astro.get_vgcc_modulation()
|
||
|
|
pre.set_vgcc_modulation(vgcc_factor)
|
||
|
|
dserine_factor = astro.get_dserine_modulation()
|
||
|
|
|
||
|
|
# --- Postsynapse ---
|
||
|
|
# Transfer released NT
|
||
|
|
if AP:
|
||
|
|
post.receive_nt(pre.NT_released)
|
||
|
|
post.step(dt, dserine_factor)
|
||
|
|
|
||
|
|
# Store data every 1 ms to save memory
|
||
|
|
if i % int(0.001/dt) == 0:
|
||
|
|
store['t'].append(t)
|
||
|
|
store['Cf'].append(pre.Cf)
|
||
|
|
store['Cs'].append(pre.Cs)
|
||
|
|
store['R'].append(pre.R)
|
||
|
|
store['P'].append(pre.P)
|
||
|
|
store['G'].append(post.G)
|
||
|
|
store['gA'].append(post.gA)
|
||
|
|
store['gN'].append(post.gN)
|
||
|
|
store['Vm'].append(post.Vm)
|
||
|
|
store['C_post'].append(post.C_post)
|
||
|
|
store['w'].append(post.w)
|
||
|
|
store['I'].append(astro.I)
|
||
|
|
store['A_Ca'].append(astro.A_Ca)
|
||
|
|
store['S'].append(astro.S)
|
||
|
|
store['AP'].append(AP)
|
||
|
|
|
||
|
|
# ============================================================================
|
||
|
|
# Plot Results
|
||
|
|
# ============================================================================
|
||
|
|
|
||
|
|
fig, axes = plt.subplots(4, 1, figsize=(10, 12), sharex=True)
|
||
|
|
|
||
|
|
t_plot = np.array(store['t'])
|
||
|
|
|
||
|
|
# Presynapse
|
||
|
|
axes[0].plot(t_plot, store['Cf'], label='Fast Ca', color='C0')
|
||
|
|
axes[0].plot(t_plot, store['Cs'], label='Slow Ca', color='C1')
|
||
|
|
axes[0].set_ylabel('Ca (µM)')
|
||
|
|
axes[0].legend(loc='upper right')
|
||
|
|
axes[0].set_title('Presynapse')
|
||
|
|
ax2 = axes[0].twinx()
|
||
|
|
ax2.plot(t_plot, store['R'], label='RRP', color='C2', linestyle='--')
|
||
|
|
ax2.plot(t_plot, store['P'], label='RP', color='C3', linestyle='--')
|
||
|
|
ax2.set_ylabel('Vesicles')
|
||
|
|
ax2.legend(loc='lower right')
|
||
|
|
|
||
|
|
# Postsynapse conductances and potential
|
||
|
|
axes[1].plot(t_plot, store['gA'], label='AMPA', color='C4')
|
||
|
|
axes[1].plot(t_plot, store['gN'], label='NMDA', color='C5')
|
||
|
|
axes[1].set_ylabel('Conductance (nS)')
|
||
|
|
axes[1].legend(loc='upper left')
|
||
|
|
ax2 = axes[1].twinx()
|
||
|
|
ax2.plot(t_plot, store['Vm'], label='Vm', color='C6', linestyle='-')
|
||
|
|
ax2.set_ylabel('Vm (mV)')
|
||
|
|
ax2.legend(loc='upper right')
|
||
|
|
|
||
|
|
# Postsynaptic calcium and weight
|
||
|
|
axes[2].plot(t_plot, store['C_post'], label='Post Ca', color='C7')
|
||
|
|
axes[2].set_ylabel('Ca (µM)')
|
||
|
|
axes[2].legend(loc='upper left')
|
||
|
|
ax2 = axes[2].twinx()
|
||
|
|
ax2.plot(t_plot, store['w'], label='Weight w', color='C8', linestyle='--')
|
||
|
|
ax2.set_ylabel('Synaptic weight')
|
||
|
|
ax2.legend(loc='upper right')
|
||
|
|
|
||
|
|
# Astrocyte
|
||
|
|
axes[3].plot(t_plot, store['I'], label='IP3', color='C9')
|
||
|
|
axes[3].plot(t_plot, store['A_Ca'], label='Astro Ca', color='C10')
|
||
|
|
axes[3].plot(t_plot, store['S'], label='Gliotrans.', color='C11', linestyle=':')
|
||
|
|
axes[3].set_ylabel('Conc. (µM / a.u.)')
|
||
|
|
axes[3].set_xlabel('Time (s)')
|
||
|
|
axes[3].legend(loc='upper right')
|
||
|
|
axes[3].set_title('Astrocyte')
|
||
|
|
|
||
|
|
plt.tight_layout()
|
||
|
|
plt.tight_layout()
|
||
|
|
# plt.show() # Comment out or replace
|
||
|
|
plt.savefig('tripartite_simulation.png', dpi=150, bbox_inches='tight')
|
||
|
|
print("Simulation finished. Plot saved as 'tripartite_simulation.png'.")
|
||
|
|
# plt.show()
|