forked from jonescompneurolab/hnn-core
/
cell.py
348 lines (303 loc) · 12.2 KB
/
cell.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
"""Establish class def for general cell features."""
# Authors: Mainak Jas <mainak.jas@telecom-paristech.fr>
# Sam Neymotin <samnemo@gmail.com>
from abc import ABC, abstractmethod
import numpy as np
from neuron import h, nrn
# Units for e: mV
# Units for gbar: S/cm^2
class _ArtificialCell:
"""The ArtificialCell class for initializing a NEURON feed source.
Parameters
----------
event_times : list
Spike times associated with a single feed source (i.e.,
associated with a unique gid).
threshold : float
Membrane potential threshold that demarks a spike.
gid : int or None (optional)
Each cell in a network is uniquely identified by it's "global ID": GID.
The GID is an integer from 0 to n_cells, or None if the cell is not
yet attached to a network. Once the GID is set, it cannot be changed.
Attributes
----------
nrn_eventvec : instance of h.Vector()
NEURON h.Vector() object of event times.
nrn_vecstim : instance of h.VecStim()
NEURON h.VecStim() object of spike sources created
from nrn_eventvec.
nrn_netcon : instance of h.NetCon()
NEURON h.NetCon() object that creates the spike
source-to-target references for nrn_vecstim.
gid : int
GID of the cell in a network (or None if not yet assigned)
"""
def __init__(self, event_times, threshold, gid=None):
# Convert event times into nrn vector
self.nrn_eventvec = h.Vector()
self.nrn_eventvec.from_python(event_times)
# load eventvec into VecStim object
self.nrn_vecstim = h.VecStim()
self.nrn_vecstim.play(self.nrn_eventvec)
# create the cell and artificial NetCon
self.nrn_netcon = h.NetCon(self.nrn_vecstim, None)
self.nrn_netcon.threshold = threshold
self._assigned_gid = None
self.gid = gid
@property
def gid(self):
return self._assigned_gid
@gid.setter
def gid(self, gid):
if gid is None:
pass
elif self._assigned_gid is None:
self._assigned_gid = gid
else:
raise RuntimeError('Global ID for this cell already assigned!')
class _Cell(ABC):
"""Create a cell object.
Parameters
----------
soma_props : dict
The properties of the soma. Must contain
keys 'L', 'diam', and 'pos'
gid : int or None (optional)
Each cell in a network is uniquely identified by it's "global ID": GID.
The GID is an integer from 0 to n_cells, or None if the cell is not
yet attached to a network. Once the GID is set, it cannot be changed.
Attributes
----------
pos : list of length 3
The position of the cell.
dipole_pp : list of h.Dipole()
The Dipole objects (see dipole.mod).
dict_currents : dict of h.Vector()
The soma currents (keys are soma_gabaa, soma_gabab etc.)
rec_v : h.Vector()
Recording of somatic voltage. Must be enabled
by running simulate_dipole(net, record_vsoma=True)
gid : int
GID of the cell in a network (or None if not yet assigned)
"""
def __init__(self, soma_props, gid=None):
# variable for the list_IClamp
self.list_IClamp = None
self.soma_props = soma_props
self.create_soma()
self.rec_v = h.Vector()
self._assigned_gid = None
self.gid = gid
def __repr__(self):
class_name = self.__class__.__name__
soma_props = self.soma_props
s = ('soma: L %f, diam %f, Ra %f, cm %f' %
(soma_props['L'], soma_props['diam'],
soma_props['Ra'], soma_props['cm']))
return '<%s | %s>' % (class_name, s)
@property
def gid(self):
return self._assigned_gid
@gid.setter
def gid(self, gid):
if gid is None:
pass
elif self._assigned_gid is None:
self._assigned_gid = gid
else:
raise RuntimeError('Global ID for this cell already assigned!')
@abstractmethod
def get_sections(self):
"""Get sections in a cell."""
pass
def create_soma(self):
"""Create soma and set geometry."""
# make L_soma and diam_soma elements of self
# Used in shape_change() b/c func clobbers self.soma.L, self.soma.diam
soma_props = self.soma_props
self.L = soma_props['L']
self.diam = soma_props['diam']
self.pos = soma_props['pos']
self.soma = h.Section(cell=self, name=soma_props['name'] + '_soma')
self.soma.L = soma_props['L']
self.soma.diam = soma_props['diam']
self.soma.Ra = soma_props['Ra']
self.soma.cm = soma_props['cm']
def move_to_pos(self):
"""Move cell to position."""
x0 = self.soma.x3d(0)
y0 = self.soma.y3d(0)
z0 = self.soma.z3d(0)
dx = self.pos[0] * 100 - x0
dy = self.pos[2] - y0
dz = self.pos[1] * 100 - z0
for s in self.get_sections():
for i in range(s.n3d()):
h.pt3dchange(i, s.x3d(i) + dx, s.y3d(i) + dy,
s.z3d(i) + dz, s.diam3d(i), sec=s)
# two things need to happen here for h:
# 1. dipole needs to be inserted into each section
# 2. a list needs to be created with a Dipole (Point Process) in each
# section at position 1
# In Cell() and not Pyr() for future possibilities
def insert_dipole(self, yscale):
"""Insert dipole into each section of this cell.
Parameters
----------
yscale : dict
Dictionary of length scales to calculate dipole without
3d shape.
"""
self.dpl_vec = h.Vector(1)
self.dpl_ref = self.dpl_vec._ref_x[0]
# dends must have already been created!!
# it's easier to use wholetree here, this includes soma
seclist = h.SectionList()
seclist.wholetree(sec=self.soma)
# create a python section list list_all
list_all = [sec for sec in seclist]
for sect in list_all:
sect.insert('dipole')
# Dipole is defined in dipole_pp.mod
self.dipole_pp = [h.Dipole(1, sec=sect) for sect in list_all]
# setting pointers and ztan values
for sect, dpp in zip(list_all, self.dipole_pp):
# assign internal resistance values to dipole point process (dpp)
dpp.ri = h.ri(1, sec=sect)
# sets pointers in dipole mod file to the correct locations
# h.setpointer(ref, ptr, obj)
h.setpointer(sect(0.99)._ref_v, 'pv', dpp)
h.setpointer(self.dpl_ref, 'Qtotal', dpp)
# gives INTERNAL segments of the section, non-endpoints
# creating this because need multiple values simultaneously
loc = np.array([seg.x for seg in sect])
# these are the positions, including 0 but not L
pos = np.array([seg.x for seg in sect.allseg()])
# diff in yvals, scaled against the pos np.array. y_long as
# in longitudinal
y_scale = (yscale[sect.name().split('_', 1)[1]] * sect.L) * pos
# y_long = (h.y3d(1, sec=sect) - h.y3d(0, sec=sect)) * pos
# diff values calculate length between successive section points
y_diff = np.diff(y_scale)
# y_diff = np.diff(y_long)
# doing range to index multiple values of the same
# np.array simultaneously
for i in range(len(loc)):
# assign the ri value to the dipole
sect(loc[i]).dipole.ri = h.ri(loc[i], sec=sect)
# range variable 'dipole'
# set pointers to previous segment's voltage, with
# boundary condition
if i > 0:
h.setpointer(sect(loc[i - 1])._ref_v,
'pv', sect(loc[i]).dipole)
else:
h.setpointer(sect(0)._ref_v, 'pv', sect(loc[i]).dipole)
# set aggregate pointers
h.setpointer(dpp._ref_Qsum, 'Qsum', sect(loc[i]).dipole)
h.setpointer(self.dpl_ref, 'Qtotal', sect(loc[i]).dipole)
# add ztan values
sect(loc[i]).dipole.ztan = y_diff[i]
# set the pp dipole's ztan value to the last value from y_diff
dpp.ztan = y_diff[-1]
self.dipole = h.Vector().record(self.dpl_ref)
def record_current_soma(self):
"""Record current at soma."""
# a soma exists at self.soma
try:
# assumes that self.synapses is a dict that exists
list_syn_soma = [key for key in self.synapses.keys()
if key.startswith('soma_')]
# matching dict from the list_syn_soma keys
self.dict_currents = dict.fromkeys(list_syn_soma)
# iterate through keys and record currents appropriately
for key in self.dict_currents:
self.dict_currents[key] = h.Vector()
self.dict_currents[key].record(self.synapses[key]._ref_i)
except:
print(
"Warning in Cell(): record_current_soma() was called,"
" but no self.synapses dict was found")
pass
def record_voltage_soma(self):
"""Record voltage at soma."""
self.rec_v.record(self.soma(0.5)._ref_v)
def syn_create(self, secloc, e, tau1, tau2):
"""Create an h.Exp2Syn synapse.
Parameters
----------
secloc : instance of nrn.Segment
The section location. E.g., soma(0.5).
e: float
Reverse potential (in mV)
tau1: float
Rise time (in ms)
tau2: float
Decay time (in ms)
Returns
-------
syn : instance of h.Exp2Syn
A two state kinetic scheme synapse.
"""
if not isinstance(secloc, nrn.Segment):
raise TypeError(f'secloc must be instance of'
f'nrn.Segment. Got {type(secloc)}')
syn = h.Exp2Syn(secloc)
syn.e = e
syn.tau1 = tau1
syn.tau2 = tau2
return syn
def setup_source_netcon(self, threshold):
"""Created for _PC.cell and specifies SOURCES of spikes.
Parameters
----------
threshold : float
The voltage threshold for action potential.
"""
nc = h.NetCon(self.soma(0.5)._ref_v, None, sec=self.soma)
nc.threshold = threshold
return nc
def parconnect_from_src(self, gid_presyn, nc_dict, postsyn):
"""Parallel receptor-centric connect FROM presyn TO this cell,
based on GID.
Parameters
----------
gid_presyn : int
The cell ID of the presynaptic neuron
nc_dict : dict
Dictionary with keys: pos_src, A_weight, A_delay, lamtha
Defines the connection parameters
postsyn : instance of h.Exp2Syn
The postsynaptic cell object.
Returns
-------
nc : instance of h.NetCon
A network connection object.
"""
from .network_builder import _PC
nc = _PC.gid_connect(gid_presyn, postsyn)
# calculate distance between cell positions with pardistance()
d = self._pardistance(nc_dict['pos_src'])
# set props here
nc.threshold = nc_dict['threshold']
nc.weight[0] = nc_dict['A_weight'] * \
np.exp(-(d**2) / (nc_dict['lamtha']**2))
nc.delay = nc_dict['A_delay'] / \
(np.exp(-(d**2) / (nc_dict['lamtha']**2)))
return nc
# pardistance function requires pre position, since it is
# calculated on POST cell
def _pardistance(self, pos_pre):
dx = self.pos[0] - pos_pre[0]
dy = self.pos[1] - pos_pre[1]
return np.sqrt(dx**2 + dy**2)
def shape_soma(self):
"""Define 3D shape of soma.
.. warning:: needed for gui representation of cell
DO NOT need to call h.define_shape() explicitly!
"""
h.pt3dclear(sec=self.soma)
# h.ptdadd(x, y, z, diam) -- if this function is run, clobbers
# self.soma.diam set above
h.pt3dadd(0, 0, 0, self.diam, sec=self.soma)
h.pt3dadd(0, self.L, 0, self.diam, sec=self.soma)