NEURON Programming Tutorial #1

Introduction

NEURON is an extensible nerve modeling and simulation program. It allows you to create complex nerve models by connecting multiple one-dimensional sections together to form arbitrary cell morphologies, and allows you to insert multiple membrane properties into these sections (including channels, synapses, ionic concentrations, and counters). The interface was designed to present the neural modeler with a intuitive environment and hide the details of the numerical methods used in the simulation.

This set of tutorials will take you, step by step, through the process of creating a complex simulation. It starts with the basics: how to create a very simple model, how to run the simulator, how to display the simulation results. Then, it moves into the more advanced topics of adding complexity to the models and use of different types of graphs to display the results. Finally it shows you how to create new templates to include in a cell library, how to connect multiple cells together, and how to add new membrane mechanisms to the simulator.

It is always good to have a final product in mind when we start the modeling task, so here is ours: We are trying to model a simple four neuron system with three of the cells making synaptic connections to the fourth. Each of these cells have basically the same morphology: a single axon with minimal arborization at the end and several main dendritic trunks with complex branching. We believe there are Hodgkin-Huxley type ion channels in the axon and possibly in the soma, but are uncertain what type of channels are in the dendrites. We have intracellular somatic voltage measurements in the post-synaptic cell from current injections in the somas of the three other cells.

Begin at the beginning: How to start the simulator

  1. The command line syntax described in this section pertains to UNIX. MSWin and MacOS users can start NEURON using the "NEURON StdGui" or "Single Compartment" icons as described in the README documentation included with the installation package. The equivalent of the UNIX command line
    nrniv prog1.hoc -
    under MSWin is simply to double click on prog1.hoc in the file manager (aka "Windows Explorer"); on the Mac just drag prog1.hoc and drop it onto the NEURON icon, as described in the README documentation.
    To exit, either type
    quit()
    in the interpreter window, or use File/Exit from the interpreter window's drag bar.
  2. Regardless of which editor you use to create your hoc files, be sure to save the files as "plain text".
  3. I wouldn't advise using the "nrn" extension. Stick with "hoc" (MSWin users may find that the Registry doesn't know what to do about "nrn" files).
--NTC
NEURON is an interpreter which can accept commands directly from the keyboard or from a program you have entered. To start the simulator you enter:

nrniv
at the operating system command line prompt. If everything is installed properly, then you should see a prompt that looks like:

oc>
This is the oc> prompt and when you see this, you can enter NEURON commands directly from the keyboard. To exit from the simulator back to the operating system prompt, you enter CTRL-D.

All of the commands in these tutorials can be entered directly from the keyboard, but this would entail quite a bit of typing, so a more practical approach is to enter the commands into a program using your favorite editor and save them to a file. This program can then be run when NEURON starts by giving the name of your program after nrniv. For example, at the operating system command prompt, enter:

nrniv prog1.nrn

where prog1.nrn is the name of your program. This will run your program and then return you to the operating system prompt. If, on the other hand, you want to run your program, and then enter more commands into NEURON before exiting back to the operating system, you need to append a "-" to the end of the line. For example,

nrniv prog1.nrn -
The final "-" lets NEURON know not to exit after running the commands in your program. After the program loads you should see the familiar oc> prompt. If you forget the final "-", you will again see the operating system command prompt. At the oc> prompt, you can enter commands just as before.

Creating and accessing sections

