Model: SingleCompHHCell
Same HH dynamics as Ex1, but the reference uses the full <cell> element with a single-compartment morphology (soma, area ≈ 1000 μm²), biophysical properties, and channelDensity — the recommended NeuroML format for maximum simulator compatibility.
For a single compartment, channelDensity × area gives the same total conductance as channelPopulation × singleChannelConductance in pointCellCondBased:
Capacitance
1.0 uF/cm² × 10⁻⁵ cm²
C = 10 pF
Na
120 mS/cm² × 10⁻⁵ cm² = 1.2 μS
120000 × 10 pS
K
36 mS/cm² × 10⁻⁵ cm² = 360 nS
36000 × 10 pS
Leak
0.3 mS/cm² × 10⁻⁵ cm² = 3 nS
300 × 10 pS
TVBO uses pointCellCondBased with standard NeuroML ion channels, giving numerical identity with the reference to within floating-point precision (max diff < 1 μV).
1. Define in TVBO
from tvbo import SimulationExperiment
# Same channels as Ex1, but pulse_delay=100ms, pulse_duration=100ms, duration=300ms
exp = SimulationExperiment.from_string("""
label: "NeuroML Ex5: Detailed HH Cell"
dynamics:
name: HodgkinHuxley
iri: neuroml:pointCellCondBased
description: >
Single-compartment HH cell matching NeuroML2 Ex5.
Uses pointCellCondBased with equivalent channel populations.
parameters:
C: { value: 10, description: "nml:10pF" }
v0: { value: -65, description: "nml:-65mV" }
thresh: { value: -20, description: "nml:-20mV" }
pulse_delay: { value: 100, description: "nml:100ms" }
pulse_duration: { value: 100, description: "nml:100ms" }
I_amp: { value: 0.08, description: "nml:0.08nA" }
components:
passive:
name: passive
iri: neuroml:ionChannelPassive
parameters:
conductance: { value: 10, description: "nml:10pS" }
number: { value: 300 }
erev: { value: -54.3, description: "nml:-54.3mV" }
na:
name: na
iri: neuroml:ionChannelHH
parameters:
conductance: { value: 10, description: "nml:10pS" }
number: { value: 120000 }
erev: { value: 50, description: "nml:50mV" }
components:
m:
name: m
iri: neuroml:gateHHrates
parameters:
instances: { value: 3 }
components:
forwardRate:
name: forwardRate
iri: neuroml:HHExpLinearRate
parameters:
rate: { value: 1, description: "nml:1per_ms" }
midpoint: { value: -40, description: "nml:-40mV" }
scale: { value: 10, description: "nml:10mV" }
reverseRate:
name: reverseRate
iri: neuroml:HHExpRate
parameters:
rate: { value: 4, description: "nml:4per_ms" }
midpoint: { value: -65, description: "nml:-65mV" }
scale: { value: -18, description: "nml:-18mV" }
h:
name: h
iri: neuroml:gateHHrates
parameters:
instances: { value: 1 }
components:
forwardRate:
name: forwardRate
iri: neuroml:HHExpRate
parameters:
rate: { value: 0.07, description: "nml:0.07per_ms" }
midpoint: { value: -65, description: "nml:-65mV" }
scale: { value: -20, description: "nml:-20mV" }
reverseRate:
name: reverseRate
iri: neuroml:HHSigmoidRate
parameters:
rate: { value: 1, description: "nml:1per_ms" }
midpoint: { value: -35, description: "nml:-35mV" }
scale: { value: 10, description: "nml:10mV" }
k:
name: k
iri: neuroml:ionChannelHH
parameters:
conductance: { value: 10, description: "nml:10pS" }
number: { value: 36000 }
erev: { value: -77, description: "nml:-77mV" }
components:
n:
name: n
iri: neuroml:gateHHrates
parameters:
instances: { value: 4 }
components:
forwardRate:
name: forwardRate
iri: neuroml:HHExpLinearRate
parameters:
rate: { value: 0.1, description: "nml:0.1per_ms" }
midpoint: { value: -55, description: "nml:-55mV" }
scale: { value: 10, description: "nml:10mV" }
reverseRate:
name: reverseRate
iri: neuroml:HHExpRate
parameters:
rate: { value: 0.125, description: "nml:0.125per_ms" }
midpoint: { value: -65, description: "nml:-65mV" }
scale: { value: -80, description: "nml:-80mV" }
integration:
step_size: 0.01
duration: 300.0
time_scale: ms
""" )
print (f"Model: { exp. dynamics. name} " )
print (f"IRI: { exp. dynamics. iri} " )
print (f"Components: { list (exp.dynamics.components.keys())} " )
Model: HodgkinHuxley
IRI: neuroml:pointCellCondBased
Components: ['k', 'na', 'passive']
The reference uses a <cell> element with morphology and channelDensity, while TVBO uses pointCellCondBased with channelPopulation. For single-compartment cells, these are mathematically equivalent — the total conductance is the same. TVBO achieves numerical identity with the reference to within floating-point precision (max diff < 1 μV).
2. Render LEMS XML
xml = exp.render("lems" )
for line in xml.split(' \n ' ):
line_stripped = line.strip()
if any (tag in line_stripped for tag in ['<ionChannel' , '<gateHH' , '<pointCell' ,
'<channelPopulation' , '<pulseGenerator' , 'Rate type=' , '<Include' ]):
print (line_stripped)
<Include file="Cells.xml"/>
<Include file="Networks.xml"/>
<Include file="Simulation.xml"/>
<ionChannelHH id="k" conductance="10pS">
<gateHHrates id="n" instances="4">
<forwardRate type="HHExpLinearRate" rate="0.1per_ms" midpoint="-55mV" scale="10mV"/>
<reverseRate type="HHExpRate" rate="0.125per_ms" midpoint="-65mV" scale="-80mV"/>
<ionChannelHH id="na" conductance="10pS">
<gateHHrates id="h" instances="1">
<forwardRate type="HHExpRate" rate="0.07per_ms" midpoint="-65mV" scale="-20mV"/>
<reverseRate type="HHSigmoidRate" rate="1per_ms" midpoint="-35mV" scale="10mV"/>
<gateHHrates id="m" instances="3">
<forwardRate type="HHExpLinearRate" rate="1per_ms" midpoint="-40mV" scale="10mV"/>
<reverseRate type="HHExpRate" rate="4per_ms" midpoint="-65mV" scale="-18mV"/>
<ionChannelPassive id="passive" conductance="10pS"/>
<pointCellCondBased id="HodgkinHuxley" C="10pF" v0="-65mV" thresh="-20mV">
<channelPopulation id="k_pop" ionChannel="k" number="36000" erev="-77mV"/>
<channelPopulation id="na_pop" ionChannel="na" number="120000" erev="50mV"/>
<channelPopulation id="passive_pop" ionChannel="passive" number="300" erev="-54.3mV"/>
<pulseGenerator id="pulseGen1" delay="100ms" duration="100ms" amplitude="0.08nA"/>
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_Ex5_DetCell.xml" )
for name, arr in ref_outputs.items():
print (f" { name} : shape= { arr. shape} " )
ex5_v.dat: shape=(30001, 2)
ex5_vars.dat: shape=(30001, 4)
4. Run TVBO Version
import numpy as np
result = exp.run("neuroml" )
da = result.integration.data
time = da.coords['time' ].values
v_data = da.sel(variable= 'v' ).values
tvbo_arr = np.column_stack([time, v_data])
print (f"TVBO: shape= { tvbo_arr. shape} , t=[ { tvbo_arr[0 ,0 ]:.4f} , { tvbo_arr[- 1 ,0 ]:.4f} ]" )
TVBO: shape=(30001, 2), t=[0.0000, 0.3000]
5. Numerical Comparison
from _nml_helpers import compare_traces
import numpy as np
ref_arr = list (ref_outputs.values())[0 ]
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' ])
v: RMSE=0.000000 max_err=0.000001 corr=1.000000 ✅
{'v': {'rmse': np.float64(4.725915035351011e-08),
'max_err': np.float64(8.239999999999637e-07),
'corr': np.float64(0.9999999999951035),
'close': True}}
6. Plot
from _nml_helpers import plot_comparison
plot_comparison(
ref_v, tvbo_v,
ref_cols= ['time' , 'v' ], tvbo_cols= ['time' , 'v' ],
title= "Ex5: Detailed HH Cell — NeuroML vs TVBO" ,
time_scale= 1.0 , time_unit= "s" ,
)