Modelling single cells in NEURON using the Python interpreter
This is an introduction to using the Python interpreter to build simple single cell models with the NEURON simulator. I assume some familiarity with Python and with the standard NEURON interpreter, hoc. If you have a NeuralEnsemble login, feel free to edit this page. The original version of these notes is as http://www.davison.webfactional.com/notes/modelling-single-cells-neuron-python/.
For information about building/installing NEURON with Python, see Install for NeuralEnsemble experimental version of NEURON or Install6.1 for the official version 6.1. For information about accessing hoc from Python, see HocFromPython.
For more information, see the NEURON documentation.
To get started, either run NEURON with the -python option:
$ nrniv -python
or use another python interpreter, such as python or ipython:
$ python
Then import the neuron and nrn modules:
>>> from neuron import * >>> from nrn import *
The order is important if using an ordinary Python interpreter: nrn is not available until after neuron has been imported.
Creating a membrane section and manipulating its attributes
Membrane sections are represented by Section objects. They are instantiated with no arguments (although in the future it would be nice to be able to set section properties in the constructor):
>>> soma = Section() >>> type(soma) <type 'nrn.Section'>
As in hoc, a section's length, axial resistance and number of segments may be accessed and changed using dot notation:
>>> soma.L 100.0 >>> soma.nseg 1 >>> soma.Ra 35.399999999999999 >>> soma.nseg = 3 >>> soma.nseg 3
Each segment of the section may be accessed in turn by iterating over the section:
>>> for seg in soma: ... print seg.x, seg.diam ... 0.0 500.0 0.166666666667 500.0 0.5 500.0 0.833333333333 500.0 1.0 500.0
or an individual segment may be accessed by calling the Section object with the x-location (0-1) as an argument:
>>> central_seg = soma(0.5) >>> type(central_seg) <type 'nrn.Segment'> >>> central_seg.diam 500.0
Note the difference in syntax with respect to hoc:
oc> soma.v(0.5)
-65
>>> soma(0.5).v
-65.0
The currently-accessed section
The concept of the currently-accessed section is less important in Python than in hoc, but it exists nonetheless. When we created the soma section above, it became the currently accessed section by default. The function cas() in the nrn module returns the currently accessed section as a Section object:
>>> soma.name() 'PySec_402d1040' >>> cas().name() 'PySec_402d1040'
If we now create a new section, soma is still the currently-accessed section:
>>> dend = Section() >>> soma == cas() True >>> dend == cas() False
To make dend the currently-accessed section, use its push() method:
>>> dend.push() >>> dend == cas() True
We can now perform operations on dend using hoc calls, e.g.:
>>> dend.L
100.0
>>> h('L = 200')
1
>>> dend.L
200.0
(The 'h' HocObject comes from the neuron module. For more information on HocObjects, see HocFromPython).
To return to the previously-access section, use the hoc pop_section() function:
>>> h.pop_section() 1.0 >>> soma == cas() True
Connecting sections together
To connect two sections, call the connect() method of the child Section object with the parent section as the argument:
>>> dend.connect(soma)
By default, the '0' end of the child is connected to the '1' end of the parent. Which point on the parent to connect to and which end of the child to connect can be controlled with additional, optional arguments:
>>> axon = Section()
>>> axon.connect(soma, 0.1, 1)
>>> for sec in dend, axon:
... sec.push()
... h.psection()
... h.pop_section()
...
<nrn.Section object at 0x402d1050>
PySec_402d1050 { nseg=1 L=200 Ra=35.4
PySec_402d1040 connect PySec_402d1050 (0), 1
/* First segment only */
insert morphology { diam=500}
insert capacitance { cm=1}
}
<nrn.Section object at 0x402d1060>
PySec_402d1060 { nseg=1 L=100 Ra=35.4
PySec_402d1040 connect PySec_402d1060 (1), 0.1
/* First segment only */
insert morphology { diam=500}
insert capacitance { cm=1}
}
As an aside, it is very easy and often very convenient to wrap multi-stage hoc commands in a Python function, e.g.:
>>> def psection(sec):
... sec.push()
... h.psection()
... h.pop_section()
...
>>> psection(soma)
PySec_402d1040 { nseg=3 L=100 Ra=35.4
/*location 0 attached to cell 0*/
/* First segment only */
insert capacitance { cm=1}
insert morphology { diam=500}
}
Inserting membrane mechanisms
>>> soma.insert('pas')
>>> soma(0.5).pas.g
0.001
>>> soma(0.5).pas.e
-70.0
Contrast with the hoc syntax:
oc> soma.g_pas(0.5)
0.001
oc> soma.e_pas(0.5)
-70
To set values for all the segments in a section, iterate over them:
>>> for seg in soma: ... if hasattr(seg, 'pas'): ... seg.pas.g = 0.01*seg.x
The reason for the hasattr() call is that mechanisms are not inserted at the section ends, e.g.:
>>> soma(0).pas Traceback (most recent call last): File "<stdin>", line 1, in ? NameError: pas, the mechanism does not exist at PySec_402d1040(0)
Another way to avoid this problem is to iterate over the mechanisms:
for seg in soma: ... for mech in seg: ... if mech.name() == 'pas': ... print seg.x, mech.g, mech.e ... 0.166666666667 0.00166666666667 -70.0 0.5 0.005 -70.0 0.833333333333 0.00833333333333 -70.0
Creating and inserting point processes
Point processes such as IClamp/s must be associated with a membrane section, so first we must access that section:
>>> soma.push()
Now we create a hoc factory for that particular point process, create the point process and return to the previously-accessed section:
>>> h('obfunc new_IClamp() { return new IClamp($1) }')
>>> stim = h.new_IClamp(0.5)
>>> h.pop_section()
>>> type(stim)
<type 'hoc.HocObject'>
>>> stim.amp
0.0
>>> stim.dur
0.0
>>> stim.del
File "<stdin>", line 1
stim.del
^
SyntaxError: invalid syntax
What happened there? del is a reserved word in Python, which sometimes conflicts with names in hoc. To access such conflicting attributes, use the getattr() and setattr() functions:
>>> setattr(stim, 'del', 50.0) >>> getattr(stim, 'del') 50.0
If you are using the experimental NEURON branch from http://neuralensemble.org/trac/nrnpy/, this process is greatly simplified:
>>> stim = IClamp(soma, 0.5) >>> type(stim) <class 'neuron.IClamp'> >>> stim.delay = 30 >>> stim.delay 30.0
(Note that delay has been substituted for del). The underlying HocObject is accessible as the attribute hoc_obj:
>>> getattr(stim.hoc_obj, 'del') 30.0
To create a new point process Python class (for example, if you have defined your own mechanism using NMODL), use the new_point_process() function:
>>> MyMechanism = new_point_process('MyMechanism')
>>> my_mech = MyMechanism(axon, 0.1)
>>> my_mech.foo
3.14159
>>> my_mech.bar
2.71828
although this will not help with conflicting attributes such as del - you will have to write your own wrapper class in this situation (the easiest solution is just to avoid Python reserved words in your NMODL mechanisms!).
