OSCR

An integrated <i>i</i> <i>n vitro</i> platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs.

Code ↔ Paper

13 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 13 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § STAR★Methods › Method details › Data analysis: Neuronal pair identification and synaptic pair calculation ↔ Scripts_Shelf/waveform_analysis.py, lines 68–130 · score 0.86 · peak trough ratio, repolarization slope, recovery slope, waveform metrics, half width, amplitude
  2. [2] § STAR★Methods › Method details › Data analysis: Neuronal pair identification and synaptic pair calculation ↔ src/cmos_analyzer/spikeinterface_metrics.py, lines 17–140 · score 0.84 · peak trough ratio, repolarization slope, recovery slope, waveform metrics, Spikeinterface, amplitude
  3. [3] § STAR★Methods › Method details › Electrophysiology › Stimulation paradigm ↔ src/cmos_recorder/Recorder_Class.py, lines 261–355 · score 0.78 · pulse duration, stimulation pulses, stimulation parameters, spontaneous recording, biphasic, DIV
  4. [4] § STAR★Methods › Method details › Data analysis: Neuronal pair identification and synaptic pair calculation ↔ src/Calculator_Class.py, lines 426–494 · score 0.77 · bivariate transfer entropy, multivariate transfer entropy, IDTxl, preselection, prefiltered, maximized
  5. [5] § STAR★Methods › Method details › Data analysis: Neuronal pair identification and synaptic pair calculation ↔ IDtxl/idtxl/bivariate_te.py, lines 177–326 · score 0.75 · temporal search depth, maximum temporal search, bivariate transfer entropy, IDTxl, JIDT, surrogate
  6. [6] § STAR★Methods › Method details › Computational model › Membrane potential ↔ network.py, lines 28–124 · score 0.70 · membrane capacitance, membrane potential, axial, cm, passive, segment
  7. [7] § Results › Structural motifs enforce axonal directionality in pre- and postsynaptic neuronal pairs ↔ Paper_scripts/Filled_channels_plotter.ipynb, lines 15–72 · score 0.62 · neurite growth direction, en passant, backward, heart
  8. [8] § Results › Structural motifs enforce axonal directionality in pre- and postsynaptic neuronal pairs ↔ Paper_scripts/Filled_channels_plotter.ipynb, lines 15–72 · score 0.55 · neurite growth direction, en passant, backward, heart
  9. [9] § STAR★Methods › Method details › Data analysis: Prediction of firing probability with machine learning models ↔ src/Converter_Class.py, the whole file · a weak match · score 0.53 · extremum electrode, spike occurs, traces, window, trained
  10. [10] § Results › Simulation-based inference finds the biophysical parameter space from the microelectrode-array data of neuronal pairs ↔ parameter_distribution_map_vs_lowprob.py, lines 174–228 · score 0.52 · axon diameter, low probability, NMDA, AMPA, latency, MAP
  11. [11] § Results › Simulation-based inference finds the biophysical parameter space from the microelectrode-array data of neuronal pairs ↔ stimulation_parameter_distribution.py, lines 174–228 · score 0.52 · axon diameter, low probability, NMDA, AMPA, latency, post
  12. [12] § STAR★Methods › Quantification and statistical analysis ↔ IDtxl/idtxl/embedding_optimization_ais_Rudelt.py, lines 13–131 · score 0.52 · confidence interval, standard deviation, Python
  13. [13] § STAR★Methods › Method details › Model fitting › Summary features ↔ network.py, lines 989–1030 · score 0.51 · spike detections, presynaptic neuron, axon, delay

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 1,999 lines · 80 KB · no license · 2 matches

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. """
  4. This is part of the LFPy package that was altered for the single_synapse project.
  5. Specifically, we have changed:
  6. - the draw_ran_pos method in the NetworkPopulation to Draw random locations for POP_SIZE cells uniformly distributed within a cylinder.
  7. - connect neurons using connect_AMPA_NMDA function instead of connect_synapse in the Network class. For each synapse, we create two Exp2Synapses,/
  8. one for AMPA and one for NMDA, and connect them to the target cell. The target cell is triggered by a spike detector on the source cell at a compartment that is
  9. the closest to the synapse location.
  10. """
  11. import numpy as np
  12. import os
  13. import scipy.stats as stats
  14. import h5py
  15. import neuron
  16. from neuron import units
  17. from LFPy.templatecell import TemplateCell
  18. import scipy.sparse as ss
  19. from warnings import warn, filterwarnings
  20. def flattenlist(lst):
  21. return [item for sublist in lst for item in sublist]
  22. ##########################################################################
  23. # NetworkCell class that has a create_synapse method that
  24. # creates a synapse on the target cell, and a create_spike_detector method that
  25. # allows for connecting to a synapse on a target cell. All other methods and
  26. # attributes are inherited from the standard LFPy.TemplateCell class
  27. ##########################################################################
  28. class NetworkCell(TemplateCell):
  29. """
  30. Similar to `LFPy.TemplateCell` with the addition of some attributes and
  31. methods allowing for spike communication between parallel RANKs.
  32. This class allow using NEURON templates with some limitations.
  33. This takes all the same parameters as the Cell class, but requires three
  34. more template related parameters
  35. Parameters
  36. ----------
  37. morphology: str
  38. path to morphology file
  39. templatefile: str
  40. File with cell template definition(s)
  41. templatename: str
  42. Cell template-name used for this cell object
  43. templateargs: str
  44. Parameters provided to template-definition
  45. v_init: float
  46. Initial membrane potential. Default to -65.
  47. Ra: float
  48. axial resistance. Defaults to 150.
  49. cm: float
  50. membrane capacitance. Defaults to 1.0
  51. passive: bool
  52. Passive mechanisms are initialized if True. Defaults to True
  53. passive_parameters: dict
  54. parameter dictionary with values for the passive membrane mechanism in
  55. NEURON ('pas'). The dictionary must contain keys 'g_pas' and 'e_pas',
  56. like the default: passive_parameters=dict(g_pas=0.001, e_pas=-70)
  57. extracellular: bool
  58. switch for NEURON's extracellular mechanism. Defaults to False
  59. dt: float
  60. Simulation time step. Defaults to 2**-4
  61. tstart: float
  62. initialization time for simulation <= 0 ms. Defaults to 0.
  63. tstop: float
  64. stop time for simulation > 0 ms. Defaults to 100.
  65. nsegs_method: 'lambda100' or 'lambda_f' or 'fixed_length' or None
  66. nseg rule, used by NEURON to determine number of segments.
  67. Defaults to 'lambda100'
  68. max_nsegs_length: float or None
  69. max segment length for method 'fixed_length'. Defaults to None
  70. lambda_f: int
  71. AC frequency for method 'lambda_f'. Defaults to 100
  72. d_lambda: float
  73. parameter for d_lambda rule. Defaults to 0.1
  74. delete_sections: bool
  75. delete pre-existing section-references. Defaults to True
  76. custom_code: list or None
  77. list of model-specific code files ([.py/.hoc]). Defaults to None
  78. custom_fun: list or None
  79. list of model-specific functions with args. Defaults to None
  80. custom_fun_args: list or None
  81. list of args passed to custom_fun functions. Defaults to None
  82. pt3d: bool
  83. use pt3d-info of the cell geometries switch. Defaults to False
  84. celsius: float or None
  85. Temperature in celsius. If nothing is specified here
  86. or in custom code it is 6.3 celcius
  87. verbose: bool
  88. verbose output switch. Defaults to False
  89. Examples
  90. --------
  91. >>> import LFPy
  92. >>> cellParameters = {
  93. >>> 'morphology': '<path to morphology.hoc>',
  94. >>> 'templatefile': '<path to template_file.hoc>',
  95. >>> 'templatename': 'templatename',
  96. >>> 'templateargs': None,
  97. >>> 'v_init': -65,
  98. >>> 'cm': 1.0,
  99. >>> 'Ra': 150,
  100. >>> 'passive': True,
  101. >>> 'passive_parameters': {'g_pas': 0.001, 'e_pas': -65.},
  102. >>> 'dt': 2**-3,
  103. >>> 'tstart': 0,
  104. >>> 'tstop': 50,
  105. >>> }
  106. >>> cell = LFPy.NetworkCell(**cellParameters)
  107. >>> cell.simulate()
  108. See also
  109. --------
  110. Cell
  111. TemplateCell
  112. """
  113. def __init__(self, **args):
  114. # suppress some warnings if section references belonging to other
  115. # NetworkCell instances are found
  116. filterwarnings(action='ignore',
  117. message="(?=.*(sections detected))",
  118. category=UserWarning)
  119. # instantiate parent class
  120. super().__init__(**args)
  121. # create list netconlist for spike detecting NetCon object(s)
  122. self._hoc_sd_netconlist = neuron.h.List()
  123. self._hoc_sd_pre_netconlist = neuron.h.List()
  124. # create list of recording device for action potentials
  125. self.spikes = []
  126. # create list of random number generators used with synapse model
  127. self.rng_list = []
  128. # create separate list for networked synapses
  129. self.netconsynapses = []
  130. # create recording device for membrane voltage
  131. self.somav = neuron.h.Vector()
  132. for sec in self.somalist:
  133. self.somav.record(sec(0.5)._ref_v)
  134. def __finitialize__(self):
  135. pass
  136. def create_synapse(self, cell, sec, x=0.5, syntype=neuron.h.ExpSyn,
  137. synparams=dict(tau=2., e=0.),
  138. assert_syn_values=False):
  139. """
  140. Create synapse object of type syntype on sec(x) of cell and
  141. append to list cell.netconsynapses
  142. TODO: Use LFPy.Synapse class if possible.
  143. Parameters
  144. ----------
  145. cell: object
  146. instantiation of class NetworkCell or similar
  147. sec: neuron.h.Section object,
  148. section reference on cell
  149. x: float in [0, 1],
  150. relative position along section
  151. syntype: hoc.HocObject
  152. NEURON synapse model reference, e.g., neuron.h.ExpSyn
  153. synparams: dict
  154. parameters for syntype, e.g., for neuron.h.ExpSyn we have:
  155. tau: float, synapse time constant
  156. e: float, synapse reversal potential
  157. assert_syn_values: bool
  158. if True, raise AssertionError if synapse attribute values do not
  159. match the values in the synparams dictionary
  160. Raises
  161. ------
  162. AssertionError
  163. """
  164. # create a synapse object on the target cell
  165. syn = syntype(x, sec=sec)
  166. if hasattr(syn, 'setRNG'):
  167. # Create the random number generator for the synapse
  168. rng = neuron.h.Random()
  169. # not sure if this is how it is supposed to be set up...
  170. rng.MCellRan4(
  171. np.random.randint(
  172. 0,
  173. 2**32 - 1),
  174. np.random.randint(
  175. 0,
  176. 2**32 - 1))
  177. rng.uniform(0, 1)
  178. # used for e.g., stochastic synapse mechanisms (cf. BBP
  179. # microcircuit portal files)
  180. syn.setRNG(rng)
  181. cell.rng_list.append(rng) # must store ref to rng object
  182. cell.netconsynapses.append(syntype(x, sec=sec))
  183. for key, value in synparams.items():
  184. setattr(cell.netconsynapses[-1], key, value)
  185. # check that synapses are parameterized correctly
  186. if assert_syn_values:
  187. try:
  188. np.testing.assert_almost_equal(
  189. getattr(cell.netconsynapses[-1], key), value)
  190. except AssertionError:
  191. raise AssertionError('{} = {} != {}'.format(
  192. key, getattr(cell.netconsynapses[-1], key), value))
  193. def create_spike_detector(self, target=None, threshold=-10.,
  194. weight=0.0, delay=0.0):
  195. """
  196. Create spike-detecting NetCon object attached to the cell's soma
  197. midpoint, but this could be extended to having multiple spike-detection
  198. sites. The NetCon object created is attached to the cell's
  199. `_hoc_sd_netconlist` attribute, and will be used by the Network class
  200. when creating connections between all presynaptic cells and
  201. postsynaptic cells on each local RANK.
  202. Parameters
  203. ----------
  204. target: None (default) or a NEURON point process
  205. threshold: float
  206. spike detection threshold
  207. weight: float
  208. connection weight (not used unless target is a point process)
  209. delay: float
  210. connection delay (not used unless target is a point process)
  211. """
  212. # create new NetCon objects for the connections. Activation times will
  213. # be triggered on the somatic voltage with a given threshold.
  214. for sec in self.somalist:
  215. # print("section", sec)
  216. self._hoc_sd_netconlist.append(neuron.h.NetCon(sec(0.5)._ref_v,
  217. target,
  218. sec=sec))
  219. self._hoc_sd_netconlist[-1].threshold = threshold
  220. self._hoc_sd_netconlist[-1].weight[0] = weight
  221. self._hoc_sd_netconlist[-1].delay = delay
  222. for sec in self.allseclist:
  223. if "soma" not in str(sec):
  224. # print("section2 ", (sec))
  225. # will need to change this to all sections using sec.nseg
  226. self._hoc_sd_pre_netconlist.append(neuron.h.NetCon(sec(0.5)._ref_v,
  227. target,
  228. sec=sec))
  229. self._hoc_sd_pre_netconlist[-1].threshold = threshold
  230. self._hoc_sd_pre_netconlist[-1].weight[0] = weight
  231. self._hoc_sd_pre_netconlist[-1].delay = delay
  232. class DummyCell(object):
  233. def __init__(self, totnsegs=0,
  234. x=None,
  235. y=None,
  236. z=None,
  237. d=None,
  238. area=None,
  239. length=None,
  240. somainds=None):
  241. """
  242. Dummy Cell object initialized with all attributes needed for LFP
  243. calculations using the LFPy.RecExtElectrode class and methods.
  244. This cell can be imagined as one "super" cell containing transmembrane
  245. currents generated by all NetworkCell segments on this RANK at once.
  246. Parameters
  247. ----------
  248. totnsegs: int
  249. total number of segments
  250. x, y, z: ndarray
  251. arrays of shape (totnsegs, 2) with (x,y,z) coordinates of start
  252. and end points of segments in units of (um)
  253. d: ndarray
  254. array of length totnsegs with segment diameters
  255. area: ndarray
  256. array of segment surface areas
  257. length: ndarray
  258. array of segment lengths
  259. """
  260. # set attributes
  261. self.totnsegs = totnsegs
  262. self.x = x if x is not None else np.array([])
  263. self.y = y if y is not None else np.array([])
  264. self.z = z if z is not None else np.array([])
  265. self.d = d if d is not None else np.array([])
  266. self.area = area if area is not None else np.array([])
  267. self.length = length if area is not None else np.array([])
  268. self.somainds = somainds if somainds is not None else np.array([])
  269. def get_idx(self, section="soma"):
  270. if section == "soma":
  271. return self.somainds
  272. else:
  273. raise ValueError('section argument must be "soma"')
  274. class NetworkPopulation(object):
  275. """
  276. NetworkPopulation class representing a group of Cell objects
  277. distributed across RANKs.
  278. Parameters
  279. ----------
  280. CWD: path or None
  281. Current working directory
  282. CELLPATH: path or None
  283. Relative path from CWD to source files for cell model
  284. (morphology, hoc routines etc.)
  285. first_gid: int
  286. The global identifier of the first cell created in this population
  287. instance. The first_gid in the first population created should be 0
  288. and cannot exist in previously created NetworkPopulation instances
  289. Cell: class
  290. class defining a Cell object, see class NetworkCell above
  291. POP_SIZE: int
  292. number of cells in population
  293. name: str
  294. population name reference
  295. cell_args: dict
  296. keys and values for Cell object
  297. pop_args: dict
  298. keys and values for Network.draw_rand_pos assigning cell positions
  299. rotation_arg: dict
  300. default cell rotations around x and y axis on the form
  301. { 'x': np.pi/2, 'y': 0 }. Can only have the keys 'x' and 'y'.
  302. Cells are randomly rotated around z-axis using the
  303. Cell.set_rotation() method.
  304. OUTPUTPATH: str
  305. path to output file destination
  306. """
  307. def __init__(self, CWD=None, CELLPATH=None, first_gid=0, Cell=NetworkCell,
  308. POP_SIZE=4, name='L5PC',
  309. cell_args=None, pop_args=None,
  310. rotation_args=None,
  311. OUTPUTPATH='example_parallel_network'):
  312. # set class attributes
  313. self.CWD = CWD
  314. self.CELLPATH = CELLPATH
  315. self.first_gid = first_gid
  316. self.Cell = Cell
  317. self.POP_SIZE = POP_SIZE
  318. self.name = name
  319. self.cell_args = cell_args if cell_args is not None else dict()
  320. self.pop_args = pop_args if pop_args is not None else dict()
  321. self.rotation_args = rotation_args if rotation_args is not None \
  322. else dict()
  323. self.OUTPUTPATH = OUTPUTPATH
  324. # set up ParallelContext
  325. self.pc = neuron.h.ParallelContext()
  326. self._SIZE = self.pc.nhost()
  327. self._RANK = self.pc.id()
  328. # create folder for output if it does not exist
  329. #if self._RANK == 0:
  330. # if not os.path.isdir(OUTPUTPATH):
  331. # os.mkdir(OUTPUTPATH)
  332. self.pc.barrier()
  333. # container of Vector objects used to record times of action potentials
  334. self._hoc_spike_vectors = []
  335. # set up population of cells on this RANK
  336. self.gids = [
  337. (i +
  338. first_gid) for i in range(POP_SIZE) if (
  339. i +
  340. first_gid) %
  341. self._SIZE == self._RANK]
  342. # we have to enter the cell's corresponding file directory to
  343. # create cell because how EPFL set their code up
  344. if CWD is not None:
  345. os.chdir(os.path.join(CWD, CELLPATH, self.name))
  346. self.cells = [Cell(**cell_args) for gid in self.gids]
  347. os.chdir(CWD)
  348. else:
  349. self.cells = [Cell(**cell_args) for gid in self.gids]
  350. # position each cell's soma in space
  351. self.soma_pos = self.draw_rand_pos(POP_SIZE=len(self.gids), **pop_args)
  352. for i, cell in enumerate(self.cells):
  353. cell.set_pos(**self.soma_pos[i])
  354. # assign a random rotation around the z-axis of each cell
  355. self.rotations = np.random.uniform(0, np.pi * 2, len(self.gids))
  356. assert 'z' not in self.rotation_args.keys()
  357. for i, cell in enumerate(self.cells):
  358. cell.set_rotation(z=self.rotations[i], **self.rotation_args)
  359. # assign gid to each cell
  360. for gid, cell in zip(self.gids, self.cells):
  361. cell.gid = gid
  362. # gather gids, soma positions and cell rotations to RANK 0, and write
  363. # as structured array.
  364. if self._RANK == 0:
  365. populationData = flattenlist(self.pc.py_allgather(
  366. zip(self.gids, self.soma_pos, self.rotations)))
  367. # create structured array for storing data
  368. dtype = [('gid', 'i8'), ('x', float), ('y', float), ('z', float),
  369. ('x_rot', float), ('y_rot', float), ('z_rot', float)]
  370. popDataArray = np.empty((len(populationData, )), dtype=dtype)
  371. for i, (gid, pos, z_rot) in enumerate(populationData):
  372. popDataArray[i]['gid'] = gid
  373. popDataArray[i]['x'] = pos['x']
  374. popDataArray[i]['y'] = pos['y']
  375. popDataArray[i]['z'] = pos['z']
  376. popDataArray[i]['x_rot'] = np.pi / 2
  377. popDataArray[i]['y_rot'] = 0.
  378. popDataArray[i]['z_rot'] = z_rot
  379. else:
  380. _ = self.pc.py_allgather(
  381. zip(self.gids, self.soma_pos, self.rotations))
  382. # sync
  383. self.pc.barrier()
  384. # def draw_rand_pos(self, POP_SIZE, radius, loc, scale, cap=None):
  385. # """
  386. # Draw some random location for POP_SIZE cells within radius radius,
  387. # at mean depth loc and standard deviation scale.
  388. # Returned argument is a list of dicts [{'x', 'y', 'z'},].
  389. # Parameters
  390. # ----------
  391. # POP_SIZE: int
  392. # Population size
  393. # radius: float
  394. # Radius of population.
  395. # loc: float
  396. # expected mean depth of somas of population.
  397. # scale: float
  398. # expected standard deviation of depth of somas of population.
  399. # cap: None, float or length to list of floats
  400. # if float, cap distribution between [loc-cap, loc+cap),
  401. # if list, cap distribution between [loc-cap[0], loc+cap[1]]
  402. # Returns
  403. # -------
  404. # soma_pos: list
  405. # List of dicts of len POP_SIZE
  406. # where dict have keys x, y, z specifying
  407. # xyz-coordinates of cell at list entry `i`.
  408. # """
  409. # x = np.empty(POP_SIZE)
  410. # y = np.empty(POP_SIZE)
  411. # z = np.empty(POP_SIZE)
  412. # for i in range(POP_SIZE):
  413. # x[i] = (np.random.rand() - 0.5) * radius * 2
  414. # y[i] = (np.random.rand() - 0.5) * radius * 2
  415. # while np.sqrt(x[i]**2 + y[i]**2) >= radius:
  416. # x[i] = (np.random.rand() - 0.5) * radius * 2
  417. # y[i] = (np.random.rand() - 0.5) * radius * 2
  418. # z = np.random.normal(loc=loc, scale=scale, size=POP_SIZE)
  419. # if cap is not None:
  420. # if type(cap) in [float, np.float32, np.float64]:
  421. # while not np.all((z >= loc - cap) & (z < loc + cap)):
  422. # inds = (z < loc - cap) ^ (z > loc + cap)
  423. # z[inds] = np.random.normal(loc=loc, scale=scale,
  424. # size=inds.sum())
  425. # elif isinstance(cap, list):
  426. # assert len(cap) == 2, \
  427. # 'cap = {} is not a length 2 list'.format(float)
  428. # while not np.all((z >= loc - cap[0]) & (z < loc + cap[1])):
  429. # inds = (z < loc - cap[0]) ^ (z > loc + cap[1])
  430. # z[inds] = np.random.normal(loc=loc, scale=scale,
  431. # size=inds.sum())
  432. # else:
  433. # raise Exception('cap = {} is not None'.format(float),
  434. # 'a float or length 2 list of floats')
  435. # soma_pos = []
  436. # for i in range(POP_SIZE):
  437. # soma_pos.append({'x': x[i], 'y': y[i], 'z': z[i]})
  438. # return soma_pos
  439. def draw_rand_pos(self, POP_SIZE, x0, y0, z0, d, h):
  440. """
  441. Draw random locations for POP_SIZE cells uniformly distributed within a cylinder.
  442. The cylinder is centered at (x0, y0, z0) with diameter d in the x-y plane
  443. and depth h in the z direction.
  444. Parameters
  445. ----------
  446. POP_SIZE : int
  447. Number of cells.
  448. x0 : float
  449. x-coordinate of the cylinder center.
  450. y0 : float
  451. y-coordinate of the cylinder center.
  452. z0 : float
  453. z-coordinate of the cylinder center.
  454. d : float
  455. Diameter of the cylinder in the x-y plane.
  456. h : float
  457. Depth of the cylinder in the z direction.
  458. Returns
  459. -------
  460. soma_pos : list of dict
  461. List of dictionaries, each with keys 'x', 'y', and 'z' specifying the coordinates.
  462. """
  463. x = np.empty(POP_SIZE)
  464. y = np.empty(POP_SIZE)
  465. z = np.empty(POP_SIZE)
  466. for i in range(POP_SIZE):
  467. # Generate x, y uniformly inside a disc of radius d/2
  468. while True:
  469. xi = (np.random.rand() - 0.5) * d + x0
  470. xi = 0
  471. zi = (np.random.rand() - 0.5) * d + z0
  472. if np.sqrt((xi - x0)**2 + (zi - z0)**2) <= d / 2:
  473. x[i] = xi
  474. z[i] = zi
  475. break
  476. else:
  477. continue
  478. # Generate z uniformly in the interval [z0 - h/2, z0 + h/2]
  479. y[i] = np.random.rand() * h + (y0 - h/2)
  480. soma_pos = []
  481. for i in range(POP_SIZE):
  482. soma_pos.append({'x': x[i], 'y': y[i], 'z': z[i]})
  483. return soma_pos
  484. class Network(object):
  485. """
  486. Network class, creating distributed populations of cells of
  487. type Cell and handling connections between cells in the respective
  488. populations.
  489. Parameters
  490. ----------
  491. dt: float
  492. Simulation timestep size
  493. tstart: float
  494. Start time of simulation
  495. tstop: float
  496. End time of simulation
  497. v_init: float
  498. Membrane potential set at first timestep across all cells
  499. celsius: float
  500. Global control of temperature, affect channel kinetics.
  501. It will also be forced when creating the different Cell objects, as
  502. LFPy.Cell and LFPy.TemplateCell also accept the same keyword
  503. argument.
  504. verbose: bool
  505. if True, print out misc. messages
  506. """
  507. def __init__(
  508. self,
  509. dt=0.1,
  510. tstart=0.,
  511. tstop=1000.,
  512. v_init=-65.,
  513. celsius=6.3,
  514. OUTPUTPATH='example_parallel_network',
  515. verbose=False):
  516. # set attributes
  517. self.dt = dt
  518. self.tstart = tstart
  519. self.tstop = tstop
  520. self.v_init = v_init
  521. self.celsius = celsius
  522. self.OUTPUTPATH = OUTPUTPATH
  523. self.verbose = verbose
  524. self.syn_data = []
  525. # we need NEURON's ParallelContext for communicating NetCon events
  526. self.pc = neuron.h.ParallelContext()
  527. self._SIZE = self.pc.nhost()
  528. self._RANK = self.pc.id()
  529. # create empty list for connections between cells (not to be confused
  530. # with each cell's list of netcons _hoc_netconlist)
  531. self._hoc_netconlist = neuron.h.List()
  532. # The different populations in the Network will be collected in
  533. # a dictionary of NetworkPopulation object, where the keys represent
  534. # population names. The names are also put in a list ordered according
  535. # to the order populations are created in (as some operations rely on
  536. # this particular order)
  537. self.populations = dict()
  538. self.population_names = []
  539. def create_population(self, CWD=None, CELLPATH=None, Cell=NetworkCell,
  540. POP_SIZE=4, name='L5PC',
  541. cell_args=None, pop_args=None,
  542. rotation_args=None):
  543. """
  544. Create and append a distributed POP_SIZE-sized population of cells of
  545. type Cell with the corresponding name. Cell-object references, gids on
  546. this RANK, population size POP_SIZE and names will be added to the
  547. lists Network.gids, Network.cells, Network.sizes and Network.names,
  548. respectively
  549. Parameters
  550. ----------
  551. CWD: path
  552. Current working directory
  553. CELLPATH: path
  554. Relative path from CWD to source files for cell model
  555. (morphology, hoc routines etc.)
  556. Cell: class
  557. class defining a Cell-like object, see class NetworkCell
  558. POP_SIZE: int
  559. number of cells in population
  560. name: str
  561. population name reference
  562. cell_args: dict
  563. keys and values for Cell object
  564. pop_args: dict
  565. keys and values for Network.draw_rand_pos assigning cell positions
  566. rotation_arg: dict
  567. default cell rotations around x and y axis on the form
  568. { 'x': np.pi/2, 'y': 0 }. Can only have the keys 'x' and 'y'.
  569. Cells are randomly rotated around z-axis using the
  570. Cell.set_rotation method.
  571. """
  572. assert name not in self.populations.keys(), \
  573. 'population name {} already taken'.format(name)
  574. # compute the first global id of this new population, based
  575. # on population sizes of existing populations
  576. first_gid = 0
  577. for p in self.populations.values():
  578. first_gid += p.POP_SIZE
  579. # create NetworkPopulation object
  580. population = NetworkPopulation(
  581. CWD=CWD,
  582. CELLPATH=CELLPATH,
  583. first_gid=first_gid,
  584. Cell=Cell,
  585. POP_SIZE=POP_SIZE,
  586. name=name,
  587. cell_args=cell_args,
  588. pop_args=pop_args,
  589. rotation_args=rotation_args,
  590. OUTPUTPATH=self.OUTPUTPATH)
  591. # associate gids of cells on this RANK such that NEURON can look up
  592. # at which RANK different cells are created when connecting the network
  593. for gid in population.gids:
  594. self.pc.set_gid2node(gid, self._RANK)
  595. # Prepare connection targets by iterating over local neurons in pop.
  596. for gid, cell in zip(population.gids, population.cells):
  597. # attach NetCon source (spike detektor) to each cell's soma with no
  598. # target to cell gid
  599. cell.create_spike_detector(None)
  600. # assosiate cell gid with the NetCon source
  601. self.pc.cell(gid, cell._hoc_sd_netconlist[-1])
  602. # record spike events
  603. population._hoc_spike_vectors.append(neuron.h.Vector())
  604. cell._hoc_sd_netconlist[-1].record(
  605. population._hoc_spike_vectors[-1])
  606. # add population object to dictionary of populations
  607. self.populations[name] = population
  608. # append population name to list (Network.populations.keys() not
  609. # unique)
  610. self.population_names.append(name)
  611. def get_connectivity_rand(self, pre='L5PC', post='L5PC', connprob=0.2):
  612. """
  613. Dummy function creating a (boolean) cell to cell connectivity matrix
  614. between pre and postsynaptic populations.
  615. Connections are drawn randomly between presynaptic cell gids in
  616. population 'pre' and postsynaptic cell gids in 'post' on this RANK with
  617. a fixed connection probability. self-connections are disabled if
  618. presynaptic and postsynaptic populations are the same.
  619. Parameters
  620. ----------
  621. pre: str
  622. presynaptic population name
  623. post: str
  624. postsynaptic population name
  625. connprob: float in [0, 1]
  626. connection probability, connections are drawn on random
  627. Returns
  628. -------
  629. ndarray, dtype bool
  630. n_pre x n_post array of connections between n_pre presynaptic
  631. neurons and n_post postsynaptic neurons on this RANK. Entries
  632. with True denotes a connection.
  633. """
  634. n_pre = self.populations[pre].POP_SIZE
  635. gids = np.array(self.populations[post].gids).astype(int)
  636. # first check if there are any postsyn cells on this RANK
  637. if gids.size > 0:
  638. # define incoming connections for cells on this RANK
  639. C = np.random.binomial(n=1, p=connprob,
  640. size=(n_pre, gids.size)
  641. ).astype(bool)
  642. if pre == post:
  643. # avoid self connections.
  644. gids_pre, gids_post = np.where(C)
  645. gids_pre += self.populations[pre].first_gid
  646. gids_post *= self._SIZE # assume round-robin dist. of gids
  647. gids_post += self.populations[post].gids[0]
  648. inds = gids_pre != gids_post
  649. gids_pre = gids_pre[inds]
  650. gids_pre -= self.populations[pre].first_gid
  651. gids_post = gids_post[inds]
  652. gids_post -= self.populations[post].gids[0]
  653. gids_post //= self._SIZE
  654. c = np.c_[gids_pre, gids_post]
  655. # create boolean matrix
  656. C = ss.csr_matrix((np.ones(gids_pre.shape[0], dtype=bool),
  657. (c[:, 0], c[:, 1])),
  658. shape=(n_pre, gids.size), dtype=bool)
  659. return C.toarray()
  660. else:
  661. return C
  662. else:
  663. return np.zeros((n_pre, 0), dtype=bool)
  664. def connect(self, pre, post, connectivity,
  665. syntype=neuron.h.ExpSyn,
  666. synparams=dict(tau=2., e=0.),
  667. weightfun=np.random.normal,
  668. weightargs=dict(loc=0.1, scale=0.01),
  669. minweight=0,
  670. delayfun=stats.truncnorm,
  671. delayargs=dict(a=0.3, b=np.inf, loc=2, scale=0.2),
  672. mindelay=None,
  673. multapsefun=stats.truncnorm,
  674. multapseargs=dict(a=(1 - 4) / 1.,
  675. b=(10 - 4) / 1,
  676. loc=4,
  677. scale=1),
  678. syn_pos_args=dict(section=['soma', 'axon'],
  679. fun=[stats.norm] * 2,
  680. funargs=[dict(loc=0, scale=100)] * 2,
  681. funweights=[0.5] * 2,
  682. z_min=-1E6, z_max=1E6,
  683. ),
  684. save_connections=False,
  685. ):
  686. """
  687. Connect presynaptic cells to postsynaptic cells. Connections are
  688. drawn from presynaptic cells to postsynaptic cells, hence connectivity
  689. array must only be specified for postsynaptic units existing on this
  690. RANK.
  691. Parameters
  692. ----------
  693. pre: str
  694. presynaptic population name
  695. post: str
  696. postsynaptic population name
  697. connectivity: ndarray / (scipy.sparse array)
  698. boolean connectivity matrix between pre and post.
  699. syntype: hoc.HocObject
  700. reference to NEURON synapse mechanism, e.g., ``neuron.h.ExpSyn``
  701. synparams: dict
  702. dictionary of parameters for synapse mechanism, keys 'e', 'tau'
  703. etc.
  704. weightfun: function
  705. function used to draw weights from a numpy.random distribution
  706. weightargs: dict
  707. parameters passed to weightfun
  708. minweight: float,
  709. minimum weight in units of nS
  710. delayfun: function
  711. function used to draw delays from a subclass of
  712. scipy.stats.rv_continuous or numpy.random distribution
  713. delayargs: dict
  714. parameters passed to ``delayfun``
  715. mindelay: float,
  716. minimum delay in multiples of dt. Ignored if ``delayfun`` is an
  717. inherited from ``scipy.stats.rv_continuous``
  718. multapsefun: function or None
  719. function reference, e.g., ``scipy.stats.rv_continuous`` used to
  720. draw a number of synapses for a cell-to-cell connection.
  721. If None, draw only one connection
  722. multapseargs: dict
  723. arguments passed to multapsefun
  724. syn_pos_args: dict
  725. arguments passed to inherited ``LFPy.Cell`` method
  726. ``NetworkCell.get_rand_idx_area_and_distribution_norm`` to find
  727. synapse locations.
  728. save_connections: bool
  729. if True (default False), save instantiated connections to HDF5 file
  730. ``Network.OUTPUTPATH/synapse_connections.h5`` as dataset
  731. ``<pre>:<post>`` using a structured ndarray with dtype
  732. ::
  733. [('gid_pre'), ('gid', 'i8'), ('weight', 'f8'), ('delay', 'f8'),
  734. ('sec', 'U64'), ('sec.x', 'f8'),
  735. ('x', 'f8'), ('y', 'f8'), ('z', 'f8')],
  736. where ``gid_pre`` is presynapic cell id,
  737. ``gid`` is postsynaptic cell id,
  738. ``weight`` connection weight, ``delay`` connection delay,
  739. ``sec`` section name, ``sec.x`` relative location on section,
  740. and ``x``, ``y``, ``z`` the corresponding
  741. midpoint coordinates of the target segment.
  742. Returns
  743. -------
  744. list
  745. Length 2 list with ndarrays [conncount, syncount] with numbers of
  746. instantiated connections and synapses.
  747. Raises
  748. ------
  749. DeprecationWarning
  750. if ``delayfun`` is not a subclass of ``scipy.stats.rv_continuous``
  751. """
  752. # check if delayfun is a scipy.stats.rv_continuous like function that
  753. # provides a function `rvs` for random variates.
  754. # Otherwise, raise some warnings
  755. if not hasattr(delayfun, 'rvs'):
  756. warn(f'argument delayfun={delayfun.__str__()} do not appear ' +
  757. 'scipy.stats.rv_continuous or scipy.stats.rv_discrete like ' +
  758. 'and will be deprecated in the future')
  759. else:
  760. if mindelay is not None:
  761. warn(f'mindelay={mindelay} not usable with ' +
  762. f'delayfun={delayfun.__str__()}')
  763. # set up connections from all cells in presynaptic to post across RANKs
  764. n0 = self.populations[pre].first_gid
  765. # gids of presynaptic neurons:
  766. gids_pre = np.arange(n0, n0 + self.populations[pre].POP_SIZE)
  767. # print(gids_pre)
  768. # count connections and synapses made on this RANK
  769. conncount = connectivity.astype(int).sum()
  770. syncount = 0
  771. # keep track of synapse positions for this connect
  772. # call on this rank such that these can be communicated and stored
  773. syn_idx_pos = []
  774. # iterate over gids on this RANK and create connections
  775. for i, (gid_post, cell) in enumerate(zip(self.populations[post].gids,
  776. self.populations[post].cells)
  777. ):
  778. # print("cell:" , cell)
  779. # do NOT iterate over all possible presynaptic neurons
  780. for gid_pre in gids_pre[connectivity[:, i]]:
  781. # print("gid pre", gid_pre,gids_pre[connectivity[:, i]] )
  782. # throw a warning if sender neuron is identical to receiving
  783. # neuron
  784. if gid_post == gid_pre:
  785. print(
  786. 'connecting cell w. gid {} to itself (RANK {})'.format(
  787. gid_post, self._RANK))
  788. # assess number of synapses
  789. #if multapsefun is None:
  790. nidx = 1
  791. """
  792. else:
  793. if hasattr(multapsefun, 'pdf'):
  794. # assume we're dealing with a scipy.stats.rv_continuous
  795. # like method. Then evaluate pdf at positive integer
  796. # values and feed as custom scipy.stats.rv_discrete
  797. # distribution
  798. d = multapsefun(**multapseargs)
  799. # number of multapses must be on interval [1, 100]
  800. xk = np.arange(1, 100)
  801. pk = d.pdf(xk)
  802. pk /= pk.sum()
  803. nidx = stats.rv_discrete(values=(xk, pk)).rvs()
  804. # this aint pretty:
  805. mssg = (
  806. 'multapsefun: '
  807. + multapsefun(**multapseargs).__str__()
  808. + f'w. multapseargs: {multapseargs} resulted '
  809. + f'in {nidx} synapses'
  810. )
  811. assert nidx >= 1, mssg
  812. elif hasattr(multapsefun, 'pmf'):
  813. # assume we're dealing with a scipy.stats.rv_discrete
  814. # like method that can be used to generate random
  815. # variates directly
  816. nidx = multapsefun(**multapseargs).rvs()
  817. mssg = (
  818. f'multapsefun: {multapsefun().__str__()} w. '
  819. + f'multapseargs: {multapseargs} resulted in '
  820. + f'{nidx} synapses'
  821. )
  822. assert nidx >= 1, mssg
  823. else:
  824. warn(f'multapsefun{multapsefun.__str__()} will be ' +
  825. 'deprecated. Use scipy.stats.rv_continuous or ' +
  826. 'scipy.stats.rv_discrete like methods instead')
  827. nidx = 0
  828. j = 0
  829. while nidx <= 0 and j < 1000:
  830. nidx = int(round(multapsefun(**multapseargs)))
  831. j += 1
  832. if j == 1000:
  833. raise Exception(
  834. 'change multapseargs as no positive '
  835. 'synapse # was found in 1000 trials')
  836. """
  837. # find synapse locations and corresponding section names
  838. idxs = cell.get_rand_idx_area_and_distribution_norm(
  839. nidx=nidx, **syn_pos_args)
  840. print(idxs)
  841. # axon_secs = [secname for secname in cell.allsecnames if 'axon' in secname]
  842. # last_axon_secname = axon_secs[-1]
  843. # idx = cell.get_idx(last_axon_secname)
  844. # idxs = [(idx[0], last_axon_secname, 1.0)]
  845. secs = cell.get_idx_name(idxs)
  846. # [57 'ball_and_stick_template[1].dend[1]' 0.5]
  847. # secs = [cell.get_idx_name(-1)]
  848. print("secs", secs)
  849. # draw weights
  850. weights = weightfun(size=nidx, **weightargs)
  851. # redraw weights less that minweight
  852. while np.any(weights < minweight):
  853. j = weights < minweight
  854. weights[j] = weightfun(size=j.sum(), **weightargs)
  855. # draw delays
  856. if hasattr(delayfun, 'rvs'):
  857. delays = delayfun(**delayargs).rvs(size=nidx)
  858. # check that all delays are > dt
  859. try:
  860. assert np.all(delays >= self.dt)
  861. except AssertionError as ae:
  862. raise ae(
  863. f'the delayfun parameter a={delayargs["a"]} '
  864. + f'resulted in delay less than dt={self.dt}'
  865. )
  866. else:
  867. delays = delayfun(size=nidx, **delayargs)
  868. # redraw delays shorter than mindelay
  869. while np.any(delays < mindelay):
  870. j = delays < mindelay
  871. delays[j] = delayfun(size=j.sum(), **delayargs)
  872. for i, ((idx, secname, secx), weight, delay) in enumerate(
  873. zip(secs, weights, delays)):
  874. cell.create_synapse(
  875. cell,
  876. # TODO: Find neater way of accessing
  877. # Section reference, this looks slow
  878. sec=list(
  879. cell.allseclist)[
  880. np.where(
  881. np.array(
  882. cell.allsecnames) == secname)[0][0]],
  883. x=secx,
  884. syntype=syntype,
  885. synparams=synparams)
  886. # connect up NetCon object
  887. # nc = self.pc.gid_connect(gid_pre, cell.netconsynapses[-1])
  888. # nc.weight[0] = weight
  889. # nc.delay = delays[i]
  890. # self._hoc_netconlist.append(nc)
  891. # Original code
  892. # nc = self.pc.gid_connect(gid_pre, cell.netconsynapses[-1])
  893. # Modified code (explicit presynaptic section spike detection):
  894. presyn_cell = self.populations[pre].cells[gid_pre - n0] # locate presynaptic cell
  895. #################
  896. # where in the presynaptic neuron a synapse is located
  897. #################
  898. chosen_sec = [sec for sec in presyn_cell.allseclist][1]#np.random.choice([sec for sec in presyn_cell.allseclist]) # pick random axon section
  899. # secpostx = 0.5
  900. secpostx = presyn_cell.get_closest_idx(z=syn_pos_args["z_min"])/presyn_cell.totnsegs
  901. # print(smt )
  902. presyn_nc = neuron.h.NetCon(chosen_sec(secpostx)._ref_v, None, sec=chosen_sec)
  903. presyn_nc.threshold = -10
  904. self.pc.cell(gid_pre, presyn_nc) # Register the section-level spike detector
  905. nc = self.pc.gid_connect(gid_pre, cell.netconsynapses[-1])
  906. nc.weight[0] = weight
  907. nc.delay = delays[i]
  908. self._hoc_netconlist.append(nc)
  909. # store also synapse indices allowing for computing LFPs
  910. # from syn.i
  911. cell.synidx.append(idx)
  912. # store gid and xyz-coordinate of synapse positions
  913. syn_idx_pos.append((gid_pre,
  914. cell.gid,
  915. weight,
  916. delays[i],
  917. secname,
  918. secx,
  919. chosen_sec,
  920. secpostx,
  921. cell.x[idx].mean(axis=-1),
  922. cell.y[idx].mean(axis=-1),
  923. cell.z[idx].mean(axis=-1)))
  924. syncount += nidx
  925. # display some connectivity stats
  926. conncount = self.pc.allreduce(conncount, 1)
  927. syncount = self.pc.allreduce(syncount, 1)
  928. if self._RANK == 0:
  929. print('Connected population {} to {}'.format(pre, post),
  930. 'by {} connections and {} synapses'.format(conncount,
  931. syncount))
  932. else:
  933. conncount = None
  934. syncount = None
  935. # gather and write syn_idx_pos data
  936. if save_connections:
  937. if self._RANK == 0:
  938. synData = flattenlist(self.pc.py_allgather(syn_idx_pos))
  939. # convert to structured array
  940. dtype = [('gid_pre', 'i8'),
  941. ('gid_post', 'i8'),
  942. ('weight', 'f8'),
  943. ('delay', 'f8'),
  944. ('sec_pre', 'S64'),
  945. ('sec_pre.x', 'f8'),
  946. ('sec_post', 'S64'),
  947. ('sec_post.x', 'f8'),
  948. ('x', 'f8'),
  949. ('y', 'f8'),
  950. ('z', 'f8')]
  951. synDataArray = np.empty((len(synData), ), dtype=dtype)
  952. for i, (gid_pre, gid, weight, delay, secname, secx,chosen_sec, secpostx, x, y, z
  953. ) in enumerate(synData):
  954. synDataArray[i]['gid_pre'] = gid_pre
  955. synDataArray[i]['gid_post'] = gid
  956. synDataArray[i]['weight'] = weight
  957. synDataArray[i]['delay'] = delay
  958. synDataArray[i]['sec_pre'] = secname
  959. synDataArray[i]['sec_pre.x'] = secx
  960. synDataArray[i]['sec_post'] = chosen_sec
  961. synDataArray[i]['sec_post.x'] = secpostx
  962. synDataArray[i]['x'] = x
  963. synDataArray[i]['y'] = y
  964. synDataArray[i]['z'] = z
  965. self.syn_data.append(synDataArray)
  966. # Dump to hdf5 file, append to file if entry exists
  967. '''
  968. with h5py.File(os.path.join(self.OUTPUTPATH,
  969. 'synapse_connections.h5'),
  970. 'a') as f:
  971. key = '{}:{}'.format(pre, post)
  972. if key in f.keys():
  973. del f[key]
  974. assert key not in f.keys()
  975. f[key] = synDataArray
  976. # save global connection data (synapse type/parameters)
  977. # equal for all synapses
  978. try:
  979. grp = f.create_group('synparams')
  980. except ValueError:
  981. grp = f['synparams']
  982. try:
  983. subgrp = grp.create_group(key)
  984. except ValueError:
  985. subgrp = grp[key]
  986. subgrp['mechanism'] = syntype.__str__().strip('()')
  987. for key, value in synparams.items():
  988. subgrp[key] = value
  989. '''
  990. else:
  991. _ = self.pc.py_allgather(syn_idx_pos)
  992. return self.pc.py_broadcast([conncount, syncount], 0)
  993. def connect_AMPA_NMDA( self, pre, post, connectivity,
  994. tau1_AMPA, tau2_AMPA,
  995. tau1_NMDA, tau2_NMDA,
  996. e_NMDA, e_AMPA,
  997. w_NMDA_mean, w_AMPA_mean,
  998. w_NMDA_std, w_AMPA_std,
  999. delay,
  1000. syn_pos_args,
  1001. syntype="StochasticExp2Syn",
  1002. save_connections=False):
  1003. """
  1004. Connect presynaptic cells to postsynaptic cells. Connections are
  1005. drawn from presynaptic cells to postsynaptic cells, hence connectivity
  1006. array must only be specified for postsynaptic units existing on this
  1007. RANK.
  1008. Parameters
  1009. ----------
  1010. pre: str
  1011. presynaptic population name
  1012. post: str
  1013. postsynaptic population name
  1014. connectivity: ndarray / (scipy.sparse array)
  1015. boolean connectivity matrix between pre and post.
  1016. syntype: hoc.HocObject
  1017. reference to NEURON synapse mechanism, e.g., ``neuron.h.ExpSyn``
  1018. Returns
  1019. -------
  1020. list
  1021. Length 2 list with ndarrays [conncount, syncount] with numbers of
  1022. instantiated connections and synapses.
  1023. Raises
  1024. ------
  1025. DeprecationWarning
  1026. if ``delayfun`` is not a subclass of ``scipy.stats.rv_continuous``
  1027. """
  1028. # set up connections from all cells in presynaptic to post across RANKs
  1029. n0 = self.populations[pre].first_gid
  1030. # gids of presynaptic neurons:
  1031. gids_pre = np.arange(n0, n0 + self.populations[pre].POP_SIZE)
  1032. # count connections and synapses made on this RANK
  1033. conncount = connectivity.astype(int).sum()
  1034. syncount = 0
  1035. # keep track of synapse positions for this connect
  1036. # call on this rank such that these can be communicated and stored
  1037. syn_idx_pos = []
  1038. # iterate over gids on this RANK and create connections
  1039. for i, (gid_post, cell) in enumerate(zip(self.populations[post].gids,
  1040. self.populations[post].cells)
  1041. ):
  1042. # do NOT iterate over all possible presynaptic neurons
  1043. for gid_pre in gids_pre[connectivity[:, i]]:
  1044. # print("gid pre", gid_pre,gids_pre[connectivity[:, i]] )
  1045. # throw a warning if sender neuron is identical to receiving
  1046. # neuron
  1047. if gid_post == gid_pre:
  1048. print(
  1049. 'connecting cell w. gid {} to itself (RANK {})'.format(
  1050. gid_post, self._RANK))
  1051. # assess number of synapses
  1052. nidx = 2
  1053. # find synapse locations and corresponding section names
  1054. idxs = cell.get_rand_idx_area_and_distribution_norm(
  1055. nidx=nidx, **syn_pos_args)
  1056. secs = cell.get_idx_name(idxs)
  1057. # draw weights
  1058. weights = [0,0 ]
  1059. # delays
  1060. delays = [delay, delay]
  1061. synparams = [dict(tau1 = tau1_AMPA,tau2 = tau2_AMPA, e= e_AMPA, weight_mean = w_AMPA_mean, weight_std = w_AMPA_std),
  1062. dict(tau1 = tau1_NMDA,tau2 = tau2_NMDA, e= e_NMDA , weight_mean = w_NMDA_mean, weight_std = w_NMDA_std)]
  1063. for i, ((idx, secname, secx), weight, delay) in enumerate(
  1064. zip(secs, weights, delays)):
  1065. cell.create_synapse(
  1066. cell,
  1067. # TODO: Find neater way of accessing
  1068. # Section reference, this looks slow
  1069. sec=list(
  1070. cell.allseclist)[
  1071. np.where(
  1072. np.array(
  1073. cell.allsecnames) == secname)[0][0]],
  1074. x=secx,
  1075. syntype=syntype,
  1076. synparams=synparams[i])
  1077. # Modified code (explicit presynaptic section spike detection):
  1078. presyn_cell = self.populations[pre].cells[gid_pre - n0] # locate presynaptic cell
  1079. #################
  1080. # where in the presynaptic neuron a synapse is located
  1081. #################
  1082. chosen_sec = [sec for sec in presyn_cell.allseclist][1]
  1083. secpostx = presyn_cell.get_closest_idx(z=syn_pos_args["z_min"])/presyn_cell.totnsegs
  1084. presyn_nc = neuron.h.NetCon(chosen_sec(secpostx)._ref_v, None, sec=chosen_sec)
  1085. presyn_nc.threshold = -10
  1086. self.pc.cell(gid_pre, presyn_nc) # Register the section-level spike detector
  1087. nc = self.pc.gid_connect(gid_pre, cell.netconsynapses[-1])
  1088. nc.weight[0] = weight
  1089. nc.delay = delays[i]
  1090. self._hoc_netconlist.append(nc)
  1091. # store also synapse indices allowing for computing LFPs
  1092. cell.synidx.append(idx)
  1093. # store gid and xyz-coordinate of synapse positions
  1094. syn_idx_pos.append((gid_pre,
  1095. cell.gid,
  1096. weight,
  1097. delays[i],
  1098. secname,
  1099. secx,
  1100. chosen_sec,
  1101. secpostx,
  1102. cell.x[idx].mean(axis=-1),
  1103. cell.y[idx].mean(axis=-1),
  1104. cell.z[idx].mean(axis=-1)))
  1105. syncount += nidx
  1106. # display some connectivity stats
  1107. conncount = self.pc.allreduce(conncount, 1)
  1108. syncount = self.pc.allreduce(syncount, 1)
  1109. if self._RANK == 0:
  1110. print('Connected population {} to {}'.format(pre, post),
  1111. 'by {} connections and {} synapses'.format(conncount,
  1112. syncount))
  1113. else:
  1114. conncount = None
  1115. syncount = None
  1116. # gather and write syn_idx_pos data
  1117. if save_connections:
  1118. if self._RANK == 0:
  1119. synData = flattenlist(self.pc.py_allgather(syn_idx_pos))
  1120. # convert to structured array
  1121. dtype = [('gid_pre', 'i8'),
  1122. ('gid_post', 'i8'),
  1123. ('weight', 'f8'),
  1124. ('delay', 'f8'),
  1125. ('sec_pre', 'S64'),
  1126. ('sec_pre.x', 'f8'),
  1127. ('sec_post', 'S64'),
  1128. ('sec_post.x', 'f8'),
  1129. ('x', 'f8'),
  1130. ('y', 'f8'),
  1131. ('z', 'f8')]
  1132. synDataArray = np.empty((len(synData), ), dtype=dtype)
  1133. for i, (gid_pre, gid, weight, delay, secname, secx,chosen_sec, secpostx, x, y, z
  1134. ) in enumerate(synData):
  1135. synDataArray[i]['gid_pre'] = gid_pre
  1136. synDataArray[i]['gid_post'] = gid
  1137. synDataArray[i]['weight'] = weight
  1138. synDataArray[i]['delay'] = delay
  1139. synDataArray[i]['sec_pre'] = secname
  1140. synDataArray[i]['sec_pre.x'] = secx
  1141. synDataArray[i]['sec_post'] = chosen_sec
  1142. synDataArray[i]['sec_post.x'] = secpostx
  1143. synDataArray[i]['x'] = x
  1144. synDataArray[i]['y'] = y
  1145. synDataArray[i]['z'] = z
  1146. self.syn_data.append(synDataArray)
  1147. else:
  1148. _ = self.pc.py_allgather(syn_idx_pos)
  1149. return self.pc.py_broadcast([conncount, syncount], 0)
  1150. def enable_extracellular_stimulation(self, electrode, t_ext=None, n=1,
  1151. model='inf'):
  1152. """
  1153. Enable extracellular stimulation with NEURON's `extracellular`
  1154. mechanism. Extracellular potentials are computed from electrode
  1155. currents using the point-source approximation.
  1156. If ``model`` is ``'inf'`` (default), potentials are computed as
  1157. (:math:`r_i` is the position of a segment :math:`i`,
  1158. :math:`r_n` is the position of an electrode :math:`n`,
  1159. :math:`\\sigma` is the conductivity of the medium):
  1160. .. math::
  1161. V_e(r_i) = \\sum_n \\frac{I_n}{4 \\pi \\sigma |r_i - r_n|}
  1162. If ``model`` is ``'semi'``, the method of images is used:
  1163. .. math::
  1164. V_e(r_i) = \\sum_n \\frac{I_n}{2 \\pi \\sigma |r_i - r_n|}
  1165. Parameters
  1166. ----------
  1167. electrode: RecExtElectrode
  1168. Electrode object with stimulating currents
  1169. t_ext: np.ndarray or list
  1170. Time in ms corresponding to step changes in the provided currents.
  1171. If None, currents are assumed to have
  1172. the same time steps as the NEURON simulation.
  1173. n: int
  1174. Points per electrode for spatial averaging
  1175. model: str
  1176. ``'inf'`` or ``'semi'``. If ``'inf'`` the medium is assumed to be
  1177. infinite and homogeneous. If ``'semi'``, the method of
  1178. images is used.
  1179. Returns
  1180. -------
  1181. v_ext: dict of np.ndarrays
  1182. Computed extracellular potentials at cell mid points
  1183. for each cell of the network's populations. Formatted as
  1184. ``v_ext = {'pop1': np.ndarray[cell, cell_seg,t_ext]}``
  1185. """
  1186. v_ext = {}
  1187. for popname in self.populations.keys():
  1188. cells = self.populations[popname].cells
  1189. v_ext[popname] = np.zeros(
  1190. (len(cells), cells[0].totnsegs, len(t_ext)))
  1191. for id_cell, cell in enumerate(cells):
  1192. v_ext[popname][id_cell] = \
  1193. cell.enable_extracellular_stimulation(
  1194. electrode, t_ext, n, model)
  1195. return v_ext
  1196. def simulate(self, probes=None,
  1197. rec_imem=False, rec_vmem=False,
  1198. rec_ipas=False, rec_icap=False,
  1199. rec_isyn=False, rec_vmemsyn=False, rec_istim=False,
  1200. rec_pop_contributions=False,
  1201. rec_variables=[], variable_dt=False, atol=0.001,
  1202. to_memory=True, to_file=False,
  1203. file_name='OUTPUT.h5',
  1204. **kwargs):
  1205. """
  1206. This is the main function running the simulation of the network model.
  1207. Parameters
  1208. ----------
  1209. probes: list of :obj:, optional
  1210. None or list of LFPykit.RecExtElectrode like object instances that
  1211. each have a public method `get_transformation_matrix` returning
  1212. a matrix that linearly maps each segments' transmembrane
  1213. current to corresponding measurement as
  1214. .. math:: \\mathbf{P} = \\mathbf{M} \\mathbf{I}
  1215. rec_imem: bool
  1216. If true, segment membrane currents will be recorded
  1217. If no electrode argument is given, it is necessary to
  1218. set rec_imem=True in order to calculate LFP later on.
  1219. Units of (nA).
  1220. rec_vmem: bool
  1221. record segment membrane voltages (mV)
  1222. rec_ipas: bool
  1223. record passive segment membrane currents (nA)
  1224. rec_icap: bool
  1225. record capacitive segment membrane currents (nA)
  1226. rec_isyn: bool
  1227. record synaptic currents of from Synapse class (nA)
  1228. rec_vmemsyn: bool
  1229. record membrane voltage of segments with Synapse (mV)
  1230. rec_istim: bool
  1231. record currents of StimIntraElectrode (nA)
  1232. rec_pop_contributions: bool
  1233. If True, compute and return single-population contributions to
  1234. the extracellular potential during simulation time
  1235. rec_variables: list of str
  1236. variables to record, i.e arg=['cai', ]
  1237. variable_dt: boolean
  1238. use variable timestep in NEURON. Can not be combimed with `to_file`
  1239. atol: float
  1240. absolute tolerance used with NEURON variable timestep
  1241. to_memory: bool
  1242. Simulate to memory. Only valid with `probes=[<probe>, ...]`, which
  1243. store measurements to -> <probe>.data
  1244. to_file: bool
  1245. only valid with `probes=[<probe>, ...]`, saves measurement in
  1246. hdf5 file format.
  1247. file_name: str
  1248. If to_file is True, file which measurements will be
  1249. written to. The file format is HDF5, default is "OUTPUT.h5", put
  1250. in folder Network.OUTPUTPATH
  1251. **kwargs: keyword argument dict values passed along to function
  1252. `__run_simulation_with_probes()`, containing some or all of
  1253. the boolean flags: `use_ipas`, `use_icap`, `use_isyn`
  1254. (defaulting to `False`).
  1255. Returns
  1256. -------
  1257. events
  1258. Dictionary with keys `times` and `gids`, where values are
  1259. ndarrays with detected spikes and global neuron identifiers
  1260. Raises
  1261. ------
  1262. Exception
  1263. if `CVode().use_fast_imem()` method not found
  1264. AssertionError
  1265. if rec_pop_contributions==True and probes==None
  1266. """
  1267. # set up integrator, use the CVode().fast_imem method by default
  1268. # as it doesn't hurt sim speeds much if at all.
  1269. cvode = neuron.h.CVode()
  1270. try:
  1271. cvode.use_fast_imem(1)
  1272. except AttributeError:
  1273. raise Exception('neuron.h.CVode().use_fast_imem() not found. '
  1274. 'Please update NEURON to v.7.4 or newer')
  1275. # test some of the inputs
  1276. if probes is None:
  1277. assert rec_pop_contributions is False, \
  1278. 'rec_pop_contributions can not be True when probes is None'
  1279. if not variable_dt:
  1280. dt = self.dt
  1281. else:
  1282. dt = None
  1283. for name in self.population_names:
  1284. for cell in self.populations[name].cells:
  1285. cell._set_soma_volt_recorder(dt)
  1286. if rec_imem:
  1287. cell._set_imem_recorders(dt)
  1288. if rec_vmem:
  1289. cell._set_voltage_recorders(dt)
  1290. if rec_ipas:
  1291. cell._set_ipas_recorders(dt)
  1292. if rec_icap:
  1293. cell._set_icap_recorders(dt)
  1294. if len(rec_variables) > 0:
  1295. cell._set_variable_recorders(rec_variables)
  1296. # run fadvance until t >= tstop, and calculate LFP if asked for
  1297. if probes is None and not rec_pop_contributions and not to_file:
  1298. if not rec_imem:
  1299. if self.verbose:
  1300. print("rec_imem==False, not recording membrane currents!")
  1301. self.__run_simulation(cvode, variable_dt, atol)
  1302. else:
  1303. self.__run_simulation_with_probes(
  1304. cvode=cvode,
  1305. probes=probes,
  1306. variable_dt=variable_dt,
  1307. atol=atol,
  1308. to_memory=to_memory,
  1309. to_file=to_file,
  1310. file_name='tmp_output_RANK_{:03d}.h5',
  1311. rec_pop_contributions=rec_pop_contributions,
  1312. **kwargs)
  1313. for name in self.population_names:
  1314. for cell in self.populations[name].cells:
  1315. # somatic trace
  1316. cell.somav = np.array(cell.somav)
  1317. if rec_imem:
  1318. cell._calc_imem()
  1319. if rec_ipas:
  1320. cell._calc_ipas()
  1321. if rec_icap:
  1322. cell._calc_icap()
  1323. if rec_vmem:
  1324. cell._collect_vmem()
  1325. if rec_isyn:
  1326. cell._collect_isyn()
  1327. if rec_vmemsyn:
  1328. cell._collect_vsyn()
  1329. if rec_istim:
  1330. cell._collect_istim()
  1331. if len(rec_variables) > 0:
  1332. cell._collect_rec_variables(rec_variables)
  1333. if hasattr(cell, '_hoc_netstimlist'):
  1334. del cell._hoc_netstimlist
  1335. # Collect spike trains across all RANKs to RANK 0
  1336. for name in self.population_names:
  1337. population = self.populations[name]
  1338. population.spike_vectors = []
  1339. for i in range(len(population._hoc_spike_vectors)):
  1340. population.spike_vectors += \
  1341. [population._hoc_spike_vectors[i].as_numpy()]
  1342. # collect spike times to RANK 0
  1343. if self._RANK == 0:
  1344. times = []
  1345. gids = []
  1346. else:
  1347. times = None
  1348. gids = None
  1349. for i, name in enumerate(self.population_names):
  1350. times_send = [x for x in self.populations[name].spike_vectors]
  1351. gids_send = [x for x in self.populations[name].gids]
  1352. if self._RANK == 0:
  1353. times.append([])
  1354. gids.append([])
  1355. times[i] += flattenlist(self.pc.py_gather(times_send))
  1356. gids[i] += flattenlist(self.pc.py_gather(gids_send))
  1357. assert len(times[-1]) == len(gids[-1])
  1358. else:
  1359. _ = self.pc.py_gather(times_send)
  1360. _ = self.pc.py_gather(gids_send)
  1361. # create final output file, summing up single RANK output from
  1362. # temporary files
  1363. if to_file and probes is not None:
  1364. # op = MPI.SUM
  1365. fname = os.path.join(
  1366. self.OUTPUTPATH,
  1367. 'tmp_output_RANK_{:03d}.h5'.format(
  1368. self._RANK))
  1369. f0 = h5py.File(fname, 'r')
  1370. if self._RANK == 0:
  1371. f1 = h5py.File(os.path.join(self.OUTPUTPATH, file_name), 'w')
  1372. dtype = []
  1373. for key, value in f0[list(f0.keys())[0]].items():
  1374. dtype.append((str(key), float))
  1375. for grp in f0.keys():
  1376. if self._RANK == 0:
  1377. # get shape from the first dataset
  1378. # (they should all be equal):
  1379. for value in f0[grp].values():
  1380. shape = value.shape
  1381. continue
  1382. f1[grp] = np.zeros(shape, dtype=dtype)
  1383. for key, value in f0[grp].items():
  1384. recvbuf = neuron.h.Vector(
  1385. value[()].astype(float).flatten())
  1386. self.pc.allreduce(recvbuf, 1)
  1387. if self._RANK == 0:
  1388. f1[grp][key] = np.array(recvbuf).reshape(value.shape)
  1389. else:
  1390. recvbuf = None
  1391. f0.close()
  1392. if self._RANK == 0:
  1393. f1.close()
  1394. os.remove(fname)
  1395. if probes is not None:
  1396. if to_memory:
  1397. # communicate and sum up measurements on each probe before
  1398. # returing spike times and corresponding gids:
  1399. for probe in probes:
  1400. probe.data = ReduceStructArray(probe.data)
  1401. return dict(times=times, gids=gids)
  1402. def __create_network_dummycell(self):
  1403. """
  1404. set up parameters for a DummyCell object, allowing for computing
  1405. the sum of all single-cell LFPs at each timestep, essentially
  1406. creating one supercell with all segments of all cell objects
  1407. present on this RANK.
  1408. """
  1409. # compute the total number of segments per population on this RANK
  1410. nsegs = [[cell.totnsegs for cell in self.populations[name].cells]
  1411. for name in self.population_names]
  1412. for i, nseg in enumerate(nsegs):
  1413. if nseg == []:
  1414. nsegs[i] = [0]
  1415. for i, y in enumerate(nsegs):
  1416. nsegs[i] = np.sum(y)
  1417. nsegs = np.array(nsegs, dtype=int)
  1418. totnsegs = nsegs.sum()
  1419. x = np.empty((0, 2))
  1420. y = np.empty((0, 2))
  1421. z = np.empty((0, 2))
  1422. d = np.array([])
  1423. area = np.array([])
  1424. length = np.array([])
  1425. somainds = np.array([], dtype=int)
  1426. nseg = 0
  1427. for name in self.population_names:
  1428. for cell in self.populations[name].cells:
  1429. x = np.r_[x, cell.x]
  1430. y = np.r_[y, cell.y]
  1431. z = np.r_[z, cell.z]
  1432. d = np.r_[d, cell.d]
  1433. area = np.r_[area, cell.area]
  1434. length = np.r_[length, cell.length]
  1435. somainds = np.r_[somainds, cell.get_idx("soma") + nseg]
  1436. nseg += cell.totnsegs
  1437. # return number of segments per population and DummyCell object
  1438. return nsegs, DummyCell(totnsegs, x, y, z, d, area, length, somainds)
  1439. def __run_simulation(self, cvode, variable_dt=False, atol=0.001):
  1440. """
  1441. Running the actual simulation in NEURON, simulations in NEURON
  1442. are now interruptable.
  1443. Parameters
  1444. ----------
  1445. cvode: neuron.h.CVode() object
  1446. variable_dt: bool
  1447. switch for variable-timestep method
  1448. atol: float
  1449. absolute tolerance with CVode for variable time-step method
  1450. """
  1451. # set maximum integration step, it is necessary for communication of
  1452. # spikes across RANKs to occur.
  1453. self.pc.set_maxstep(10)
  1454. # time resolution
  1455. neuron.h.dt = self.dt
  1456. # needed for variable dt method
  1457. if variable_dt:
  1458. cvode.active(1)
  1459. cvode.atol(atol)
  1460. else:
  1461. cvode.active(0)
  1462. # initialize state
  1463. neuron.h.finitialize(self.v_init * units.mV)
  1464. # initialize current- and record
  1465. if cvode.active():
  1466. cvode.re_init()
  1467. else:
  1468. neuron.h.fcurrent()
  1469. neuron.h.frecord_init()
  1470. # Starting simulation at tstart
  1471. neuron.h.t = self.tstart
  1472. # only needed if LFPy.Synapse classes are used.
  1473. for name in self.population_names:
  1474. for cell in self.populations[name].cells:
  1475. cell._load_spikes()
  1476. # advance simulation until tstop
  1477. neuron.h.continuerun(self.tstop * units.ms)
  1478. def __run_simulation_with_probes(self, cvode,
  1479. probes=None,
  1480. variable_dt=False,
  1481. atol=0.001,
  1482. rtol=0.,
  1483. to_memory=True,
  1484. to_file=False,
  1485. file_name=None,
  1486. use_ipas=False, use_icap=False,
  1487. use_isyn=False,
  1488. rec_pop_contributions=False
  1489. ):
  1490. """
  1491. Running the actual simulation in NEURON with list of probes.
  1492. Each object in `probes` must have a public method
  1493. `get_transformation_matrix` which returns a linear mapping of
  1494. transmembrane currents to corresponding measurement.
  1495. Parameters
  1496. ----------
  1497. cvode: neuron.h.CVode() object
  1498. probes: list of :obj:, optional
  1499. None or list of LFPykit.RecExtElectrode like object instances that
  1500. each have a public method `get_transformation_matrix` returning
  1501. a matrix that linearly maps each segments' transmembrane
  1502. current to corresponding measurement as
  1503. .. math:: \\mathbf{P} = \\mathbf{M} \\mathbf{I}
  1504. variable_dt: bool
  1505. switch for variable-timestep method
  1506. atol: float
  1507. absolute tolerance with CVode for variable time-step method
  1508. rtol: float
  1509. relative tolerance with CVode for variable time-step method
  1510. to_memory: bool
  1511. Boolean flag for computing extracellular potentials,
  1512. default is True.
  1513. If True, the corresponding <probe>.data attribute will be set.
  1514. to_file: bool or None
  1515. Boolean flag for computing extracellular potentials to file
  1516. <OUTPUTPATH/file_name>, default is False. Raises an Exception if
  1517. `to_memory` is True.
  1518. file_name: formattable str
  1519. If to_file is True, file which extracellular potentials will be
  1520. written to. The file format is HDF5, default is
  1521. "output_RANK_{:03d}.h5". The output is written per RANK, and the
  1522. RANK # will be inserted into the corresponding file name.
  1523. use_ipas: bool
  1524. if True, compute the contribution to extracellular potentials
  1525. across the passive leak channels embedded in the cells membranes
  1526. summed over populations
  1527. use_icap: bool
  1528. if True, compute the contribution to extracellular potentials
  1529. across the membrane capacitance embedded in the cells membranes
  1530. summed over populations
  1531. use_isyn: bool
  1532. if True, compute the contribution to extracellular potentials
  1533. across the excitatory and inhibitory synapses embedded in the cells
  1534. membranes summed over populations
  1535. rec_pop_contributions: bool
  1536. if True, compute and return single-population contributions to the
  1537. extracellular potential during each time step of the simulation
  1538. Returns
  1539. -------
  1540. Raises
  1541. ------
  1542. Exception:
  1543. - `if to_memory == to_file == True`
  1544. - `if to_file == True and file_name is None`
  1545. - `if to_file == variable_dt == True`
  1546. - `if <probe>.cell is not None`
  1547. """
  1548. if to_memory and to_file:
  1549. raise Exception('to_memory and to_file can not both be True')
  1550. if to_file and file_name is None:
  1551. raise Exception
  1552. # create a dummycell object lumping together needed attributes
  1553. # for calculation of extracellular potentials etc. The population_nsegs
  1554. # array is used to slice indices such that single-population
  1555. # contributions to the potential can be calculated.
  1556. population_nsegs, network_dummycell = self.__create_network_dummycell()
  1557. # set cell attribute on each probe, assuming that each probe was
  1558. # instantiated with argument cell=None
  1559. for probe in probes:
  1560. if probe.cell is None:
  1561. probe.cell = network_dummycell
  1562. else:
  1563. raise Exception('{}.cell!=None'.format(probe.__class__))
  1564. # create list of transformation matrices; one for each probe
  1565. transforms = []
  1566. if probes is not None:
  1567. for probe in probes:
  1568. transforms.append(probe.get_transformation_matrix())
  1569. # reset probe.cell to None, as it is no longer needed
  1570. for probe in probes:
  1571. probe.cell = None
  1572. # set maximum integration step, it is necessary for communication of
  1573. # spikes across RANKs to occur.
  1574. # NOTE: Should this depend on the minimum delay in the network?
  1575. self.pc.set_maxstep(10)
  1576. # Initialize NEURON simulations of cell object
  1577. neuron.h.dt = self.dt
  1578. # needed for variable dt method
  1579. if variable_dt:
  1580. cvode.active(1)
  1581. cvode.atol(atol)
  1582. else:
  1583. cvode.active(0)
  1584. # initialize state
  1585. neuron.h.finitialize(self.v_init * units.mV)
  1586. # use fast calculation of transmembrane currents
  1587. cvode.use_fast_imem(1)
  1588. # initialize current- and record
  1589. if cvode.active():
  1590. cvode.re_init()
  1591. else:
  1592. neuron.h.fcurrent()
  1593. neuron.h.frecord_init()
  1594. # Starting simulation at tstart
  1595. neuron.h.t = self.tstart
  1596. # create list of cells across all populations to simplify loops
  1597. cells = []
  1598. for name in self.population_names:
  1599. cells += self.populations[name].cells
  1600. # load spike times from NetCon, only needed if LFPy.Synapse class
  1601. # is used
  1602. for cell in cells:
  1603. cell._load_spikes()
  1604. # define data type for structured arrays dependent on the boolean
  1605. # arguments
  1606. dtype = [('imem', float)]
  1607. if use_ipas:
  1608. dtype += [('ipas', float)]
  1609. if use_icap:
  1610. dtype += [('icap', float)]
  1611. if use_isyn:
  1612. dtype += [('isyn_e', float), ('isyn_i', float)]
  1613. if rec_pop_contributions:
  1614. dtype += list(zip(self.population_names,
  1615. [float] * len(self.population_names)))
  1616. # setup list of structured arrays for all extracellular potentials
  1617. # at each contact from different source terms and subpopulations
  1618. if to_memory:
  1619. for probe, M in zip(probes, transforms):
  1620. probe.data = np.zeros((M.shape[0],
  1621. int(self.tstop / self.dt) + 1),
  1622. dtype=dtype)
  1623. # signals for each probe will be stored here during simulations
  1624. if to_file:
  1625. # ensure right ending:
  1626. if file_name.split('.')[-1] != 'h5':
  1627. file_name += '.h5'
  1628. outputfile = h5py.File(
  1629. os.path.join(
  1630. self.OUTPUTPATH,
  1631. file_name.format(
  1632. self._RANK)),
  1633. 'w')
  1634. # define unique group names for each probe
  1635. names = []
  1636. for probe, M in zip(probes, transforms):
  1637. name = probe.__class__.__name__
  1638. i = 0
  1639. while True:
  1640. if name + '{}'.format(i) not in names:
  1641. names.append(name + '{}'.format(i))
  1642. break
  1643. i += 1
  1644. # create groups
  1645. for i, (name, probe, M) in enumerate(zip(names, probes,
  1646. transforms)):
  1647. # can't do it this way until h5py issue #740
  1648. # (https://github.com/h5py/h5py/issues/740) is fixed:
  1649. # outputfile['{}'.format(name)] = np.zeros((M.shape[0],
  1650. # int(network.tstop / network.dt) + 1), dtype=dtype)
  1651. probe.data = outputfile.create_group('{}'.format(name))
  1652. for key, val in dtype:
  1653. probe.data[key] = np.zeros((M.shape[0],
  1654. int(self.tstop / self.dt)
  1655. + 1),
  1656. dtype=val)
  1657. # temporary vector to store membrane currents at each timestep:
  1658. imem = np.zeros(network_dummycell.totnsegs, dtype=dtype)
  1659. def get_imem(imem):
  1660. '''helper function to gather currents across all cells
  1661. on this RANK'''
  1662. i = 0
  1663. totnsegs = 0
  1664. if use_isyn:
  1665. imem['isyn_e'] = 0. # must reset these for every iteration
  1666. imem['isyn_i'] = 0. # because we sum over synapses
  1667. for cell in cells:
  1668. for sec in cell.allseclist:
  1669. for seg in sec:
  1670. imem['imem'][i] = seg.i_membrane_
  1671. if use_ipas:
  1672. imem['ipas'][i] = seg.i_pas
  1673. if use_icap:
  1674. imem['icap'][i] = seg.i_cap
  1675. i += 1
  1676. if use_isyn:
  1677. for idx, syn in zip(cell.synidx, cell.netconsynapses):
  1678. if hasattr(syn, 'e') and syn.e > -50:
  1679. imem['isyn_e'][idx + totnsegs] += syn.i
  1680. else:
  1681. imem['isyn_i'][idx + totnsegs] += syn.i
  1682. totnsegs += cell.totnsegs
  1683. return imem
  1684. # run fadvance until time limit, and calculate LFPs for each timestep
  1685. tstep = 0
  1686. while neuron.h.t < self.tstop:
  1687. if neuron.h.t >= 0:
  1688. imem = get_imem(imem)
  1689. for j, (probe, M) in enumerate(zip(probes, transforms)):
  1690. probe.data['imem'][:, tstep] = M @ imem['imem']
  1691. if use_ipas:
  1692. probe.data['ipas'][:, tstep] = \
  1693. M @ (imem['ipas'] * network_dummycell.area * 1E-2)
  1694. if use_icap:
  1695. probe.data['icap'][:, tstep] = \
  1696. M @ (imem['icap'] * network_dummycell.area * 1E-2)
  1697. if use_isyn:
  1698. probe.data['isyn_e'][:, tstep] = M @ imem['isyn_e']
  1699. probe.data['isyn_i'][:, tstep] = M @ imem['isyn_i']
  1700. if rec_pop_contributions:
  1701. for j, (probe, M) in enumerate(zip(probes, transforms)):
  1702. k = 0 # counter
  1703. for nsegs, pop_name in zip(population_nsegs,
  1704. self.population_names):
  1705. cellinds = np.arange(k, k + nsegs)
  1706. probe.data[pop_name][:, tstep] = \
  1707. M[:, cellinds] @ imem['imem'][cellinds, ]
  1708. k += nsegs
  1709. tstep += 1
  1710. neuron.h.fadvance()
  1711. if neuron.h.t % 1000. == 0.:
  1712. if self._RANK == 0:
  1713. print('t = {} ms'.format(neuron.h.t))
  1714. try:
  1715. # calculate LFP after final fadvance(), skipped if IndexError is
  1716. # encountered
  1717. imem = get_imem(imem)
  1718. for j, (probe, M) in enumerate(zip(probes, transforms)):
  1719. probe.data['imem'][:, tstep] = M @ imem['imem']
  1720. if use_ipas:
  1721. probe.data['ipas'][:, tstep] = \
  1722. M @ (imem['ipas'] * network_dummycell.area * 1E-2)
  1723. if use_icap:
  1724. probe.data['icap'][:, tstep] = \
  1725. M @ (imem['icap'] * network_dummycell.area * 1E-2)
  1726. if use_isyn:
  1727. probe.data['isyn_e'][:, tstep] = M @ imem['isyn_e']
  1728. probe.data['isyn_i'][:, tstep] = M @ imem['isyn_i']
  1729. if rec_pop_contributions:
  1730. for j, (probe, M) in enumerate(zip(probes, transforms)):
  1731. k = 0 # counter
  1732. for nsegs, pop_name in zip(population_nsegs,
  1733. self.population_names):
  1734. cellinds = np.arange(k, k + nsegs)
  1735. probe.data[pop_name][:, tstep] = \
  1736. M[:, cellinds] @ imem['imem'][cellinds, ]
  1737. k += nsegs
  1738. except IndexError:
  1739. pass
  1740. if to_file:
  1741. outputfile.close()
  1742. def ReduceStructArray(sendbuf):
  1743. """
  1744. simplify MPI Reduce for structured ndarrays with floating point numbers
  1745. Parameters
  1746. ----------
  1747. sendbuf: structured ndarray
  1748. Array data to be reduced (default: summed)
  1749. Returns
  1750. -------
  1751. recvbuf: structured ndarray or None
  1752. Reduced array on RANK 0, None on all other RANKs
  1753. """
  1754. pc = neuron.h.ParallelContext()
  1755. RANK = pc.id()
  1756. if RANK == 0:
  1757. shape = sendbuf.shape
  1758. dtype_names = sendbuf.dtype.names
  1759. else:
  1760. shape = None
  1761. dtype_names = None
  1762. shape = pc.py_broadcast(shape, 0)
  1763. dtype_names = pc.py_broadcast(dtype_names, 0)
  1764. if RANK == 0:
  1765. reduced = np.zeros(shape,
  1766. dtype=list(zip(dtype_names,
  1767. ['f8' for i in range(len(dtype_names)
  1768. )])))
  1769. else:
  1770. reduced = None
  1771. for name in dtype_names:
  1772. recvbuf = neuron.h.Vector(sendbuf[name].flatten())
  1773. pc.allreduce(recvbuf, 1)
  1774. if RANK == 0:
  1775. reduced[name] = np.array(recvbuf).reshape(shape)
  1776. return reduced

