# 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.")