from neuron import h
from blenderneuron.commnode import CommNode
from blenderneuron.nrn.neuronrootgroup import NeuronRootGroup
from collections import OrderedDict
import re, math
from hashlib import sha1
try:
import xmlrpclib
except:
import xmlrpc.client as xmlrpclib
[docs]class NeuronNode(CommNode):
# Match Cell[n].section[y] pattern e.g. MC1[0].soma
section_rx = re.compile('(.+?])\.?(.*)')
def __init__(self, server_end=None, *args, **kwargs):
self.roots = None
self.section_index = None
self.synapse_sets = {} # 'set_name': [(netcon, syn, head, neck)]
self.parallel_ctx = None
self.mpimap = None
self.mpirank = None
def init():
h.load_file('stdrun.hoc')
self.server.register_function(self.get_roots)
self.server.register_function(self.set_sim_params)
self.server.register_function(self.get_sim_params)
self.server.register_function(self.initialize_groups)
self.server.register_function(self.update_groups)
self.server.register_function(self.create_synapses)
if server_end is None:
server_end = "NEURON"
super(NeuronNode, self).__init__(server_end, on_server_setup=init, *args, **kwargs)
[docs] def init_mpi(self, parallel_ctx, mpimap):
self.parallel_ctx = parallel_ctx
self.mpimap = mpimap
self.mpirank = parallel_ctx.id()
self.current_gid = 0
[docs] def get_roots(self):
self.roots = h.SectionList()
self.roots.allroots()
self.roots = list(self.roots)
self.update_section_index()
return [
{
"index": i,
"name": sec.name()
}
for i, sec in enumerate(self.roots)
]
[docs] def update_section_index(self):
all_sec = h.allsec()
self.section_index = {sec.name(): sec for sec in all_sec}
[docs] def initialize_groups(self, blender_groups, send_back=True):
self.groups = OrderedDict()
if self.section_index is None:
self.update_section_index()
for blender_group in blender_groups:
name = blender_group["name"]
nrn_group = NeuronRootGroup()
nrn_group.from_skeletal_blender_group(blender_group, node=self)
self.groups[name] = nrn_group
if send_back:
return self.get_group_dicts()
[docs] def get_group_dicts(self, compressed=True):
if any([g.record_activity for g in self.groups.values()]):
h.run()
result = [group.to_dict(include_activity=group.record_activity,
include_root_children=True,
include_coords_and_radii=True)
for group in self.groups.values()]
if compressed:
return self.compress(result)
else:
return result
[docs] def update_groups(self, blender_groups):
for blender_group in blender_groups:
name = blender_group["name"]
if name not in self.groups:
self.initialize_groups(blender_groups, send_back=False)
nrn_group = self.groups[name]
nrn_group.from_updated_blender_group(blender_group)
[docs] def set_sim_params(self, params):
h.tstop = params["tstop"]
h.dt = params["dt"]
h.cvode.atol(params["atol"])
h.celsius = params["celsius"]
h.cvode_active(int(params["cvode"]))
[docs] def get_sim_params(self):
params = {}
params["t"] = h.t
params["tstop"] = h.tstop
params["dt"] = h.dt
params["atol"] = h.cvode.atol()
params["celsius"] = h.celsius
params["cvode"] = str(h.cvode_active())
return params
[docs] @staticmethod
def clamp_section_x(x_in):
"""
When placing synapses, avoid using 0 and 1 section locations, esp. if
extracellular potentials will be computed. See the warning comment in:
https://www.neuron.yale.edu/neuron/static/py_doc/modelspec/programmatic/
mechanisms/mech.html?highlight=i_membrane#mech-extracellular
:param x_in: 0-1 section location
:return: section location constrained to 0.001-0.999
"""
return min(max(x_in, 0.001), 0.999)
[docs] def rank_section_name(self, single_rank_name):
if self.mpimap is not None:
cell_name, section_name = self.section_rx.match(single_rank_name).groups()
entry = self.mpimap[cell_name]
# If cell is not on this rank, return None
if self.mpirank != entry['rank']:
return None
# Otherwise get its multi-rank section name
mpi_cell_name = entry['name']
mpi_name = mpi_cell_name
if section_name != '':
mpi_name += '.' + section_name
return mpi_name
else:
return single_rank_name
[docs] def segment_gid(self, sec_name, seg_id, has_spine):
"""
Only one gid can be associated with a segment. If more than one gid is assigned to a seg,
only the last gid will send events to the post- synapse.
However, if the segment index can be computed from section.x location, locations that
refer to the same segment e.g. section(0.49) and section(0.51) when nseg=3, can be identified.
This method generates a unique gid for each segment based on the section name and segment
index. Where section(0) -> seg_id=0 and section(1) -> seg_id=nseg-1
If a spine was added, its assumed that the pre-syn location is at the head of the spine.
:param sec_name:
:param seg_id:
:param has_spine:
:return:
"""
# Generate a segment address e.g. MyCell[2].soma[1] from soma(0.5) with nseg=3
seg_address = sec_name + ('[%s]' % seg_id)
if has_spine:
seg_address += '.spine_head'
# Compute the SHA1 9-digit hash of the address
# 9 digits is the largest safe number that is accepted by pc.set_gid2node
# In practice, this will be unique for each segment
# And same on each machine and version of python (unlike hash())
gid = int(sha1(seg_address.encode('utf-8')).hexdigest(), 16) % (10 ** 9)
return gid
[docs] def create_synapses(self, syn_set):
set_name = syn_set['name']
syn_entries = syn_set['entries']
synapses = self.synapse_sets[set_name] = []
for entry in syn_entries:
rank_source_section = self.rank_section_name(entry['source_section'])
rank_dest_section = self.rank_section_name(entry['dest_section'])
source_on_rank = rank_source_section is not None
dest_on_rank = rank_dest_section is not None
if dest_on_rank:
dest_sec = self.section_index[rank_dest_section]
dest_x = self.clamp_section_x(entry['dest_x'])
else:
dest_sec = None
dest_x = 0
if source_on_rank:
source_sec = self.section_index[rank_source_section]
source_x = self.clamp_section_x(entry['source_x'])
else:
source_sec = None
source_x = 0
# At first, assume no spines
neck = None
head = None
# Unless indicated otherwise
if source_on_rank and entry['create_spine']:
prefix = set_name+"_"+entry['prefix']+"["+str(len(synapses))+"]"
# Create the spine head
head = h.Section(name=prefix + ".head")
# basic passive params
head.insert('pas')
# Add the 3D coords
self.add_spine_pt3d(head, entry['head_start'], entry['head_diameter'])
self.add_spine_pt3d(head, entry['head_end'], entry['head_diameter'])
# Create the head (if there is enough room)
if entry['neck_start'] is not None:
neck = h.Section(name=prefix+".neck")
neck.insert('pas')
self.add_spine_pt3d(neck, entry['neck_start'], entry['neck_diameter'])
self.add_spine_pt3d(neck, entry['neck_end'], entry['neck_diameter'])
# Connect the spine together to the source section
neck.connect(source_sec(source_x), 0)
head.connect(neck)
else:
# If there is no neck, connect the head to section directly
head.connect(source_sec(source_x), 0)
# Point process should now be placed on the spine head
source_sec = head
source_x = 0.5
source_gid = self.segment_gid(entry['source_section'], entry['source_seg_i'], entry['create_spine'])
netcon, syn = self.create_netcon_syn(
entry['dest_syn'],
dest_sec,
dest_x,
entry['dest_syn_params'],
source_sec,
source_x,
entry['threshold'],
entry['delay'],
entry['weight'],
source_on_rank, dest_on_rank,
source_gid
)
# If reciprocal, create a NetCon+Syn going in the opposite direction
if entry['is_reciprocal']:
dest_gid = self.segment_gid(entry['dest_section'], entry['dest_seg_i'], False)
netcon_recip, syn_recip = self.create_netcon_syn(
entry['source_syn'],
source_sec,
source_x,
entry['source_syn_params'],
dest_sec,
dest_x,
entry['threshold'],
entry['delay'],
entry['weight'],
dest_on_rank, source_on_rank,
dest_gid
)
else:
netcon_recip, syn_recip = None, None
# Keep references to the synapse parts
synapses.append((netcon, syn, neck, head, netcon_recip, syn_recip))
[docs] def create_netcon_syn(self,
syn_class_name, syn_sec, syn_sec_x, syn_params,
source_sec, source_x, threshold, delay, weight,
source_on_rank, syn_on_rank, source_gid):
netcon, syn = None, None
if syn_on_rank:
# Create synapse point process
# e.g. syn = h.ExpSyn(dend(0.5))
syn_class = getattr(h, syn_class_name)
syn = syn_class(syn_sec(syn_sec_x))
if syn_params != '':
syn_params = eval(syn_params)
for key in syn_params.keys():
setattr(syn, key, syn_params[key])
# If both source and syn on rank, no rank span -> classic NetCon
if syn_on_rank and source_on_rank:
netcon = h.NetCon(
source_sec(source_x)._ref_v,
syn,
threshold,
delay,
weight,
sec=source_sec
)
# Connection spans ranks
elif source_on_rank or syn_on_rank:
if source_on_rank:
# Assign a gid to a given segment only once
if self.parallel_ctx.gid_exists(source_gid) == 0:
self.assign_gid_to_source_seg(
source_sec,
source_x,
threshold,
source_gid
)
else: # syn_on_rank
netcon = self.parallel_ctx.gid_connect(source_gid, syn)
netcon.delay = delay
netcon.weight[0] = weight
return netcon, syn
[docs] def assign_gid_to_source_seg(self, pre_sec, pre_sec_x, threshold, gid):
# Assign the gid the current rank
self.parallel_ctx.set_gid2node(gid, self.mpirank)
# Create the dummy connection from source segment
nc = h.NetCon(pre_sec(pre_sec_x)._ref_v, None, sec=pre_sec)
nc.threshold = threshold
# Assign the gid to the source segment (through the dummy netcon)
self.parallel_ctx.cell(gid, nc)
[docs] @staticmethod
def add_spine_pt3d(sec, xyz, diam):
h.pt3dadd(xyz[0], xyz[1], xyz[2], diam, sec=sec)