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 namewhere 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 CellThis 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.
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:
nrnivmodlThis 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.
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
for i = 0,ncells-1 setpointer precells[i].presyn.pstim, cell.postsyn[i].stimNow 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).
Good Luck!