codex/synaptic-conductance-and-shunting.py

synaptic-conductance-and-shunting.py
"""Passive conductance illustrations; writes basename.png in the CWD.

Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
Illustrative constant steps; no action-potential generation or reset.
Units: mV, ms, pF, nS and pA, so pF/nS = ms and nS*mV = pA.
"""
from pathlib import Path

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np

BLUE = "#23779a"
ORANGE = "#ba5418"
PURPLE = "#7950a1"
GREEN = "#328164"
INK = "#243446"


def voltage(t, ge=0., gi=0., ei=-65., current=0.):
    """Exact constant-coefficient solution, starting at leak rest."""
    capacitance, gl, el, ee, v0 = 200., 10., -65., 0., -65.
    total = gl+ge+gi
    steady = (gl*el+ge*ee+gi*ei+current)/total
    tau = capacitance/total
    return steady+(v0-steady)*np.exp(-t/tau)


def main():
    plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 10,
                         "axes.spines.top": False, "axes.spines.right": False})
    fig = plt.figure(figsize=(11, 6.8), facecolor="white")
    grid = fig.add_gridspec(2, 2, width_ratios=[1, 1.3], hspace=.49, wspace=.32)
    currents = fig.add_subplot(grid[:, 0])
    responses = fig.add_subplot(grid[0, 1])
    probe = fig.add_subplot(grid[1, 1])
    fig.suptitle("Synaptic conductance: driving force and shunting", fontsize=15, y=.97)
    v = np.linspace(-85., 10., 600)
    for e, color in [(0., BLUE), (-60., PURPLE), (-65., ORANGE), (-75., GREEN)]:
        currents.plot(v, e-v, color=color, lw=2, label=f"E = {e:g} mV")
        currents.plot(e, 0, "o", color=color, ms=5)
    currents.axhline(0, color="#7b8590", lw=.8)
    currents.axvline(-65, color="#8793a0", ls=":", lw=1)
    currents.set(title="A. Current through 1 nS", xlabel="Membrane potential V (mV)",
                 ylabel="Inward-positive current g(E − V) (pA)", xlim=(-85, 10), ylim=(-90, 90))
    currents.legend(loc="upper right", frameon=True, fontsize=9)
    currents.text(-82, 82, "Positive: inward current", fontsize=9, color=INK)
    currents.text(-82, -83, "Negative: outward current", fontsize=9, color=INK)
    currents.annotate("Zero current at E\ncan still mean a shunt", xy=(-65, 0), xytext=(-82, -42),
                      arrowprops={"arrowstyle": "->", "color": INK}, fontsize=9, color=INK)
    currents.grid(alpha=.16)

    t = np.linspace(0, 60, 601)
    curves = [
        (dict(ge=2), BLUE, "-", "Excitatory alone"),
        (dict(ge=2, gi=20), ORANGE, "-", "Excitatory + rest shunt"),
        (dict(gi=20, ei=-60), PURPLE, "--", "Depolarizing alone"),
        (dict(ge=2, gi=20, ei=-60), PURPLE, "-", "Excitatory + depolarizing"),
        (dict(gi=20, ei=-75), GREEN, "-", "Hyperpolarizing alone"),
    ]
    for params, color, style, name in curves:
        responses.plot(t, voltage(t, **params), color=color, ls=style, lw=2, label=name)
    responses.set_title("B. Synaptic conductance steps", loc="left", pad=62)
    responses.axhline(-65, color="#8793a0", ls=":", lw=1)
    responses.set(xlabel="Time after opening (ms)",
                  ylabel="V (mV)", xlim=(0, 60), ylim=(-73, -52))
    responses.legend(loc="lower center", bbox_to_anchor=(.5, 1.14),
                     fontsize=8.3, frameon=False, ncol=2, borderaxespad=0)
    responses.grid(alpha=.16)

    probe.plot(t, voltage(t, current=100), color=BLUE, lw=2, label="100 pA; no shunt")
    probe.plot(t, voltage(t, current=100, gi=20), color=ORANGE, lw=2,
               label="100 pA + rest shunt")
    probe.plot(t, voltage(t, gi=20), color=ORANGE, lw=1.6, ls="--", label="Rest shunt alone")
    probe.set(title="C. Same test current, different voltage response", xlabel="Time after step (ms)",
              ylabel="V (mV)", xlim=(0, 60), ylim=(-66, -54))
    probe.text(59, -55.6, "No shunt: 10 mV steady rise; τ = 20 ms", color=BLUE, ha="right", fontsize=9)
    probe.text(59, -60.8, "With shunt: 3.33 mV steady rise; τ = 6.67 ms", color=ORANGE, ha="right", fontsize=9)
    probe.text(3, -64.65, "Rest shunt alone: no voltage change",
               color=ORANGE, fontsize=9)
    probe.grid(alpha=.16)
    fig.text(.5, .025, "Illustrative passive model: C = 200 pF, leak = 10 nS at −65 mV; "
             "synaptic steps: gₑ = 2 nS at 0 mV, gᵢ = 20 nS • no spike/reset",
             ha="center", color=INK, fontsize=9)
    fig.subplots_adjust(left=.075, right=.98, top=.78, bottom=.12)
    fig.savefig(Path(__file__).with_suffix(".png").name, dpi=110, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()