NMT Half-Centre Oscillator

Two ionic neurons with reciprocal voltage-driven inhibition, implementing Task 2 of the Neuromorphic Toolbox proposal.

Abstract

Task 2 of the Neuromorphic Toolbox proposal (P23) asks us to couple two sodium/potassium neurons through reciprocal graded inhibition, apply an asymmetric current onset and compare their voltages and spike times with Toolbox reference traces.

Results

Response to asymmetric current onset

Neuron 1 produces 20 spikes and neuron 2 produces 20 spikes during the 200 ms recording. After equal drive begins at 10 ms, their recorded spike events alternate. Figure 1 shows the voltages, reciprocal inhibition and joint voltage trajectory.

Four panels show both HCO membrane voltages, directional inhibitory conductances, a voltage phase portrait and the prescribed injected currents.
Figure 1.

A: membrane voltage and sampled upward −20 mV crossings for n1 in black and n2 in red. B: inhibitory conductance n1→n2 in black and n2→n1 in red; colour identifies the source neuron. C: n2 voltage against n1 voltage over the same 200 ms, in grey, with direction arrows, a black start dot and a red end square. D: injected currents, n1 solid black and n2 dashed red; equal currents overlap after 10 ms. Dotted lines mark the 5 ms kick onset and 10 ms equal-drive onset. States are sampled after each 0.001 ms step; initial values are added at zero for display. nF/µS/nA are a consistent unit convention for the proposal’s numerical values, whose capacitance, conductance and current units are unstated.

Methods

1. Couple the proposal’s ionic neurons with graded inhibition

Each cell uses the Task 1 ionic model. For neuron index i∈{1,2}i\in\{1,2\} and channel x∈{Na,K}x\in\{\mathrm{Na},\mathrm{K}\},

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

Here tt is time in ms, ViV_i is membrane voltage in mV, zx,iz_{x,i} is filtered voltage in mV, τx\tau_x is the channel time constant in ms, gx,ig_{x,i} and gˉx\bar g_x are channel and maximum conductance in µS, γx\gamma_x is the sigmoid slope in mV−1^{-1}, and bxb_x is its midpoint in mV. The argument uu is dimensionless.

For the graded synapse from neuron jj to neuron ii, with j≠ij\ne i,

dzj→idt=Vj−zj→iτs,gj→i=gˉs σ ⁣(γs(zj→i−bs)).\frac{\mathrm{d}z_{j\to i}}{\mathrm{d}t} =\frac{V_j-z_{j\to i}}{\tau_s}, \qquad g_{j\to i}=\bar g_s\,\sigma\!\left(\gamma_s(z_{j\to i}-b_s)\right).

Here zj→iz_{j\to i} is the synaptic gate voltage in mV, τs\tau_s its time constant in ms, gj→ig_{j\to i} and gˉs\bar g_s the synaptic and maximum conductance in µS, γs\gamma_s the slope in mV−1^{-1} and bsb_s the midpoint in mV. The membrane equation gains the inhibitory conductance term:

CdVidt=Ii−gL(Vi−VL)−gNa,i(Vi−ENa)−gK,i(Vi−EK)−gj→i(Vi−Es).C\frac{\mathrm{d}V_i}{\mathrm{d}t} = I_i-g_L(V_i-V_L) -g_{\mathrm{Na},i}(V_i-E_{\mathrm{Na}}) -g_{\mathrm{K},i}(V_i-E_{\mathrm{K}}) -g_{j\to i}(V_i-E_s).

Here CC is capacitance in nF, IiI_i injected current in nA, gLg_L leak conductance in µS, and VLV_L, ENaE_{\mathrm{Na}}, EKE_{\mathrm{K}} and EsE_s are leak, sodium, potassium and inhibitory reversal potentials in mV. These units preserve the proposal’s numerical equations; its C/g/I units require confirmation against the Toolbox.

At every step of length Δt=0.001\Delta t=0.001 ms, all ionic and synaptic gates advance by forward Euler using the preceding membrane voltages. New gate values determine conductances; both membranes then advance using those new conductances and the old voltages. The cells have no voltage reset.

2. Represent both synapses as native graph connections

SNNLab 0.24 lets a custom synapse declare input_unit="mV" and output_unit="uS". Each projection reads pre.voltage at the beginning of the timestep and delivers the newly updated conductance to post.inhibitory. Dimensionless projection weights are one; the physical maximum conductance belongs inside the synapse, after the nonlinear gate. Zero added delay avoids shifting the proposal’s coupling by an extra timestep.

The following callback implementation is loaded directly from this experiment:

import torch


def neuron_initial_state(ctx):
    voltage = torch.full(
        ctx.shape, ctx.config["initial_voltage_mv"], device=ctx.device, dtype=ctx.dtype
    )
    return dict(
        voltage=voltage,
        z_na=voltage.clone(),
        z_k=voltage.clone(),
        current=torch.zeros_like(voltage),
        step_index=torch.zeros(ctx.shape, device=ctx.device, dtype=torch.int64),
    )


