range morphology and mechanism syntax for sections in python

When Python is the interpreter, what is a good
design for the interface to the basic NEURON
concepts.

Moderator: hines

Post Reply
nicholasmg
Posts: 7
Joined: Sat Feb 29, 2020 11:16 am
Location: University of Colorado

range morphology and mechanism syntax for sections in python

Post by nicholasmg » Mon May 11, 2020 11:26 pm

I am trying to replicate the hoc syntax for specifying a range along a section as described in the documentation for diam in hoc syntax.
Specifically, if I want the diameter of a section to vary from 1.5um to 0.5um along the length, according to the link above I would specify this as:

Code: Select all

// hoc
diam(0:1)=1.5:0.5
This does not translate to python:

Code: Select all

# python
from neuron import h
axon = h.Section(name='axon')
axon.diam(0:1)=1.5:0.5
obviously does not work I can't figure out how to do this, using psection() to directyly edit it does not work either:

Code: Select all

# python
axon.psection()['morphology']['diam'] = 1,2,3
I would also like to do this for adding mechanisms such as voltage gated ion channels, for example if I have na16.mod with a gbar_na16 parameter compiled and want to change conductance along a segment, in hoc I would do:

Code: Select all

//hoc
access axon
insert na16

axon{
gbar_na16(0:0.5) = 	0	:	100
//... so on
}
How would I do that in python?

ramcdougal
Posts: 205
Joined: Fri Nov 28, 2008 3:38 pm
Location: Yale School of Public Health

Re: range morphology and mechanism syntax for sections in python

Post by ramcdougal » Tue May 12, 2020 9:08 am

You'd have to do it with a for loop. Here's a generic function that does this for you:

Code: Select all

def range_assignment(sec, var, start, stop):
    """linearly assign values between start and stop to each segment.var in section"""
    import numpy as np
    for seg, val in zip(sec, np.linspace(start, stop, sec.nseg)):
        setattr(seg, var, val)
