Ex8: Adaptive Exponential IF

AdEx integrate-and-fire neuron with adaptation current — bursting variant

Model: AdEx (adExIaFCell)

The Adaptive Exponential Integrate-and-Fire model Brette2005?:

\[C\frac{dv}{dt} = -g_L(v - E_L) + g_L \Delta_T \exp\!\left(\frac{v - V_T}{\Delta_T}\right) - w + I_{\text{ext}}\] \[\tau_w\frac{dw}{dt} = a(v - E_L) - w\]

Spike event: if \(v > \text{thresh}\), then \(v \leftarrow \text{reset}\), \(w \leftarrow w + b\).

The NeuroML example includes four variants (burst2, burst4, burstChaos, rebound).


1. Define in TVBO (bursting variant)

from tvbo import SimulationExperiment

exp = SimulationExperiment.from_string("""
label: "NeuroML Ex8: AdEx (bursting)"
dynamics:
  name: AdaptiveExponentialIF
  parameters:
    C:      { value: 281.0,  description: "Capacitance (pF)" }
    gL:     { value: 30.0,   description: "Leak conductance (nS)" }
    EL:     { value: -70.6,  description: "Leak reversal (mV)" }
    VT:     { value: -50.4 }
    thresh: { value: -40.4 }
    reset:  { value: -48.5 }
    delT:   { value: 2.0 }
    tauw:   { value: 40.0 }
    a:      { value: 4.0,    description: "Subthreshold adaptation (nS)" }
    b:      { value: 80.0,   description: "Spike-triggered adaptation (pA, = 0.08 nA)" }
    refract: { value: 0.0, unit: ms, description: "Refractory period" }
  derived_variables:
    I_ext:
      equation:
        rhs: "800.0"
      description: "Constant current 0.8 nA = 800 pA (pulseGen1: delay=0, duration=2000ms)"
  state_variables:
    v:
      equation:
        rhs: "(-gL*(v - EL) + gL*delT*exp((v - VT)/delT) - w + I_ext) / C"
      initial_value: -70.6
      variable_of_interest: true
    w:
      equation:
        rhs: "(a*(v - EL) - w) / tauw"
      initial_value: 0.0
  events:
    spike:
      condition: { rhs: "v > thresh" }
      affect:    { rhs: "v = reset; w = w + b" }
network:
  number_of_nodes: 1
integration:
  method: euler
  step_size: 0.025
  duration: 300.0
  time_scale: ms
""")
print(f"Model: {exp.dynamics.name}, SVs: {list(exp.dynamics.state_variables.keys())}")
Model: AdaptiveExponentialIF, SVs: ['v', 'w']

2. Render LEMS XML

xml = exp.render("lems")
print(xml[:1500])

<Lems>

  <!-- Tell jLEMS/jNeuroML which component is the simulation entry point. -->
  <Target component="sim_NeuroML_Ex8__AdEx__bursting_"/>

  <!-- ════════════════════════════════════════════════════════════════
       Dimensions & Units (inline — no external includes needed)
       ════════════════════════════════════════════════════════════════ -->

  <!-- Dimensions -->
  <Dimension name="none"/>
  <Dimension name="time" t="1"/>
  <Dimension name="voltage" m="1" l="2" t="-3" i="-1"/>
  <Dimension name="per_time" t="-1"/>
  <Dimension name="conductance" m="-1" l="-2" t="3" i="2"/>
  <Dimension name="capacitance" m="-1" l="-2" t="4" i="2"/>
  <Dimension name="current" i="1"/>
  <Dimension name="resistance" m="1" l="2" t="-3" i="-2"/>
  <Dimension name="concentration" l="-3" n="1"/>
  <Dimension name="substance" n="1"/>
  <Dimension name="charge" t="1" i="1"/>
  <Dimension name="temperature" k="1"/>

  <!-- Units -->
  <Unit symbol="s" dimension="time" power="0"/>
  <Unit symbol="ms" dimension="time" power="-3"/>
  <Unit symbol="us" dimension="time" power="-6"/>
  <Unit symbol="V" dimension="voltage" power="0"/>
  <Unit symbol="mV" dimension="voltage" power="-3"/>
  <Unit symbol="A" dimension="current" power="0"/>
  <Unit symbol="mA" dimension="current" power="-3"/>
  <Unit symbol="nA" dimension="current" power="-9"/>
  <Unit symbol="pA" dimension="current" power="-12"/>
  <Unit symbol="S" dimension="conductance" power="0"/>
  <Unit symbol="mS" dimension="conductance" po

3. Run Reference

import sys, os
sys.path.insert(0, os.path.dirname(os.path.abspath(".")))
from _nml_helpers import run_lems_example

ref_outputs = run_lems_example("LEMS_NML2_Ex8_AdEx.xml")
for name, arr in ref_outputs.items():
    print(f"  {name}: shape={arr.shape}")
  adEx_2burst.dat: shape=(12001, 3)
  adEx_4burst.dat: shape=(12001, 3)
  adEx_chaos.dat: shape=(12001, 3)
  adEx_rebound.dat: shape=(12001, 3)

4. Run TVBO

import numpy as np

result = exp.run("neuroml")
da = result.integration.data
tvbo_arr = np.column_stack([da.coords['time'].values, da.values])
print(f"TVBO: shape={tvbo_arr.shape}")
TVBO: shape=(12001, 3)

5. Comparison

The NeuroML example has 4 cells. We compare the burst2 variant (first column):

from _nml_helpers import compare_traces, plot_comparison
import numpy as np

ref_arr = list(ref_outputs.values())[0]
# First data column should be adExBurst2/v
ref_v = ref_arr[:, [0, 1]]
tvbo_v = tvbo_arr[:, [0, 1]]

compare_traces(ref_v, tvbo_v, ref_cols=['time', 'v'], tvbo_cols=['time', 'v'])

plot_comparison(
    ref_v, tvbo_v,
    ref_cols=['time', 'v'], tvbo_cols=['time', 'v'],
    title="Ex8: AdEx (bursting) — NeuroML vs TVBO",
)
  v: RMSE=48.553404  max_err=70.529400  corr=1.000000  ⚠️

All AdEx Variants (NeuroML reference)

import matplotlib.pyplot as plt

fig, axes = plt.subplots(4, 1, figsize=(10, 10), sharex=True)
labels = ['Burst2', 'Burst4', 'BurstChaos', 'Rebound']
t_ref = ref_arr[:, 0] * 1000  # ms
for i in range(min(4, ref_arr.shape[1]-1)):
    axes[i].plot(t_ref, ref_arr[:, i+1] * 1000, color='C0')
    axes[i].set_ylabel('v (mV)')
    axes[i].set_title(labels[i] if i < len(labels) else f'col {i+1}')
    axes[i].grid(True, alpha=0.3)
axes[-1].set_xlabel("Time (ms)")
fig.suptitle("Ex8: All AdEx Variants (NeuroML reference)", fontsize=13)
fig.tight_layout()
plt.show()