Simulating a regular spiking Izhikevich neuron

In this section, we wish to simulate a single regular spiking Izhikevich neuron ([Izh07]) and record/visualise its membrane potential (as shown in the figure below):

Membrane potential for neuron recorded from the simulation

Fig. 8 Membrane potential of the simulated regular spiking Izhikevich neuron.

This plot, saved as example-single-izhikevich2007cell-sim-v.png, is generated using the following Python NeuroML script:

#!/usr/bin/env python3
"""
Simulating a regular spiking Izhikevich neuron with NeuroML.

File: izhikevich-single-neuron.py
"""

from neuroml import NeuroMLDocument
from neuroml import Izhikevich2007Cell
from neuroml import Population
from neuroml import Network
from neuroml import PulseGenerator
from neuroml import ExplicitInput
import neuroml.writers as writers
from neuroml.utils import validate_neuroml2
from pyneuroml import pynml
from pyneuroml.lems import LEMSSimulation
import numpy as np


# Create a new NeuroML model document
nml_doc = NeuroMLDocument(id="IzhSingleNeuron")

# Define the Izhikevich cell and add it to the model in the document
izh0 = Izhikevich2007Cell(
    id="izh2007RS0", v0="-60mV", C="100pF", k="0.7nS_per_mV", vr="-60mV",
    vt="-40mV", vpeak="35mV", a="0.03per_ms", b="-2nS", c="-50.0mV", d="100pA")
nml_doc.izhikevich2007_cells.append(izh0)

# Create a network and add it to the model
net = Network(id="IzhNet")
nml_doc.networks.append(net)

# Create a population of defined cells and add it to the model
size0 = 1
pop0 = Population(id="IzhPop0", component=izh0.id, size=size0)
net.populations.append(pop0)

# Define an external stimulus and add it to the model
pg = PulseGenerator(
    id="pulseGen_%i" % 0, delay="0ms", duration="1000ms",
    amplitude="0.07 nA"
)
nml_doc.pulse_generators.append(pg)
exp_input = ExplicitInput(target="%s[%i]" % (pop0.id, 0), input=pg.id)
net.explicit_inputs.append(exp_input)

# Write the NeuroML model to a file
nml_file = 'izhikevich2007_single_cell_network.nml'
writers.NeuroMLWriter.write(nml_doc, nml_file)
print("Written network file to: " + nml_file)

# Validate the NeuroML model against the NeuroML schema
validate_neuroml2(nml_file)

################################################################################
## The NeuroML file has now been created and validated. The rest of the code
## involves writing a LEMS simulation file to run the model

# Create a simulation instance of the model
simulation_id = "example-single-izhikevich2007cell-sim"
simulation = LEMSSimulation(sim_id=simulation_id,
                            duration=1000, dt=0.1, simulation_seed=123)
simulation.assign_simulation_target(net.id)
simulation.include_neuroml2_file(nml_file)

# Define the output file to store simulation outputs
# we record the neuron's membrane potential
simulation.create_output_file(
    "output0", "%s.v.dat" % simulation_id
)
simulation.add_column_to_output_file("output0", 'IzhPop0[0]', 'IzhPop0[0]/v')

# Save the simulation to a file
lems_simulation_file = simulation.save_to_file()

# Run the simulation using the jNeuroML simulator
pynml.run_lems_with_jneuroml(
    lems_simulation_file, max_memory="2G", nogui=True, plot=False
)

# Load the data from the file and plot the graph for the membrane potential
# using the pynml generate_plot utility function.
data_array = np.loadtxt("%s.v.dat" % simulation_id)
pynml.generate_plot(
    [data_array[:, 0]], [data_array[:, 1]],
    "Membrane potential", show_plot_already=False,
    save_figure_to="%s-v.png" % simulation_id,
    xaxis="time (s)", yaxis="membrane potential (V)"
)

Declaring the model in NeuroML

Python is the suggested programming language to use for working with NeuroML.

The Python NeuroML tools and libraries provide a convenient, easy to use interface to use NeuroML.

Let us step through the different sections of the Python script. To start writing a model in NeuroML, we first create a NeuroMLDocument. This document is the top level container for everything that the model should contain.

# Create a new NeuroML model document
nml_doc = NeuroMLDocument(id="IzhSingleNeuron")

