codex/whole-cell-access-resistance-and-clamp-response.py

whole-cell-access-resistance-and-clamp-response.py
"""Original equivalent circuit and synthetic passive clamp responses.

Tested with Python 3.14.4, matplotlib 3.10.7, numpy 2.3.5.
Run from _media to generate the matching opaque PNG in the working directory.
The linear circuit uses deviations from fixed leak reversal E_L = -70 mV.
Seal leakage, pipette capacitance, active channels and feedback dynamics omitted.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np

RS = 20e6  # ohm
RM = 200e6  # ohm
CM = 50e-12  # farad
EL = -70e-3  # volt


def current_clamp(t_seconds, injected_ampere=50e-12):
    vm = EL + injected_ampere * RM * (-np.expm1(-np.asarray(t_seconds) / (RM * CM)))
    return vm, vm + injected_ampere * RS


def voltage_clamp(t_seconds, command_step_volt=10e-3):
    tau = (RS * RM / (RS + RM)) * CM
    v = command_step_volt * RM / (RS + RM) * (-np.expm1(-np.asarray(t_seconds) / tau))
    current = (command_step_volt - v) / RS
    return EL + v, current


def resistor(ax, a, b, y):
    ax.plot([a, a + 0.03], [y, y], color="#333333")
    xx = np.linspace(a + 0.03, b - 0.03, 9)
    yy = np.array([0, 0.035, -0.035, 0.035, -0.035, 0.035, -0.035, 0.035, 0]) + y
    ax.plot(xx, yy, color="#333333", lw=1.7)
    ax.plot([b - 0.03, b], [y, y], color="#333333")


def circuit(ax):
    ax.set(xlim=(0, 1), ylim=(0, 1))
    ax.axis("off")
    ax.text(0.03, 0.80, "Pipette node", fontsize=12)
    ax.text(0.03, 0.67, r"$v_p = V_p-E_L$", fontsize=12)
    ax.plot([0.12, 0.30], [0.52, 0.52], color="#333333")
    resistor(ax, 0.30, 0.47, 0.52)
    ax.text(0.30, 0.72, r"$R_s = 20\ \mathrm{M\Omega}$", fontsize=12)
    ax.plot([0.47, 0.85], [0.52, 0.52], color="#333333")
    ax.scatter([0.54], [0.52], color="#24689a", zorder=3)
    ax.text(0.55, 0.78, r"Membrane node $v=V-E_L$", fontsize=12)
    ax.plot([0.59, 0.59], [0.52, 0.36], color="#333333")
    ax.plot([0.59, 0.59], [0.30, 0.17], color="#333333")
    ax.plot([0.54, 0.64], [0.36, 0.36], color="#333333", lw=2)
    ax.plot([0.54, 0.64], [0.30, 0.30], color="#333333", lw=2)
    ax.text(0.39, 0.24, r"$C_m=50\ \mathrm{pF}$", fontsize=12, ha="center")
    ax.plot([0.85, 0.85], [0.52, 0.42], color="#333333")
    ax.plot([0.85, 0.85], [0.25, 0.17], color="#333333")
    ax.plot([0.85, 0.825, 0.875, 0.825, 0.875, 0.85],
            [0.42, 0.39, 0.35, 0.31, 0.28, 0.25], color="#333333", lw=1.7)
    ax.text(0.90, 0.30, r"$R_m$" + "\n200 MΩ", fontsize=12)
    ax.plot([0.59, 0.85], [0.17, 0.17], color="#333333")
    ax.text(0.64, 0.025, "Fixed reference: E_L = −70 mV", fontsize=11)
    ax.annotate("", xy=(0.25, 0.52), xytext=(0.13, 0.52),
                arrowprops={"arrowstyle": "->", "color": "#ad3e45", "lw": 2})
    ax.text(0.08, 0.35, "I into cell", fontsize=11)


def main():
    plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
                         "axes.spines.right": False, "figure.facecolor": "white"})
    fig = plt.figure(figsize=(10.8, 8.0))
    gs = fig.add_gridspec(2, 2, height_ratios=[0.85, 1], hspace=0.44, wspace=0.34)
    circuit(fig.add_subplot(gs[0, :]))
    cc = fig.add_subplot(gs[1, 0])
    vc = fig.add_subplot(gs[1, 1])
    t_cc = np.linspace(0, 60e-3, 601)
    vm, vp = current_clamp(t_cc)
    cc.plot(t_cc * 1000, vm * 1000, color="#24689a", label="Membrane V")
    cc.plot(t_cc * 1000, vp * 1000, "--", color="#c5741b", label="Pipette Vp (uncompensated)")
    cc.axhline(-60, color="#aaaaaa", lw=0.8)
    cc.set(title="Current clamp: +50 pA step at t = 0",
           xlabel="Time after step (ms)", ylabel="Potential (mV)", ylim=(-71, -57))
    cc.legend(fontsize=9, loc="lower right")
    cc.text(3, -58.8, "Membrane rise: 10 mV; τ = 10 ms\nAccess voltage error: +1 mV", fontsize=10)
    t_vc = np.linspace(0, 6e-3, 601)
    _, current = voltage_clamp(t_vc)
    vc.plot(t_vc * 1000, current * 1e12, color="#ad3e45")
    vc.axhline(10e-3 / (RS + RM) * 1e12, color="#aaaaaa", lw=0.8)
    vc.set(title="Voltage clamp: +10 mV command step",
           xlabel="Time after step (ms)", ylabel="Current into cell (pA)", ylim=(0, 550))
    vc.text(1.3, 335, "Initial: 500 pA\nFinal: 45.45 pA\nτ = 0.909 ms\nFinal membrane rise: 9.09 mV", fontsize=10)
    fig.suptitle("Whole-cell access resistance and passive responses", fontsize=16)
    fig.text(0.5, 0.025, "Synthetic linear model, no experimental data. No active channels, seal leak,\n"
             "pipette capacitance, compensation or amplifier dynamics; ideal command at the pipette node.",
             ha="center", fontsize=10)
    fig.subplots_adjust(left=0.08, right=0.97, top=0.87, bottom=0.15)
    fig.savefig(Path(__file__).stem + ".png", dpi=100, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()