\author{Michael Hines}
Introduction
NEURON is designed around the notion of
one-dimensional cable ``sections'' which
can be connected together to form any kind of branched cable and which
can be endowed with properties which may vary with position along the
section. The design goal is to allow the user to talk about neuron
properties in terms that are familiar to the neurophysiologist and,
conversely, to keep entirely separate the numerical issues (e.g. number of
spatial segments) from the specification of morphology.
A nerve model is described by:
- Declaring the section names. e.g.
-
create soma, axon, dendrite[3]
- Connecting the sections together. e.g.
-
soma connect axon(0), 0
for i=0,2 {
soma connect dendrite[i](0), 1
}
- Specifying the properties of the sections
-
soma { nseg=1 L=50 diam=50 insert hh gnabar_hh=.5*.120 }
axon { nseg=20 L=1000 diam=5 insert hh }
for i=0,2 dendrite[i] {
nseg=5 L=200 diam(0:1)=10:3 insert pas e_pas=-65 g_pas=.001
}
After a nerve model is fully described, it can be simulated by
initializing its state variables (e.g. voltage) and integrating
them in time. The user can initialize states explicitly at the
interpreter level or use a built-in function to perform a standard form
of initialization. For example, the built-in
function, finitialize(-65),
will initialize time
to 0, membrane potential to -65 millivolts
and the m,h,n HH state variables to their
steady state values at that potential.
A more general alternative would be to construct an explicit
initialization procedure. For example, an initialization procedure to do the
same thing as the built-in procedure is
proc init() {
t = 0
forall { /* iterates over all declared sections */
v = -65 rates_hh(-65)
if (ismembrane("hh")) { m_hh=minf_hh h_hh=hinf_hh n_hh=ninf_hh }
}
}
A procedure to integrate the variables by the amount of time
given by its argument might be
proc integrate() {local tstop
tstop = t + $1
while (t < tstop) {
fadvance()
}
}
Section properties
The greatest problem with our first nerve simulation program, CABLE,
was that the user was responsible for knowing the correspondence between
segment number and position on the nerve. All nerve properties were
assigned via vector variables in which
the index of the vector was the segment number.
Changing the number of segments that describe the
nerve was an error prone and laborious process.
This problem is overcome by the notion of a named section which can
be thought of as a one-dimensional cable of length, L,
(in $\mu m$) with a continuous position parameter,
$0 \leq x \leq 1$, in which $x = 1$ corresponds to absolute position L.
Most properties are functions of the position parameter and are called
``range variables''. Examples are the diameter in \micron, diam, the
membrane potential in mV, v, and, on occasion, the
maximum HH sodium conductance in \mhocm, gnabar. We return to
a discussion of range variables after discussing how we determine
which section we are talking about.
Section Name
Of course, since every section has a length called L (along with
other names which can be shared by many sections) it is always necessary to
specify {\em which} section is being discussed. There are three methods
of specifying which section a property refers to (with each being
compact in some contexts and cumbersome in others). They are given
below in order of precedence (highest first).
- Dot notation.
- This takes precedence over the other methods and
is described by the syntax {\em sectionname.varname}\ \ . Examples
are
dendrite[2].L = dendrite[1].L + dendrite[0].L
axon.v = soma.v
print soma.gnabar
axon.nseg = 2*axon.nseg
This notation is necessary when one needs to refer to more than
one section within a single statement.
- Stack of sections.
- The syntax is
sectionname stmt}
and means that the currently selected section during the
execution of stmt
is sectionname. This method was used in the introduction and
is most useful in programming since the user has explicit control over
the scope of the section and can set several range variables.
Notice that after the stmt is executed the currently selected
section reverts
to the name (if any) it had before sectionname was seen. The programmer
is allowed to
nest these statements to any level.
Avoid the error:
soma L=10 diam=10
which sets soma.L, then pops the section stack and sets diam of
whatever section is then on the stack.
- Default section.
- The syntax
access sectionname
defines a default section name to be the currently selected section when the
first two methods are not in effect. There is often a conceptually
privileged section which gets most of the use and it is useful to
declare that as the default section. e.g.
access soma
With this, one can, with a minimum of typing, get values of voltage, etc. at
the command line level.
In general, this statement should only be used once to give default access
to a priviledged section. It's bad programming practice to change the
default access within anything other than an initialization procedure.
The ``sectionname'' form is almost always the right way to
use the section stack.
The above paragraph discussed explicit selection of a single section.
A way of selecting a whole class of sections is through a special
looping command with the syntax `` forall stmt'' which
executes stmt once for each section declared in the create
statement. (The order in which the sections are specified in the
forall statement is unspecified). Within stmt it is
usually necessary to determine which section is, in fact selected, and
that is done with the functions:
- sectionname(str)
- The argument receives the name of the
currently selected section. (Note that stris a string variable and
must have been previously declared in a strdef statement.
- issection(str)
- The function returns the value 1 if the
currently selected section matches the regular expression defined by
str (which may be a literal string enclosed in pairs of double quotes
or a string variable that was assigned some specific string) and 0 otherwise.
The str may be a regular expression which can match a subset of
the section names and uses a syntax similar to the unix ``grep'' filter.
The rules which differ from grep are
- <...> replaces [...]
to avoid conflicts with indexed sections.
- {{\em ibegin}-{\em iend}} matches any integer between
ibegin and iend. Thus "a[{8-15}]" matches sections a[8] through
a[15]. Note that there can be no spaces in the braces.
- We always begin a match from the beginning of the section name.
This makes the carat character obsolete. If you don't want to require
a match from the beginning, use ``.*''. (Note that the dot matches
any character and the asterisk means 0 or more occurrences of the
previous character).
- We always close the string with an implicit $ to requre a match
at the end of the string. If you don't require a match at the end, close
the regular expression with ``.*''.
Examples of the use of regular expressions are:
- {issection("soma")} True only if selected section is soma.
- {issection("d.*")} True only if the selected section begins
with the letter d.
- {issection("dendrite[<456>]")} True only for singly dimensioned
dendrites with index 4, 5, or 6.
Generally, one or more ``issection'' functions appear in an ``if'' statement.
We note in passing that the function {\tt ismembrane("{\em mechanism}")}
has proven to be extremely useful as a selection property for sections.
See the section on membrane mechanisms for a fuller description.
For quick selection based on section names the following short forms
have been added.
{\tt ifsec "{\em name}"} can be used
in place of {\tt if (issection(".*{\em name}.*"))}\\
{\tt forsec "{\em name}"} can be used in place of {\tt forall ifsec "{\em name}"}\\
\end{quote}
- Section pointers
-
It is sometimes convenient to make use of the internal section pointers to
specify sections. e.g. effectively passing a section argument to a function.
The following functions should be avoided unless absolutely necessary.
- {\tt this\_section()}
- Returns a number that can be used
as a pointer to the currently accessed section.
- {\tt push\_section(i)}
- Pushes the section with pointer value i onto
the section stack and makes it the currently accessed section.
- {\tt pop\_section()}
- Pops the section stack. Note that the section
stack is cleared after every direct command to the interpreter. Therefore
a sequence of direct commands which manipulate the section stack should
be enclosed by braces. That is, push\_section cannot be used in place of
an {\tt access} statement which causes a section to be the default
section until changed by a subsequent {\tt access} statement.
Range Variables
The goal of separating property specification from segment number is
achieved through the use of ``range variables''. Only two variables
deal with the section as a whole, L which is the length of the
section, and nseg which is the number of segments into which
the section is divided for numerical purposes. One can change nseg
and not have to change any other property to keep the morphology and
membrane properties the same. (CAVEAT: a change to nseg discards the
internal representation of the section and therefore any code which
specifies the section must be re-executed. This is not a problem in
practice since a convenient style is to have nseg set explicitly along
with other properties as in the example in the Introduction and use the
editor to change its value.)
A range variable is assigned a value in one of two ways.
The simplest and most
common is as a constant. For example, diam is the range variable
name for the diameter of a section. The statement, {\tt axon.diam = 10},
specifies that the diameter of the axon is uniform over its entire
length. (An equivalent statement is ``{\tt axon diam=10}'').
On occasion it may be desired to vary the value of a property
along a length of section and this can be done with the syntax
\begin{quote}
{\tt rangevar({\em xmin} : {\em xmax} ) = {\em e1} : {\em e2}}
\end{quote}
in which the four italicised symbols are expressions with {\em e1} being
the value of the property at {\em xmin} and {\em e2} being the value of the
property at {\em xmax}. Note that there is a constraint,
$0 \leq xmin \leq xmax \leq 1$, on the value of the position expressions.
Linear interpolation is used to store the values of the property within
the segments in the
position range between {\em xmin} and {\em xmax}. In this manner
the user can approximate a continuously varying property as a piecewise
continuous function. (Of course the adequacy of the approximation will
depend on nseg).
The value of a range variable at the center of a segment
can appear in any expression using the
syntax, ``{\tt rangevar({\em xval})}'' in which $0 \leq xval \leq 1$. The value
returned is the value at the center of the segment containing {\em xval}, NOT
the linear interpolation of the values stored at the centers of
adjacent segments.
If the parentheses are omitted, the position defaults to a value of .5.
A special form of the for statement is available
\begin{quote}
{\tt for ({\em var}) {\em stmt}}
\end{quote}
in which, for each value of position that defines the center
of each segment in the selected section (along with position 0 and 1),
{\em var} is assigned that value and the {\em stmt} is executed. For
example to plot the membrane potential as a function of position along the
axon and dendrite 1 and 2:
plot(1) axon for (x) plot(x*L, v(x))
plot(1) dendrite[2] for (x) plot(x*L, v(x))
Connecting Sections
Any branched cable can be constructed by connecting sections using the
syntax:
\begin{quote}
{\tt connect {\em sectionname} (0), {\em xparent}}\ \ , or\\
{\tt connect {\em sectionname} (1), {\em xparent}}
\end{quote}
Here, position 0 or 1 of {\em sectionname} is connected to the currently
specified section at position {\em xparent} where $0 \leq xparent \leq 1$.
For example, to connect 20 spines at intervals of 10 microns along
the distal end of a dendrite
in which the head of the spine is defined to be position 0 and spines
are connected at the base, consider
create dendrite, spine[20]
access dendrite
dlen = L = 1000
for i=0,19 {
connect spine[i](1), 1 - i*10/dlen
}
The introduction also gives an example of how to connect sections.
Use the {\tt topology()} function to get a simple visualization of
the way the sections are connected.
Membrane Mechanisms
When a section is created, the only range variable
properties that are automatically
generated are
\begin{tabular}{l l}
v & membrane potential in \mv\\
diam & diameter in \micron\\
cm & specific membrane capacitance in \ufcm
\end{tabular}
Other mechanisms such as channels must be explicitly inserted within a
section via the ``{\tt insert} {\em mechanism}'' statement.
Mechanisms that are always available are hh and pas.
The range variables which are generated with ``{\tt insert pas}''
are
\begin{tabular}{l l}
g_pas & specific membrane conductance in \mhocm\\
e_pas & reversal potential in \mv
\end{tabular}
The range variables which are generated with ``{\tt insert hh}''
are
\begin{tabular}{l l l}
gnabar_hh &.120 \mhocm & Maximum specific sodium channel conductance\\
gkbar_hh & .036 \mhocm & Maximum potassium channel conductance\\
gl_hh & .0003 \mhocm & Leakage conductance\\
ena & 50 \mv & Sodium reversal potential\\
ek & -77 \mv & Potassium reversal potential\\
el_hh & -54.3 \mv & Leakage reversal potential\\
m_hh && sodium activation state variable\\
h_hh && sodium inactivation state variable\\
n_hh && potassium activation state variable\\
ina & \macm & sodium current\\
ik & \macm & potassium current
\end{tabular}
Other membrane mechanisms can be made available
by specifying their current-voltage relations using the model description
language (see MODL).
If a membrane mechanism, say {\tt X},
is inserted in a section and if that section
is the currently specified section, then the function
ismembrane("X")
will return the value 1 (0 otherwise). This is very useful
in any initialization procedures.
The function, {\tt psection()}, prints all non-default values for
all range variables that exist in the currently specified section.
To peruse a list of all values of all sections, try
forall { psection() xred("",0,0,0)}
(The xred function will just wait until you type a return key).
Point processes
Not all i-v relations are appropriately expressed in terms of
per unit area. Currents due to electrode stimuli of current and voltage
clamp as well as synaptic currents are more conveniently handled in terms
of absolute current in \na\ and absolute conductance in \umho.
Point Processes use an object syntax to manage their creation, insertion,
attributes, and destruction. Three kinds of point processes are always
available: PulseStim, VClamp, and AlphaSynapse.
Other user defined point processes can be linked into NEURON using the
model description language (see NMODL).
Point processes are created by setting an objectvar to a new instance
of the point process. eg.
objectvar stim
soma stim = new PulseStim(.5)
which inserts a pulse stimulus in the middle of the soma section.
When the pulse stimulus is no longer referenced by any object variable
the point process is removed from the section and destroyed. eg.
objectvar nil
stim = nil
or
objectvar stim
would destroy the above pulse stimulus since no other object variable
is referencing it.
A point process can be relocated to a new position as in
dendrite[2] stim.loc(0)
without losing the values of its attributes.
Note that point processes can exist exactly at x=0 and x=1 as well as
at the center of any segment within a section. When a segment containing
a point process is destroyed (eg. by re-createing the section or changing
nseg) the point process still exists but its attributes are lost and it
must be relocated to a specific location on some section.
Point processes also differ from density mechanisms in that any number of
the same kind of point process can exist at the same location. Thus
several PulseStim's can be used to implement a pulse train (although
it would probably be more convenient to create a PulseTrain using the
model description language).
The location of a point process can be determined via the .get_loc function
that pushes the section onto the section stack and returns the arc position,
($0 \leq x \leq 1$). Don't forget to use pop_section() to pop the section
stack after using this object function. Eg:
strdef sec
proc print_pp_location() { local x //arg1 must be a point process
x = $o1.get_loc()
sectionname(sec)
printf("%s located at %s(%g)\n", $o1, sec, x)
pop_section()
}