network.py at commit bd3f89e, no license · at the source

Overview

Authors: Giulia Amos1, Vaiva Vasiliauskaitė1, Jens Duru1, Maria Leonor Azevedo Saramago1, Tim Schmid1, Alexandre Suter1, Ferran Cid Torren1, Joël Küchler1, Tobias Ruff1, János Vörös1, Katarina Vulić1
ORCID iDs: Katarina Vulić
  1. Laboratory of Biosensors and Bioelectronics (LBB), Institute for Biomedical Engineering, D-ITET, ETH Zurich, 8092 Zurich, Switzerland
Institutions: ETH Zurich (Switzerland); Institute for Biomedical Engineering (Switzerland)
Journal: iScience, volume 29, issue 5, article 115488
Dates: received 28 August 2025; accepted 23 March 2026; published online 1 April 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.isci.2026.115488 · PMID 42016309 · PMCID PMC13092622 · OpenAlex W7147267386
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism), cellular / molecular (subfield)
Methods: Statistics, Preprocessing, Evoked potentials, Connectivity, Graphs, Single-unit activity, calcium imaging, Machine learning
Keywords: Cellular physiology, Cellular neuroscience, Stem cells research
Topic: Photoreceptor and optogenetics research (Cellular and Molecular Neuroscience, Neuroscience), according to OpenAlex
Citations: cited by 1 paper (Europe PMC); 126 references in the paper

