Basic Structure of NineML Abstraction Layer¶
In this tutorial, we build the Izhikevich model neuron. It is defined by the following dynamics:
\frac{dV}{dt} = 0.04V^2 + 5V + 140 -u + I \frac{du}{dt} = a(bV -u)
where if v> 30mV then we have a spike
v \leftarrow c u \leftarrow u + d
where a,b,c,d are parameters of our neuron and I is the injected current. V and U are state variables, which need to be solved over time.
Interfaces: Parameters and Ports¶
We begin by defining the interface to our neuron. The interface is composed of
- Parameters: values used to instantiate a component of a particular type. In this case, these would be
a, b, c and d. Parameters are set once at the beginning of a simulation.
- Ports: which allow the component to communicate with other components
during the simulation. Ports are divided into two categories:
- Event ports, which transmit or receive single, discrete events at
points in time. For example, an event could represent a neuron spiking.
- Analog ports, which transmit or receive continuous signals, for
example the membrane voltage of the neuron.
Furthermore, ports have a
direction
, specifying whether they represent information coming from the componentsend
, or information flowing into the component,recv
(Andreduce
, which will be discussed later.)
In this case, the neuron receives an injected current I, which will be a
recv
Analog-port. Other components (such as synapses) may be interested in
the neuron’s voltage, V, so we should transmit this as a send
Analog-port.
When the neuron reaches the condition for firing (v> 30mV), we may also
want to notify other components about this event, so we also have a send
Event-port.
We can build a ComponentClass
with this interface with the following code:
If you try running this code, you will receive the following error:
nineml.exceptions.exceptions.NineMLRuntimeError: Unable to find an Alias or State variable for analog-port: V
This is because we have defined a component and promised that we will transmit a value over the port V, but we have not defined V anywhere. We will fix this next.
Dynamics: Regimes & StateVariables¶
Now that we have defined the interface of the ComponentClass
, we now need
to define the internal dynamics of the system, to give it some behaviour. A
ComponentClass can contain StateVariables
, which are variables that
describe the internal state of the neuron. Typically, these are specified by
first-order-differential equations with-respect-to time. In our example, the
Izhikevich model has 2 state-variables, U and V.
The state-variables can have different behaviours when operating in different Regimes. A regime can be considered the ‘mode’ of the component; at any time, the component will be in a single ‘regime’, and it is possible to move between regimes. for example, an integrate-and-fire neuron with an explicit refractory period could be modelled as a component with two regimes, a default regime, where injected current affects membrane voltage, where and a second refractory regime where the voltage is fixed to a certain value.
For this model, the differential-equations governing the state variables never change, so we only need a single regime.
In this case, we have specified the state-variables for this component by
explicitly providing a list of the state-variables to the Dynamics
blocks.
This is not essential, if it is not provided, it will automatically be inferred
from the state-variable definitions in the Regimes, but if it is given, it must
match exactly.
This code should now run; but we are missing the condition, v> 30mV.
Transitions: Events, Conditions & Assignments¶
We have discussed that component can contain multiple Regimes
. In order to
move between regimes; we introduce the idea of Transition
s. A transition
can be:
- OnEvent - A transition triggered by an event arriving on a
recv
EventPort.- OnCondition - A transition triggered by a condition.
When a transition
occurs, three things can optionally occur:
- An event can be emitted on a
send
EventPort, for example, when a membrane voltage reaches a threshold values, we may want to send an event to signal a spike occurring.- StateVariables can be changes through
StateAssignment
. For example, a transition in a synapse component may cause the post-synaptic conductance to increase by a fixed amount.- The component can switch to another regime; i.e. respond to another set of differential equations.
For the Izhikevich model, we will use an ‘OnCondition’ transition, which should update the state-variables, U and V according to the equations:
v \leftarrow c u \leftarrow u + d
We will also emit a spike on the EventPort spikeoutput
, as this might be
useful if we want to use this component as part of a larger system. Since we
only have a single regime, we will not change regime.
Multiple Regimes & Transitions¶
We have only discussed the case of a single regime. A leaky integrate-and-fire model with refractory period has two dynamical regimes - the sub-threshold regime and the refractory regime. Just for fun, we’ll define the component in a single step:
Note that here we used the name of the regime in the to
argument to the
On
transition constructor, rather than a Regime
object. These references
are resolved automatically when the component is built.
If the differential equations for a StateVariable
are not defined within a
regime, then it is assumed that the state-variable does not change in that
Regime, i.e. d/dt = 0.
Further Classes¶
Aliases¶
Aliases are motivated by 2 cases; firstly that we would like to be able to
send
something other than pure StateVariables
, and that often we end
up re-using calculations. For example, if we want to define a conductance-based
synapse in NineML, then we would like to specify the current in the
post-synaptic neuron.
Note
When specifying Aliases, we use the syntax :=
instead of =
In this case, we define an Alias
, I, which can used in a send
port.
Aliases can also be used on the right-hand-side of other aliases, Condition
s, StateAssignment
s and TimeDerivative
s.
Reduce Ports¶
We have discussed send
and recv
ports, but there is another
port-mode, which is reduce
. A reduce
port is also a port that takes
in data; but it can take information from multiple send
ports. A typical
example might be the injected current into a neuron. Current can come into a
neuron from current injection, synapses or membrane channels. A recv
port
is not sufficient in this case, because a recv
port can only take
information from one other send
port. Instead, we use a reduce
port,
which takes an additional parameter reduce_op
. This specifies how the
incoming data should be defined. For example, to calculate the total current
flowing into a cell, we would add
all the current sources together, so we
would create the port as:
p = AnalogPort(name="I", mode='reduce', reduce_op='+')
See the docs for AnalogPort for more information.
Specifying Mathematical Strings¶
When specifying mathematics, we use a notation similar to C/C++. That is:
(3B + 1)V^2
is not valid, it should be written as:
V * V * (3*B + 1)
Depending on what is being specified, we specify the mathematics slightly differently:
Aliases should be of the form:
alias := some * equationTimeDerivatives for a state-variable, S, should be of the form:
dS/dt = some * equationStateAssignments must be written out in full; there is no in-place operators:
g += q # Invalid g = g + q # Valid