codexion-channel-current-and-reversal.py
ion-channel-current-and-reversal.py
"""Ohmic toy currents; writes an opaque PNG in the current directory.
Tested with Python 3.14.4, NumPy 2.3.5 and Matplotlib 3.10.7.
All parameters are stipulated, not empirical. V = inside minus outside;
conventional current is inward-positive. Units: nS * mV = pA.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
BLUE = "#207ea2"
ORANGE = "#bc5b22"
INK = "#263749"
def current(voltage, conductance, reversal):
"""Inward-positive current in pA for mV and nS inputs."""
return conductance * (reversal - np.asarray(voltage))
def main():
plt.rcParams.update({"font.family": "DejaVu Sans", "font.size": 10,
"axes.spines.top": False, "axes.spines.right": False})
fig, (single, pair) = plt.subplots(1, 2, figsize=(11.0, 5.5), facecolor="white")
fig.subplots_adjust(left=.085, right=.98, top=.75, bottom=.2, wspace=.3)
fig.suptitle("Reversal means zero NET current; ion movement can continue", y=.97,
fontsize=15, color=INK)
fig.text(.5, .9, "V = inside − outside • conventional current: inward positive, outward negative",
ha="center", fontsize=11, color=INK)
v = np.linspace(-115., 60., 701)
for ax in [single, pair]:
ax.axhline(0, color="#82909c", lw=.9)
ax.set(xlabel="Membrane potential V (mV)",
ylabel="Inward-positive current (pA)")
ax.grid(alpha=.17)
single.plot(v, current(v, 10., -80.), color=BLUE, lw=2.4)
single.set(title="A. One Ohmic pathway: g = 10 nS, E = −80 mV",
xlim=(-115, -45), ylim=(-400, 400))
single.set_xticks([-110, -100, -80, -60, -50])
for probe, label, delta in [(-100., "+200 pA", (5, 8)),
(-80., "0 pA", (9, 10)),
(-60., "−200 pA", (-62, -22))]:
y = current(probe, 10., -80.)
single.plot(probe, y, "o", color=BLUE, ms=6)
single.annotate(label, (probe, y), xytext=delta, textcoords="offset points",
color=BLUE, fontsize=10)
single.axvline(-80, color=BLUE, ls=":", lw=1)
single.text(-112, 335, "Positive: net positive charge enters", fontsize=9, color=INK)
single.text(-112, -355, "Negative: net positive charge leaves", fontsize=9, color=INK)
balance = (10.*-80. + 2.*50.) / 12.
pair.plot(v, current(v, 10., -80.), color=ORANGE, lw=2,
label="K: 10 nS, E = −80 mV")
pair.plot(v, current(v, 2., 50.), color=BLUE, lw=2,
label="Na: 2 nS, E = +50 mV")
pair.plot(v, current(v, 10., -80.) + current(v, 2., 50.),
color=INK, lw=2.2, ls="--", label="Total: 12 nS, E = −58.3333 mV")
pair.set(title="B. Two pathways across the same membrane",
xlim=(-110, 60), ylim=(-1500, 650))
pair.set_xticks([-100, -80, -60, 0, 50])
pair.legend(loc="lower left", fontsize=9, frameon=True)
pair.axvline(balance, color="#82909c", ls=":", lw=1)
for g, e, color in [(10., -80., ORANGE), (2., 50., BLUE)]:
pair.plot(balance, current(balance, g, e), "o", color=color, ms=5)
pair.plot(balance, 0, "o", color=INK, ms=5)
pair.annotate("At −58.3333 mV:\nNa: +216.6667 pA\nK: −216.6667 pA\nTotal: 0 pA",
xy=(balance, 0), xytext=(-10, 390), fontsize=9, color=INK,
va="top", arrowprops={"arrowstyle": "->", "color": INK},
bbox={"boxstyle": "round,pad=.3", "fc": "white", "ec": "#ccd4dc"})
fig.text(.5, .07, "Stipulated constant conductances and fixed gradients • "
"instantaneous passive current balance, not a full resting-voltage model",
ha="center", fontsize=9.5, color=INK)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), dpi=120,
facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()