diff --git a/doc/whats_new.rst b/doc/whats_new.rst index a47e79725..c74ede50e 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -7,6 +7,22 @@ What's new? .. currentmodule:: hnn_core +.. _0.2: + +Current +------- + +Changelog +~~~~~~~~~ +- Store all connectivity information under :attr:`~hnn_core.Network.connectivity` before building the network, by `Nick Tolley`_ in `#276 `_ + +Bug +~~~ + +API +~~~ +- New API for defining cell-cell connections. Custom connections can be added with :func:`~hnn_core.Network.add_connection`, by `Nick Tolley`_ in `#276 `_ + .. _0.1: 0.1 diff --git a/examples/plot_connectivity.py b/examples/plot_connectivity.py new file mode 100644 index 000000000..104ed24e6 --- /dev/null +++ b/examples/plot_connectivity.py @@ -0,0 +1,96 @@ +""" +===================== +07. Plot Connectivity +===================== + +This example demonstrates how to modify the network connectivity. +""" + +# Author: Nick Tolley + +import os.path as op + +############################################################################### +# Let us import ``hnn_core``. + +import hnn_core +from hnn_core import read_params, Network, simulate_dipole + +hnn_core_root = op.dirname(hnn_core.__file__) + +############################################################################### +# Then we read the parameters file +params_fname = op.join(hnn_core_root, 'param', 'default.json') +params = read_params(params_fname) + +############################################################################### +# To explore how to modify network connectivity, we will start with simulating +# the evoked response from the +# :ref:`evoked example `, and +# explore how it changes with new connections. We first instantiate the +# network. (Note: Setting ``add_drives_from_params=True`` loads a set of +# predefined drives without the drives API shown previously). +net = Network(params, add_drives_from_params=True) + +############################################################################### +# Instantiating the network comes with a predefined set of connections that +# reflect the canonical neocortical microcircuit. ``net.connectivity`` +# is a list of dictionaries which detail every cell-cell, and drive-cell +# connection. +print(len(net.connectivity)) +print(net.connectivity[0]) +print(net.connectivity[-1]) + +############################################################################### +# Data recorded during simulations are stored under +# :class:`~hnn_core.Cell_Response`. To test multiple network structures, we can +# create a copy of the original network. The copied network is then simulated. +net_erp = net.copy() +dpl_erp = simulate_dipole(net_erp, n_trials=1) +net_erp.cell_response.plot_spikes_raster() + +############################################################################### +# We can modify the connectivity list to test the effect of different +# connectivity patterns. For example, we can remove all layer 2 inhibitory +# connections. +new_connectivity = [conn for conn in net.connectivity + if conn['src_type'] != 'L2_basket'] +net.connectivity = new_connectivity + +net_remove = net.copy() +dpl_remove = simulate_dipole(net_remove, n_trials=1) +net_remove.cell_response.plot_spikes_raster() + +############################################################################### +# That's a lot of spiking! Since basket cells are inhibitory, removing these +# connections increases network wide excitability. We can additionally add +# new connections using ``net.add_connection()``. Let's try connecting a +# single layer 2 basket cell, to every layer 2 pyramidal cell. We can utilize +# ``net.gid_ranges`` to help +# find the gids of interest. +print(net.gid_ranges) +src_gid = net.gid_ranges['L2_basket'][0] +target_gids = net.gid_ranges['L2_pyramidal'] +location, receptor = 'soma', 'gabaa' +weight, delay, lamtha = 1.0, 1.0, 70 +net.add_connection(src_gid, target_gids, location, receptor, + delay, weight, lamtha) + +net_add = net.copy() +dpl_add = simulate_dipole(net_add, n_trials=1) +net_add.cell_response.plot_spikes_raster() + +############################################################################### +# Adding more inhibitory connections did not completely restore the normal +# spiking. L2 basket and pyramidal cells rhythymically fire in the gamma +# range (30-80 Hz). As a final step, we can see how this change in spiking +# activity impacts the aggregate current dipole. +import matplotlib.pyplot as plt +from hnn_core.viz import plot_dipole +fig, axes = plt.subplots(2, 1, sharex=True, figsize=(6, 6), + constrained_layout=True) +dpls = [dpl_erp[0], dpl_remove[0], dpl_add[0]] +plot_dipole(dpls, ax=axes[0], layer='agg', show=False) +axes[0].legend(['Normal', 'No L2 Basket', 'Single L2 Basket']) +net_erp.cell_response.plot_spikes_hist( + ax=axes[1], spike_types=['evprox', 'evdist']) diff --git a/examples/plot_firing_pattern.py b/examples/plot_firing_pattern.py index 019bfcb4d..be81147f4 100644 --- a/examples/plot_firing_pattern.py +++ b/examples/plot_firing_pattern.py @@ -84,7 +84,7 @@ ############################################################################### # Here, we explain more details about the data structures and how they can # be used to better interpret the data. The cell IDs (gids) uniquely define -# neurons in the network and are stored in the :class:`~hnn_core.Network` +# neurons in the network and are stored in the :class:`~hnn_core.Network` # object as a dictionary gid_ranges = net.gid_ranges print(net.gid_ranges) diff --git a/hnn_core/network.py b/hnn_core/network.py index cc735d449..2fd0de3ad 100644 --- a/hnn_core/network.py +++ b/hnn_core/network.py @@ -14,7 +14,9 @@ from .feed import _drive_cell_event_times from .drives import _get_target_populations, _add_drives_from_params from .drives import _check_drive_parameter_values, _check_poisson_rates +from .params import _long_name from .viz import plot_spikes_hist, plot_spikes_raster, plot_cells +from .externals.mne import _validate_type, _check_option def read_spikes(fname, gid_ranges=None): @@ -184,6 +186,13 @@ class Network(object): index for trials, second for event time lists for each drive cell). external_biases : dict of dict (bias parameters for each cell type) The parameters of bias inputs to cell somata, e.g., tonic current clamp + connectivity : list of dict + List of dictionaries specifying each cell-cell and drive-cell + connection + threshold : float + Firing threshold of all cells. + delay : float + Synaptic delay in ms. """ def __init__(self, params, add_drives_from_params=False, @@ -221,6 +230,11 @@ def __init__(self, params, add_drives_from_params=False, self.external_drives = dict() self.external_biases = dict() + # network connectivity + self.connectivity = list() + self.threshold = self.params['threshold'] + self.delay = 1.0 + # contents of pos_dict determines all downstream inferences of # cell counts, real and artificial self.pos_dict = _create_cell_coords(n_pyr_x=self.params['N_pyr_x'], @@ -233,6 +247,8 @@ def __init__(self, params, add_drives_from_params=False, self.n_cells = sum(len(self.pos_dict[src]) for src in self.cellname_list) + self._set_default_connections() + if add_drives_from_params: _add_drives_from_params(self) @@ -549,6 +565,29 @@ def _attach_drive(self, name, drive, weights_ampa, weights_nmda, location, # Every time pos_dict is updated, gid_ranges must be updated too self._update_gid_ranges() + # Update connectivity_list + nc_dict = { + 'A_delay': self.delay, + 'threshold': self.threshold, + } + receptors = ['ampa', 'nmda'] + if drive['type'] == 'gaussian': + receptors = ['ampa'] + # conn-parameters are for each target cell type + for target_cell, drive_conn in drive['conn'].items(): + for receptor in receptors: + if len(drive_conn[receptor]) > 0: + nc_dict['lamtha'] = drive_conn[ + receptor]['lamtha'] + nc_dict['A_delay'] = drive_conn[ + receptor]['A_delay'] + nc_dict['A_weight'] = drive_conn[ + receptor]['A_weight'] + loc = drive_conn['location'] + self._all_to_all_connect( + drive['name'], target_cell, loc, receptor, + deepcopy(nc_dict), unique=drive['cell_specific']) + def _create_drive_conns(self, target_populations, weights_by_receptor, location, space_constant, synaptic_delays, cell_specific=True): @@ -764,6 +803,239 @@ def _get_src_type_and_pos(self, gid): return src_type, src_pos, src_type in self.cellname_list + # connections: + # this NODE is aware of its cells as targets + # for each syn, return list of source GIDs. + # for each item in the list, do a: + # nc = pc.gid_connect(source_gid, target_syn), weight,delay + # Both for synapses AND for external inputs + def _set_default_connections(self): + nc_dict = { + 'A_delay': self.delay, + 'threshold': self.threshold, + } + + # source of synapse is always at soma + + # layer2 Pyr -> layer2 Pyr + # layer5 Pyr -> layer5 Pyr + nc_dict['lamtha'] = 3. + loc = 'proximal' + for target_cell in ['L2Pyr', 'L5Pyr']: + for receptor in ['nmda', 'ampa']: + key = f'gbar_{target_cell}_{target_cell}_{receptor}' + nc_dict['A_weight'] = self.params[key] + self._all_to_all_connect( + target_cell, target_cell, loc, receptor, + nc_dict, allow_autapses=False) + + # layer2 Basket -> layer2 Pyr + src_cell = 'L2Basket' + target_cell = 'L2Pyr' + nc_dict['lamtha'] = 50. + loc = 'soma' + for receptor in ['gabaa', 'gabab']: + key = f'gbar_L2Basket_L2Pyr_{receptor}' + nc_dict['A_weight'] = self.params[key] + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + # layer5 Basket -> layer5 Pyr + src_cell = 'L5Basket' + target_cell = 'L5Pyr' + nc_dict['lamtha'] = 70. + loc = 'soma' + for receptor in ['gabaa', 'gabab']: + key = f'gbar_L5Basket_{target_cell}_{receptor}' + nc_dict['A_weight'] = self.params[key] + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + # layer2 Pyr -> layer5 Pyr + src_cell = 'L2Pyr' + nc_dict['lamtha'] = 3. + receptor = 'ampa' + for loc in ['proximal', 'distal']: + key = f'gbar_L2Pyr_{target_cell}' + nc_dict['A_weight'] = self.params[key] + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + # layer2 Basket -> layer5 Pyr + src_cell = 'L2Basket' + nc_dict['lamtha'] = 50. + key = f'gbar_L2Basket_{target_cell}' + nc_dict['A_weight'] = self.params[key] + loc = 'distal' + receptor = 'gabaa' + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + # xx -> layer2 Basket + src_cell = 'L2Pyr' + target_cell = 'L2Basket' + nc_dict['lamtha'] = 3. + key = f'gbar_L2Pyr_{target_cell}' + nc_dict['A_weight'] = self.params[key] + loc = 'soma' + receptor = 'ampa' + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + src_cell = 'L2Basket' + nc_dict['lamtha'] = 20. + key = f'gbar_L2Basket_{target_cell}' + nc_dict['A_weight'] = self.params[key] + loc = 'soma' + receptor = 'gabaa' + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + # xx -> layer5 Basket + src_cell = 'L5Basket' + target_cell = 'L5Basket' + nc_dict['lamtha'] = 20. + loc = 'soma' + receptor = 'gabaa' + key = f'gbar_L5Basket_{target_cell}' + nc_dict['A_weight'] = self.params[key] + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict, + allow_autapses=False) + + src_cell = 'L5Pyr' + nc_dict['lamtha'] = 3. + key = f'gbar_L5Pyr_{target_cell}' + nc_dict['A_weight'] = self.params[key] + loc = 'soma' + receptor = 'ampa' + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + src_cell = 'L2Pyr' + key = f'gbar_L2Pyr_{target_cell}' + nc_dict['A_weight'] = self.params[key] + loc = 'soma' + receptor = 'ampa' + self._all_to_all_connect( + src_cell, target_cell, loc, receptor, nc_dict) + + def _all_to_all_connect(self, src_cell, target_cell, + loc, receptor, nc_dict, + allow_autapses=True, unique=False): + """Generate connectivity list given lists of sources and targets. + + Parameters + ---------- + src_cell : str + Source cell type. + target_cell : str + Target cell type. + loc : str + Location of synapse on target cell. Must be + 'proximal', 'distal', or 'soma'. + receptor : str + Synaptic receptor of connection. Must be one of: + 'ampa', 'nmda', 'gabaa', or 'gabab'. + nc_dict : dict + The connection dictionary containing keys + A_delay, A_weight, lamtha, and threshold. + allow_autapses : bool + If True, allow connecting neuron to itself. + unique : bool + If True, each target cell gets one "unique" feed. + If False, all src_type cells are connected to + all target_type cells. + """ + src_gids = self.gid_ranges[_long_name(src_cell)] + target_gids = self.gid_ranges[_long_name(target_cell)] + + src_start = src_gids[0] # Necessary for unique feeds + for target_gid in target_gids: + if unique: + src_gids = [target_gid + src_start] + for src_gid in src_gids: + if not allow_autapses and src_gid == target_gid: + continue + self.add_connection( + src_gid, target_gid, loc, receptor, + nc_dict['A_weight'], nc_dict['A_delay'], nc_dict['lamtha']) + + def add_connection(self, src_gid, target_gid, loc, receptor, + weight, delay, lamtha): + """Appends connections to connectivity list + + Parameters + ---------- + src_gid : int + Integer identifying source cell ID. + target_gid : list | range | int + Integer or list of integers identifying target cell ID. + loc : str + Location of synapse on target cell. Must be + 'proximal', 'distal', or 'soma'. Note that inhibitory synapses + (receptor='gabaa' or 'gabab') of L2 pyramidal neurons are only + valid loc='soma'. + receptor : str + Synaptic receptor of connection. Must be one of: + 'ampa', 'nmda', 'gabaa', or 'gabab'. + weight : float + Synaptic weight on target cell. + delay : float + Synaptic delay in ms. + lamtha : float + Space constant. + """ + conn = dict() + threshold = self.threshold + + _validate_type(src_gid, int, 'src_gid', 'int') + _validate_type(target_gid, (list, range, int), 'target_gid', + 'list, range or int') + if isinstance(target_gid, int): + target_gid = [target_gid] + + # Ensure gids in range of Network.gid_ranges + assert np.sum([src_gid in gid_range for + gid_range in self.gid_ranges.values()]) == 1 + + conn['src_gid'] = src_gid + conn['src_type'] = self.gid_to_type(src_gid) + + # Ensure string inputs + _validate_type(loc, str, 'loc') + _validate_type(receptor, str, 'receptor') + + valid_loc = ['proximal', 'distal', 'soma'] + _check_option('loc', loc, valid_loc) + conn['loc'] = loc + + valid_receptor = ['ampa', 'nmda', 'gabaa', 'gabab'] + _check_option('receptor', receptor, valid_receptor) + conn['receptor'] = receptor + + # Create and validate nc_dict + conn['nc_dict'] = dict() + arg_names = ['delay', 'weight', 'lamtha', 'threshold'] + nc_dict_keys = ['A_delay', 'A_weight', 'lamtha', 'threshold'] + nc_conn_items = [delay, weight, lamtha, threshold] + for key, arg_name, item in zip(nc_dict_keys, arg_names, nc_conn_items): + _validate_type(item, (int, float), arg_name, 'int or float') + conn['nc_dict'][key] = item + + for t_gid in target_gid: + _validate_type(t_gid, int, 'target_gid', 'list or int') + conn['target_gid'] = t_gid + conn['target_type'] = self.gid_to_type(t_gid) + assert np.sum([t_gid in gid_range for + gid_range in self.gid_ranges.values()]) == 1 + + self.connectivity.append(deepcopy(conn)) + + def clear_connectivity(self): + """Remove all connections defined in Network.connectivity_list""" + self.connectivity = list() + def plot_cells(self, ax=None, show=True): """Plot the cells using Network.pos_dict. diff --git a/hnn_core/network_builder.py b/hnn_core/network_builder.py index a11386281..58fe1c10f 100644 --- a/hnn_core/network_builder.py +++ b/hnn_core/network_builder.py @@ -10,7 +10,7 @@ from .cell import _ArtificialCell from .pyramidal import L2Pyr, L5Pyr from .basket import L2Basket, L5Basket -from .params import _long_name +from .params import _long_name, _short_name # a few globals _PC = None @@ -303,7 +303,6 @@ def _build(self): record_isoma=record_isoma) self.state_init() - self._parnet_connect() # set to record spikes and somatic voltages self._spike_times = h.Vector() @@ -318,6 +317,7 @@ def _build(self): self._record_spikes() self.move_cells_to_pos() # position cells in 2D grid + self._connect_celltypes() if _get_rank() == 0: print('[Done]') @@ -415,169 +415,50 @@ def _create_cells_and_feeds(self, threshold, record_vsoma=False, _PC.cell(drive_cell.gid, drive_cell.nrn_netcon) self._drive_cells.append(drive_cell) - def _connect_celltypes(self, src_type, target_type, loc, - receptor, nc_dict, unique=False, - allow_autapses=True): - """Connect two cell types for a particular receptor. - - Parameters - ---------- - src_type : str - The source cell type - target_type : str - The target cell type - loc : str - If 'proximal' or 'distal', the corresponding - dendritic sections from Cell.sect_loc['proximal'] - or Cell.Sect_loc['distal'] are used - receptor : str - The receptor. - nc_dict : dict - The connection dictionary containing keys - A_delay, A_weight, lamtha, and threshold. - unique : bool - If True, each target cell gets one "unique" feed. - If False, all src_type cells are connected to - all target_type cells. - allow_autapses : bool - If True, allow connecting neuron to itself. - """ + def _connect_celltypes(self): + """Connect two cell types for a particular receptor.""" + net = self.net - connection_name = f'{src_type}_{target_type}_{receptor}' - if connection_name not in self.ncs: - self.ncs[connection_name] = list() - assert len(self.cells) == len(self._gid_list) - len(self._drive_cells) - # NB this assumes that REAL cells are first in the _gid_list - for gid_target, target_cell in zip(self._gid_list, self.cells): - is_target_gid = (gid_target in - self.net.gid_ranges[_long_name(target_type)]) - if _PC.gid_exists(gid_target) and is_target_gid: - gid_srcs = net.gid_ranges[_long_name(src_type)] - if unique: - gid_srcs = [gid_target + net.gid_ranges[src_type][0]] - for gid_src in gid_srcs: - - if not allow_autapses and gid_src == gid_target: - continue - - pos_idx = gid_src - net.gid_ranges[_long_name(src_type)][0] - # NB pos_dict for this drive must include ALL cell types! - nc_dict['pos_src'] = net.pos_dict[ - _long_name(src_type)][pos_idx] - - # get synapse locations - syn_keys = list() - if loc in ['proximal', 'distal']: - for sect in target_cell.sect_loc[loc]: - syn_keys.append(f'{sect}_{receptor}') - else: - syn_keys = [f'{loc}_{receptor}'] - - for syn_key in syn_keys: - nc = target_cell.parconnect_from_src( - gid_src, nc_dict, target_cell.synapses[syn_key]) - self.ncs[connection_name].append(nc) - - # connections: - # this NODE is aware of its cells as targets - # for each syn, return list of source GIDs. - # for each item in the list, do a: - # nc = pc.gid_connect(source_gid, target_syn), weight,delay - # Both for synapses AND for external inputs - def _parnet_connect(self): - params = self.net.params - nc_dict = { - 'A_delay': 1., - 'threshold': params['threshold'], - } + connectivity = self.net.connectivity - # source of synapse is always at soma - - # layer2 Pyr -> layer2 Pyr - # layer5 Pyr -> layer5 Pyr - nc_dict['lamtha'] = 3. - for target_cell in ['L2Pyr', 'L5Pyr']: - for receptor in ['nmda', 'ampa']: - key = f'gbar_{target_cell}_{target_cell}_{receptor}' - nc_dict['A_weight'] = params[key] - self._connect_celltypes(target_cell, target_cell, 'proximal', - receptor, nc_dict, - allow_autapses=False) - - # layer2 Basket -> layer2 Pyr - target_cell = 'L2Pyr' - nc_dict['lamtha'] = 50. - for receptor in ['gabaa', 'gabab']: - nc_dict['A_weight'] = params[f'gbar_L2Basket_L2Pyr_{receptor}'] - self._connect_celltypes('L2Basket', target_cell, 'soma', receptor, - nc_dict) - - # layer5 Basket -> layer5 Pyr - target_cell = 'L5Pyr' - nc_dict['lamtha'] = 70. - for receptor in ['gabaa', 'gabab']: - key = f'gbar_L5Basket_{target_cell}_{receptor}' - nc_dict['A_weight'] = params[key] - self._connect_celltypes('L5Basket', target_cell, 'soma', receptor, - nc_dict) - - # layer2 Pyr -> layer5 Pyr - nc_dict['lamtha'] = 3. - for loc in ['proximal', 'distal']: - nc_dict['A_weight'] = params[f'gbar_L2Pyr_{target_cell}'] - self._connect_celltypes('L2Pyr', target_cell, loc, 'ampa', - nc_dict) - # layer2 Basket -> layer5 Pyr - nc_dict['lamtha'] = 50. - nc_dict['A_weight'] = params[f'gbar_L2Basket_{target_cell}'] - self._connect_celltypes('L2Basket', target_cell, 'distal', 'gabaa', - nc_dict) - - # xx -> layer2 Basket - target_cell = 'L2Basket' - nc_dict['lamtha'] = 3. - nc_dict['A_weight'] = params[f'gbar_L2Pyr_{target_cell}'] - self._connect_celltypes('L2Pyr', target_cell, 'soma', 'ampa', - nc_dict) - nc_dict['lamtha'] = 20. - nc_dict['A_weight'] = params[f'gbar_L2Basket_{target_cell}'] - self._connect_celltypes('L2Basket', target_cell, 'soma', 'gabaa', - nc_dict) - - # xx -> layer5 Basket - target_cell = 'L5Basket' - nc_dict['lamtha'] = 20. - nc_dict['A_weight'] = params[f'gbar_L5Basket_{target_cell}'] - self._connect_celltypes('L5Basket', target_cell, 'soma', 'gabaa', - nc_dict, allow_autapses=False) - nc_dict['lamtha'] = 3. - nc_dict['A_weight'] = params[f'gbar_L5Pyr_{target_cell}'] - self._connect_celltypes('L5Pyr', target_cell, 'soma', 'ampa', - nc_dict) - nc_dict['A_weight'] = params[f'gbar_L2Pyr_{target_cell}'] - self._connect_celltypes('L2Pyr', target_cell, 'soma', 'ampa', - nc_dict) - - # loop over _all_ drives, _connect_celltypes picks ones on this rank - for drive in self.net.external_drives.values(): + assert len(self.cells) == len(self._gid_list) - len(self._drive_cells) + # Gather indeces of targets on current node + connectivity = [conn for conn in connectivity if + _PC.gid_exists(conn['target_gid'])] + target_gids = [conn['target_gid'] for conn in connectivity] + target_filter = {} + for idx in range(len(self.cells)): + gid = self._gid_list[idx] + if gid in target_gids: + target_filter[gid] = idx + + for conn in connectivity: + src_type, src_gid = conn['src_type'], conn['src_gid'] + target_type, target_gid = conn['target_type'], conn['target_gid'] + loc, receptor = conn['loc'], conn['receptor'] + nc_dict = conn['nc_dict'] + target_cell = self.cells[target_filter[target_gid]] + connection_name = f'{_short_name(src_type)}_'\ + f'{_short_name(target_type)}_{receptor}' + if connection_name not in self.ncs: + self.ncs[connection_name] = list() + pos_idx = src_gid - net.gid_ranges[_long_name(src_type)][0] + # NB pos_dict for this drive must include ALL cell types! + nc_dict['pos_src'] = net.pos_dict[ + _long_name(src_type)][pos_idx] + + # get synapse locations + syn_keys = list() + if loc in ['proximal', 'distal']: + for sect in target_cell.sect_loc[loc]: + syn_keys.append(f'{sect}_{receptor}') + else: + syn_keys = [f'{loc}_{receptor}'] - receptors = ['ampa', 'nmda'] - if drive['type'] == 'gaussian': - receptors = ['ampa'] - # conn-parameters are for each target cell type - for target_cell_type, drive_conn in drive['conn'].items(): - for receptor in receptors: - if len(drive_conn[receptor]) > 0: - nc_dict['lamtha'] = drive_conn[ - receptor]['lamtha'] - nc_dict['A_delay'] = drive_conn[ - receptor]['A_delay'] - nc_dict['A_weight'] = drive_conn[ - receptor]['A_weight'] - self._connect_celltypes( - drive['name'], target_cell_type, - drive_conn['location'], receptor, nc_dict, - unique=drive['cell_specific']) + for syn_key in syn_keys: + nc = target_cell.parconnect_from_src( + src_gid, nc_dict, target_cell.synapses[syn_key]) + self.ncs[connection_name].append(nc) # setup spike recording for this node def _record_spikes(self): diff --git a/hnn_core/tests/test_network.py b/hnn_core/tests/test_network.py index 587a60a16..39045f80f 100644 --- a/hnn_core/tests/test_network.py +++ b/hnn_core/tests/test_network.py @@ -146,22 +146,72 @@ def test_network(): assert nc.threshold == params['threshold'] # create a new connection between cell types + net = Network(deepcopy(params), add_drives_from_params=True) nc_dict = {'A_delay': 1, 'A_weight': 1e-5, 'lamtha': 20, 'threshold': 0.5} - network_builder._connect_celltypes( - 'bursty1', 'L5Basket', 'soma', 'gabaa', nc_dict, - unique=False) + net._all_to_all_connect('bursty1', 'L5_basket', + 'soma', 'gabaa', nc_dict, unique=False) + network_builder = NetworkBuilder(net) assert 'bursty1_L5Basket_gabaa' in network_builder.ncs n_conn = len(net.gid_ranges['bursty1']) * len(net.gid_ranges['L5_basket']) assert len(network_builder.ncs['bursty1_L5Basket_gabaa']) == n_conn # try unique=True - network_builder._connect_celltypes( - 'extgauss', 'L5Basket', 'soma', 'gabaa', nc_dict, - unique=True) + net = Network(deepcopy(params), add_drives_from_params=True) + net._all_to_all_connect('extgauss', 'L5_basket', + 'soma', 'gabaa', nc_dict, unique=True) + network_builder = NetworkBuilder(net) n_conn = len(net.gid_ranges['L5_basket']) assert len(network_builder.ncs['extgauss_L5Basket_gabaa']) == n_conn + # Test inputs for connectivity API + net = Network(deepcopy(params), add_drives_from_params=True) + n_conn = len(network_builder.ncs['L2Basket_L2Pyr_gabaa']) + kwargs_default = dict(src_gid=0, target_gid=35, + loc='soma', receptor='gabaa', + weight=5e-4, delay=1.0, lamtha=3.0) + net.add_connection(**kwargs_default) # smoke test + network_builder = NetworkBuilder(net) + assert len(network_builder.ncs['L2Basket_L2Pyr_gabaa']) == n_conn + 1 + nc = network_builder.ncs['L2Basket_L2Pyr_gabaa'][-1] + assert nc.weight[0] == kwargs_default['weight'] + + kwargs = kwargs_default.copy() + kwargs['target_gid'] = [35, 36] + net.add_connection(**kwargs) + + kwargs_bad = dict(src_gid=1.0, target_gid=1.0, loc=1.0, receptor=1.0, + weight='1.0', delay='1.0', lamtha='1.0') + for arg in kwargs_bad.keys(): + match = (f'{arg} must be an instance of') + with pytest.raises(TypeError, match=match): + kwargs = kwargs_default.copy() + kwargs[arg] = kwargs_bad[arg] + net.add_connection(**kwargs) + + match = 'target_gid must be an instance of' + with pytest.raises(TypeError, match=match): + kwargs = kwargs_default.copy() + kwargs['target_gid'] = [35, '36'] + net.add_connection(**kwargs) + + for arg in ['src_gid', 'target_gid']: + with pytest.raises(AssertionError): + kwargs = kwargs_default.copy() + kwargs[arg] = -1 + net.add_connection(**kwargs) + + for arg in ['loc', 'receptor']: + string_arg = 'invalid_string' + match = f"Invalid value for the '{arg}' parameter" + with pytest.raises(ValueError, match=match): + kwargs = kwargs_default.copy() + kwargs[arg] = string_arg + net.add_connection(**kwargs) + + net.clear_connectivity() + assert len(net.connectivity) == 0 + def test_tonic_biases(): """Test tonic biases."""