Next, all entities that we want to use in the model must be defined. The NeuroML specification includes many standard entities, and it is possible to also define new entities that may not already be included in the NeuroML specification. We will look at the pre-defined entities, and how NeuroML may be extended later when we look at the NeuroML standard in detail. For now, we limit ourselves to defining a new Izhikevich2007Cell (definition of this here). The Izhikevich neuron model can take sets of parameters to show different types of spiking behaviour. Here, we define an instance of the general Izhikevich cell using parameters that exhibit regular spiking.

Units in NeuroML

NeuroML defines a standard set of units that can be used in models. Learn more about units and dimensions in NeuroML and LEMS here.

Once defined, we add this to our NeuroMLDocument.

# Define the Izhikevich cell and add it to the model in the document
izh0 = Izhikevich2007Cell(
    id="izh2007RS0", v0="-60mV", C="100pF", k="0.7nS_per_mV", vr="-60mV",
    vt="-40mV", vpeak="35mV", a="0.03per_ms", b="-2nS", c="-50.0mV", d="100pA")
nml_doc.izhikevich2007_cells.append(izh0)

Now that the neuron has been defined, we declare a network with a population of these neurons to create a network. Here, our model includes one network which includes only one population, which in turn only consists of a single neuron. Once the network, its populations, and their neurons have been declared, we again them to our model:

# Create a network and add it to the model
net = Network(id="IzhNet")
nml_doc.networks.append(net)

# Create a population of defined cells and add it to the model
size0 = 1
pop0 = Population(id="IzhPop0", component=izh0.id, size=size0)
net.populations.append(pop0)

To record the membrane potential of the neuron, we must give it some external input that makes it spike. As with the neuron, we create and add a pulse generator to our network. We then connect it to our neuron, the target using an explicit input.

# Define an external stimulus and add it to the model
pg = PulseGenerator(
    id="pulseGen_%i" % 0, delay="0ms", duration="1000ms",
    amplitude="0.07 nA"
)
nml_doc.pulse_generators.append(pg)
exp_input = ExplicitInput(target="%s[%i]" % (pop0.id, 0), input=pg.id)
net.explicit_inputs.append(exp_input)

This completes our model. It includes a single network, with one population of one neuron that is driven by one pulse generator. At this point, we can save our model to a file and validate it to check if it conforms to the NeuroML schema (more on this later).

# Write the NeuroML model to a file
nml_file = 'izhikevich2007_single_cell_network.nml'
writers.NeuroMLWriter.write(nml_doc, nml_file)
print("Written network file to: " + nml_file)

# Validate the NeuroML model against the NeuroML schema
validate_neuroml2(nml_file)

The generated NeuroML model

We have now defined our model in NeuroML. Let us investigate the generated NeuroML file:

<neuroml xmlns="http://www.neuroml.org/schema/neuroml2"  xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.neuroml.org/schema/neuroml2 https://raw.github.com/NeuroML/NeuroML2/development/Schemas/NeuroML2/NeuroML_v2.2.xsd" id="IzhSingleNeuron">
    <izhikevich2007Cell id="izh2007RS0" C="100pF" v0="-60mV" k="0.7nS_per_mV" vr="-60mV" vt="-40mV" vpeak="35mV" a="0.03per_ms" b="-2nS" c="-50.0mV" d="100pA"/>
    <pulseGenerator id="pulseGen_0" delay="0ms" duration="1000ms" amplitude="0.07 nA"/>
    <network id="IzhNet">
        <population id="IzhPop0" component="izh2007RS0" size="1"/>
        <explicitInput target="IzhPop0[0]" input="pulseGen_0"/>
    </network>
</neuroml>

NeuroML files are written in XML. So, they consist of tags and attributes and can be processed by general purpose XML tools. Each entity between chevrons is a tag: <..>, and each tag may have multiple attributes that are defined using the name=value format. For example <neuroml ..> is a tag, that contains the id attribute with value NML2_SimpleIonChannel.

XML Tutorial

For details on XML, have a look through this tutorial.

Is this XML well-formed?

A NeuroML file needs to be both 1) well-formed, as in complies with the general rules of the XML language syntax, and 2) valid, i.e. contains the expected NeuroML specific tags/attributes.

Is the XML shown above well-formed? See for yourself. Copy the NeuroML file listed above and check it using an online XML syntax checker.

Let us step through this file to understand the different constructs used in it. The first segment introduces the neuroml tag that includes information on the specification that this NeuroML file adheres to.

<neuroml xmlns="http://www.neuroml.org/schema/neuroml2"  xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.neuroml.org/schema/neuroml2 https://raw.github.com/NeuroML/NeuroML2/development/Schemas/NeuroML2/NeuroML_v2.2.xsd" id="IzhSingleNeuron">

