NMT Single-Neuron Compatibility

SNNLab implementation of the Neuromorphic Toolbox's sigmoid-gated sodium/potassium neuron.

Abstract

Task 1 of the Neuromorphic Toolbox proposal (P23) asks us to reproduce a single sodium/potassium spiking neuron’s response to a current step in SNNLab, then compare its voltage and spike times with Toolbox reference traces.

Results

Single-neuron current response

The current step produces 4 spikes. The sodium gate responds faster than the potassium gate; the delayed rise in potassium conductance accompanies repolarisation and the voltage undershoot after each spike (Figure 1).

Four panels show membrane voltage, sodium and potassium gate voltages, ionic conductances and injected current over 50 ms.
Figure 1.

A: membrane voltage in black; red dots mark sampled upward −20 mV crossings. B: sodium gate voltage in black and potassium gate voltage in red; these are filtered voltages, not gate probabilities. C: corresponding sodium and potassium conductances, with the same colours. D: injected current, held constant over each input interval. All panels share the same time axis; the dashed line marks current onset at 10 ms. States are sampled after each timestep, with initial values added at zero for display. Capacitance, conductance and current use the consistent nF/µS/nA convention; the proposal does not specify their units explicitly.

Methods

1. Implement the proposal’s equations

The proposal defines a leaky membrane with sodium and potassium currents. For each channel x∈{Na,K}x \in \{\mathrm{Na},\mathrm{K}\}, the gate voltage zxz_x follows membrane voltage VV with time constant τx\tau_x:

dzxdt=V−zxτx,gx=gˉx σ ⁣(γx(zx−bx)),σ(u)=11+exp⁡(−u).\frac{\mathrm{d}z_x}{\mathrm{d}t} = \frac{V-z_x}{\tau_x}, \qquad g_x = \bar g_x\,\sigma\!\left(\gamma_x(z_x-b_x)\right), \qquad \sigma(u)=\frac{1}{1+\exp(-u)}.

Here tt is time in ms, VV and zxz_x are in mV, τx\tau_x is in ms, gxg_x is the channel conductance, gˉx\bar g_x is its maximum conductance, γx\gamma_x is the sigmoid slope in mV−1^{-1}, and bxb_x is its midpoint in mV. The sigmoid σ\sigma maps the dimensionless argument uu to a value between zero and one; zxz_x itself is a voltage, not a probability.

The membrane equation is

CdVdt=Iext−gL(V−VL)−gNa(V−ENa)−gK(V−EK).C\frac{\mathrm{d}V}{\mathrm{d}t} = I_{\mathrm{ext}}-g_L(V-V_L) -g_{\mathrm{Na}}(V-E_{\mathrm{Na}}) -g_{\mathrm{K}}(V-E_{\mathrm{K}}).

Here CC is membrane capacitance, IextI_{\mathrm{ext}} is injected current, gLg_L is leak conductance, VLV_L is the leak reversal potential, and ENaE_{\mathrm{Na}} and EKE_{\mathrm{K}} are the sodium and potassium reversal potentials. We embed the proposal’s numerical values in nF, µS and nA, which are consistent with mV and ms; the proposal does not explicitly label the capacitance, conductance or current units. Saved values appear in the appendix.

Forward Euler must advance the gates before the membrane. At timestep index nn, with step length Δt\Delta t, we compute

zxn+1=zxn+Δtτx(Vn−zxn),gxn+1=gˉx σ ⁣(γx(zxn+1−bx)),z_x^{n+1}=z_x^n+\frac{\Delta t}{\tau_x}(V^n-z_x^n), \qquad g_x^{n+1}=\bar g_x\,\sigma\!\left(\gamma_x(z_x^{n+1}-b_x)\right), Vn+1=Vn+ΔtC[Iextn−gL(Vn−VL)−gNan+1(Vn−ENa)−gKn+1(Vn−EK)].V^{n+1}=V^n+\frac{\Delta t}{C}\left[ I_{\mathrm{ext}}^n-g_L(V^n-V_L) -g_{\mathrm{Na}}^{n+1}(V^n-E_{\mathrm{Na}}) -g_{\mathrm{K}}^{n+1}(V^n-E_{\mathrm{K}}) \right].

Superscripts denote sampled timesteps; IextnI_{\mathrm{ext}}^n applies over [nΔt,(n+1)Δt)[n\Delta t,(n+1)\Delta t). The gate and conductance updates produce panels B and C, and the membrane update produces panel A. There is no voltage reset.

2. Express the dynamics as SNNLab extensions

The implementation below is loaded directly from this experiment’s custom/dynamics.py. SNNLab supplies the previous tensor state, timestep, configuration and incoming currents through NeuronContext. The callback returns the new state and binary upward-crossing events. The initializer sets voltage and both gates to −70 mV; the synapse passes current unchanged.

import torch


def conductances(voltage_na, voltage_k, cfg):
    return (
        cfg["g_na_us"]
        * torch.sigmoid(cfg["gamma_na_per_mv"] * (voltage_na - cfg["bias_na_mv"])),
        cfg["g_k_us"]
        * torch.sigmoid(cfg["gamma_k_per_mv"] * (voltage_k - cfg["bias_k_mv"])),
    )


def initial_state(ctx):
    voltage = torch.full(
        ctx.shape,
        ctx.config["initial_voltage_mv"],
        dtype=ctx.dtype,
        device=ctx.device,
    )
    g_na, g_k = conductances(voltage, voltage, ctx.config)
    return dict(
        voltage=voltage,
        z_na=voltage.clone(),
        z_k=voltage.clone(),
        g_na=g_na,
        g_k=g_k,
    )


