codex/neuronal-refractory-recovery-curves.py

neuronal-refractory-recovery-curves.py
"""Original stipulated paired-pulse recovery; never experimental data.

Tested: Python 3.14.4, matplotlib 3.10.7, numpy 2.3.5.
Output matching basename.png in the current working directory.
Units: ms and nA. The absolute hold and scalar recovery are imposed rules,
not sodium/potassium channel dynamics or a Hodgkin-Huxley simulation.
"""
from pathlib import Path

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

HOLD_MS = 2.0
TAU_MS = 3.0
BASE_NA = 0.2
EXCESS = 2.0


def required_current(delay_ms):
    delay_ms = np.asarray(delay_ms, dtype=float)
    return np.where(delay_ms < HOLD_MS, np.inf,
                    BASE_NA * (1 + EXCESS * np.exp(-(delay_ms-HOLD_MS)/TAU_MS)))


def earliest_delay(current_na):
    if current_na <= BASE_NA:
        return np.inf
    return HOLD_MS + max(0.0, TAU_MS*np.log(EXCESS/(current_na/BASE_NA-1)))


def main():
    plt.rcParams.update({'font.size': 12, 'font.family': 'DejaVu Sans'})
    fig, ax = plt.subplots(figsize=(9.2, 5.5), layout='constrained')
    fig.set_facecolor('white')
    ax.set_facecolor('white')
    ax.axvspan(0, HOLD_MS, color='#e6e6e6')
    delay = np.linspace(HOLD_MS, 16, 700)
    ax.plot(delay, required_current(delay), color='#225f91', lw=2.8,
            label='Required amplitude after the hold')
    ax.scatter([HOLD_MS], [0.6], color='#225f91', s=35, zorder=5)
    ax.axhline(BASE_NA, color='#666666', ls='--', lw=1.5)
    ax.text(15.7, 0.182, 'Asymptotic baseline: 0.2 nA', ha='right', va='top', fontsize=11)
    for amplitude, color, label_y in [(0.35, '#a74321', 0.27), (0.5, '#267550', 0.55)]:
        first = earliest_delay(amplitude)
        ax.axhline(amplitude, color=color, lw=1.5, ls=':')
        ax.scatter([first], [amplitude], color=color, s=55, zorder=6)
        ax.plot([first, first], [0.13, amplitude], color=color, lw=1, ls='--')
        ax.annotate(f'{amplitude:g} nA test\nFirst passes at {first:.3f} ms',
                    xy=(first, amplitude), xytext=(8.8, label_y),
                    arrowprops={'arrowstyle': '->', 'color': color}, color=color,
                    bbox={'facecolor': 'white', 'edgecolor': 'none', 'pad': 3})
    ax.text(1.0, 0.54, 'Absolute\nhold\n\nNo second\nevent', ha='center', va='center', fontsize=11,
            bbox={'facecolor': '#e6e6e6', 'edgecolor': 'none', 'pad': 3})
    ax.text(9.5, 0.63, r'$I_{\rm req}=0.2[1+2e^{-(\Delta-2)/3}]$ nA', ha='center', fontsize=13)
    ax.set(xlim=(0, 16), ylim=(0.13, 0.69),
           xlabel='Delay from conditioning event, Δ (ms)',
           ylabel='Test-pulse amplitude (nA)')
    ax.spines[['top', 'right']].set_visible(False)
    ax.grid(axis='y', color='#eeeeee')
    ax.set_axisbelow(True)
    fig.suptitle('Paired-pulse recovery: an imposed example', fontsize=17)
    ax.set_title('Chosen 2 ms hold; 3 ms recovery constant; fixed 0.2 ms pulse width', fontsize=11, pad=13)
    fig.text(0.99, -0.025, 'Original phenomenological rule — not recordings or a channel simulation',
             ha='right', fontsize=10)
    fig.savefig(Path(__file__).with_suffix('.png').name, dpi=120,
                facecolor='white', bbox_inches='tight')
    plt.close(fig)


if __name__ == '__main__':
    main()