# -*- coding: utf-8 -*-
r"""
Parallelized ring network of ball and stick cells.
AUTHORS:
- THOMAS MCTAVISH (2010-11-04): initial version. Modified version of the
project by Hines and Carnevale. (Hines M.L. and Carnevale N.T,
Translating network models to parallel hardware in NEURON,
Journal of Neuroscience Methods 169 (2008) 425-455).
"""
from itertools import izip
import numpy
from neuron import h as nrn
from ballandstick import *
[docs]class Ring:
"""A network of *N* ball-and-stick cells where cell n makes an
excitatory synapse onto cell n + 1 and the last, Nth cell in the
network projects to the first cell.
"""
def __init__(self, N=5, stim_w=0.004, stim_spike_num=1, syn_w=0.01, \
syn_delay=5, playlist=[]):
"""
:param N: Number of cells.
:param stim_w: Weight of the stimulus
:param stim_spike_num: Number of spikes generated in the stimulus
:param syn_w: Synaptic weight
:param syn_delay: Delay of the synapse
:param playlist: Cells ids to run in playback mode on this host.
"""
self._N = N # Total number of cells in the net
self.cells = [] # Cells on this host
self.nclist = [] # NetCon list on this host
self.gidlist = [] # List of global identifiers on this host
self.stim = None # Stimulator
self.stim_w = stim_w # Weight of stim
self.stim_spike_num = stim_spike_num # Number of stim spikes
self.syn_w = syn_w # Synaptic weight
self.syn_delay = syn_delay # Synaptic delay
self.t_vec = nrn.Vector() # Spike time of all cells on this host
self.id_vec = nrn.Vector() # Ids of spike times on this host
self.playlist = playlist # If non-empty, these cell ids will run on
# this host in playback mode.
#### Make a new ParallelContext object
self.pc = nrn.ParallelContext()
self.set_numcells(N) # Actually build the net -- at least the portion
# of cells on this host.
def __del__(self):
"""In the case of multiple runs where NEURON is not quit and reloaded,
we need to clear NEURON so that we can run a new network.
There is a particular order that things need to be deleted:
1) Any recording vectors
2) pc.gid_clear(0)
3) Destroy NetCons
4) Destroy Cells"""
self.t_vec = [] # Must come before gid_clear
self.id_vec = [] # Must come before gid_clear
self.pc.gid_clear(0)
self.nclist = [] # Synaptic NetCon list on this host
self.stim = None
self.cells = [] # Cells on this host
[docs] def set_numcells(self, N, radius=50):
"""Create, layout, and connect N cells."""
self._N = N
self.set_gids()
self.create_cells()
self.connect_cells()
self.connect_stim()
[docs] def set_gids(self):
"""Set the gidlist on this host."""
if len(self.playlist) > 0:
self.gidlist = self.playlist
else:
self.gidlist = []
#### Round-robin counting.
#### Each host as an id from 0 to pc.nhost() - 1.
for i in range(int(self.pc.id()), self._N, int(self.pc.nhost())):
self.gidlist.append(i)
[docs] def create_cells(self):
"""Create cell objects on this host and set their location."""
self.cells = []
N = self._N
r = 50 # Radius of cell locations from origin (0,0,0) in microns
for i in self.gidlist:
cell = BallAndStick(nameprefix='cell_%d_' %i)
# When cells are created, the soma location is at (0,0,0) and
# the dendrite extends along the X-axis.
# First, at the origin, rotate about Z.
cell.rotateZ(i*2*numpy.pi/N)
# Then reposition
x_loc = float(numpy.sin(i*2*numpy.pi/N))*r
y_loc = float(numpy.cos(i*2*numpy.pi/N))*r
cell.set_position(x_loc, y_loc, 0)
self.cells.append(cell)
#### Tell this host it has this gid
#### gids can be any integer, they just need to be unique.
#### In this simple case, we set the gid to i.
self.pc.set_gid2node(i, int(self.pc.id()))
#### Means to tell the ParallelContext that this cell is
#### a source for all other hosts. NetCon is temporary.
nc = cell.connect2target(None)
self.pc.cell(i, nc)
#### Record spikes of this cell
self.pc.spike_record(i, self.t_vec, self.id_vec)
# OLD WAY
# def connect_cells(self):
# self.nclist = []
# N = self._N
# for i in range(N):
# src = self.cells[i]
# tgt_syn = self.cells[(i+1)%N].synlist[0]
# nc = src.connect2target(tgt_syn)
# nc.weight[0] = self.syn_w
# nc.delay = self.syn_delay
# nc.record(self.t_vec, self.id_vec, i)
# self.nclist.append(nc)
[docs] def connect_cells(self):
"""Connect cell n to cell n + 1."""
self.nclist = []
N = self._N
for i in self.gidlist:
src_gid = (i-1 + N) % N
tgt_gid = i
if self.pc.gid_exists(tgt_gid):
target = self.pc.gid2cell(tgt_gid)
syn = target.synlist[0]
nc = self.pc.gid_connect(src_gid, syn)
nc.weight[0] = self.syn_w
nc.delay = self.syn_delay
self.nclist.append(nc)
[docs] def connect_stim(self):
"""Connect a spike generator on the first cell in the network."""
#### If the first cell is not on this host, return
if not self.pc.gid_exists(0):
return
self.stim = nrn.NetStim()
self.stim.number = self.stim_spike_num
self.stim.start = 9
self.ncstim = nrn.NetCon(self.stim, self.cells[0].synlist[0])
self.ncstim.delay = 1
self.ncstim.weight[0] = self.stim_w # NetCon weight is a vector.
[docs] def get_spikes(self):
"""Get the spikes as a list of lists."""
return spiketrainutil.netconvecs_to_listoflists(self.t_vec, self.id_vec)
[docs] def write_spikes(self, file_name='out.spk'):
"""Append the spike output file with spikes on this host. The output
format is the timestamp followed by a tab then the gid of the source
followed by a newline.
:param file_name: is the full or relative path to a spike output file.
.. note::
When parallelized, each process will write to the same file so it
is opened in append mode. The order in which the processes write is
arbitrary so while the spikes within the process may be ordered by
time, the output file will be unsorted. A quick way to sort a file
is with the bash command sort, which can be called after all
processes have written the file with the following format::
exec_cmd = 'sort -k 1n,1n -k 2n,2n ' + file_name + \
' > ' + 'sorted_' + file_name
os.system(exec_cmd)
"""
self.pc.barrier()
with open(file_name, 'a') as spk_file: # Append
for (t, id) in izip(self.t_vec, self.id_vec):
spk_file.write('%.3f\t%d\n' %(t, id)) # timestamp, id
self.pc.barrier()