Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[MRG] refactoring of external feed class #108

Merged
merged 4 commits into from Jul 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
36 changes: 36 additions & 0 deletions hnn_core/cell.py
Expand Up @@ -14,6 +14,42 @@
# Units for gbar: S/cm^2


class _ArtificialCell:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need unit tests for the new object!

"""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.

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.
"""
def __init__(self, event_times, threshold):
# 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


class _Cell(object):
"""Create a cell object.

Expand Down
55 changes: 18 additions & 37 deletions hnn_core/feed.py
Expand Up @@ -5,7 +5,6 @@
# Christopher Bailey <bailey.cj@gmail.com>

import numpy as np
from neuron import h


class ExtFeed(object):
Expand Down Expand Up @@ -39,36 +38,34 @@ class ExtFeed(object):

Attributes
----------
nrn_eventvec : instance of NEURON Vector
A vector of event times
nrn_vecstim : instance of NEURON VecStim
A VecStim is an artificial spiking cell that generates events at
times that are specified in a (NEURON) Vector (see vecevent.mod)
event_times : list
A list of event times
feed_type : str
The feed type corresponding to the given gid (e.g., 'extpois',
'extgauss', 'common', 'evprox', 'evdist')
params : dict
Parameters of the given feed type
seed : int
The seed
gid : int
The cell ID
"""

def __init__(self, feed_type, target_cell_type, params, gid):
# VecStim setup
self.nrn_eventvec = h.Vector()
self.nrn_vecstim = h.VecStim()
self.params = params
# used to determine cell type-specific parameters for
# (not used for 'common', such as for rhythmic alpha/beta input)
self.cell_type = target_cell_type
self.feed_type = feed_type
self.gid = gid
self.set_prng() # sets seeds for random num generator
# sets event times into self.nrn_eventvec (Vector)
# and plays into self.nrn_vecstim (VecStim)
self.event_times = list()
self.set_event_times()

def __repr__(self):
class_name = self.__class__.__name__
repr_str = "<%s of type '%s' " % (class_name, self.feed_type)
repr_str += 'with %d events ' % len(self.nrn_eventvec)
repr_str += 'with %d events ' % len(self.event_times)
repr_str += '| seed %d, gid %d>' % (self.seed, self.gid)
return repr_str

Expand Down Expand Up @@ -108,7 +105,7 @@ def set_event_times(self, inc_evinput=0.0):
elif len(matches) > 1:
raise ValueError('Ambiguous external feed: %s' % self.feed_type)

# Each of these methods creates self.nrn_eventvec for playback
# Each of these methods creates self.event_times
# Return values not checked: False if all weights for given feed type
# are zero. Designed to be silent so that zeroing input weights
# effectively disables each.
Expand All @@ -120,8 +117,6 @@ def set_event_times(self, inc_evinput=0.0):
self._create_extgauss()
elif self.feed_type == 'common':
self._create_common_input()
# load eventvec into VecStim object
self.nrn_vecstim.play(self.nrn_eventvec)

# based on cdf for exp wait time distribution from unif [0, 1)
# returns in ms based on lamtha in Hz
Expand All @@ -139,9 +134,9 @@ def _create_extpois(self):
lamtha = self.params[self.cell_type][3] # ind 3 is frequency (lamtha)
# values MUST be sorted for VecStim()!
# start the initial value
val_pois = np.array([])
if lamtha > 0.:
t_gen = t0 + self._t_wait(lamtha)
val_pois = np.array([])
if t_gen < T:
np.append(val_pois, t_gen)
# vals are guaranteed to be monotonically increasing, no need to
Expand All @@ -151,19 +146,17 @@ def _create_extpois(self):
t_gen += self._t_wait(lamtha)
if t_gen < T:
val_pois = np.append(val_pois, t_gen)
else:
val_pois = np.array([])
# checks the distribution stats
# if len(val_pois):
# xdiff = np.diff(val_pois/1000)
# print(lamtha, np.mean(xdiff), np.var(xdiff), 1/lamtha**2)
# Convert array into nrn vector
# if len(val_pois)>0: print('val_pois:',val_pois)
self.nrn_eventvec.from_python(val_pois)
return self.nrn_eventvec.size() > 0
self.event_times = val_pois.tolist()