The first attribute, xmlns defines the XML namespace. All the tags that are defined for use in NeuroML are defined for use in the NeuroML namespace. This prevents conflicts with other XML schemas that may use the same tags. Read more on XML namespaces here.

The remaining lines in this snippet refer to the XML Schema that is defined for NeuroML. XML itself does not define any tags, so any tags can be used in a general XML document. Here is an example of a valid XML document, a simple HTML snippet:

<html>
<head>
<title>A title</title>
</head>
</html>

NeuroML, however, does not use these tags. It defines its own set of standard tags using an XML Schema. In other words, the NeuroML XML schema defines the structure and contents of a valid NeuroML document. Various tools can then compare NeuroML documents to the NeuroML Schema to validate them.

Purpose of the NeuroML schema

The NeuroML Schema defines the structure and contents of a valid NeuroML document.

The xmlns:xi attribute documents that NeuroML has a defined XML Schema. The next attribute, xsi:schemaLocation tells us the locations of the NeuroML Schema. Here, two locations are provided:

We will look at the NeuroML schema in detail in later sections. All NeuroML files must include the neuroml tag, and the attributes related to the NeuroML Schema. The last attribute, id is the identification (or the name) of this particular NeuroML document.

The remaining part of the file is the declaration of the model and its dynamics:

    <izhikevich2007Cell id="izh2007RS0" C="100pF" v0="-60mV" k="0.7nS_per_mV" vr="-60mV" vt="-40mV" vpeak="35mV" a="0.03per_ms" b="-2nS" c="-50.0mV" d="100pA"/>
    <pulseGenerator id="pulseGen_0" delay="0ms" duration="1000ms" amplitude="0.07 nA"/>
    <network id="IzhNet">
        <population id="IzhPop0" component="izh2007RS0" size="1"/>
        <explicitInput target="IzhPop0[0]" input="pulseGen_0"/>
    </network>

The cell, is defined in the izhikevich2007Cell tag, which has a number of attributes (see here for more):

  • id: the name that we want to give to this cell. To refer to it later, for example,

  • v0: the initial membrane potential for the cell,

  • C: the leak conductance,

  • k: conductance per voltage,

  • vr: the membrane potential after a spike,

  • vt: the threshold membrane potential, to detect a spike,

  • vpeak: the peak membrane potential,

  • a, b, c, and d: are parameters of the Izhikevich neuron model.

Similarly, the pulseGenerator is also defined, and the network tag includes the population and explicitInput. We observe that even though we have declared the entities, and the values for parameters that govern them, we do not state what and how these parameters are used. This is because NeuroML is a declarative language that defines the structure of models. We do not need to define how the dynamics of the different parts of the model are implemented. As we will see further below, these are already defined in NeuroML.

NeuroML is a declarative language.

Users describe the various components of the model but do not need to worry about how they are implemented.

We have seen how an Izhikevich cell can be declared in NeuroML, with all its parameters. However, given that NeuroML develops a standard and defines what tags and attributes can be used, let us see how these are defined for the Izhikevich cell. The Izhikevich cell is defined in version 2 of the NeuroML schema here:

    <xs:complexType name="Izhikevich2007Cell">
        <xs:complexContent>
            <xs:extension base="BaseCellMembPotCap">
                <xs:attribute name="v0" type="Nml2Quantity_voltage" use="required"/>
                <xs:attribute name="k" type="Nml2Quantity_conductancePerVoltage" use="required"/>
                <xs:attribute name="vr" type="Nml2Quantity_voltage" use="required"/>
                <xs:attribute name="vt" type="Nml2Quantity_voltage" use="required"/>
                <xs:attribute name="vpeak" type="Nml2Quantity_voltage" use="required"/>
                <xs:attribute name="a" type="Nml2Quantity_pertime" use="required"/>
                <xs:attribute name="b" type="Nml2Quantity_conductance" use="required"/>
                <xs:attribute name="c" type="Nml2Quantity_voltage" use="required"/>
                <xs:attribute name="d" type="Nml2Quantity_current" use="required"/>
            </xs:extension>
        </xs:complexContent>
    </xs:complexType>

The xs: prefix indicates that these are all part of an XML Schema. The Izhikevich cell and all its parameters are defined in the schema. As we saw before, parameters of the model are defined as attributes in NeuroML files. So, here in the schema, they are also defined as attributes of the complexType that the schema describes. The schema also specifies which of the parameters are necessary, and what their dimensions (units) are using the use and type properties.