def step(ctx):
    cfg, state, dt = ctx.config, ctx.state, ctx.dt_ms
    voltage = state["voltage"]
    z_na = state["z_na"] + dt / cfg["tau_na_ms"] * (voltage - state["z_na"])
    z_k = state["z_k"] + dt / cfg["tau_k_ms"] * (voltage - state["z_k"])
    g_na, g_k = conductances(z_na, z_k, cfg)
    ionic = (
        cfg["leak_us"] * (voltage - cfg["leak_reversal_mv"])
        + g_na * (voltage - cfg["e_na_mv"])
        + g_k * (voltage - cfg["e_k_mv"])
    )
    current = ctx.excitatory - ctx.inhibitory
    updated = voltage + dt * (current - ionic) / cfg["capacitance_nf"]
    threshold = cfg["spike_threshold_mv"]
    spikes = ((voltage < threshold) & (updated >= threshold)).to(voltage.dtype)
    return {
        **state,
        "voltage": updated,
        "z_na": z_na,
        "z_k": z_k,
        "g_na": g_na,
        "g_k": g_k,
    }, spikes


def inject_current(ctx):
    """Pass the projected signed current unchanged, without decay or clipping."""
    return {"value": ctx.drive.to(ctx.state["value"].dtype)}

The recipe registers these importable callbacks once per process, then uses CustomNeuron and CustomSynapse in the authored network. This registration excerpt exposes the gate voltages and conductances as named state ports:

from snnlab import extensions, lang
from experiments.exp146.custom import dynamics

extensions.register_neuron(
    "pinglab.exp146.toolbox_ei/v1",
    dynamics.step,
    initialize=dynamics.initial_state,
    input_unit="nA",
    state_units={"z_na": "mV", "z_k": "mV", "g_na": "uS", "g_k": "uS"},
    capture_safe=True,
)
extensions.register_synapse(
    "pinglab.exp146.injected_current/v1",
    dynamics.inject_current,
    output_unit="nA",
    capture_safe=True,
)

The network contains one custom neuron and a continuous input connected to its excitatory port through the current extension at unit weight, with direct initialization scaling. The recipe passes the saved neuron settings as the custom neuron’s configuration. Native graph outputs retain voltage, gates, conductances, injected current and crossing events.

3. Run the current-step protocol and check the traces

Voltage and both gates start at −70 mV. We execute 50,000 steps of Δt=0.001\Delta t=0.001 ms, for a total of 50 ms, with float64 arithmetic on CPU. Injected current is zero before 10 ms and 0.8 nA from 10 ms to the end (panel D). An upward crossing satisfies Vn<−20V^n<-20 mV and Vn+1≥−20V^{n+1}\geq-20 mV; its recorded time is (n+1)Δt(n+1)\Delta t, without interpolation.

An independent scalar Euler implementation checks the saved trajectory.

The maximum voltage difference was 4.55e-13 mV, with identical spike counts and a maximum crossing-time difference of 0 ms.

The proposed acceptance thresholds are a maximum voltage error below 0.1 mV, crossing-time error below 0.05 ms and identical spike counts. Passing the independent numerical comparison does not establish agreement with the Toolbox: its reference traces have not been supplied.

Network diagram

Compiled SNNLab circuit with continuous external current driving one custom neuron.
Figure 2.

Circuit exported from the executed compiled bundle. Sodium and potassium dynamics live inside the custom neuron; the input projection passes injected current without decay. No recurrent graph connection or spike reset is used.

Parameters

Values come from the native simulation-start snapshot and its recorded compute configuration. Na / K pairs refer to sodium / potassium. The proposal’s gate voltages are filtered membrane voltages. Each step advances both gates from the preceding voltage, evaluates their sigmoid conductances, then advances the membrane voltage with those new conductances and the current interval’s input.

ParameterSaved value
Network
Neurons1
Injected-current weight1
SynapseMemoryless pass-through current
Neuron
Capacitance (nF)0.01
Leak conductance (µS) / reversal (mV)0.01 / -70
Na / K maximum conductance (µS)1 / 10
Na / K reversal (mV)50 / -85
Na / K gate time constant (ms)1 / 10
Na / K sigmoid slope (mV⁻¹)1 / 1
Na / K sigmoid midpoint (mV)-60 / -60
Initial membrane and gate voltages (mV)-70
Observed upward-crossing threshold (mV)-20
Input and simulation
Integrationgate-first forward Euler
Timestep / duration (ms)0.001 / 50
Pulse onset (ms) / amplitude (nA)10 / 0.8
Device / dtypecpu / float64
Unit conventionmV, ms, nF, uS, nA; C/g/I units assumed
Validation
Voltage tolerance (mV)0.1
Spike-time tolerance (ms)0.05
Toolbox referenceToolbox reference traces not supplied

Dataset

Upstream: —

Downstream: —

RunDateDurationSizeEvidenceStorageRan onVerification
exp146-r003-present1s286.2 KBCurrentR2LocalLocal: absent; R2: catalog-verified
exp146-r002-analyse<1s1.9 MBCurrentR2LocalLocal: absent; R2: catalog-verified
exp146-r001-compute5s1.9 MBCurrentR2LocalLocal: absent; R2: catalog-verified