# mu and sigma vals come from p
def _create_evoked(self, inc=0.0):
val_evoked = np.array([])
if self.cell_type in self.params.keys():
# assign the params
mu = self.params['t0'] + inc
Expand All @@ -178,11 +171,7 @@ def _create_evoked(self, inc=0.0):
val_evoked = val_evoked[val_evoked > 0]
# vals must be sorted
val_evoked.sort()
self.nrn_eventvec.from_python(val_evoked)
else:
# return an empty eventvec list
self.nrn_eventvec.from_python([])
return self.nrn_eventvec.size() > 0
self.event_times = val_evoked.tolist()

def _create_extgauss(self):
# assign the params
Expand All @@ -202,8 +191,7 @@ def _create_extgauss(self):
val_gauss.sort()
# if len(val_gauss)>0: print('val_gauss:',val_gauss)
# Convert array into nrn vector
self.nrn_eventvec.from_python(val_gauss)
return self.nrn_eventvec.size() > 0
self.event_times = val_gauss.tolist()

def _create_common_input(self):
"""Creates the common ongoing external inputs.
Expand Down Expand Up @@ -242,7 +230,7 @@ def _create_common_input(self):
events_per_cycle = 2
# If frequency is 0, create empty vector if input times
if not f_input:
t_input = []
t_input = np.array([])
elif distribution == 'normal':
# array of mean stimulus times, starts at t0
isi_array = np.arange(t0, self.params['tstop'], 1000. / f_input)
Expand Down Expand Up @@ -286,12 +274,5 @@ def _create_common_input(self):
else:
print("Indicated distribution not recognized. "
"Not making any common feeds.")
t_input = []
# Convert array into nrn vector
self.nrn_eventvec.from_python(t_input)
return self.nrn_eventvec.size() > 0

def connect_to_target(self, threshold):
nc = h.NetCon(self.nrn_vecstim, None) # why is target always None??
nc.threshold = threshold
return nc
t_input = np.array([])
self.event_times = t_input.tolist()
1 change: 0 additions & 1 deletion hnn_core/network.py
Expand Up @@ -135,7 +135,6 @@ def __init__(self, params):
# assign gid to hosts, creates list of gids for this node in _gid_list
# _gid_list length is number of cells assigned to this id()
self._gid_list = []

self.trial_idx = 0

def __repr__(self):
Expand Down
48 changes: 21 additions & 27 deletions hnn_core/neuron.py
Expand Up @@ -8,6 +8,7 @@
from neuron import h

from .feed import ExtFeed
from .cell import _ArtificialCell
from .pyramidal import L2Pyr, L5Pyr
from .basket import L2Basket, L5Basket

Expand Down Expand Up @@ -249,12 +250,10 @@ def __init__(self, net):
# create cells (and create self.origin in create_cells_pyr())
self.cells = []

self.common_feeds = []
# external unique input list dictionary
self.unique_feeds = dict.fromkeys(self.net.p_unique)
# initialize the lists in the dict
for key in self.unique_feeds.keys():
self.unique_feeds[key] = []
# artificial cells must be appended to a list in order to preserve
# the NEURON hoc objects and the corresonding python references
# initialized by _ArtificialCell()
self._feed_cells = []
self._build()

def _build(self):
Expand Down Expand Up @@ -380,16 +379,14 @@ def _create_all_spike_sources(self):

# new ExtFeed: target cell type irrelevant (None) since input
# timing will be identical for all cells
# XXX common_feeds is a list of dict
self.common_feeds.append(
ExtFeed(feed_type=src_type,
target_cell_type=None,
params=self.net.p_common[p_ind],
gid=gid))

# create the cell and artificial NetCon
_PC.cell(gid, self.common_feeds[-1].connect_to_target(
self.net.params['threshold']))
common_feed = ExtFeed(feed_type=src_type,
target_cell_type=None,
params=self.net.p_common[p_ind],
gid=gid)
self._feed_cells.append(
_ArtificialCell(common_feed.event_times,
self.net.params['threshold']))
_PC.cell(gid, self._feed_cells[-1].nrn_netcon)

# external inputs can also be Poisson- or Gaussian-
# distributed, or 'evoked' inputs (proximal or distal)
Expand All @@ -400,17 +397,14 @@ def _create_all_spike_sources(self):

# new ExtFeed, where now both feed type and target cell type
# specified because these feeds have cell-specific parameters
# XXX unique_feeds is a dict of dict
self.unique_feeds[src_type].append(
ExtFeed(feed_type=src_type,
target_cell_type=target_cell_type,
params=self.net.p_unique[src_type],
gid=gid))
_PC.cell(
gid,
self.unique_feeds[src_type]
[-1].connect_to_target(
self.net.params['threshold']))
unique_feed = ExtFeed(feed_type=src_type,
target_cell_type=target_cell_type,
params=self.net.p_unique[src_type],
gid=gid)
self._feed_cells.append(
_ArtificialCell(unique_feed.event_times,
self.net.params['threshold']))
_PC.cell(gid, self._feed_cells[-1].nrn_netcon)
else:
raise ValueError('No parameters specified for external feed '
'type: %s' % src_type)
Expand Down
19 changes: 17 additions & 2 deletions hnn_core/tests/test_cell.py
Expand Up @@ -4,6 +4,7 @@
import hnn_core
from hnn_core import read_params, Network
from hnn_core.neuron import NeuronNetwork
from hnn_core.cell import _ArtificialCell

matplotlib.use('agg')

Expand All @@ -15,5 +16,19 @@ def test_cell():
params = read_params(params_fname)

net = Network(params)
with NeuronNetwork(net) as neuron_network:
neuron_network.cells[0].plot_voltage()
with NeuronNetwork(net) as neuron_net:
neuron_net.cells[0].plot_voltage()


def test_artificial_cell():
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring is missing. It's displayed during test

"""Test artificial cell object."""
event_times = [1, 2, 3]
threshold = 0.0
artificial_cell = _ArtificialCell(event_times, threshold)
assert artificial_cell.nrn_eventvec.to_python() == event_times
# the h.VecStim() object defined in vecevent.mod should contain a 'play()'
# method
assert hasattr(artificial_cell.nrn_vecstim, 'play')
# the h.Netcon() instance should reference the h.VecStim() instance
assert artificial_cell.nrn_netcon.pre() == artificial_cell.nrn_vecstim
assert artificial_cell.nrn_netcon.threshold == threshold
44 changes: 8 additions & 36 deletions hnn_core/tests/test_feed.py
@@ -1,13 +1,9 @@
# Authors: Mainak Jas <mainakjas@gmail.com>
# Christopher Bailey <bailey.cj@gmail.com>

from copy import deepcopy
import os.path as op
import pytest

import hnn_core
from hnn_core import read_params, Network, Params
from hnn_core.neuron import NeuronNetwork
from hnn_core import Params
from hnn_core.feed import ExtFeed
from hnn_core.params import create_pext

Expand All @@ -33,44 +29,20 @@ def test_extfeed():
params=p_unique[feed_type],
gid=0)
print(feed) # test repr

# XXX but 'common' (rhythmic) feeds are not
for ii in range(len(p_common)): # len == 0 for def. params
feed = ExtFeed(feed_type='common',
target_cell_type=None,
params=p_common[ii],
gid=0)
print(feed) # test repr


def test_external_common_feeds():
"""Test external common feeds to proximal and distal dendrites."""
hnn_core_root = op.join(op.dirname(hnn_core.__file__), '..')
params_fname = op.join(hnn_core_root, 'param', 'default.json')
params = read_params(params_fname)

# default parameters have no common inputs (distal or proximal),
params.update({'input_dist_A_weight_L2Pyr_ampa': 5.4e-5,
'input_dist_A_weight_L5Pyr_ampa': 5.4e-5,
't0_input_dist': 50,
'input_prox_A_weight_L2Pyr_ampa': 5.4e-5,
'input_prox_A_weight_L5Pyr_ampa': 5.4e-5,
't0_input_prox': 50})

net = Network(deepcopy(params))
neuron_network = NeuronNetwork(net)
assert len(neuron_network.common_feeds) == 2 # (distal & proximal)
for ei in neuron_network.common_feeds:
assert ei.feed_type == 'common'
assert ei.cell_type is None # artificial cell
assert hasattr(ei, 'nrn_eventvec')
assert hasattr(ei, 'nrn_vecstim')
assert ei.nrn_eventvec.hname().startswith('Vector')
assert hasattr(ei.nrn_vecstim, 'play')
# parameters should lead to > 0 input spikes
assert len(ei.nrn_eventvec.as_numpy()) > 0

assert feed.feed_type == 'common'
assert feed.cell_type is None # artificial cell
# parameters should lead to 0 input spikes for default params
assert len(feed.event_times) == 0
# check that ei.p_ext matches params
loc = ei.params['loc'][:4] # loc=prox or dist
loc = feed.params['loc'][:4] # loc=prox or dist
for layer in ['L2', 'L5']:
key = 'input_{}_A_weight_{}Pyr_ampa'.format(loc, layer)
assert ei.params[layer + 'Pyr_ampa'][0] == params[key]
assert feed.params[layer + 'Pyr_ampa'][0] == params[key]