Ex9: FitzHugh-Nagumo

Classic FitzHugh-Nagumo oscillator — dimensionless 2-variable model

Model: FitzHugh-Nagumo

The simplest oscillatory model in NeuroML2, dimensionless:

\[\frac{dV}{dt} = \frac{V - V^3/3 - W + I}{1\,\text{s}}\] \[\frac{dW}{dt} = \frac{0.08\,(V + 0.7 - 0.8\,W)}{1\,\text{s}}\]

Parameters: \(I = 0.8\) (constant external current). Simulated for 200 s at 0.01 s step.


1. Define in TVBO

from tvbo import SimulationExperiment

exp = SimulationExperiment.from_string("""
label: "NeuroML Ex9: FitzHugh-Nagumo"
dynamics:
  name: FitzHughNagumo
  parameters:
    I: { value: 0.8 }
  state_variables:
    V:
      equation: { rhs: "V - V**3/3 - W + I" }
      initial_value: 0.0
      variable_of_interest: true
    W:
      equation: { rhs: "0.08*(V + 0.7 - 0.8*W)" }
      initial_value: 0.0
network:
  number_of_nodes: 1
integration:
  method: euler
  step_size: 0.01
  duration: 200.0
  time_scale: s
""")
print(f"Model: {exp.dynamics.name}")
Model: FitzHughNagumo

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_Ex9__FitzHugh_Nagumo"/>

  <!-- ════════════════════════════════════════════════════════════════
       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_Ex9_FN.xml")
for name, arr in ref_outputs.items():
    print(f"  {name}: shape={arr.shape}")
  ex9.dat: shape=(20001, 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=(20001, 3)

5. Numerical Comparison

from _nml_helpers import compare_traces
import numpy as np

ref_arr = list(ref_outputs.values())[0]
# NeuroML output: time, V, W
compare_traces(
    ref_arr, tvbo_arr,
    ref_cols=['time', 'V', 'W'],
    tvbo_cols=['time', 'V', 'W'],
)
  V: RMSE=0.000000  max_err=0.000000  corr=1.000000  ✅
  W: RMSE=0.000000  max_err=0.000000  corr=1.000000  ✅
{'V': {'rmse': np.float64(2.633452091144944e-20),
  'max_err': np.float64(1.734723475976807e-18),
  'corr': np.float64(1.0),
  'close': True},
 'W': {'rmse': np.float64(6.329394294663337e-22),
  'max_err': np.float64(5.421010862427522e-20),
  'corr': np.float64(1.0),
  'close': True}}

6. Plot

from _nml_helpers import plot_comparison

plot_comparison(
    ref_arr, tvbo_arr,
    ref_cols=['time', 'V', 'W'],
    tvbo_cols=['time', 'V', 'W'],
    title="Ex9: FitzHugh-Nagumo — NeuroML vs TVBO",
    time_unit="s",
)

Phase Portrait

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 5))
ax.plot(ref_arr[:, 1], ref_arr[:, 2], label='NeuroML', alpha=0.7)
ax.plot(tvbo_arr[:, 1], tvbo_arr[:, 2], '--', label='TVBO', alpha=0.7)
ax.set_xlabel("V")
ax.set_ylabel("W")
ax.set_title("Phase Portrait: V vs W")
ax.legend()
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()