This schema gives us all the information we need to describe an Izhikevich cell in NeuroML. Using the specification in the Schema, any number of Izhikevich cells can be defined in a NeuroML file with the necessary parameter sets to create networks of Izhikevich cells.

As is evident, XML files are excellent for storing structured data, but may not be easy to write by hand. However, NeuroML users are not expected to write in XML. They should use the Python tools as demonstrated here.

Simulating the model

Until now, we have just declared the model. We have not, however, included any information related to the simulation of this model. The same model may be instantiated many times with different random seeds and so on to give rise to different simulations and behaviours. In NeuroML, the information required to simulate the model is provided using LEMS. We will not go into the details of LEMS just yet. We will limit ourselves to the bits necessary to simulate our Izhikevich neuron only.

The following lines of code instantiate a new simulation with certain simulation parameters: duration, dt, simulation_seed. Additionally, they also define what information is being recorded from the simulation. In this case, we create an output file, and then add a new column to record the membrane potential v from our one neuron in the one population in it. You can read more about recording from NeuroML simulations here.

Finally, like we had saved our NeuroML model to a file, we also save our LEMS document to a file.

# Create a simulation instance of the model
simulation_id = "example-single-izhikevich2007cell-sim"
simulation = LEMSSimulation(sim_id=simulation_id,
                            duration=1000, dt=0.1, simulation_seed=123)
simulation.assign_simulation_target(net.id)
simulation.include_neuroml2_file(nml_file)

# Define the output file to store simulation outputs
# we record the neuron's membrane potential
simulation.create_output_file(
    "output0", "%s.v.dat" % simulation_id
)
simulation.add_column_to_output_file("output0", 'IzhPop0[0]', 'IzhPop0[0]/v')

# Save the simulation to a file
lems_simulation_file = simulation.save_to_file()

The generated LEMS file is shown below:

<Lems>
    
    <!-- 

        This LEMS file has been automatically generated using PyNeuroML v0.5.13 (libNeuroML v0.2.57)

     -->
    
    <!-- Specify which component to run -->
    <Target component="example-single-izhikevich2007cell-sim"/>

    <!-- Include core NeuroML2 ComponentType definitions -->
    <Include file="Cells.xml"/>
    <Include file="Networks.xml"/>
    <Include file="Simulation.xml"/>
    
    <Include file="izhikevich2007_single_cell_network.nml"/>
   
    <Simulation id="example-single-izhikevich2007cell-sim" length="1000ms" step="0.1ms" target="IzhNet" seed="123">  <!-- Note seed: ensures same random numbers used every run -->
        
        <OutputFile id="output0" fileName="example-single-izhikevich2007cell-sim.v.dat">
            <OutputColumn id="IzhPop0[0]" quantity="IzhPop0[0]/v"/> 
        </OutputFile>
        
    </Simulation>

</Lems>

Similar to NeuroML, LEMS also has a well defined schema. I.e., a set of valid tags define a LEMS file. We observe that whereas the NeuroML tags were related to the modelling parameters, the LEMS tags are related to simulation. We also note that our NeuroML model has been “included” in the LEMS file, so that all entities defined there are now known to the LEMS simulation also. Like NeuroML, users are not expected to write the LEMS XML component by hand. They should continue to use the NeuroML Python tools.

Finally, pyNeuroML also includes functions that allow you to run the simulation from the Python script itself:

# Run the simulation using the jNeuroML simulator
pynml.run_lems_with_jneuroml(
    lems_simulation_file, max_memory="2G", nogui=True, plot=False
)

Here, we are running our simulation using the jNeuroML simulator, which is bundled with pyNeuroML. Since NeuroML is a well defined standard, models defined in NeuroML can also be run using other supported simulators.

Plotting the recorded membrane potential

Once we have simulated our model and the data has been collected in the specified file, we can analyse the data. pyNeuroML also includes some helpful functions to quickly plot various recorded variables. The last few lines of code shows how the membrane potential plot at the top of the page is generated.

# Load the data from the file and plot the graph for the membrane potential
# using the pynml generate_plot utility function.
data_array = np.loadtxt("%s.v.dat" % simulation_id)
pynml.generate_plot(
    [data_array[:, 0]], [data_array[:, 1]],
    "Membrane potential", show_plot_already=False,
    save_figure_to="%s-v.png" % simulation_id,
    xaxis="time (s)", yaxis="membrane potential (V)"
)

The next section is an interactive Jupyter notebook where you can play with this example. Click the “launch” button in the top right hand corner to run the notebook in a configured service. You do not need to install any software on your computer to run these notebooks.