NEURON Programming Tutorial #3

Introduction

At the beginning of the first tutorial, we defined our final product to be a model of four neurons with an axon and three main dendritic trunks with each section of the neurons containing (possibly) different types of channels.

In this tutorial, we will explore how to create the different sections and their properties, how to connect them together and finally how to display the data in a new dynamic graph--the space plot.

Cables

As was described in the first tutorial, NEURON works with cylindrical sections. So far, we have only created a single section containing a single compartment representing the soma. We are now going to add several more sections representing the axon and the three dendrites. These can be created just as the soma:

create soma, axon, dend[3]
Each section listed after the create command is separated by a comma. In the case of the dendrites, we use an array to represent the three dendrites. We could have created three different sections named dend1, dend2 and dend3, but, as you will see below, representing sections (and objects) as arrays has many benefits. The array indices are numbered from zero, so to access the first element of the array of dendrites, you need to use dend[0], and to access the last element, you need to use dend[2]. If arrays are unfamiliar, you can read about them in most programming books on the C Language.

One small note on programming style: In the future, if we want to re-use this code for another neuron that contain 4 dendritic trunks, we will have to change all of the 3's to 4's in the code. We can avoid this by using a variable to represent the number of dendrites. We need to create and reference the maximum number of dendrites through this variable throughout the program. For example:

ndend = 3
create soma, axon, dend[ndend]
This will give us the same result as above, but now if want to re-use this code in the future to represent 4 dendrites, all we have to do is change ndend from 3 to 4.