def neuron_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 = cfg["g_na_us"] * torch.sigmoid(
        cfg["gamma_na_per_mv"] * (z_na - cfg["bias_na_mv"])
    )
    g_k = cfg["g_k_us"] * torch.sigmoid(
        cfg["gamma_k_per_mv"] * (z_k - cfg["bias_k_mv"])
    )
    # Use integer intervals so current changes exactly at 5 and 10 ms.
    index = state["step_index"]
    current = torch.where(
        (index >= cfg["kick_start_step"]) & (index < cfg["kick_stop_step"]),
        torch.full_like(voltage, cfg["kick_na"]),
        torch.where(
            index >= cfg["drive_start_step"],
            torch.full_like(voltage, cfg["drive_na"]),
            torch.zeros_like(voltage),
        ),
    )
    outward = (
        cfg["leak_us"] * (voltage - cfg["leak_reversal_mv"])
        + g_na * (voltage - cfg["e_na_mv"])
        + g_k * (voltage - cfg["e_k_mv"])
        + ctx.inhibitory * (voltage - cfg["synapse_reversal_mv"])
    )
    updated = voltage + dt / cfg["capacitance_nf"] * (current - outward)
    spikes = (
        (voltage < cfg["spike_threshold_mv"]) & (updated >= cfg["spike_threshold_mv"])
    ).to(voltage.dtype)
    return {
        **state,
        "voltage": updated,
        "z_na": z_na,
        "z_k": z_k,
        "current": current,
        "step_index": index + 1,
    }, spikes


def synapse_initial_state(ctx):
    gate = torch.full(
        ctx.shape, ctx.config["initial_gate_mv"], device=ctx.device, dtype=ctx.dtype
    )
    value = ctx.config["g_max_us"] * torch.sigmoid(
        ctx.config["gamma_per_mv"] * (gate - ctx.config["bias_mv"])
    )
    return dict(gate=gate, value=value)


def synapse_step(ctx):
    gate = ctx.state["gate"] + ctx.dt_ms / ctx.config["tau_ms"] * (
        ctx.drive - ctx.state["gate"]
    )
    value = ctx.config["g_max_us"] * torch.sigmoid(
        ctx.config["gamma_per_mv"] * (gate - ctx.config["bias_mv"])
    )
    return dict(gate=gate, value=value)

Each neuron declares conductance input ports. Because native custom neuron ports share one input unit, its prescribed injected current is calculated inside the neuron callback from saved schedule parameters and an integer step counter. The counter is retained in native continuation state. This implements the fixed offline protocol; it does not provide an interactive current input.

3. Run one HCO and validate its saved trajectory

Both membrane voltages and every gate start at −70 mV. Neuron 1 receives 1.0 nA for 5≤t<105\leq t<10 ms, then 0.8 nA; neuron 2 receives 0.8 nA for 10≤t<20010\leq t<200 ms. All other input intervals have zero current. One HCO runs for 200 ms in float64 on CPU; no batch benchmark is performed. Spikes are sampled upward −20 mV crossings, timed at the end of the crossing timestep without interpolation. Strict alternation after 10 ms means every consecutive event belongs to the other cell, with no simultaneous events.

Against independent forward-Euler equations, maximum voltage differences are 1.73e-10 mV for n1 and 1.20e-10 mV for n2. Both cells have matching reference spike counts.

The proposal’s thresholds are maximum voltage difference below 0.1 mV, spike-time difference below 0.05 ms and identical counts for each neuron. Toolbox reference traces have not been supplied, so this comparison establishes agreement with the written equations only.

Network diagram

Compiled native graph with two ionic neurons connected by reciprocal graded inhibition.
Figure 2.

The two voltage-driven projections are explicit graph edges. The unconnected clock input supplies execution axes; fixed current schedules are part of each custom neuron. The circuit is exported from the executed compiled bundle.

Parameters

Both cells share the listed ionic settings, and both synapses share the listed gate settings. Values come from the original native simulation-start snapshot and recorded compute configuration. Na / K denotes sodium / potassium.

ParameterSaved value
Network
Neurons / reciprocal synapses2 / 2
HCOs executed together1
Voltage-drive projection weight1
Added projection delay (ms)0
Neurons
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 ionic gate voltages (mV)-70
Observed upward-crossing threshold (mV)-20
Synapses
Maximum conductance (µS)1
Inhibitory reversal potential (mV)-85
Gate time constant (ms)10
Sigmoid slope (mV⁻¹) / midpoint (mV)1 / -55
Initial gate voltage (mV)-70
Input and simulation
Integrationgate-first forward Euler; beginning-of-step voltage sources
Timestep / duration (ms)0.001 / 200
n1 kick onset / stop (ms)5 / 10
n1 kick amplitude (nA)1
Equal drive 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
exp147-r003-present5s549.7 KBCurrentR2LocalLocal: absent; R2: catalog-verified
exp147-r002-analyse<1s14 MBCurrentR2LocalLocal: absent; R2: catalog-verified
exp147-r001-compute29s14.1 MBCurrentR2LocalLocal: absent; R2: catalog-verified