NEURON Programming Tutorial #4

Introduction

We are finally ready to finish the multiple cell model we started in the first tutorial. In this tutorial, we will create a template of the cell from which we can create multiple new copies of the cell, and then we will connect the cells together using a pair of new point processes that mimic the action of a pre- and post-synaptic connection.

Templates

In the last tutorial, we finished the first cell that we need in our four cell system. Now, we need to copy it three more times, and connect the cells together. NEURON provides us with an simple way to create multiple copies of the same cell: templates.

A template is an object definition--it defines a prototype of an object from which we can create multiple copies. After defining the template, we must declare the object variable that we will use to reference the objects, just like we have in the past with the IClamp and AlphaSynapse. Then, we can create a new instance of the object from the template that is an exact copy of the template. After we create the object from the template, we can either use it as it is or we can modify it to fit our needs.

For our system, we turn the cell we created in the last tutorial into a template from which we can create all four cells. The structure of a template is as follows:

begintemplate name
code
endtemplate name
where name is the name of the template you want to create, and code is any program commands that you want the template to include. In the code section, there are several procedures and commands that have special significance: public, external, and init().

The public statement is used to tell NEURON what parts of the template can be accessed outside of the template definition. Normally, if there are no public names, then the code inside the template is completely private and nothing, aside from the name of the template itself, is accessible from the rest of the program code. For example, if we create a neuron template and we want to be able to put a pulse stimulus in the soma of the cell we create, we would need to give access to the soma section via the public command:

begintemplate Cell
public soma

create soma, axon, dend[1]

proc init() {
    ndend = $1

    create soma, axon, dend[ndend]

    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)
}
endtemplate Cell
This example allows access to soma outside of the template, so we could create our four cells and insert the pulse stimulus as follows:

ncells = 3

objectvar cell, precells[ncells]
cell = new Cell(3)
for i = 1, ncells-1 {
    precells[i] = new Cell(2)
}

objectvar stim[ncells]
for i = 1, ncells-1 precells[i].soma {
    stim[i] = new IClamp(0.5)
    stim[i].dur = 0.1
    stim[i].amp = 500
}
After declaring the cell with the objectvar command and creating the object with the new command, we can access the soma using dot notation (e.g., precells[2].soma.L is the length of the soma in one of the cells we created).

This example also illustrates the init() procedure. Most templates will have a special procedure called init() which is automatically called when a new object is created from the template. This is very useful to initialize the newly created object. Since arguments can be passed to init(), you can affect the initialization of the object via the parameters you pass to the new command. For example, the cell = new Cell(3) command will create a new object from the template Cell [Note that case is important]. The parameter "3" is passed to the init() procedure in the template, and init() uses this information to create a cell with three dendrites.

Positioning cells in 3-D

Each time we create a new section and connect it to others, NEURON places the section in a 3-D space and assigns an X, Y and Z coordinate to each end of the section. When creating multiple cells, as we have above, each cell is given a different Z coordinate for all of its sections. The X and Y coordinates of each cell are determined by how the individual sections are connected. This makes viewing the cells difficult since they are not arranged how we would normally think of them. Fortunately, NEURON provides a way to reposition each section in the 3-D space.

We can use two functions to reposition each section: pt3dclear() and pt3dadd(). The first, pt3dclear(), will erase any 3-D positioning information associated with the section. The second, pt3dadd(), takes four arguments (X, Y, Z, and diam) and will add a new coordinate to the section. Usually there are coordinates for each end of the section which can be set by making two calls to pt3dadd()--once for the "0" end of the section and once for the "1" end of the section.

In the example program that accompanies this tutorial, prog4.nrn, we have positioned the cells so that the axons of the three pre-synaptic cells are next to the dendrite of the post-synaptic cell that they excite.

Specifying 3-D position data for each segment also allows data from reconstructed cells to be read into NEURON. For more information on positioning cells and on including reconstructed cells, please see the NEURON manuals.

Adding synapses to NEURON

To connect multiple cells together via synapses, we need to create a link between an action potential in a pre-synaptic cell and a conductance change in a post-synaptic cell. NEURON does not come with a standard mechanism to accomplish this, so we must create our own using the MODL modeling language.

For our example, we are not interested in the details of calcium concentration in the synaptic terminals, neurotransmitter release or post-synaptic receptor binding affinities; rather, we only want an action potential at the end of the pre-synaptic axon to trigger a simple postsynaptic conductance change (in the form of an alpha function) after a short synaptic delay. We can do this by adding two point processes to NEURON.

The two point processes we need to create, APTrig and TrigSyn, are intimately related. The first, APTrig, is a simple trigger which starts the alpha synapse in the second point process. Once the action potential (AP) in the pre-synaptic cell reaches a certain threshold, thresh, we want to start the synaptic delay count down for beginning the post-synaptic conductance change. Since the AP will exceed the threshold for some time and we only want the trigger to start on the rising phase of the AP, we need to keep track of when the AP is firing. We can do this by checking to see if the voltage, v, is greater than or equal to thresh, and is not firing. During the synaptic delay, we do not want to trigger another event, so we will also keep track of when the APTrig is counting down the synaptic delay, delay. The code to implement the APTrig is as follows (from aptrig.mod):