After creating the soma, axon and dendrite sections, we need to initialize their properties and add their channels. We already have the code to initialize the soma, but here we will use a different method for the initialization (see access method #3 in the first tutorial):

soma {
    nseg = 1
    diam = 100
    L = 100
    insert hh
}
The above code will set the number of segments, the diameter and length of the soma as well as insert Hodgkin-Huxley channels.

The axon is 5000 um long and 10 um in diameter, and also contains Hodgkin-Huxley channels. We could create the axon as follows:

axon {
    nseg = 1
    diam = 10
    L = 5000
    insert hh
}
but this has the disadvantage of only have one compartment in the axon. What this does is make the entire axon behave as if it were a single point--without any spatial differences. Normally we think of action potentials traveling down the axon from the soma to the synaptic terminals implying that spatial dimensions are important. To increase the spatial resolution of the axon, we can simply increase the number of segments in the section. In cable theory terms, this has the effect of increasing the spatial resolution of the cable equation. This is very important for accurate simulations. If the section's number of segments is too small, then the spatial resolution is low and you might not be able to see the fine details in the simulation results. If the section's number of segments is too large, then you might be unnecessarily extending the simulation time. By analyzing the effects of spatial resolution on the results (in addition to other analyses), you can achieve efficient and accurate simulation results.

The reason for NEURON's distinction between sections (high level conceptual modeling construct) and segments (low level numerical simulation detail) is to allow you to create models that do not rely on the number of segments (or compartments) in the model. Rather, you can create model entirely independent of the numerical details. Then, if during the analysis you find you need to increase the spatial resolution in a certain part of the model, you can do so simply by increasing nseg for that section.

For our simulation, we are interested in seeing the action potential travel down the axon, so we want to increase the number of segments:

axon {
    nseg = 50
    diam = 10
    L = 5000
    insert hh
}
Fifty segments in the axon is not a magic number, but it more than enough to see the action potential travel down the axon.

Lastly, we need to set the properties of the dendrites. In our model, all three of the dendrites have identical morphologies, so we can set the properties of all three to be the same. Usually we will use a for loop to step through each element in the array in turn. The format of the for loop is:

for var = lower-bound, upper-bound command

where var is the loop variable, lower-bound is the lower bound of the loop, upper-bound is the upper bound of the for loop and command is the command we want to run through the loop. upper-bound and lower-bound are inclusive. For example, to initialize the three dendrites we do the following:

for i = 0, ndend-1 dend[i] {
    nseg = 5
    diam = 25
    L = 500
    insert pas
}
Now that we have all of the section created and their properties set, we need to connect them together.

Connecting the sections

Since NEURON uses the abstraction of sections (instead of specifying individual compartments), we need a way to access specific points along the section. This is handled by using the section name followed by a number between 0 and 1 in parenthesis which represents the percent along the section you want to access. For example, axon(0) refers to a point at the proximal end of the axon, axon(1) refers to a point at the distal end of the axon, and axon(0.2) refers to a point 20% down the axon.

			  Section Name: axon
	    +--------------------------------------------+
            |                                            |
            +--------------------------------------------+
            0       0.2                                  1

            ^        ^                                   ^
            |        |                                   |
axon(0) ----+        |                                   |
axon(0.2) -----------+                                   |
axon(1) -------------------------------------------------+
We use this notation when connecting sections to one other since we need to specify what point of one section to connect to a point on another section and when we place point processes in a section. Note: This method of specifying a point on the section is entirely independent of the number of segments in the section. Thus, when you insert a point process (as in the first tutorial) 20% down the section, it is physically inserted into a segment 20% down the section. Later, if you increase the spatial resolution of the segment, the point process will still be physically inserted into a segment 20% down the section.

For our simulation, we are going to connect the axon to the soma, and the dendrites to the other end of the soma. We use the connect command to accomplish this:

connect axon(1), soma(0)
for i = 0, ndend-1 connect dend[i](0), soma(1)
This will connect the "1" end of the axon to the "0" end of the soma and the "0" ends of the dendrites to the "1" end of the soma (using the for command).

By connecting sections together, in this way, we can create arbitrarily complex morphologies.

Adding alpha synapses

Now instead of using a current clamp in the soma to excite the cell, we are going to use alpha synapses in each of the three dendrites. Alpha synapses refer to the shape of the function (an alpha function) used to model the conductance change in the post-synaptic neuron resulting from the binding of neurotransmitter released from the pre-synaptic neuron.

We must first declare the object variables associated with the alpha synapse objects:

objectvar syn[ndend]
Since there will be one alpha synapse per dendrite, we want to create the same number of synapses as dendrites. For our simulation, we will place the synapses 80% of the way out from the soma and initialize the alpha synapse properties as follows:

for i = 0, ndend-1 dend[i] {
    syn[i] = new AlphaSynapse(0.8)
    syn[i].tau = 0.1
    syn[i].onset = 0
    syn[i].gmax = 1
    syn[i].e = 15
}
Since we connected the "0" end of the dendrites to the soma, 80% of the way out from the soma is "0.8". Had we connected the "1" end, then 80% of the way out would be "0.2".

Another short note on style: As was introduced in the first tutorial, it is common to group related commands into a procedure (or function). For our program, the commands to set the properties of the sections can all be grouped into a single procedure. But, because NEURON dynamically interprets your commands, each section and object variable must be declared before they are used. This means that in order for NEURON to know how to interpret commands about a section, it must declared before the code that accesses it is interpreted by NEURON. For example, the following code would return an error since mydend is not created before the procedure is interpreted:

proc createdend() {local i
    ndend = $1
    create mydend[ndend]
    for i = 0, ndend-1 mydend[i].L = 500
}
Even though the create mydend command exists, mydend is not created until the procedure createdend is called. Thus, when NEURON interprets the mydend[i].L = 500 command, it does not yet have any section named mydend, so it returns an error. To correct this we need to create the mydend sections outside of the procedure. Now we have a different problem: we do not know the correct number of mydend sections to create, since this information is passed into the array. NEURON solves this problem by allowing you to redeclare a section or an array of sections inside a procedure. For this simple example, we need to create an array of mydend sections before the procedure createdend. The number of elements in the array does not matter, since we will re-create the array inside the procedure:

create mydend[1]

proc createdend() {local i
    ndend = $1
    create mydend[ndend]
    for i = 0, ndend-1 mydend[i].L = 500
}
In general, the two rules we need to follow are:

  1. If a section or object is referenced inside a procedure, create or declare it before the procedure in which it is used.
  2. When creating or declaring an array of sections or objects that will be recreated or redeclared inside of a procedure, create or declare an array of length 1 before the procedure in which the sections or objects are recreated or redeclared.
Luckily for our program, we know exactly how many dendrites to create, so we do not encounter the problem above. After grouping the commands together into a procedure, our program looks like:

load_proc("nrnmainmenu")

tstop = 10
ndend = 3

create soma, axon, dend[ndend]
access soma

objectvar syn[ndend]

proc init_cell() {
    soma {
	nseg = 1
	diam = 100
	L = 100

	insert hh
    }
    axon {
	nseg = 50
	diam = 10
	L = 5000

	insert hh
    }
    for i = 0, ndend-1 dend[i] {
	nseg = 5
	diam = 25
	L = 500

	insert pas
    }

    connect axon(1), soma(0)
    for i = 0, ndend-1 connect dend[i](0), soma(1)

    for i = 0, ndend-1 dend[i] {
	syn[i] = new AlphaSynapse(0.8)
	syn[i].tau = 0.1
	syn[i].onset = 0
	syn[i].gmax = 1
	syn[i].e = 15
    }
}

init_cell()
The final command, init_cell(), calls the procedure to initialize the cell when NEURON loads the program.

With this program you can create time plot graphs to see what is happening during the simulation. To see what is happening at different points in a section, you can specify the point along the section you want to graph (in parenthesis) from the Variable to graph window. For example, to see what is happening to the voltage at the end of the axon furthest away from the soma, you could plot axon.v(0) on a voltage graph, or to see what is happening to the passive current in the middle of the second dendrite, you could plot dend[1].i_pas(0.5) on a current graph.

Space plots

Time plots allow you to see exactly what happens to a variable vs. time at a single point in the neuron. To see what happens at all points in a section (or a group of sections), you can use a space plot. Unfortunately, to generate a static graph of a variable vs. both time and space you need to produce a 3-D graph, and monitor technology has not produced cheap 3-D monitors yet. So, NEURON gives you the next best thing: a movie-like graph called a space plot. This dynamic graph will plot a variable (e.g., voltage) vs. space for each time step in the simulation.

To create a space plot, select Shape plot from the New Graph menu in the NEURON Main Panel. This will open a shape plot window in which a schematic representation of the neuron will appear. From this window, you can select from the menu (with the right mouse button) a Space Plot. By default, voltage is the variable to plot, but you can change this with the Plot What? menu item (e.g., to ina or m_hh). To create the space plot graph, you need to select a section of the neuron to include in the space plot by clicking the left mouse button at the beginning of the section (in the schematic picture), dragging the mouse across the section you want to plot (while holding the mouse button down), and releasing the mouse button when you have covered the sections you want to plot. A line will appear from where you first clicked the mouse button to the current location of the pointer. When you release the mouse button, the sections you selected will be highlighted in color, and a new window with the space plot will be opened. Press the Init & Run button in the RunControl window to see the space plot in action.

After creating your space plot, you can save it to a session and add a line to the end of your program to load the session when the program is started:

xopen("prog3.ses")
With the space plots you can watch the action potential travel down the axon.

After creating the space plots, we recommend that you play with the number of segments in the axon (and dendrites) to see what effect they have on the simulation. Currently, NEURON requires that each time you change the number of segments in a section, you must also re-insert all of the membrane mechanisms (e.g., channels and point processes) before re-running the simulation. The can be most easily accomplished by changing the nseg parameter defined in the init_cell() function, and then re-running the program. You can also create a new procedure or modify our init_cell() procedure to insert all of the membrane mechanisms. Then, after changing the number of segments (nseg) in the sections, you can call this procedure to re-insert the membrane mechanisms in the sections.

What's next

In the next tutorial we will explore how to connect multiple cells together and how to create point processes.


Kevin E. Martin (martin@cs.unc.edu)