648 lines
32 KiB
Python
648 lines
32 KiB
Python
# Tripartite Synapse - Multi-Scale Computational Model
|
|
|
|
import numpy as np
|
|
|
|
# -----------------------------------------------------------------------
|
|
# PARAMETERS
|
|
# -----------------------------------------------------------------------
|
|
|
|
dt = 1.0 # ms - high-freq timestep
|
|
dt_slow = 1000.0 # ms - astrocyte / slow loop timestep
|
|
dt_meta = 60_000.0 # ms - metabolic loop timestep
|
|
|
|
High_Freq_Multiplier = int(dt_slow / dt) # 1000
|
|
Metabolic_Multiplier = int(dt_meta / dt) # 60000
|
|
|
|
# Unit-conversion scalar — derived once, used wherever a biological rate
|
|
# constant is expressed in /s but the loop timestep is in ms.
|
|
# Multiplying a /s rate by dt_s gives a dimensionless per-step increment:
|
|
# increment = k [/s] * Ca * dt_s [s/step] = dimensionless / step
|
|
dt_s = dt / 1000.0 # ms -> s (= 0.001 s per 1 ms step)
|
|
|
|
# -- Calcium influx & buffering --
|
|
N_VGCC = 100 # ORIG: number of presynaptic VGCCs
|
|
# [CASCADE 6] Sets the absolute ceiling of Ca2+ influx.
|
|
# effective_conductance can only reduce from here.
|
|
V_pre_voltage = -10.0 # ORIG: mV - membrane potential during AP
|
|
k_flux = 0.05 # ORIG: Ca2+ influx per open channel (a.u.)
|
|
|
|
B_total = 1.0 # ORIG: total buffer capacity (normalised)
|
|
# [CASCADE 4] When B_free -> 0 (buffer saturated),
|
|
# all raw_influx enters Ca_micro directly,
|
|
# accelerating residual Ca2+ build-up.
|
|
tau_buffer_rebind = 200.0 # NEW: ms - time for saturated buffer to recharge
|
|
# [CASCADE 4] Slow recharge means buffer stays saturated
|
|
# during sustained bursting, removing its
|
|
# protective role against Ca2+ accumulation.
|
|
|
|
# -- Ca2+ clearance rate constants (per ms) --
|
|
# [CASCADE 3] These three constants define maximum clearance capacity.
|
|
# PMCA and SERCA are multiplied by pump_scale (ATP-gated);
|
|
# NCX runs independently but cannot fully compensate when they fail.
|
|
k_PMCA = 0.03 # NEW: plasma membrane Ca-ATPase - primary high-affinity pump
|
|
# [CASCADE 3] First pump to fail under low ATP. Its rate
|
|
# constant is the largest ATP-dependent term;
|
|
# halving pump_scale loses 0.015/ms of clearance.
|
|
k_NCX = 0.10 # NEW: sodium-calcium exchanger - fast, NOT ATP-dependent
|
|
# [CASCADE 3 note] NCX keeps clearing during pump failure,
|
|
# but k_NCX alone cannot prevent Ca2+
|
|
# accumulation when PMCA + SERCA are both
|
|
# impaired - it is a floor, not a rescue.
|
|
k_SERCA = 0.01 # NEW: ER pump - slowest, ATP-dependent
|
|
# [CASCADE 3] Compounds PMCA failure; also stops loading
|
|
# Ca_ER, so the ER store cannot buffer spikes.
|
|
ATP_half = 0.3 # NEW: Hill half-saturation for ATP-dependent pumps
|
|
# [CASCADE 3] At ATP_level = ATP_half pumps run at 50% speed.
|
|
# Below this value pump failure is disproportionately
|
|
# severe because the Hill curve is steepest here.
|
|
|
|
# -- CDI (calcium-dependent inactivation) --
|
|
k_CDI_rise = 0.8 # ORIG: CDI build rate — units /s per unit Ca_micro
|
|
# Biological rate constant expressed in per-second units.
|
|
# Applied inside map_calcium_to_inactivation via * dt_s.
|
|
# [CASCADE 5] Higher value = VGCCs lock shut sooner at any [Ca2+].
|
|
Ca_micro_saturation = 2.0 # NEW: normalisation ceiling for CDI recovery
|
|
# [CASCADE 5] When Ca_micro = Ca_micro_saturation,
|
|
# CDI_recovery_rate -> 0 completely.
|
|
k_CDI_rec = 0.015 # NEW: CDI de-inactivation rate — units /s
|
|
# Biological rate constant expressed in per-second units.
|
|
# Applied in the loop via * dt_s.
|
|
# [CASCADE 5] Lower value = slower recovery = longer silence
|
|
# window after pump failure.
|
|
|
|
# -- Vesicle pools --
|
|
Max_RRP = 20 # ORIG: maximum readily-releasable pool size
|
|
# [CASCADE 1] Smaller Max_RRP -> exhausted in fewer spikes.
|
|
Max_RP = 200 # ORIG: maximum reserve pool size
|
|
# [CASCADE 1] Once depleted, only the glutamine shuttle
|
|
# (Loop 3, minutes timescale) can refill it.
|
|
p_release_base = 0.1 # ORIG: base release probability per vesicle per ms
|
|
# [CASCADE 1] Multiplied by Ca_micro inside stochastic_release,
|
|
# so higher Ca_micro during a burst draws MORE
|
|
# vesicles per spike - a positive feedback that
|
|
# accelerates RRP depletion.
|
|
|
|
# -- Trace integrator --
|
|
tau_Tr_Ca = 1000.0 # ORIG: ms - calcium trace decay
|
|
T_high = 0.6 # ORIG: trace threshold -> fast recruitment
|
|
T_low = 0.2 # ORIG: trace threshold -> slow recruitment
|
|
k_rec_fast = 0.005 # ORIG: fast RP->RRP recruitment rate (/ms)
|
|
# [CASCADE 1] Even at maximum, recruitment lags release at
|
|
# high firing. When N_RP also falls, the term
|
|
# (rate * N_RP * headroom) shrinks on both sides,
|
|
# accelerating collapse toward N_RRP = 0.
|
|
k_rec_slow = 0.0005 # ORIG: slow RP->RRP recruitment rate (/ms)
|
|
|
|
# -- Postsynaptic --
|
|
tau_membrane = 20.0 # ORIG: ms - membrane time constant
|
|
tau_desensitization = 500.0 # ORIG: ms - receptor desensitization decay
|
|
|
|
# -- eCB retrograde brake --
|
|
tau_eCB_rise = 2000.0 # ORIG: ms - rise time constant
|
|
# [CASCADE 6] eCB is a slow-onset brake (~2 s rise).
|
|
# Provides early partial conductance
|
|
# suppression before CDI lock-out is complete.
|
|
tau_eCB_decay = 10_000.0 # ORIG: ms - decay time constant
|
|
# [CASCADE 6] Slow decay (10 s) means eCB persists as
|
|
# a partial brake even as the burst ends,
|
|
# prolonging the silence window.
|
|
eCB_threshold = 0.7 # ORIG: V_post level that triggers eCB synthesis
|
|
# [CASCADE 6] Requires sustained postsynaptic depolarisation;
|
|
# acts as an integrating safety threshold.
|
|
|
|
# -- mGluR presynaptic autoreceptor --
|
|
Km_mGluR = 0.5 # NEW: Michaelis-Menten half-saturation for NT_cleft
|
|
# [CASCADE 6] Lower Km -> autoreceptor saturates at smaller
|
|
# NT_cleft -> faster VGCC suppression onset.
|
|
tau_mGluR = 2000.0 # NEW: ms - mGluR activation / desensitization time constant
|
|
# [CASCADE 6] Governs how quickly the autoreceptor engages
|
|
# and releases as NT_cleft rises and falls.
|
|
alpha_mGluR = 0.4 # NEW: max fractional VGCC suppression by mGluR
|
|
# [CASCADE 6] Upper bound of this term. Combined with eCB,
|
|
# both brakes together can suppress ~70% of
|
|
# conductance before CDI becomes significant.
|
|
|
|
# -- Astrocyte / IP3 --
|
|
tau_IP3 = 3000.0 # ORIG: ms
|
|
IP3_threshold = 0.8 # ORIG
|
|
|
|
# -- Glutamine shuttle --
|
|
conversion_efficiency_base = 0.8 # ORIG: fraction of Gln pool converted per cycle
|
|
# [CASCADE 1] Sets max RP-refill rate.
|
|
# Degraded by metabolic fatigue via
|
|
# conversion_efficiency in Loop 3.
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# HELPER FUNCTIONS
|
|
# -----------------------------------------------------------------------
|
|
|
|
def compute_flux(conductance, voltage):
|
|
# ORIG: Ca2+ influx into microdomain.
|
|
# [CASCADE 4] Raw influx that, after buffer capture, loads Ca_micro.
|
|
# Collapses to near zero once CASCADE 6 locks effective_conductance.
|
|
return k_flux * conductance * abs(voltage)
|
|
|
|
|
|
def stochastic_release(N_RRP, Ca_micro):
|
|
# ORIG: Binomial vesicle release from RRP.
|
|
# [CASCADE 1] p scales with Ca_micro: higher Ca_micro during the early burst
|
|
# draws MORE vesicles per spike than at rest - a positive feedback
|
|
# that accelerates RRP depletion beyond what recruitment can match.
|
|
p = min(1.0, p_release_base * Ca_micro)
|
|
return int(np.random.binomial(int(N_RRP), p))
|
|
|
|
|
|
def map_trace_to_speed(Tr_Ca):
|
|
# ORIG: Maps calcium trace to RP->RRP recruitment rate.
|
|
# [CASCADE 1] Even at maximum (k_rec_fast = 0.005/ms), recruitment lags
|
|
# release at high firing. As N_RP and headroom both fall,
|
|
# refill_amount shrinks and N_RRP collapses.
|
|
if Tr_Ca > T_high:
|
|
return k_rec_fast
|
|
elif Tr_Ca < T_low:
|
|
return k_rec_slow
|
|
else:
|
|
t = (Tr_Ca - T_low) / (T_high - T_low)
|
|
return k_rec_slow + t * (k_rec_fast - k_rec_slow)
|
|
|
|
|
|
def map_calcium_to_inactivation(Ca_micro):
|
|
# ORIG: Ca2+ drives CDI increment each ms.
|
|
# k_CDI_rise is in /s. Multiplying by dt_s converts it to a
|
|
# dimensionless per-step increment:
|
|
# increment = k_CDI_rise [/s] * Ca_micro * dt_s [s/step]
|
|
# Using dt_s here (not dt) makes the unit conversion explicit and
|
|
# avoids the implicit /1000 anti-pattern.
|
|
# [CASCADE 5] Fires every ms that Ca_micro > 0, including inter-spike
|
|
# intervals under pump failure. Result: CDI_factor accumulates
|
|
# across spikes instead of resetting, climbing toward 1.
|
|
return k_CDI_rise * Ca_micro * dt_s
|
|
|
|
|
|
def compute_pump_atp_factor(ATP_level):
|
|
# NEW: Hill function - ATP gates PMCA and SERCA speed.
|
|
# [CASCADE 3] Mechanistic bridge from CASCADE 2 (ATP_level, written by
|
|
# Loop 3 once per minute) to CASCADE 4 (Ca_micro staying elevated).
|
|
# ATP_level = 1.0 -> pump_scale ~= 0.92 (near full speed)
|
|
# ATP_level = 0.3 -> pump_scale = 0.50 (half speed)
|
|
# ATP_level = 0.1 -> pump_scale ~= 0.10 (severe failure)
|
|
# Hill exponent of 2 gives a sigmoidal response: small drops
|
|
# have modest effect; drops below ATP_half are disproportionate.
|
|
return (ATP_level ** 2) / (ATP_level ** 2 + ATP_half ** 2)
|
|
|
|
|
|
def compute_EPSP(receptor_conductance):
|
|
# ORIG: postsynaptic potential increment.
|
|
return receptor_conductance * 0.1
|
|
|
|
|
|
def compute_postsynaptic_eCB_signal(V_post_history):
|
|
# ORIG: eCB synthesis triggered by sustained high postsynaptic activity.
|
|
# [CASCADE 6] Integrates V_post over a 2 s window. Acts as a slow retrograde
|
|
# warning signal; its tau_eCB_decay of 10 s means it outlasts
|
|
# the burst and keeps conductance partially suppressed during recovery.
|
|
recent_mean = (np.mean(V_post_history[-2000:])
|
|
if len(V_post_history) >= 2000
|
|
else np.mean(V_post_history))
|
|
if recent_mean > eCB_threshold:
|
|
return recent_mean - eCB_threshold
|
|
return 0.0
|
|
|
|
|
|
def compute_astrocyte_metabolic_health(Glucose_level, step):
|
|
# ORIG + NEW ATP output.
|
|
# [CASCADE 2] Glucose_level is the root upstream variable of the slow cascade.
|
|
# Under sustained high firing, demand outpaces vascular delivery
|
|
# and Glucose_level falls. This function converts that into:
|
|
#
|
|
# ATP_level -> [CASCADE 2->3 bridge]
|
|
# Read every ms by compute_pump_atp_factor() in Loop 1.
|
|
# This is how minute-scale metabolic state reaches the
|
|
# millisecond Ca2+ clearance equations.
|
|
#
|
|
# conversion_efficiency -> [CASCADE 1, slow refill arm]
|
|
# Gates glutamine shuttle throughput; low efficiency starves
|
|
# N_RP refill, prolonging vesicle depletion.
|
|
#
|
|
# Both outputs degrade together as Glucose_level -> 0,
|
|
# tightening CASCADE 1 and CASCADE 3-5 simultaneously.
|
|
health = np.clip(Glucose_level, 0.0, 1.0)
|
|
return health, health # (conversion_efficiency, ATP_level)
|
|
|
|
|
|
def trigger_slow_astrocyte_calcium_wave():
|
|
# ORIG: placeholder - would release gliotransmitters over ~10 s.
|
|
pass
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# STATE VARIABLES (initial values)
|
|
# -----------------------------------------------------------------------
|
|
|
|
# -- Presynaptic Ca2+ --
|
|
Ca_micro = 0.0 # ORIG: free cytosolic [Ca2+] in microdomain
|
|
# [CASCADE 4] Central victim variable.
|
|
# Normally returns to ~0 between spikes;
|
|
# stays elevated when pumps fail.
|
|
Ca_ER = 0.5 # NEW: Ca2+ stored in ER (loaded by SERCA)
|
|
Ca_buffer_bound = 0.0 # NEW: Ca2+ currently held by buffer proteins
|
|
B_free = 1.0 # NEW: free buffer binding sites (= B_total at rest)
|
|
# [CASCADE 4] B_free -> 0 during sustained bursting.
|
|
# Once saturated, capture_fraction -> 0 and
|
|
# raw_influx enters Ca_micro unattenuated.
|
|
|
|
# -- CDI --
|
|
CDI_factor = 0.0 # ORIG: fractional VGCC inactivation (0 -> 1)
|
|
# [CASCADE 5] Primary silence variable; rises with Ca_micro,
|
|
# recovery suppressed by Ca_micro.
|
|
# [CASCADE 6] Appears as (1 - CDI_factor) in effective_conductance;
|
|
# when CDI_factor -> 1 conductance collapses to 0.
|
|
|
|
# -- Vesicle pools --
|
|
N_RRP = 15 # ORIG: readily-releasable pool (docked vesicles)
|
|
# [CASCADE 1] Depleted in seconds at high firing rate.
|
|
N_RP = 150 # ORIG: reserve pool
|
|
# [CASCADE 1] Depleted over tens of seconds; only the glutamine
|
|
# shuttle (Loop 3) can refill it on minute timescales.
|
|
|
|
# -- Calcium trace --
|
|
Tr_Ca = 0.0 # ORIG: integrative calcium memory; drives RP->RRP recruitment speed.
|
|
|
|
# -- NT in cleft --
|
|
NT_cleft = 0.0 # ORIG: normalised NT concentration
|
|
# [CASCADE 6] Drives mGluR_activation (fastest conductance brake).
|
|
|
|
# -- Postsynaptic --
|
|
V_post = 0.0 # ORIG
|
|
receptor_conductance = 0.0 # ORIG
|
|
Desensitization_level = 0.0 # ORIG
|
|
V_post_history = [] # ORIG: rolling buffer for eCB computation
|
|
|
|
# -- Retrograde / autoreceptor --
|
|
eCB_level = 0.0 # ORIG: endocannabinoid retrograde brake (0 -> 1)
|
|
# [CASCADE 6] Second multiplicative suppressor of
|
|
# effective_conductance. Slow onset (~2 s),
|
|
# slow decay (~10 s).
|
|
mGluR_activation = 0.0 # NEW: presynaptic autoreceptor occupancy (0 -> 1)
|
|
# [CASCADE 6] Third multiplicative suppressor. Fastest
|
|
# onset because it reads NT_cleft directly,
|
|
# no postsynaptic relay required.
|
|
|
|
# -- Astrocyte --
|
|
IP3 = 0.0 # ORIG
|
|
Glutamine_pool = 50.0 # ORIG
|
|
|
|
# -- Metabolic --
|
|
ATP_level = 1.0 # NEW: normalised ATP (1 = replete)
|
|
# [CASCADE 2->3] Bridge variable: written by Loop 3
|
|
# (minutes), read every ms in Loop 1
|
|
# via compute_pump_atp_factor().
|
|
conversion_efficiency = 0.8 # ORIG
|
|
Glucose_level = 1.0 # ORIG: external glucose supply (0 -> 1)
|
|
# [CASCADE 2] Root input of the slow cascade arm.
|
|
# Set < 1.0 to activate metabolic silencing.
|
|
# Try 0.2 to observe full pump failure.
|
|
|
|
# -- NT cleft decay --
|
|
tau_NT_decay = 5.0 # ms - passive NT diffusion / dilution out of cleft
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# MAIN SIMULATION LOOP
|
|
# -----------------------------------------------------------------------
|
|
|
|
def run_simulation(spike_train, total_steps):
|
|
"""
|
|
spike_train : list of int timestep indices at which an AP arrives
|
|
total_steps : int number of 1 ms steps to simulate
|
|
"""
|
|
|
|
global Ca_micro, Ca_ER, Ca_buffer_bound, B_free
|
|
global CDI_factor
|
|
global N_RRP, N_RP
|
|
global Tr_Ca, NT_cleft
|
|
global V_post, receptor_conductance, Desensitization_level, V_post_history
|
|
global eCB_level, mGluR_activation
|
|
global IP3, Glutamine_pool
|
|
global ATP_level, conversion_efficiency, Glucose_level
|
|
|
|
log = {k: [] for k in [
|
|
"Ca_micro", "Ca_ER", "CDI_factor", "B_free",
|
|
"N_RRP", "N_RP", "Tr_Ca", "NT_cleft",
|
|
"V_post", "eCB_level", "mGluR_activation",
|
|
"released_NT", "ATP_level",
|
|
]}
|
|
|
|
spike_set = set(spike_train)
|
|
|
|
for step in range(total_steps):
|
|
|
|
# ==============================================================
|
|
# LOOP 1 - HIGH-FREQUENCY (dt = 1 ms)
|
|
# ==============================================================
|
|
|
|
V_pre = 1 if step in spike_set else 0
|
|
released_NT = 0
|
|
|
|
# -- 1A. PRESYNAPTIC SPIKE HANDLING ----------------------------
|
|
if V_pre == 1:
|
|
|
|
# [CASCADE 6] OUTCOME of the full cascade.
|
|
# All three suppression terms must be near zero
|
|
# for significant Ca2+ influx to occur.
|
|
#
|
|
# Onset timing during a burst:
|
|
# mGluR*alpha_mGluR rises first (~seconds, reads NT_cleft)
|
|
# eCB_level rises second (~2-10 s, retrograde)
|
|
# CDI_factor locks last but is irreversible until
|
|
# Ca_micro falls (needs pump recovery)
|
|
effective_conductance = (
|
|
N_VGCC
|
|
* (1.0 - eCB_level) # [CASCADE 6] retrograde brake
|
|
* (1.0 - CDI_factor) # [CASCADE 5->6] CDI lock-out
|
|
* (1.0 - mGluR_activation * alpha_mGluR) # [CASCADE 6] autoreceptor brake
|
|
)
|
|
|
|
raw_influx = compute_flux(effective_conductance, V_pre_voltage)
|
|
|
|
# NEW: Buffer proteins capture a fraction of raw_influx immediately.
|
|
# [CASCADE 4] capture_fraction = B_free / B_total.
|
|
# Early in a burst B_free ~= B_total so most influx is
|
|
# captured and Ca_micro rises slowly (buffer is protective).
|
|
# As bursting continues B_free -> 0 (buffer saturates),
|
|
# capture_fraction -> 0 and full raw_influx enters Ca_micro,
|
|
# accelerating the residual Ca2+ accumulation of CASCADE 4.
|
|
capture_fraction = B_free / B_total
|
|
captured = raw_influx * capture_fraction
|
|
B_free = max(0.0, B_free - captured)
|
|
Ca_buffer_bound += captured
|
|
Ca_micro += (raw_influx - captured)
|
|
|
|
# ORIG: NT release from RRP.
|
|
# [CASCADE 1] Each spike draws from N_RRP via stochastic_release.
|
|
# Draw rate (p * N_RRP) exceeds refill rate
|
|
# (k_rec_fast * N_RP * headroom) at high firing,
|
|
# driving N_RRP -> 0.
|
|
if N_RRP > 0:
|
|
released_NT = stochastic_release(N_RRP, Ca_micro)
|
|
N_RRP = max(0, N_RRP - released_NT) # floor guard
|
|
NT_cleft += released_NT
|
|
|
|
# -- 1B. Ca2+ CLEARANCE (runs every ms, spike or not) ----------
|
|
|
|
# [CASCADE 3] pump_scale links CASCADE 2 (ATP depletion) to
|
|
# CASCADE 4 (residual Ca2+ accumulation).
|
|
# Written from ATP_level which is updated once per minute
|
|
# in Loop 3; read here every millisecond.
|
|
pump_scale = compute_pump_atp_factor(ATP_level)
|
|
|
|
# [CASCADE 3->4] PMCA: primary high-affinity Ca2+ extrusion pump.
|
|
# ATP-dependent. First pump to be impaired; largest
|
|
# single loss of clearance capacity when pump_scale drops.
|
|
cleared_PMCA = k_PMCA * Ca_micro * pump_scale
|
|
|
|
# [CASCADE 3 note] NCX: fast, NOT ATP-dependent.
|
|
# Continues clearing during metabolic failure.
|
|
# Acts as a partial floor but cannot alone prevent
|
|
# Ca2+ accumulation when PMCA + SERCA are impaired.
|
|
# Also enables the auto-reset: once high-frequency
|
|
# drive stops, NCX gradually lowers Ca_micro and
|
|
# allows CDI recovery even without restored ATP.
|
|
cleared_NCX = k_NCX * Ca_micro
|
|
|
|
# [CASCADE 3->4] SERCA: ER pump, ATP-dependent.
|
|
# Compounds PMCA failure. Also stops loading Ca_ER,
|
|
# so the ER store cannot buffer subsequent spikes.
|
|
cleared_SERCA = k_SERCA * Ca_micro * pump_scale
|
|
|
|
Ca_micro -= (cleared_PMCA + cleared_NCX + cleared_SERCA)
|
|
Ca_micro = max(0.0, Ca_micro)
|
|
|
|
Ca_ER += cleared_SERCA # SERCA fills the ER store when ATP is available
|
|
|
|
# NEW: Buffer recharge - bound Ca2+ slowly re-releases back to cytosol.
|
|
# [CASCADE 4] During pump failure, released buffer Ca2+ is not promptly
|
|
# cleared, so it adds to Ca_micro and sustains its elevation
|
|
# between spikes - a secondary positive feedback within CASCADE 4.
|
|
rebind_flux = Ca_buffer_bound * dt / tau_buffer_rebind
|
|
Ca_micro += rebind_flux
|
|
Ca_buffer_bound = max(0.0, Ca_buffer_bound - rebind_flux)
|
|
B_free = B_total - Ca_buffer_bound
|
|
|
|
# -- 1C. CDI - RISE AND RECOVERY --------------------------------
|
|
|
|
# [CASCADE 5] CDI increment: fires every ms proportional to Ca_micro.
|
|
# Under normal conditions Ca_micro returns to ~0 between
|
|
# spikes and CDI is balanced by recovery below.
|
|
# Under pump failure (CASCADE 4) Ca_micro stays > 0
|
|
# between spikes, so this increment fires continuously
|
|
# and CDI_factor climbs monotonically toward 1.
|
|
CDI_factor += map_calcium_to_inactivation(Ca_micro)
|
|
|
|
# [CASCADE 5] CDI recovery: maximised when Ca_micro = 0, suppressed
|
|
# as Ca_micro approaches Ca_micro_saturation.
|
|
# This is the self-locking mechanism of the cascade:
|
|
# pump failure -> Ca_micro stays high
|
|
# Ca_micro high -> CDI_recovery_rate -> 0
|
|
# CDI no recovery -> CDI_factor -> 1
|
|
# CDI_factor -> 1 -> effective_conductance -> 0 (CASCADE 6)
|
|
# k_CDI_rec is in /s; multiply by dt_s for a per-step decrement.
|
|
CDI_recovery_rate = k_CDI_rec * (1.0 - Ca_micro / Ca_micro_saturation)
|
|
CDI_factor = np.clip(CDI_factor - CDI_recovery_rate * dt_s, 0.0, 1.0)
|
|
|
|
# -- 1D. TRACE INTEGRATOR --------------------------------------
|
|
# ORIG: integrates Ca_micro; drives RP->RRP recruitment speed.
|
|
Tr_Ca = Tr_Ca + (Ca_micro - Tr_Ca / tau_Tr_Ca) * dt
|
|
|
|
# -- 1E. RP -> RRP RECRUITMENT (with pool guards) --------------
|
|
# [CASCADE 1] Only counter-force to vesicle depletion.
|
|
# refill_amount = rate * N_RP * (Max_RRP - N_RRP)
|
|
# As N_RP falls (reservoir empties) and N_RRP -> 0
|
|
# (headroom = Max_RRP, maximised), the N_RP term collapses
|
|
# and refill becomes negligible - depletion becomes
|
|
# self-sustaining once N_RP is exhausted.
|
|
current_recruitment_rate = map_trace_to_speed(Tr_Ca)
|
|
refill_amount = current_recruitment_rate * N_RP * (Max_RRP - N_RRP)
|
|
refill_amount = max(0.0, refill_amount) # never negative
|
|
refill_amount = min(refill_amount, N_RP) # cannot exceed RP supply
|
|
|
|
N_RRP = min(N_RRP + refill_amount, Max_RRP) # ceiling guard
|
|
N_RP = max(0.0, N_RP - refill_amount) # floor guard
|
|
|
|
# -- 1F. POSTSYNAPTIC FAST RESPONSE ----------------------------
|
|
# ORIG: receptor activation and desensitization.
|
|
effective_NT = released_NT * (1.0 - Desensitization_level)
|
|
receptor_conductance += effective_NT * 0.05
|
|
receptor_conductance *= (1.0 - dt / tau_membrane)
|
|
|
|
V_post += compute_EPSP(receptor_conductance) - (V_post / tau_membrane) * dt
|
|
V_post = max(0.0, V_post)
|
|
|
|
Desensitization_level += NT_cleft * 0.001 * dt
|
|
Desensitization_level -= (Desensitization_level / tau_desensitization) * dt
|
|
Desensitization_level = np.clip(Desensitization_level, 0.0, 1.0)
|
|
|
|
# ORIG: NT diffuses / dilutes out of cleft each ms.
|
|
NT_cleft *= (1.0 - dt / tau_NT_decay)
|
|
NT_cleft = max(0.0, NT_cleft)
|
|
|
|
V_post_history.append(V_post)
|
|
if len(V_post_history) > 5000:
|
|
V_post_history.pop(0)
|
|
|
|
# -- RECORD -----------------------------------------------------
|
|
log["Ca_micro"].append(Ca_micro)
|
|
log["Ca_ER"].append(Ca_ER)
|
|
log["CDI_factor"].append(CDI_factor)
|
|
log["B_free"].append(B_free)
|
|
log["N_RRP"].append(N_RRP)
|
|
log["N_RP"].append(N_RP)
|
|
log["Tr_Ca"].append(Tr_Ca)
|
|
log["NT_cleft"].append(NT_cleft)
|
|
log["V_post"].append(V_post)
|
|
log["eCB_level"].append(eCB_level)
|
|
log["mGluR_activation"].append(mGluR_activation)
|
|
log["released_NT"].append(released_NT)
|
|
log["ATP_level"].append(ATP_level)
|
|
|
|
# ==============================================================
|
|
# LOOP 2 - SLOW / ASTROCYTE (dt_slow = 1 s)
|
|
# ==============================================================
|
|
|
|
if (step % High_Freq_Multiplier) == 0:
|
|
|
|
# ORIG: Astrocyte clears NT from cleft via EAATs.
|
|
cleared_NT = NT_cleft * 0.3
|
|
NT_cleft = max(0.0, NT_cleft - cleared_NT)
|
|
|
|
# ORIG: IP3 integrates clearance load.
|
|
IP3 += cleared_NT - (IP3 / tau_IP3) * dt_slow
|
|
IP3 = max(0.0, IP3)
|
|
|
|
if IP3 > IP3_threshold:
|
|
trigger_slow_astrocyte_calcium_wave()
|
|
|
|
# ORIG: eCB retrograde signal.
|
|
# [CASCADE 6] Second conductance brake. Onset ~2 s after sustained
|
|
# postsynaptic activity crosses eCB_threshold. Its slow
|
|
# decay (10 s) means it persists as a partial brake
|
|
# during the recovery window, preventing premature
|
|
# re-engagement of the synapse after a silence event.
|
|
eCB_signal = compute_postsynaptic_eCB_signal(V_post_history)
|
|
if eCB_signal > 0:
|
|
eCB_level += eCB_signal * (dt_slow / tau_eCB_rise)
|
|
else:
|
|
eCB_level -= eCB_level * (dt_slow / tau_eCB_decay)
|
|
eCB_level = np.clip(eCB_level, 0.0, 1.0)
|
|
|
|
# NEW: mGluR presynaptic autoreceptor feedback.
|
|
# [CASCADE 6] Fastest conductance brake. Reads NT_cleft directly
|
|
# (no postsynaptic relay), so it rises within seconds
|
|
# of burst onset when NT_cleft is highest.
|
|
# At saturation it suppresses up to alpha_mGluR = 40%
|
|
# of conductance, acting as the first partial brake
|
|
# and slightly slowing Ca2+ influx before CDI builds.
|
|
# Decays rapidly when NT_cleft falls (e.g. after vesicle
|
|
# depletion in CASCADE 1), so it does not contribute to
|
|
# the irreversible lock-out - that role belongs to CDI.
|
|
mGluR_target = NT_cleft / (NT_cleft + Km_mGluR)
|
|
mGluR_activation += (mGluR_target - mGluR_activation) * (dt_slow / tau_mGluR)
|
|
mGluR_activation = np.clip(mGluR_activation, 0.0, 1.0)
|
|
|
|
# ==============================================================
|
|
# LOOP 3 - METABOLIC (dt_meta = 1 min)
|
|
# ==============================================================
|
|
|
|
if (step % Metabolic_Multiplier) == 0:
|
|
|
|
# [CASCADE 2] Root of the slow cascade arm.
|
|
# Glucose_level is the external input that sets the
|
|
# pace of ATP replenishment. Under sustained demand
|
|
# it falls, reducing ATP_level.
|
|
#
|
|
# ATP_level written here is the CASCADE 2->3 bridge:
|
|
# it is read every millisecond in Loop 1 by
|
|
# compute_pump_atp_factor(), carrying the minute-scale
|
|
# metabolic state into the Ca2+ clearance equations.
|
|
#
|
|
# conversion_efficiency (also written here) is the
|
|
# CASCADE 1 slow-refill arm: low efficiency starves
|
|
# N_RP, making vesicle depletion more severe and
|
|
# prolonged after each burst episode.
|
|
conversion_efficiency, ATP_level = compute_astrocyte_metabolic_health(
|
|
Glucose_level, step
|
|
)
|
|
|
|
# [CASCADE 1 - slow refill] Glutamine shuttle rebuilds N_RP.
|
|
# Gated by conversion_efficiency; when low, N_RP cannot
|
|
# recover between bursts, so successive episodes deplete
|
|
# faster - a cumulative fatigue effect.
|
|
refill_RP = Glutamine_pool * conversion_efficiency
|
|
N_RP = min(Max_RP, N_RP + refill_RP)
|
|
Glutamine_pool = max(0.0, Glutamine_pool - refill_RP)
|
|
|
|
return log
|
|
|
|
|
|
# -----------------------------------------------------------------------
|
|
# EXAMPLE USAGE
|
|
# -----------------------------------------------------------------------
|
|
|
|
if __name__ == "__main__":
|
|
import matplotlib.pyplot as plt
|
|
|
|
total_steps = 10_000 # 10 seconds of simulated time
|
|
|
|
# 20 Hz burst for 2 s, then silence.
|
|
# Set Glucose_level = 0.2 before calling run_simulation() to engage
|
|
# the full metabolic cascade (CASCADES 2-5).
|
|
spike_train = list(range(0, 2000, 50))
|
|
|
|
results = run_simulation(spike_train, total_steps)
|
|
|
|
t = np.arange(total_steps) * dt
|
|
|
|
fig, axes = plt.subplots(6, 1, figsize=(12, 14), sharex=True)
|
|
fig.suptitle("Tripartite Synapse - Metabolic Silencing Cascade", fontsize=13)
|
|
|
|
axes[0].plot(t, results["Ca_micro"], color="darkorange", lw=0.8)
|
|
axes[0].set_ylabel("[Ca2+] free (a.u.)")
|
|
axes[0].set_title("CASCADE 4 - residual Ca2+ stays high under pump failure",
|
|
fontsize=9, loc="left")
|
|
|
|
axes[1].plot(t, results["CDI_factor"], color="firebrick", lw=0.8, label="CDI factor")
|
|
axes[1].plot(t, results["B_free"], color="steelblue", lw=0.8, label="Buffer free")
|
|
axes[1].set_ylabel("CDI / Buffer (0-1)")
|
|
axes[1].set_title("CASCADE 5 - CDI lock-out | CASCADE 4 secondary - buffer saturation",
|
|
fontsize=9, loc="left")
|
|
axes[1].legend(fontsize=8)
|
|
|
|
axes[2].plot(t, results["N_RRP"], color="teal", lw=0.8, label="RRP")
|
|
axes[2].plot(t, results["N_RP"], color="purple", lw=0.8, label="RP")
|
|
axes[2].set_ylabel("Vesicles")
|
|
axes[2].set_title("CASCADE 1 - vesicle depletion (fast)", fontsize=9, loc="left")
|
|
axes[2].legend(fontsize=8)
|
|
|
|
axes[3].plot(t, results["NT_cleft"], color="darkgreen", lw=0.8, label="NT cleft")
|
|
axes[3].plot(t, results["mGluR_activation"], color="saddlebrown", lw=0.8, label="mGluR (CASCADE 6)")
|
|
axes[3].plot(t, results["eCB_level"], color="crimson", lw=0.8, label="eCB (CASCADE 6)")
|
|
axes[3].set_ylabel("Cleft / Feedback")
|
|
axes[3].set_title("CASCADE 6 - three multiplicative brakes on effective_conductance",
|
|
fontsize=9, loc="left")
|
|
axes[3].legend(fontsize=8)
|
|
|
|
axes[4].plot(t, results["V_post"], color="navy", lw=0.8)
|
|
axes[4].set_ylabel("V_post (a.u.)")
|
|
axes[4].set_title("CASCADE 6 result - postsynaptic silence", fontsize=9, loc="left")
|
|
|
|
axes[5].plot(t, results["ATP_level"], color="goldenrod", lw=0.8)
|
|
axes[5].set_ylabel("ATP level (0-1)")
|
|
axes[5].set_title("CASCADE 2 - set Glucose_level < 1.0 to activate this arm",
|
|
fontsize=9, loc="left")
|
|
axes[5].set_xlabel("Time (ms)")
|
|
|
|
plt.tight_layout()
|
|
plt.savefig("./synapse_simulation.png", dpi=150)
|
|
plt.close()
|
|
print("Done.") |