AdExNeuron

July 13, 2026 · View on GitHub

Python module: sc_neurocore.neurons.models.adex

Rust engine: sc_neurocore_engine.AdExNeuron

Acceleration kernels: Rust safety, Go, Julia, and Mojo

Primary model source: Brette & Gerstner (2005), doi:10.1152/jn.00686.2005

AdExNeuron implements the adaptive exponential integrate-and-fire model. It combines an exponential approach to spike initiation with a second state variable for subthreshold and spike-triggered adaptation.

Maintained equations

For membrane state v, adaptation state w, and injected current I, the maintained normalised recurrence evaluates

[ \dot v = \frac{-(v-v_{rest}) + \Delta_T \exp((v-v_{rh})/\Delta_T)}{\tau}

  • \frac{-w+I}{C_m}, ]

[ \dot w = \frac{a(v-v_{rest})-w}{\tau_w}. ]

The baseline path advances both candidates with explicit Euler. The exponential argument is clipped to [-20, 20] as a maintained numerical guard. If the candidate voltage reaches v_threshold, the same step emits an event, sets v = v_reset, and commits w = w_candidate + b. Invalid input, state, or candidate values fail before either state variable is committed.

The DOI establishes the model equations and reset/adaptation structure. The repository defaults are a configurable maintained operating point; they are not presented as the fitted parameter set of a particular cell in the paper.

Python API

from sc_neurocore.neurons.models.adex import AdExNeuron

neuron = AdExNeuron()
event = neuron.step(current=250.0)

trace, events = neuron.simulate(
    n_steps=1_000,
    current=500.0,
    backend="auto",
)

step() returns 0 or 1. simulate() returns the post-update voltage trace and the total event count, and advances the instance to the final (v, w). Passing zero steps returns an empty trace without changing state.

Parameters

ParameterDefaultMeaning
v-65.0Initial membrane voltage
w0.0Initial adaptation state
v_rest-65.0Resting voltage
v_reset-68.0Post-event reset voltage
v_threshold-50.0Candidate-voltage event threshold
v_rh-55.0Exponential rheobase voltage
delta_t2.0Exponential slope factor
tau20.0Membrane time constant
tau_w100.0Adaptation time constant
a0.5Subthreshold adaptation coupling
b7.0Event-triggered adaptation increment
c_m200.0Membrane capacitance scale
dt0.1Integration step
integrator"baseline_euler"baseline_euler, rk4, or rosenbrock

All state and current values must be finite. delta_t, tau, tau_w, c_m, and dt must also be positive.

Backend contract

BackendPublic selectionSupported contract
Pythonbackend="python"Full parameter surface; baseline Euler, RK4, and Rosenbrock
Rust enginebackend="rust"Factory-default state and parameters; baseline Euler
Juliabackend="julia"Full numeric state and parameter surface; baseline Euler
Gobackend="go"Full numeric state and parameter surface through a C-shared bridge; baseline Euler
Mojobackend="mojo"Full numeric state and parameter surface through a C ABI; baseline Euler

Compiled model-specific lanes reject RK4 or Rosenbrock rather than silently changing the configured integrator. Those integrators remain available in the Python model and in the separate generic polyglot RK4 dispatcher.

For baseline Euler, backend="auto" follows the measured order recorded by the committed benchmark: Mojo, Julia, Go, compatible Rust, then Python. Alternative integrators use Python. Explicit backend requests fail closed when their runtime or shared library is unavailable.

Executed parity envelope

The acceleration tests execute every compiled lane without skip decorators. With maintained defaults over 1,000 steps, all five backends preserve these event-count goldens:

CurrentEvents
0.00
200.04
500.012

Rust and Julia reproduce the Python trace exactly in the enrolled benchmark. Go differs by at most 7.11e-15 and Mojo by at most 7.40e-13, below the declared 5e-12 absolute bound. A non-default full-parameter case is also exercised for Julia, Go, and Mojo, including final (v, w) parity.

The Python-to-Verilog route is tracked separately. The existing Q16.16 Icarus co-simulation compares Python and generated RTL event counts over 500 steps at I=1000 with a declared maximum two-percent gap. The independent reference trace adex_resting_adaptation_doi re-derives subthreshold explicit-Euler features from the DOI-backed equations and matches the committed feature set to 1e-12.

Benchmark evidence

benchmarks/results/bench_adex.json is generated by benchmarks/bench_adex.py. It records source hashes, runtimes, CPU affinity, governor, host load, event parity, final states, and voltage-trace error.

The committed single-logical-CPU run uses 100,000 steps, seven repeats, and I=500. Its median call times are:

BackendMedian call timeSpeed-up vs PythonEvents
Mojo4.658 ms472.75x1065
Julia5.040 ms436.91x1065
Go12.807 ms171.95x1065
Rust58.988 ms37.33x1065
Python2202.136 ms1.00x1065

The run used a powersave governor on a non-isolated, loaded workstation. These numbers are local source-regression evidence for this workload, not a hardware throughput claim or a general ranking across machines.

Reproduce it from a checkout with every optional runtime built:

PYTHONPATH=src:bridge taskset -c <cpu> python \
  benchmarks/bench_adex.py \
  --json benchmarks/results/bench_adex.json

Evidence surfaces

  • tests/test_model_adex.py — model equations, integrators, validation, and Python/Rust public behaviour.
  • tests/test_adex_backends.py — four compiled lanes, full-parameter transport, auto routing, ABI rejection, and loader failures.
  • tests/test_bench_adex.py — benchmark schema, fail-closed behaviour, source hashes, and real five-backend execution.
  • src/sc_neurocore/accel/go/services/adex_test.go — Go recurrence goldens, reset, fail-closed state, and native benchmark.
  • src/sc_neurocore/accel/julia/adex_parity_test.jl — Julia goldens and mutation-free rejection.
  • tests/test_cosim_adex.py — Q16.16 Python-to-Verilog event-count envelope.
  • tests/test_reference_adex.py — independent DOI-backed reference features.

Current boundary

The compiled model-specific kernels implement the maintained baseline-Euler recurrence. They do not claim polyglot RK4/Rosenbrock support. The Rust engine class does not expose parameter injection, so the Python dispatcher accepts it only for the factory-default contract. The generated RTL evidence is an H1 co-simulation result with its declared event-count tolerance; it is not an ASIC or FPGA timing result.