PROCEDURE check() {
VERBATIM
    if (v >= thresh && !firing && !trigger) {
	firing = 1;
	time = t;
	trigger = 1;
	n += 1.;
    }

    if (firing && v < thresh) {
	firing = 0;
    }

    if (trigger) {
	if (t >= time + delay) {
	    trigger = 0;
	    if (&(pstim)) {
		pstim += mag;
	    }
	}
    }
    return 0;
ENDVERBATIM
}
The VERBATIM and ENDVERBATIM statements tells the MODL compiler to compile the code between these two statements exactly as it appears in the point process definition. The code between these statements implements our AP trigger, and keeps a count of the number of times an AP has triggered a post-synaptic conductance change in the variable n.

We now have the code to implement the part of the model that needs to be solved, but several other sections in MODL files are needed to complete the model description. These are described below:

INDEPENDENT
This declares the independent variable used in the simulation. In our model, as in nearly all models, this is time, t.
UNITS
This block describes the units we will be using in the model description.
PARAMETER
These are variables that are set by the user and not changed by the model. For example, in our simulation, we need to set the the values for the threshold, synaptic delay and magnitude of the post-synaptic conductance change.
STATE
These are the unknown variables in the equation we are solving, and are usually solved for in the BREAKPOINT block (see below). APTrig does not use this type of variable.
ASSIGNED
These are variables that are computed directly in the assignment statements in the procedures we use to solve our equation. In APTrig, we keep track of several assigned variables, including the number of AP triggers (n), whether or not an AP is firing (firing) or in the synaptic delay phase (trigger), the time the trigger started (time), and the pointer to the post-synaptic stimulus (pstim).
INITIAL
This block initializes the variables we use in the model. We can also call procedures to initialize the variables.
BREAKPOINT
This block is used to specify the blocks that contain the model equations. The SOLVE statement specifies the block where the model equations are specified and the method use to solve them. For APTrig, the procedure check is specified, and during the simulation, it is called at each time step to solve for the unknown and assigned variables.
KINETIC
This block describes a series of chemical reactions with rate constants. For TrigSyn, the post-synaptic alpha conductance change is described as a set of chemical reactions.
NEURON
This special block specifies the type of the model, specifies any ions or currents that are used, and tells NEURON which variables can be accessed by the user and their type. For our models, we declare them as POINT_PROCESSes and declare most of the variables as range variables. In APTrig, we need a special POINTER variable, pstim, since we must access a variable, stim in our other point process, TrigSyn. Below we show how to initialize the pointer variable. See the NMODL manual for a complete description of the statements allowed in the NEURON block.
For further information on the MODL language see the NEURON, MODL and NMODL manuals. After creating the model descriptions, we still need to add the new models to NEURON. To do this, we need to compile the model descriptions of our new point processes into NEURON. At the operating system prompt, enter:

nrnivmodl
This will translate the model description files into C code, compile this code, and create a new version of NEURON with our new models. This new version of NEURON is created in the current directory and is called special. In the past, we have started NEURON with nrniv at the operating system prompt. For programs that use our new point processes, we need to use the new version of NEURON; therefore, for our final program we need to run special instead of nrniv.

Now that we have a special version of NEURON that contains our new point processes, we can use them to connect our cells via synapses.

Using the new point processes

In our template, we need to add the pre-synaptic APTrig point process to the axon and the post-synaptic TrigSyn point process to each dendrite. Just as with other point processes, we must first declare the object variables for each point process and then create the new point processes. We can then initialize the point process parameters we declared in our model description.

Our template now looks like:

begintemplate Cell
public soma, axon, dend, presyn, postsyn

create soma, axon, dend[1]

objectvar postsyn[1]
objectvar presyn

proc init() {local i
    ndend = $1

    create soma, axon, dend[ndend]

    objectvar postsyn[ndend]
    objectvar presyn

    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] {
	postsyn[i] = new TrigSyn(0.8)
	postsyn[i].tau = 0.1
	postsyn[i].e = 15
    }

    axon presyn = new APTrig(0)
    presyn.mag = 2.8
}
endtemplate Cell

Connecting our cells

To finish the synapse, we need to connect the pre- and post-synaptic point processes together by setting the pointer variable, pstim, from the APTrig point process to point to amplitude of the stimulus, stim, in the TrigSyn point processes. This can be accomplished with the setpointer command:

for i = 0,ncells-1 setpointer precells[i].presyn.pstim, cell.postsyn[i].stim
Now when an AP reaches threshold at the "0" end of the axon of pre-synaptic cell i and the trigger synaptic delay has passed, the pstim += mag statement in the APTrig point process tells NEURON to increase cell.postsyn[i].stim by mag (from the TrigSyn point process in the post-synaptic cell).

Where to go from here

There are many more commands, functions and options in NEURON, and currently the best place to learn about them is the help system. There are also several manuals, and, hopefully, there will soon be a book on NEURON.

Good Luck!


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