The first step in creating our model is to build the neuron's morphology. Cells can usually be described by reducing their complex structures into cylindrical sections. For example, the simplest reduction is to remove the cell's axon and dendrites leaving only the cell body. By modeling only the cell body, you possibly lose a tremendous amount of useful information, but you can always increase the morphological complexity as needed (see Tutorial #2).

From our experimental measurements, we know that our cell to model is approximately 100 um in length and averages about 100 um in diameter. In NEURON, the cell's geometry is described in terms of cylindrical sections. Before we set the section's properties, we must first create a new section, soma, as follows:

create soma
This command creates a new section with the default properties (the number of segments in the section [1], the diameter [500 um], the length [100 um], the capacitance [1 uF/cm^2], the axial resistance used to connect sections together [34.5 ohm-cm] and the membrane voltage [-65 mV]). For our simulation, we need to change one of our soma's properties--the diameter of the section (diam)--but for program clarity, it is also advisable to set the number of segments (nseg) and the length of the section (L).

Since NEURON deals with many different sections each with their own unique name, we must tell NEURON which section we are working on when we want to access a section parameter. There are three ways to do this in NEURON. First we can access the section:

access soma
This tells NEURON that all subsequent references to properties of a section are intended for the soma. Now we can specify the properties we want to set:

nseg = 1
diam = 100
L = 100
The second way to access section properties is to use dot notation. Here, we access the section properties with the section name followed by a "dot" or period (i.e., ".") followed by the property name:

soma.nseg = 1
soma.diam = 100
soma.L = 100
With dot notation, the currently accessed section is not changed.

Methods #1 and #2 can both set and retrieve values. Above, we set the values. One way to retrieve the value is with the print command. If you have a currently accessed section (from the access command), you can print a property of that section with:

print diam
or you can print the property of any section by using the dot notation:

print soma.diam
You can examine the default values of the parameters by using the print command.

The third way is to group all of the properties together and preface the group with the section name you want to access. This has the advantage of not changing the currently accessed section (as in method #2):

soma nseg = 1
soma diam = 100
soma L = 100
or you can group multiple properties between braces:

soma {
    nseg = 1
    diam = 100
    L = 100
}
The third method is only used to set values.

We will use all three methods of specifying properties in the tutorials, but for tutorial #1 we mainly will use the first method. Thus, the program so far is:

create soma
access soma

nseg = 1
diam = 100
L = 100

Inserting membrane properties

Each section in NEURON has the default properties (see above) automatically inserted, but other mechanisms (e.g., channels) with their own properties must be explicitly inserted into a section. NEURON includes two built-in channel membrane mechanisms: Hodgkin-Huxley channels (hh) and passive channels (pas). Each of these mechanisms can be inserted using the insert command. If there is a currently accessed section, you can insert a new mechanism into that section by:

insert hh
You can also use the third method to insert a new mechanism:

soma insert pas
You can add as many properties into each section as you need for your model.

When you add a new membrane mechanism to a section, you add the new membrane mechanism's properties and their default values to the section. For example, if you add passive channels to the section, you will introduce two new properties to the section: g_pas (specific membrane conductance [S/cm^2]) and e_pas (reversal potential [mV]). For our simulation, we want to insert Hodgkin-Huxley channels into the soma. Since soma is the currently accessed section, we can do this with:

insert hh
The Hodgkin-Huxley channels add the following new properties to the section:

It also adds the following state variables, that can be displayed with the print command:

You can also define other membrane mechanisms with their own properties using the model description language.

To display all of the properties and their current values in a particular section, you can call the psection function. To see the properties of the currently accessed section:

psection()
or for a particular section:

soma psection()
or to list the properties of all sections in the model:

forall psection()
For our model, we can accept the default values for the other properties. Thus, our program looks like:

create soma
access soma

nseg = 1
diam = 100
L = 100

insert hh

Adding point processes

NEURON makes the distinction between mechanisms that are attributed to an entire section (e.g., HH channels) and mechanisms that are associated with a particular point in the section (e.g., voltage clamp or synapse). While the former is most conveniently expressed in terms of per unit area, the point processes are more conveniently expressed in absolute terms (e.g., current injection is usually expressed in terms of nA instead of nA/cm^2). Point processes also differ in that you insert several in the same segment.

In NEURON, point processes are handled as objects which means that to create one you need to first create an object variable to be associated with the object and then create a new object. To declare object variables, you enter the following:

objectvar stim
This creates an object variable named stim, and now you need to create the actual object. Newly created objects need to be associated with a particular section, so we need to either have a currently accessed section or to specify the section name with which the object is to be associated. The optional section name is give first followed by the assignment of the object variable to a new instance of a particular object (in this case a pulse stimulus) with the location of the object in the section given in parentheses. The location is specified with a number between 0 and 1 (inclusive) where the number represents the percent along the section to place the point process. For example:

stim = new IClamp(0.5)
or with the section name:

soma stim = new IClamp(0.5)
Since the currently accessed section in our program is soma, both of these commands will accomplish the same result (i.e., creating a new pulse stimulus in the middle of the soma).

There are several built-in point processes: IClamp, VClamp and AlphaSynapse. Additional point processes can be added to the simulator with the model description language. As with channels, each point process has its own set of properties. Below are the IClamp and AlphaSynapse point processes' properties (we will only be using these two in the tutorials).

IClamp:

AlphaSynapse:
In our model, we want to stimulate the soma by giving it a current pulse. We can accomplish this by adding a IClamp as above and then setting its properties. Point processes (and other objects) differ from sections in that there is no concept of a currently accessed point process. Thus, we cannot set the properties in all the same ways we set them in sections. Rather, we must use the dot notation. So, with the IClamp now our program looks like:

create soma
access soma

nseg = 1
diam = 100
L = 100

insert hh

objectvar stim
stim = new IClamp(0.5)

stim.del = 0
stim.dur = 0.1
stim.amp = 30

Running a simulation

Instead of
load_proc("nrnmainmenu")
use the command
load_file("nrngui.hoc")
--NTC
To ease the process of running simulations, NEURON has a standard run library of useful functions. These library functions are used to initialize the simulator, begin and end simulations, and plot variables vs. time or space during a simulation run. The most useful of these functions is the run() function. It initializes and runs a simulation.

To load the standard run library we must ask NEURON to load a function from the standard run library. When NEURON finds the file that contains this function, it will load all of the functions in this file into the simulator. Any function that we know is in the standard run library will do, but there is one that will always be in the library: nrnmainmenu. Thus, we usually enter the following command to load the library:

load_proc("nrnmainmenu")
The name of the function must be in quotes for this to work. After adding this line to your program, try starting the simulator with the program. At the oc> prompt, enter run() to run the simulation. After the simulation completes, NEURON returns you to the oc> prompt. Our program does not generate any output, so nothing will be printed, but if you print the voltage in the soma you will see that it changed from -65 mV to -76.135654 mV. To print the soma's voltage, enter:

print soma.v
or just:

print v
since soma is the most recently accessed section. This is the value at the end of the simulation time. As the default, simulations run for 5 ms of simulated time, but this can be changed by setting the tstop variable to another value (in ms). For example, to run a simulation for 1 ms, enter:

tstop = 1
run()
This will set the simulation stop time to 1 ms and run the simulation from the beginning. You can now print the soma's voltage to see what it is at that time. Try it for several different stop times (0.5, 1, 2, 4 and 10 ms).

Programming Style

In the last section we repeatedly entered the following three commands:

tstop = value
run()
print v
This results in a tremendous amount of typing, so it would be nice if we could define our own procedure to perform these commands for us.

In NEURON, you can define procedures with the proc statement. It contains three parts: the proc keyword, the name of the procedure you want to define, and the statements you want to be in the procedure. For example:

proc rununtil() {
    tstop = $1
    run()
    print "V = ", soma.v, "mV"
}
This defines a new procedure, named rununtil, which will run our simulation until a certain value and then print the soma's voltage. The parenthesis after the name are required--they tell NEURON that this is a procedure name and not a variable. Arguments can be passed to this function, and they are accessed via the $# variable where # is the number of the argument. In this case, tstop is assigned the value of the first parameter passed to the rununtil procedure.

After adding this procedure to your program, you can restart the simulator with the new program and display the soma's voltages at different times by simply entering:

rununtil(value)

where value is the number of milliseconds to run the simulation.

Organizing the simulation into procedures and functions is highly recommended. It leads to much clearer, more easily maintained and easier to debug simulations. For more information on procedures and functions, see the manuals and help documents.

In the example code provided along with this tutorial, you will notice that there are comments throughout the code. In the example programs, these are delimited by /* at the beginning of the comment and */ at the end of the comment. Another way to delimit comments is to place // at the beginning of the comment line which makes the rest of the line a comment (i.e., the end of the comment is delimited by the end of the line). Placing comments throughout your code will help you and others understand your code, and are highly recommended.

What's next

In the next tutorial we will look at the graphical user interface and how to add graphs to display simulation intermediate and final results.


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