Abstract

Studying synaptic transmission is facilitated in experimental systems that isolate individual neuronal connections. We developed an integrated platform combining polydimethylsiloxane (PDMS) microstructures with high-density microelectrode arrays to isolate, record, and manipulate neuronal pairs from human induced pluripotent stem cell (hiPSC)-derived neurons. The system maintained hundreds of parallel neuronal pairs for over 100 days, demonstrating functional synapses through pharmacological validation. We coupled this platform with a biophysical Hodgkin-Huxley model and simulation-based inference to extract mechanistic parameters from the electrophysiological data. As a proof-of-concept application, we analyzed shifts in model parameter distributions following a stimulation protocol. The biophysical model revealed α-amino-3-hydroxy-5-methyl-4-isoxazole propionic acid (AMPA) and N-methyl-D-aspartate (NMDA) receptor-specific alterations after stimulation, providing quantitative insights into synaptic plasticity mechanisms. This integrated approach combines isolated hiPSC-derived synaptic pairs, stable parallel long-term recordings, and mechanistic modeling to enable systematic studies of human synaptic transmission.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 13 matches between paragraphs and lines of code.

altiki/TE_code

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 092f95c94506fcd434d509ce1d26c192b9ce1e49, 24 February 2026
Languages: Python (146), JavaScript (13), CUDA (7), Shell (2), C (1), MATLAB (1), Jupyter (1)
Size: 448 files, 171 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: environment (IDtxl/setup.py), tests, documentation, 1 notebook
Not found: README, license file, CITATION.cff, continuous integration
Tools: NumPy (116 files), SciPy (15 files), Matplotlib (11 files), NetworkX (4 files), h5py (2 files), pandas (2 files), statsmodels (2 files), Elephant (1 file), Neo (1 file), scikit-learn (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
171 files

altiki/cmos_toolbox_w_spike_sorter

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: 620fb40d397d6cc20691de2e745493717b0d2f63, 11 March 2026
Languages: Python (48), Jupyter (9)
Size: 108 files, 57 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: README, environment (requirements.txt), 9 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (44 files), Matplotlib (35 files), pandas (32 files), seaborn (14 files), SciPy (11 files), SpikeInterface (10 files), h5py (7 files), scikit-learn (4 files), OpenCV (2 files), Pillow (2 files), statannotations (2 files), statsmodels (1 file)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
58 files

gitlab.ethz.ch/vvasiliau/single_synapse

License: none: the authors keep all their rights
State: the link answers, verified on 28 September 2026
Evidence: files inventoried
Commit: bd3f89e4312eee6fc94dad8318784d66d4866eef, 13 June 2025
Languages: NEURON (33), Python (9), C (8), C++ (2), Jupyter (2)
Size: 213 files, 54 scripts
Software Heritage: not archived
Found in: “Data and code availability”
Holds: README, environment (environment.yaml), 2 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NEURON (35 files), NumPy (7 files), Matplotlib (6 files), PyTorch (6 files), SciPy (5 files), pandas (4 files), h5py (2 files), LFPy (2 files)
Availability: 1 check, the latest on 28 September 2026: the link answers
  • 28 September 2026: the link answers
55 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 3 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 282 scripts, each with its path and the digest of its content;
  • 13 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and code availability

Data and code are publicly available at the following locations. • Data are available through the ETH Research collection: https://doi.org/10.3929/ethz-c-000797128. • Original code has been deposited at GitHub and GitLab: information-theoretic pipeline: https://github.com/altiki/TE_code.git; recording, processing and spike plotting: https://github.com/altiki/cmos_toolbox_w_spike_sorter.git; the simulation code: https://gitlab.ethz.ch/vvasiliau/single_synapse/. • All other items can be requested from the lead contact.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 28 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 3 keywords, 2 funders, 114 references.

Cite

This paper

Amos, G., Vasiliauskaitė, V., Duru, J., Azevedo Saramago, M. L., Schmid, T., Suter, A., Torren, F. C., Küchler, J., Ruff, T., Vörös, J., & Vulić, K. (2026). An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs. iScience, 29(5), 115488. https://doi.org/10.1016/j.isci.2026.115488

BibTeX

@article{amos2026integrated,
author = {Amos, Giulia and Vasiliauskaitė, Vaiva and Duru, Jens and Azevedo Saramago, Maria Leonor and Schmid, Tim and Suter, Alexandre and Torren, Ferran Cid and Küchler, Joël and Ruff, Tobias and Vörös, János and Vulić, Katarina},
title = {{An integrated \&lt;i\&gt;i\&lt;/i\&gt; \&lt;i\&gt;n vitro\&lt;/i\&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs}},
journal = {iScience},
year = {2026},
month = apr,
volume = {29},
number = {5},
pages = {115488},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/j.isci.2026.115488},
url = {https://doi.org/10.1016/j.isci.2026.115488},
pmid = {42016309},
pmcid = {PMC13092622}
}

RIS

TY - JOUR
AU - Amos, Giulia
AU - Vasiliauskaitė, Vaiva
AU - Duru, Jens
AU - Azevedo Saramago, Maria Leonor
AU - Schmid, Tim
AU - Suter, Alexandre
AU - Torren, Ferran Cid
AU - Küchler, Joël
AU - Ruff, Tobias
AU - Vörös, János
AU - Vulić, Katarina
TI - An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/04/01
VL - 29
IS - 5
SP - 115488
SN - 2589-0042
PB - Elsevier
DO - 10.1016/j.isci.2026.115488
UR - https://doi.org/10.1016/j.isci.2026.115488
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.isci.2026.115488",
"type": "article-journal",
"title": "An integrated &lt;i&gt;i&lt;/i&gt; &lt;i&gt;n vitro&lt;/i&gt; platform and biophysical modeling approach for studying synaptic transmission in isolated neuronal pairs",
"container-title": "iScience",
"author": [
{
"family": "Amos",
"given": "Giulia"
},
{
"family": "Vasiliauskaitė",
"given": "Vaiva"
},
{
"family": "Duru",
"given": "Jens"
},
{
"family": "Azevedo Saramago",
"given": "Maria Leonor"
},
{
"family": "Schmid",
"given": "Tim"
},
{
"family": "Suter",
"given": "Alexandre"
},
{
"family": "Torren",
"given": "Ferran Cid"
},
{
"family": "Küchler",
"given": "Joël"
},
{
"family": "Ruff",
"given": "Tobias"
},
{
"family": "Vörös",
"given": "János"
},
{
"family": "Vulić",
"given": "Katarina"
}
],
"container-title-short": "iScience",
"volume": "29",
"issue": "5",
"page": "115488",
"DOI": "10.1016/j.isci.2026.115488",
"PMID": "42016309",
"PMCID": "PMC13092622",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.isci.2026.115488",
"language": "en",
"issued": {
"date-parts": [
[
2026,
4,
1
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1371/journal.pcbi.1014730 [code]
A unified model of short- and long-term plasticity: Effects on network connectivity and information capacity.
Journal: PLoS computational biology
In common: Elephant, Neo, NetworkX, 6 other tools, 7 references
[2] doi:10.1371/journal.pcbi.1014283 [code]
Spatial richness of neural magnetic fields.
Journal: PLoS computational biology
In common: LFPy, Elephant, Neo, 7 other tools, 2 references
[3] doi: [code]
Naturalistic behavior and self-generated neural activity predictive of self-correction
Journal: bioRxiv : the preprint server for biology
In common: Elephant, Neo, SpikeInterface, 10 other tools
[4] doi:10.7554/elife.110588 [code]
Opening the black box toward a modular approach to spike sorting.
Journal: eLife
In common: Neo, SpikeInterface, NetworkX, 9 other tools, 2 references
[5] doi:10.1016/j.patter.2026.101590 [code]
Density-based longitudinal neuron tracking in high-density electrophysiological recordings.
Journal: Patterns (New York, N.Y.)
In common: Neo, SpikeInterface, h5py, 9 other tools, 2 references
[6] doi:10.1371/journal.pcbi.1014752 [code]
Hierarchical feature binding in a spiking neural network model of the primate ventral visual pathway.
Journal: PLoS computational biology
In common: Elephant, Neo, OpenCV, 5 other tools, 3 references
[7] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: SpikeInterface, NetworkX, OpenCV, 10 other tools
[8] doi:10.1126/sciadv.aef0343 [code]
Learning induces activation-mechanism-dependent neural plasticity in an intracortical microstimulation task.
Journal: Science advances
In common: Neo, SpikeInterface, Pillow, 7 other tools, 1 reference
[9] doi:10.1038/s41467-026-72057-9 [code]
Sex-specific behavioral feedback modulates sensorimotor processing and drives flexible social behavior.
Journal: Nature communications
In common: statannotations, OpenCV, h5py, 9 other tools
[10] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: NetworkX, OpenCV, h5py, 9 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.