(If the setattr feels obscure, note that we're only using it because we don't know in advance what variable we want to set... if we knew it was always going to be the diameter we could replace it with seg.diam = val, but we don't.)

You can use that as in the following interactive session:

Code: Select all

>>> from neuron import h
>>> 
>>> axon = h.Section(name='axon')
>>> axon.nseg = 5
>>> 
>>> range_assignment(axon, 'diam', 1.5, 0.5)
>>> 
>>> print(axon.psection()['morphology']['diam'])
[1.5, 1.25, 1.0, 0.75, 0.5]

nicholasmg
Posts: 7
Joined: Sat Feb 29, 2020 11:16 am
Location: University of Colorado

Re: range morphology and mechanism syntax for sections in python

Post by nicholasmg » Tue May 12, 2020 10:18 am

thanks! that is helpful.
So within a section, whatever nseg you set is as fine grained as you can get in terms of specifying morphology or mechanisms within a section?

I see a lot of hoc code specifying seemingly arbitrary ranges for mechanisms and sizes like this:

Code: Select all

//hoc
axon{
	gbar_na16(0 : 0.168	) =  0 : 100
	//...
	gbar_na16(0.168 : 0.252) = 100:175
	//...
}
So in code like this, is axon.L/axon.nseg the number of 'steps' and 0:0.168 would be recomputed as 16.8% of the nsegs composing the length? Would the assigned range of 0:100 would be split to n steps based on however many nsegs compose 16.8% of that region? How is rounding done (implicit conversion to int)?

Thanks again for your help!

ted
Site Admin
Posts: 5723
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: range morphology and mechanism syntax for sections in python

Post by ted » Tue May 12, 2020 12:14 pm

Although hoc allows this kind of syntax for specifying variation of a range variable along a section
gbar_na16(0 : 0.168 ) = 0 : 100
nobody should ever use it. Nobody. Ever.

Why? It's obscure--nobody will know what it means, as this thread so clearly demonstrates. Just because a language offers a particular syntax is not sufficient reason to use that syntax, no matter how "compact" or "clever" or "elegant" or "idiomatic" it may seem. Obscure code is not maintainable code. Remember that "maintainable code is more important than clever code" (G. van Rossum, quoted at https://blog.dropbox.com/topics/company ... you--guido).

Returning to this particular hoc syntax--it's so clever, elegant, and idiomatic that it fooled even ramcdougal. Why? You can't count on the range variable ever being equal to either of the values on the right hand side of the statement. And that's why it is NOT equivalent to
for seg, val in zip(sec, np.linspace(start, stop, sec.nseg)): setattr(seg, var, val)


Demonstration: executing this hoc code

Code: Select all

create axon
axon.nseg = 5
axon.diam(0:1) = 1:10
for (x) print x, axon.diam(x)
generates this output
0 1.9
0.1 1.9
0.3 3.7
0.5 5.5
0.7 7.3
0.9 9.1
1 9.1
Notice that diam never equals 1 or 10.

However, executing this Python code

Code: Select all

from neuron import h
def range_assignment(sec, var, start, stop):
    """linearly assign values between start and stop to each segment.var in section"""
    import numpy as np
    for seg, val in zip(sec, np.linspace(start, stop, sec.nseg)):
        setattr(seg, var, val)

axon = h.Section(name='axon')
axon.nseg = 5
range_assignment(axon, 'diam', 1.0, 10.0)
print('axon(0).diam is %f' % axon(0).diam)
for seg in axon:
  print('%4.2f %4.2f' % (seg.x, seg.diam))
print('axon(1).diam is %f' % axon(1).diam)
generates this output
axon(0).diam is 1.000000
0.10 1.00
0.30 3.25
0.50 5.50
0.70 7.75
0.90 10.00
axon(1).diam is 10.000000
With only one exception, these diam values all differ from what the hoc code produced. It is not hard to create an example in which the hoc and Python code create totally different diam values. There's nothing special about diam; it's just convenient because every section automatically has it. I could have generated the same result with any range variable.

What would I do? I'd create a parameterized function that specifies the value of the range value of interest as a function of position (either normalized or anatomical distance from some reference point), and use that. And I'd write it in a way that anybody can figure it out--even C programmers. Apologies if that's neither hocish nor Pythonic enough, but simple readability/maintainability beats elegant obscurity.

ramcdougal
Posts: 205
Joined: Fri Nov 28, 2008 3:38 pm
Location: Yale School of Public Health

Re: range morphology and mechanism syntax for sections in python

Post by ramcdougal » Tue May 12, 2020 1:06 pm

Oof. My mistake. Ted is right, of course.

The key thing I was missing is that the values should be set based on the position of the center of the segment, which means that they must be strictly inside the interval specified. I was effectively interpolating from the center of the first segment to the center of the last segment rather than over the entire section.

The corrected version of range_assignment is as follows:

Code: Select all

def range_assignment(sec, var, start, stop):
    """linearly assign values between start and stop to each segment.var in section"""
    delta = stop - start
    for seg in sec:
        setattr(seg, var, start + seg.x * delta)
So yeah, I second Ted's suggestion: just have a function that calculates things and takes e.g. a segment and gets the .x value from it, or whatever...

One extra caveat: diam is not special, but morphology is. While you can specify diameters on a per-segment basis, if you're doing anything that's supposed to be a "realistic" morphology, you probably want to not use diam (which is tied to segments) and instead specify morphology using 3D points (e.g. sec.pt3dadd, sec.x3d, sec.y3d. sec.z3d, etc... these 3D points are independent of the spatial discretization).

ted
Site Admin
Posts: 5723
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: range morphology and mechanism syntax for sections in python

Post by ted » Tue May 12, 2020 1:29 pm

ramcdougal wrote:have a function that calculates things and takes e.g. a segment and gets the .x value from it
Exactly. The hoc
secname.varname(range0:range1) = y0:y1
statements are just a way of saying that the values of secname.varname are governed by a piecewise linear function with breakpoints at xy coordinates (range0, y0), (range1, y1) etc.. Then, in pseudocode,

Code: Select all

for each segment in the section
  use linear interpolation to calculate the value of the range variable at the segment's center (.x value)
By the way, most programming languages have a built-in method for linear resampling of data, so nobody could accuse you of being too obscure if you used numpy.interp.

nicholasmg
Posts: 7
Joined: Sat Feb 29, 2020 11:16 am
Location: University of Colorado

Re: range morphology and mechanism syntax for sections in python

Post by nicholasmg » Tue May 12, 2020 11:57 pm

Thanks a lot for the detailed responses and help ted and ramcdougal!

Ted- it is encouraging to know that I am not the only one who found that syntax confusing! I agree with you and Guido (and the zen of python) that clarity is much more important than cleverness.

I had no intention of using the confusing syntax, but I was trying to understand what was going on because that syntax is used throughout the documentation (for example: https://www.neuron.yale.edu/neuron/stat ... f-geometry) and also in a number of ModelDB models I have been looking at where ion channel gradients change along a structure (the axon initial segment in my case).
I now see that the complimentary page in the python docs uses linear interpolation and explicit segment iteration to specify the changes https://www.neuron.yale.edu/neuron/stat ... f-geometry as you all recommended in this thread.

ramcdougal- The last function you provided works beautifully, thank you all for explaining this and helping me out.
I will use the 3D reconstruction and pt3d* for morphology in the future, but wanted to make sure I knew how everything worked with simpler models first.

I am still curious about my question in the last post about whether a segment is the smallest discrete unit from which you can change mechanisms? Or could I chop a segment up in to smaller discrete units? I do not necessarily want to do that, but I do want to understand the parts of the underlying model.


Nick

ted
Site Admin
Posts: 5723
Joined: Wed May 18, 2005 4:50 pm
Location: Yale University School of Medicine
Contact:

Re: range morphology and mechanism syntax for sections in python

Post by ted » Wed May 13, 2020 1:07 pm

the confusing syntax . . . is used throughout the documentation (for example: https://www.neuron.yale.edu/neuron/stat ... f-geometry)
There is indeed a single instance in a throwaway example. I notice that the syntax is not explained on that page.
the complimentary page in the python docs uses linear interpolation and explicit segment iteration to specify the changes https://www.neuron.yale.edu/neuron/stat ... f-geometry
Exercise to the reader: execute the hoc and python examples and compare the resulting segment diameters. Do they match?

Returning to the problematical hoc syntax:
and also in a number of ModelDB models I have been looking at where ion channel gradients change along a structure
Consider a modeler who doesn't know much about NEURON and wants to reproduce one of those models in some other programming language. Or consider almost any modeler, including probably most NEURON users, who wants to reproduce one of those models in some other programming language. How many of them will end up with the correct spatial variation of channel density (or diameter or whatever other range variable they were varying)? Probably very few.

I think the syntax should be deprecated, and that the only place it should appear in the Programmer's Reference is in the documentation of the syntax itself. Definitely should not be used to illustrate anything else.
whether a segment is the smallest discrete unit from which you can change mechanisms? Or could I chop a segment up in to smaller discrete units?
Do the experiment. Try assigning values to a range variable at intervals smaller than normalized segment length, then find out what happened by printing the values at those locations. Then share your findings in this thread.

nicholasmg
Posts: 7
Joined: Sat Feb 29, 2020 11:16 am
Location: University of Colorado

Re: range morphology and mechanism syntax for sections in python

Post by nicholasmg » Fri May 15, 2020 11:47 am

thanks ted, here are the results:

Code: Select all

from neuron import h
import numpy as np

axon = h.Section(name="axon")
axon.L = 10
axon.diam = 2
axon.nseg = 5
axon.insert("hh")
axon.psection()

# {'point_processes': {},
#  'density_mechs': {'hh': {'gnabar': [0.12, 0.12, 0.12, 0.12, 0.12],
#    'gkbar': [0.036, 0.036, 0.036, 0.036, 0.036],
#    'gl': [0.0003, 0.0003, 0.0003, 0.0003, 0.0003],
#    'el': [-54.3, -54.3, -54.3, -54.3, -54.3],
#    'gna': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'gk': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'il': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'm': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'h': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'n': [0.0, 0.0, 0.0, 0.0, 0.0]}},
#  'ions': {'na': {'ena': [50.0, 50.0, 50.0, 50.0, 50.0],
#    'nai': [10.0, 10.0, 10.0, 10.0, 10.0],
#    'nao': [140.0, 140.0, 140.0, 140.0, 140.0],
#    'ina': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'dina_dv_': [0.0, 0.0, 0.0, 0.0, 0.0]},
#   'k': {'ek': [-77.0, -77.0, -77.0, -77.0, -77.0],
#    'ki': [54.4, 54.4, 54.4, 54.4, 54.4],
#    'ko': [2.5, 2.5, 2.5, 2.5, 2.5],
#    'ik': [0.0, 0.0, 0.0, 0.0, 0.0],
#    'dik_dv_': [0.0, 0.0, 0.0, 0.0, 0.0]}},
#  'morphology': {'L': 10.0,
#   'diam': [2.0, 2.0, 2.0, 2.0, 2.0],
#   'pts3d': [],
#   'parent': None,
#   'trueparent': None},
#  'nseg': 5,
#  'Ra': 35.4,
#  'cm': [1.0, 1.0, 1.0, 1.0, 1.0],
#  'regions': set(),
#  'species': set(),
#  'name': 'axon',
#  'hoc_internal_name': '__nrnsec_0x11df18000',
#  'cell': None}
A list of 5 values is given for gnabar_hh, so it appears that nseg (5) is the max number of steps you can take in this section.

Code: Select all

for i in np.arange(0, 1.1, 0.1):
    axon(i).gnabar_hh = i
    print(f"axon position {i} = {axon(i).gnabar_hh}")
    
# axon position 0.0 = 0.0
# axon position 0.1 = 0.1
# axon position 0.2 = 0.2
# axon position 0.30000000000000004 = 0.30000000000000004
# axon position 0.4 = 0.4
# axon position 0.5 = 0.5
# axon position 0.6000000000000001 = 0.6000000000000001
# axon position 0.7000000000000001 = 0.7000000000000001
# axon position 0.8 = 0.8
# axon position 0.9 = 0.9
# axon position 1.0 = 1.0
 
That code works, which would imply that you can actually specify much finer grained detail than nseg.

However:

Code: Select all

    
for i in np.arange(0,1.1, 0.1):
    print(f"axon position {i} = {axon(i).gnabar_hh}")
    
# axon position 0.0 = 0.1
# axon position 0.1 = 0.1
# axon position 0.2 = 0.30000000000000004
# axon position 0.30000000000000004 = 0.30000000000000004
# axon position 0.4 = 0.5
# axon position 0.5 = 0.5
# axon position 0.6000000000000001 = 0.7000000000000001
# axon position 0.7000000000000001 = 0.7000000000000001
# axon position 0.8 = 1.0
# axon position 0.9 = 1.0
# axon position 1.0 = 1.0
This information is also reflected in axon.psection().

It looks like the answer is that you are allowed to do as fine grained an assignment as you want, but it will silently fail and implicitly assign to the nearest normalized segment position instead. Is this correct? If so, an interpreter warning or failure would be a good idea.. I'd be happy to help implement or develop test cases.

If I am correct, the best bet for assigning gradients of things like conductances along a section is to explicitly iterate through the segments assigning a value to each segment:

Code: Select all

for seg in axon:
    seg.gnabar_hh = 0
    print(f"segment position (seg.x) is {seg.x} gnabar_hh is {seg.gnabar_hh}")
In regards to:
Consider a modeler who doesn't know much about NEURON and wants to reproduce one of those models in some other programming language. Or consider almost any modeler, including probably most NEURON users, who wants to reproduce one of those models in some other programming language. How many of them will end up with the correct spatial variation of channel density (or diameter or whatever other range variable they were varying)? Probably very few.
I agree, and this is my situation: Translating hoc code from published papers to python to run my own experiments, but trying to understand exactly what parts of the original code were doing (correct or not). It highlights the need for explicit documentation or, even better, interpreter warnings/errors when things should not be used.

I would like to contribute documentation to clear up this confusion and help others who run into this, do you accept documentation contributions for the website? How would I go about doing this?

Thanks again,
Nick

Post Reply