Two ways to represent the same NeuroML component type in TVBO
Use iri: neuroml:* to reference a built-in NeuroML2 type, or write the equations yourself — both simulate identically.
Two authoring styles, one model
TVBO offers two ways to describe a dynamics model that corresponds to a standard NeuroML2 ComponentType.
IRI reference — set iri: neuroml:<TypeName>, supply parameter values and initial conditions. TVBO uses the iri to look up the equations from the NeuroML2 library at render time, emitting a plain <Component> that reuses the built-in definition. No equation.rhs entries are needed in the YAML.
Inline YAML — omit the iri and write the state_variables with their equation.rhs strings. TVBO generates a custom LEMS <ComponentType> from those equations and can also run the model via any other backend (JAX, NumPy, …). Both paths are valid LEMS; the difference is in what the rendered file contains and which backends are available.
Dynamics name : FitzHughNagumo1969_IRI
IRI : neuroml:fitzHughNagumo1969Cell
Rendered LEMS (IRI path)
The LEMS file contains no custom ComponentType — it references the fitzHughNagumo1969Cell type that is already defined inside Cells.xml:
xml_iri = exp_iri.render("lems")# Show only the lines that reveal the component instantiationfor line in xml_iri.splitlines(): stripped = line.strip()if ( stripped.startswith('<Component')or stripped.startswith('<Include')or stripped.startswith('<ComponentType')or'fitzHugh'in strippedor stripped.startswith('<network')or stripped.startswith('<population') ):print(line)
Dynamics name : FitzHughNagumo1969_YAML
IRI : None
Rendered LEMS (inline path)
The LEMS file now defines the ComponentType, including the <TimeDerivative> elements generated from the YAML equations:
xml_yaml = exp_yaml.render("lems")# Show the ComponentType blockinside =Falsefor line in xml_yaml.splitlines(): stripped = line.strip()if stripped.startswith('<ComponentType') and'FitzHugh'in stripped: inside =Trueif inside:print(line)if inside and stripped.startswith('</ComponentType>'):break
<ComponentType name="FitzHughNagumo1969_YAML">
<!-- Parameters -->
<Parameter name="I" dimension="none"/>
<Parameter name="a" dimension="none"/>
<Parameter name="b" dimension="none"/>
<Parameter name="phi" dimension="none"/>
<!-- Coupling inputs -->
<!-- Initial condition parameters -->
<Parameter name="V_0" dimension="none"/>
<Parameter name="W_0" dimension="none"/>
<!-- Time conversion for derivatives.
When all parameters and state variables carry proper LEMS dimensions,
LEMS handles unit conversion natively (e.g. tau="30 ms" → 0.03 s).
No SEC constant is needed and TimeDerivatives use the RHS directly.
When dimensions are "none" (dimensionless models), / SEC converts
from model time to SI seconds.
all_dimensioned=False needs_sec=True time_scale=ms -->
<Constant name="SEC" dimension="time" value="1ms"/>
<!-- Exposures (one per state variable) -->
<Exposure name="V" dimension="none"/>
<Exposure name="W" dimension="none"/>
<Dynamics>
<!-- State variables -->
<StateVariable name="V" dimension="none" exposure="V"/>
<StateVariable name="W" dimension="none" exposure="W"/>
<!-- Derived variables (simple and conditional/piecewise) -->
<!-- ── Flat dynamics (no spike events) ── -->
<!-- Time derivatives -->
<TimeDerivative variable="V" value="(I + V - W - V^3/3) / SEC"/>
<TimeDerivative variable="W" value="(phi*(V + a - W*b)) / SEC"/>
<!-- Initial conditions -->
<OnStart>
<StateAssignment variable="V" value="V_0"/>
<StateAssignment variable="W" value="W_0"/>
</OnStart>
<!-- Events (non-spike) -->
</Dynamics>
</ComponentType>
Key observation: <TimeDerivative variable="V" value="..."/> — the equations appear verbatim (translated to LEMS syntax by LEMSPrinter).
What the two LEMS files differ in
# Count meaningful differences: ComponentType definitionsiri_has_ct ='<ComponentType'in xml_iriyaml_has_ct ='<ComponentType'in xml_yamlprint(f"IRI path defines a custom ComponentType : {iri_has_ct}")print(f"YAML path defines a custom ComponentType : {yaml_has_ct}")# Both should have the same Component instantiation parameter valuesimport redef extract_component_params(xml): m = re.search(r'<Component[^>]+fitzHugh[^>]*/>', xml, re.DOTALL)ifnot m: m = re.search(r'<Component[^>]+FitzHugh[^>]*/>', xml, re.DOTALL)return m.group(0) if m else"(not found)"print()print("IRI Component element:", extract_component_params(xml_iri))print()print("YAML Component element:", extract_component_params(xml_yaml))
IRI path defines a custom ComponentType : False
YAML path defines a custom ComponentType : True
IRI Component element: (not found)
YAML Component element: <Component id="FitzHughNagumo1969_YAML_inst" type="FitzHughNagumo1969_YAML" I="1.0" a="0.7" b="0.08" phi="0.08" V_0="0.0" W_0="0.0"/>
Numerical comparison
Run both experiments with jNeuroML and confirm the traces are identical.
import sys, ossys.path.insert(0, os.path.join(os.path.abspath("."), "examples"))from tvbo.adapters.neuroml import run_lems_exampleimport numpy as np# Run the database version which uses the IRI — it matches the canonical# NML2_AbstractCells example. We compare our two inline-rendered files# by running them through jNeuroML via a tempfile.import tempfile, subprocessfrom pathlib import Pathfrom pyneuroml import JNEUROML_VERSIONimport pyneuroml_jar = ( Path(pyneuroml.__file__).parent /"lib"/f"jNeuroML-{JNEUROML_VERSION}-jar-with-dependencies.jar")def run_xml_string(xml_string, label):"""Write LEMS XML to a temp file, run jNeuroML, return output array."""with tempfile.TemporaryDirectory() as tmpdir: tmpdir = Path(tmpdir) (tmpdir /"results").mkdir() lems_file = tmpdir /"sim.xml" lems_file.write_text(xml_string) result = subprocess.run( ["java", "-jar", str(_jar), str(lems_file), "-nogui"], capture_output=True, text=True, cwd=tmpdir, )if result.returncode !=0:raiseRuntimeError(f"{label} jNeuroML failed:\n{result.stderr[-2000:]}") dat_files =list((tmpdir /"results").glob("*.dat"))ifnot dat_files:raiseRuntimeError(f"{label}: no .dat output files found")return np.loadtxt(dat_files[0])arr_iri = run_xml_string(xml_iri, "IRI path")arr_yaml = run_xml_string(xml_yaml, "Inline YAML path")print(f"IRI output shape : {arr_iri.shape}")print(f"YAML output shape : {arr_yaml.shape}")
max_diff = np.max(np.abs(arr_iri - arr_yaml))print(f"Maximum absolute difference across all columns : {max_diff:.2e}")
Maximum absolute difference across all columns : 0.00e+00
Both traces are numerically identical — the difference is zero (or machine epsilon at most), because NeuroML’s built-in fitzHughNagumo1969Cell defines exactly the equations we wrote in YAML.
Overlay plot
import matplotlib.pyplot as plttime = arr_iri[:, 0]V_iri = arr_iri[:, 1]V_yaml = arr_yaml[:, 1]fig, axes = plt.subplots(1, 2, figsize=(11, 3.8))# Left: both V traces on top of each otherax = axes[0]ax.plot(time, V_iri, lw=2.5, label="IRI reference")ax.plot(time, V_yaml, lw=1.0, ls='--', color='tomato', label="Inline YAML")ax.set_xlabel("Time (ms)")ax.set_ylabel("V (dimensionless)")ax.set_title("FHN1969 — V trace")ax.legend()# Right: pointwise differenceax = axes[1]ax.plot(time, V_iri - V_yaml, color='steelblue')ax.set_xlabel("Time (ms)")ax.set_ylabel("ΔV")ax.set_title("IRI − Inline YAML (difference)")ax.axhline(0, color='k', lw=0.6, ls='--')plt.tight_layout()plt.show()
When to use each approach
Situation
Recommended style
The model is a standard NeuroML2 component (iafTauCell, izhikevichCell, fitzHughNagumo1969Cell, …)
iri: neuroml:<TypeName>
You are porting an existing NeuroML2 file to TVBO
iri: neuroml:<TypeName>
The model is novel or does not match any built-in type
Inline YAML with equations
You want equations visible in the TVBO spec file
Inline YAML
You want the LEMS output to be maximally concise
iri: neuroml:<TypeName>
The iri field is optional: TVBO runs correctly either way. Setting it instructs the LEMS adapter to emit a reference to the standard type rather than generate a custom ComponentType, which can simplify validation against the NeuroML2 schema.