def _get_edges(self, source_node=None, target_node=None): ''' Called by Graph.get_edges if source_node and target_node are not both integers. ''' g = self._graph edges = None if source_node is None: if target_node is None: edges = g.es elif is_integer(target_node): edges = g.es.select(_target_eq=target_node) else: edges = g.es.select(_target_in=target_node) elif is_integer(source_node): if target_node is None: edges = g.es.select(_source_eq=source_node) else: edges = g.es.select(_source_eq=source_node, _target_in=target_node) else: if target_node is None: edges = g.es.select(_source_in=source_node) elif is_integer(target_node): edges = g.es.select(_source_in=source_node, _target_eq=target_node) else: edges = g.es.select(_source_in=source_node, _target_in=target_node) return [e.tuple for e in edges]
def _get_edges(self, source_node=None, target_node=None): ''' Called by Graph.get_edges if source_node and target_node are not both integers. ''' g = self._graph if source_node is not None: source_node = \ {source_node} if is_integer(source_node) else set(source_node) # source only if target_node is None: if g.is_directed(): return [e for e in g._unique if e[0] in source_node] return [ e for e in g._unique if e[0] in source_node or e[1] in source_node ] # source and target target_node = \ {target_node} if is_integer(target_node) else set(target_node) if g.is_directed(): return [ e for e in g._unique if e[0] in source_node and e[1] in target_node ] return [ e for e in g._unique if e[0] in source_node and e[1] in target_node or e[1] in source_node and e[0] in target_node ] if target_node is None: # return all edges return list(g._unique) # target only target_node = \ {target_node} if is_integer(target_node) else set(target_node) if g.is_directed(): return [e for e in g._unique if e[1] in target_node] return [ e for e in g._unique if e[0] in target_node or e[1] in target_node ]
def add_to_group(self, group_name, ids): ''' Add neurons to a specific group. Parameters ---------- group_name : str or int Name or index of the group. ids : list or 1D-array Neuron ids. ''' idx = None if is_integer(group_name): assert 0 <= group_name < len(self), "Group index does not exist." idx = group_name else: idx = list(self.keys()).index(group_name) if ids: self[group_name].ids += list(ids) # update number of neurons max_id = np.max(ids) _update_max_id_and_size(self, max_id) self._neuron_group[np.array(ids)] = idx if -1 in list(self._neuron_group): self._is_valid = False else: self._is_valid = True
def edge_id(self, edge): ''' Return the ID a given edge or a list of edges in the graph. Raises an error if the edge is not in the graph or if one of the vertices in the edge is nonexistent. Parameters ---------- edge : 2-tuple or array of edges Edge descriptor (source, target). Returns ------- index : int or array of ints Index of the given `edge`. ''' g = self._graph if nonstring_container(edge) and len(edge): if is_integer(edge[0]): return g.edge_index[g.edge(*edge)] elif nonstring_container(edge[0]): Edge = g.edge return [g.edge_index[Edge(*e)] for e in edge] raise AttributeError("`edge` must be either a 2-tuple of ints or " "an array of 2-tuples of ints.")
def binning(x, bins='bayes', log=False): """ Binning function providing automatic binning using Bayesian blocks in addition to standard linear and logarithmic uniform bins. .. versionadded:: 0.7 Parameters ---------- x : array-like Array of data to be histogrammed bins : int, list or 'auto', optional (default: 'bayes') If `bins` is 'bayes', in use bayesian blocks for dynamic bin widths; if it is an int, the interval will be separated into log : bool, optional (default: False) Whether the bins should be evenly spaced on a logarithmic scale. """ x = np.asarray(x) new_bins = None if bins == 'bayes': return bayesian_blocks(x) elif nonstring_container(bins): return bins elif is_integer(bins): if log: return np.logspace(np.log10(np.maximum(x.min(), 1e-10)), np.log10(x.max()), bins) else: return np.linspace(x.min(), x.max(), bins) else: raise ValueError("unrecognized bin code: '" + str(bins) + "'.")
def _get_edges(self, source_node=None, target_node=None): ''' Called by Graph.get_edges if source_node and target_node are not both integers. ''' nx = nngt._config["library"] g = self._graph target_node = \ [target_node] if is_integer(target_node) else target_node if source_node is not None: source_node = \ [source_node] if is_integer(source_node) else source_node if target_node is None: if g.is_directed(): return list(g.out_edges(source_node)) return [ e if e[0] <= e[1] else e[::-1] for e in g.edges(source_node) ] res_iter = nx.edge_boundary(g, source_node, target_node) if g.is_directed(): return list(res_iter) return [e if e[0] <= e[1] else e[::-1] for e in res_iter] if target_node is None: # return all edges return list(g.edges) if g.is_directed(): return list(g.in_edges(target_node)) return [e if e[0] <= e[1] else e[::-1] for e in g.edges(target_node)]
def degree_distrib(graph, deg_type="total", nodes=None, weights=None, log=False, num_bins='bayes'): ''' Degree distribution of a graph. Parameters ---------- graph : :class:`~nngt.Graph` or subclass the graph to analyze. deg_type : string, optional (default: "total") type of degree to consider ("in", "out", or "total"). nodes : list of ints, optional (default: None) Restrict the distribution to a set of nodes (default: all nodes). weights : bool or str, optional (default: binary edges) Whether edge weights should be considered; if ``None`` or ``False`` then use binary edges; if ``True``, uses the 'weight' edge attribute, otherwise uses any valid edge attribute required. log : bool, optional (default: False) use log-spaced bins. num_bins : int, list or str, optional (default: 'bayes') Any of the automatic methodes from :func:`numpy.histogram`, or 'bayes' will provide automatic bin optimization. Otherwise, an int for the number of bins can be provided, or the direct bins list. See also -------- :func:`numpy.histogram`, :func:`~nngt.analysis.binning` Returns ------- counts : :class:`numpy.array` number of nodes in each bin deg : :class:`numpy.array` bins ''' degrees = graph.get_degrees(deg_type, nodes, weights) if num_bins == 'bayes' or is_integer(num_bins): num_bins = binning(degrees, bins=num_bins, log=log) elif log: deg = degrees[degrees > 0] counts, bins = np.histogram(np.log(deg), num_bins) return counts, np.exp(bins) return np.histogram(degrees, num_bins)
def degree_distrib(graph, deg_type="total", node_list=None, use_weights=False, log=False, num_bins='bayes'): ''' Degree distribution of a graph. .. versionchanged:: 0.7 Inclusion of automatic binning. Parameters ---------- graph : :class:`~nngt.Graph` or subclass the graph to analyze. deg_type : string, optional (default: "total") type of degree to consider ("in", "out", or "total"). node_list : list or numpy.array of ints, optional (default: None) Restrict the distribution to a set of nodes (default: all nodes). use_weights : bool, optional (default: False) use weighted degrees (do not take the sign into account: all weights are positive). log : bool, optional (default: False) use log-spaced bins. num_bins : int, list or str, optional (default: 'bayes') Any of the automatic methodes from :func:`numpy.histogram`, or 'bayes' will provide automatic bin optimization. Otherwise, an int for the number of bins can be provided, or the direct bins list. See also -------- :func:`numpy.histogram`, :func:`~nngt.analysis.binning` Returns ------- counts : :class:`numpy.array` number of nodes in each bin deg : :class:`numpy.array` bins ''' degrees = graph.get_degrees(deg_type, node_list, use_weights) if num_bins == 'bayes' or is_integer(num_bins): num_bins = binning(degrees, bins=num_bins, log=log) return np.histogram(degrees, num_bins)
def __init__(self, nodes=None, ntype=1, model=None, neuron_param=None): ''' Create a group of neurons (empty group is default, but it is not a valid object for most use cases). .. versionchanged:: 0.8 Removed `syn_model` and `syn_param`. Parameters ---------- nodes : int or array-like, optional (default: None) Desired size of the group or, a posteriori, NNGT indices of the neurons in an existing graph. ntype : int, optional (default: 1) Type of the neurons (1 for excitatory, -1 for inhibitory). model : str, optional (default: None) NEST model for the neuron. neuron_param : dict, optional (default: model defaults) Dictionary containing the parameters associated to the NEST model. Returns ------- A new :class:`~nngt.core.NeuralGroup` instance. ''' assert ntype in (1, -1), "`ntype` can either be 1 or -1." neuron_param = {} if neuron_param is None else neuron_param.copy() self._has_model = False if model is None else True self._neuron_model = model if nodes is None: self._desired_size = None self._ids = [] elif nonstring_container(nodes): self._desired_size = None self._ids = list(nodes) elif is_integer(nodes): self._desired_size = nodes self._ids = [] else: raise InvalidArgument('`nodes` must be either array-like or int.') self._nest_gids = None self.neuron_param = neuron_param if self._has_model else None self.neuron_type = ntype
def __setitem__(self, key, value): self._validity_check(key, value) int_key = None if is_integer(key): new_key = tuple(self.keys())[key] int_key = key OrderedDict.__setitem__(self, new_key, value) else: OrderedDict.__setitem__(self, key, value) int_key = list(super(NeuralPop, self).keys()).index(key) # update pop size/max_id group_size = len(value.ids) max_id = np.max(value.ids) if group_size != 0 else 0 _update_max_id_and_size(self, max_id) self._neuron_group[value.ids] = int_key if -1 in list(self._neuron_group): self._is_valid = False else: if self._desired_size is not None: self._is_valid = (self._desired_size == self._size) else: self._is_valid = True
def edge_id(self, edge): ''' Return the ID a given edge or a list of edges in the graph. Raises an error if the edge is not in the graph or if one of the vertices in the edge is nonexistent. Parameters ---------- edge : 2-tuple or array of edges Edge descriptor (source, target). Returns ------- index : int or array of ints Index of the given `edge`. ''' if is_integer(edge[0]): return self[edge[0]][edge[1]]["eid"] elif nonstring_container(edge[0]): return [self[e[0]][e[1]]["eid"] for e in edge] else: raise AttributeError("`edge` must be either a 2-tuple of ints or " "an array of 2-tuples of ints.")
def plot_activity(gid_recorder=None, record=None, network=None, gids=None, axis=None, show=False, limits=None, histogram=False, title=None, fignum=None, label=None, sort=None, average=False, normalize=1., decimate=None, transparent=True, kernel_center=0., kernel_std=None, resolution=None, cut_gaussian=5., **kwargs): ''' Plot the monitored activity. .. versionchanged:: 1.2 Switched `hist` to `histogram` and default value to False. .. versionchanged:: 1.0.1 Added `axis` parameter, restored missing `fignum` parameter. Parameters ---------- gid_recorder : tuple or list of tuples, optional (default: None) The gids of the recording devices. If None, then all existing spike_recs are used. record : tuple or list, optional (default: None) List of the monitored variables for each device. If `gid_recorder` is None, record can also be None and only spikes are considered. network : :class:`~nngt.Network` or subclass, optional (default: None) Network which activity will be monitored. gids : tuple, optional (default: None) NEST gids of the neurons which should be monitored. axis : matplotlib axis object, optional (default: new one) Axis that should be use to plot the activity. This takes precedence over `fignum`. show : bool, optional (default: False) Whether to show the plot right away or to wait for the next plt.show(). histogram : bool, optional (default: False) Whether to display the histogram when plotting spikes rasters. limits : tuple, optional (default: None) Time limits of the plot (if not specified, times of first and last spike for raster plots). title : str, optional (default: None) Title of the plot. fignum : int, or dict, optional (default: None) Plot the activity on an existing figure (from ``figure.number``). This parameter is ignored if `axis` is provided. label : str or list, optional (default: None) Add labels to the plot (one per recorder). sort : str or list, optional (default: None) Sort neurons using a topological property ("in-degree", "out-degree", "total-degree" or "betweenness"), an activity-related property ("firing_rate" or neuronal property) or a user-defined list of sorted neuron ids. Sorting is performed by increasing value of the `sort` property from bottom to top inside each group. normalize : float or list, optional (default: None) Normalize the recorded results by a given float. If a list is provided, there should be one entry per voltmeter or multimeter in the recorders. If the recording was done through `monitor_groups`, the population can be passed to normalize the data by the nuber of nodes in each group. decimate : int or list of ints, optional (default: None) Represent only a fraction of the spiking neurons; only one neuron in `decimate` will be represented (e.g. setting `decimate` to 5 will lead to only 20% of the neurons being represented). If a list is provided, it must have one entry per NeuralGroup in the population. kernel_center : float, optional (default: 0.) Temporal shift of the Gaussian kernel, in ms (for the histogram). kernel_std : float, optional (default: 0.5% of simulation time) Characteristic width of the Gaussian kernel (standard deviation) in ms (for the histogram). resolution : float or array, optional (default: `0.1*kernel_std`) The resolution at which the firing rate values will be computed. Choosing a value smaller than `kernel_std` is strongly advised. If resolution is an array, it will be considered as the times were the firing rate should be computed (for the histogram). cut_gaussian : float, optional (default: 5.) Range over which the Gaussian will be computed (for the histogram). By default, we consider the 5-sigma range. Decreasing this value will increase speed at the cost of lower fidelity; increasing it with increase the fidelity at the cost of speed. **kwargs : dict "color" and "alpha" values can be overriden here. Warning ------- Sorting with "firing_rate" only works if NEST gids form a continuous integer range. Returns ------- lines : list of lists of :class:`matplotlib.lines.Line2D` Lines containing the data that was plotted, grouped by figure. ''' import matplotlib.pyplot as plt recorders = _get_nest_gids([]) lst_labels, lines, axes, labels = [], {}, {}, {} # normalize recorders and recordables if gid_recorder is not None: assert record is not None, "`record` must also be provided." if len(record) != len(gid_recorder): raise InvalidArgument('`record` must either be the same for all ' 'recorders, or contain one entry per ' 'recorder in `gid_recorder`') for rec in gid_recorder: if nest_version == 3: recorders = _get_nest_gids(gid_recorder) else: if isinstance(gid_recorder[0], tuple): recorders.append(rec) else: recorders.append((rec, )) else: prop = {'model': spike_rec} if nest_version == 3: recorders = nest.GetNodes(properties=prop) else: recorders = [(gid, ) for gid in nest.GetNodes((0, ), properties=prop)[0]] record = tuple("spikes" for _ in range(len(recorders))) # get gids and groups gids = network.nest_gids if (gids is None and network is not None) \ else gids if gids is None: gids = [] for rec in recorders: gids.extend(nest.GetStatus(rec)[0]["events"]["senders"]) gids = np.unique(gids) num_group = 1 if network is None else len(network.population) num_lines = max(num_group, len(recorders)) # sorting sorted_neurons = np.array([]) if len(gids): sorted_neurons = np.arange(np.max(gids) + 1).astype(int) - np.min(gids) + 1 attr = None if sort is not None: assert network is not None, "`network` is required for sorting." if nonstring_container(sort): attr = sort sorted_neurons = _sort_neurons(attr, gids, network) sort = "user defined sort" else: data = None if sort.lower() in ("firing_rate", "b2"): # get senders data = [[], []] for rec in recorders: info = nest.GetStatus(rec)[0] if str(info["model"]) == spike_rec: data[0].extend(info["events"]["senders"]) data[1].extend(info["events"]["times"]) data = np.array(data).T sorted_neurons, attr = _sort_neurons(sort, gids, network, data=data, return_attr=True) elif network is not None and network.is_spatial(): sorted_neurons, attr = _sort_neurons("space", gids, network, data=None, return_attr=True) # spikes plotting colors = palette_discrete(np.linspace(0, 1, num_lines)) num_raster, num_detec, num_meter = 0, 0, 0 fignums = fignum if isinstance(fignum, dict) else {} decim = [] if decimate is None: decim = [None for _ in range(num_lines)] elif is_integer(decimate): decim = [decimate for _ in range(num_lines)] elif nonstring_container(decimate): assert len(decimate) == num_lines, "`decimate` should have one " +\ "entry per plot." decim = decimate else: raise AttributeError( "`decimate` must be either an int or a list of `int`.") # set labels if label is None: lst_labels = [None for _ in range(len(recorders))] else: if isinstance(label, str): lst_labels = [label] else: lst_labels = label if len(label) != len(recorders): _log_message( logger, "WARNING", 'Incorrect length for `label`: expecting {} but got ' '{}.\nIgnoring.'.format(len(recorders), len(label))) lst_labels = [None for _ in range(len(recorders))] datasets = [] max_time = 0. for rec in recorders: info = nest.GetStatus(rec)[0] if len(info["events"]["times"]): max_time = max(max_time, np.max(info["events"]["times"])) datasets.append(info) if kernel_std is None: kernel_std = max_time * 0.005 if resolution is None: resolution = 0.5 * kernel_std # plot for info, var, lbl in zip(datasets, record, lst_labels): fnum = fignums.get(info["model"], fignum) if info["model"] not in labels: labels[info["model"]] = [] lines[info["model"]] = [] if str(info["model"]) == spike_rec: if spike_rec in axes: axis = axes[spike_rec] c = colors[num_raster] times, senders = info["events"]["times"], info["events"]["senders"] sorted_ids = sorted_neurons[senders] l = raster_plot(times, sorted_ids, color=c, show=False, limits=limits, sort=sort, fignum=fnum, axis=axis, decimate=decim[num_raster], sort_attribute=attr, network=network, histogram=histogram, transparent=transparent, hist_ax=axes.get('histogram', None), kernel_center=kernel_center, kernel_std=kernel_std, resolution=resolution, cut_gaussian=cut_gaussian) num_raster += 1 if l: fig_raster = l[0].figure.number fignums[spike_rec] = fig_raster axes[spike_rec] = l[0].axes labels[spike_rec].append(lbl) lines[spike_rec].extend(l) if histogram: axes['histogram'] = l[1].axes elif "detector" in str(info["model"]): c = colors[num_detec] times, senders = info["events"]["times"], info["events"]["senders"] sorted_ids = sorted_neurons[senders] l = raster_plot(times, sorted_ids, fignum=fnum, color=c, axis=axis, show=False, histogram=histogram, limits=limits, kernel_center=kernel_center, kernel_std=kernel_std, resolution=resolution, cut_gaussian=cut_gaussian) if l: fig_detect = l[0].figure.number num_detec += 1 fignums[info["model"]] = fig_detect labels[info["model"]].append(lbl) lines[info["model"]].extend(l) if histogram: axes['histogram'] = l[1].axes else: da_time = info["events"]["times"] # prepare axis setup fig = None if axis is None: fig = plt.figure(fnum) fignums[info["model"]] = fig.number else: fig = axis.get_figure() lines_tmp, labels_tmp = [], [] if nonstring_container(var): m_colors = palette_discrete(np.linspace(0, 1, len(var))) axes = fig.axes if axis is not None: # multiple y axes on a single subplot, adapted from # https://matplotlib.org/examples/pylab_examples/ # multiple_yaxis_with_spines.html axes = [axis] axis.name = var[0] if len(var) > 1: axes.append(axis.twinx()) axes[-1].name = var[1] if len(var) > 2: fig.subplots_adjust(right=0.75) for i, name in zip(range(len(var) - 2), var[2:]): new_ax = axis.twinx() new_ax.spines["right"].set_position( ("axes", 1.2 * (i + 1))) axes.append(new_ax) _make_patch_spines_invisible(new_ax) new_ax.spines["right"].set_visible(True) axes[-1].name = name if not axes: axes = _set_new_plot(fig.number, names=var)[1] labels_tmp = [lbl for _ in range(len(var))] for subvar, c in zip(var, m_colors): c = kwargs.get('color', c) alpha = kwargs.get('alpha', 1) for ax in axes: if ax.name == subvar: da_subvar = info["events"][subvar] if isinstance(normalize, nngt.NeuralPop): da_subvar /= normalize[num_meter].size elif nonstring_container(normalize): da_subvar /= normalize[num_meter] elif normalize is not None: da_subvar /= normalize lines_tmp.extend( ax.plot(da_time, da_subvar, color=c, alpha=alpha)) ax.set_ylabel(subvar) ax.set_xlabel("time") if limits is not None: ax.set_xlim(limits[0], limits[1]) else: num_axes, ax = len(fig.axes), axis if axis is None: ax = fig.add_subplot(num_axes + 1, 1, num_axes + 1) da_var = info["events"][var] c = kwargs.get('color', None) alpha = kwargs.get('alpha', 1) lines_tmp.extend( ax.plot(da_time, da_var / normalize, color=c, alpha=alpha)) labels_tmp.append(lbl) ax.set_ylabel(var) ax.set_xlabel("time") labels[info["model"]].extend(labels_tmp) lines[info["model"]].extend(lines_tmp) num_meter += 1 if spike_rec in axes: ax = axes[spike_rec] if limits is not None: ax.set_xlim(limits[0], limits[1]) else: t_min, t_max, idx_min, idx_max = np.inf, -np.inf, np.inf, -np.inf for l in ax.lines: t_max = max(np.max(l.get_xdata()), t_max) t_min = min(np.min(l.get_xdata()), t_max) idx_min = min(np.min(l.get_ydata()), idx_min) idx_max = max(np.max(l.get_ydata()), idx_max) dt = t_max - t_min didx = idx_max - idx_min pc = 0.02 if not np.any(np.isinf((t_max, t_min))): ax.set_xlim([t_min - pc * dt, t_max + pc * dt]) if not np.any(np.isinf((idx_min, idx_max))): ax.set_ylim([idx_min - pc * didx, idx_max + pc * didx]) for recorder in fignums: fig = plt.figure(fignums[recorder]) if title is not None: fig.suptitle(title) if label is not None: fig.legend(lines[recorder], labels[recorder]) if show: plt.show() return lines
def types(graph, inhib_nodes=None, inhib_frac=None): ''' @todo Define the type of a set of neurons. If no arguments are given, all edges will be set as excitatory. Parameters ---------- graph : :class:`~nngt.Graph` or subclass Graph on which edge types will be created. inhib_nodes : int, float or list, optional (default: `None`) If `inhib_nodes` is an int, number of inhibitory nodes in the graph (all connections from inhibitory nodes are inhibitory); if it is a float, ratio of inhibitory nodes in the graph; if it is a list, ids of the inhibitory nodes. inhib_frac : float, optional (default: `None`) Fraction of the selected edges that will be set as refractory (if `inhib_nodes` is not `None`, it is the fraction of the nodes' edges that will become inhibitory, otherwise it is the fraction of all the edges in the graph). Returns ------- t_list : :class:`~numpy.ndarray` List of the edges' types. ''' t_list = np.repeat(1., graph.edge_nb()) edges = graph.edges_array num_inhib = 0 idx_inhib = [] if inhib_nodes is None and inhib_frac is None: graph.new_edge_attribute("type", "double", val=1.) return t_list else: n = graph.node_nb() if inhib_nodes is None: # set inhib_frac*num_edges random inhibitory connections num_edges = graph.edge_nb() num_inhib = int(num_edges * inhib_frac) num_current = 0 while num_current < num_inhib: new = randint(0, num_edges, num_inhib - num_current) idx_inhib = np.unique(np.concatenate((idx_inhib, new))) num_current = len(idx_inhib) t_list[idx_inhib.astype(int)] *= -1. else: # get the dict of inhibitory nodes num_inhib_nodes = 0 idx_nodes = {} if nonstring_container(inhib_nodes): idx_nodes = {i: -1 for i in inhib_nodes} num_inhib_nodes = len(idx_nodes) if isinstance(inhib_nodes, np.float): if inhib_nodes > 1: raise InvalidArgument( "Inhibitory ratio (float value for `inhib_nodes`) " "must be smaller than 1.") num_inhib_nodes = int(inhib_nodes * n) if is_integer(inhib_nodes): num_inhib_nodes = int(inhib_nodes) while len(idx_nodes) != num_inhib_nodes: indices = randint(0, n, num_inhib_nodes - len(idx_nodes)) di_tmp = {i: -1 for i in indices} idx_nodes.update(di_tmp) for v in edges[:, 0]: if v in idx_nodes: idx_inhib.append(v) idx_inhib = np.unique(idx_inhib) # set the inhibitory edge indices for v in idx_inhib: idx_edges = np.argwhere(edges[:, 0] == v) n = len(idx_edges) if inhib_frac is not None: idx_inh = [] num_inh = n * inhib_frac i = 0 while i != num_inh: ids = randint(0, n, num_inh - i) idx_inh = np.unique(np.concatenate((idx_inh, ids))) i = len(idx_inh) t_list[idx_inh] *= -1. else: t_list[idx_edges] *= -1. graph.set_edge_attribute("type", value_type="double", values=t_list) return t_list
def plot_activity(gid_recorder=None, record=None, network=None, gids=None, show=False, limits=None, hist=True, title=None, label=None, sort=None, average=False, normalize=1., decimate=None, transparent=True): ''' Plot the monitored activity. Parameters ---------- gid_recorder : tuple or list of tuples, optional (default: None) The gids of the recording devices. If None, then all existing "spike_detector"s are used. record : tuple or list, optional (default: None) List of the monitored variables for each device. If `gid_recorder` is None, record can also be None and only spikes are considered. network : :class:`~nngt.Network` or subclass, optional (default: None) Network which activity will be monitored. gids : tuple, optional (default: None) NEST gids of the neurons which should be monitored. show : bool, optional (default: False) Whether to show the plot right away or to wait for the next plt.show(). hist : bool, optional (default: True) Whether to display the histogram when plotting spikes rasters. limits : tuple, optional (default: None) Time limits of the plot (if not specified, times of first and last spike for raster plots). title : str, optional (default: None) Title of the plot. fignum : int, optional (default: None) Plot the activity on an existing figure (from ``figure.number``). label : str or list, optional (default: None) Add labels to the plot (one per recorder). sort : str or list, optional (default: None) Sort neurons using a topological property ("in-degree", "out-degree", "total-degree" or "betweenness"), an activity-related property ("firing_rate" or neuronal property) or a user-defined list of sorted neuron ids. Sorting is performed by increasing value of the `sort` property from bottom to top inside each group. normalize : float or list, optional (default: None) Normalize the recorded results by a given float. If a list is provided, there should be one entry per voltmeter or multimeter in the recorders. If the recording was done through `monitor_groups`, the population can be passed to normalize the data by the nuber of nodes in each group. decimate : int or list of ints, optional (default: None) Represent only a fraction of the spiking neurons; only one neuron in `decimate` will be represented (e.g. setting `decimate` to 5 will lead to only 20% of the neurons being represented). If a list is provided, it must have one entry per NeuralGroup in the population. Warning ------- Sorting with "firing_rate" only works if NEST gids form a continuous integer range. Returns ------- lines : list of lists of :class:`matplotlib.lines.Line2D` Lines containing the data that was plotted, grouped by figure. ''' lst_rec, lst_labels, lines, labels = [], [], {}, {} num_fig = np.max(plt.get_fignums()) if plt.get_fignums() else 0 # normalize recorders and recordables if gid_recorder is not None: if len(record) != len(gid_recorder): raise InvalidArgument('`record` must either be the same for all ' 'recorders, or contain one entry per ' 'recorder in `gid_recorder`') for rec in gid_recorder: if isinstance(gid_recorder[0], tuple): lst_rec.append(rec[0]) else: lst_rec.append(rec) else: lst_rec = nest.GetNodes((0, ), properties={'model': 'spike_detector'})[0] record = tuple("spikes" for _ in range(len(lst_rec))) # get gids and groups gids = network.nest_gid if (gids is None and network is not None) else gids if gids is None: gids = [] for rec in lst_rec: gids.extend(nest.GetStatus([rec])[0]["events"]["senders"]) gids = np.unique(gids) num_group = len(network.population) if network is not None else 1 # sorting sorted_neurons = np.array([]) if len(gids): sorted_neurons = np.arange(np.max(gids) + 1).astype(int) - np.min(gids) + 1 attr = None if sort is not None: assert network is not None, "`network` is required for sorting." if nonstring_container(sort): attr = sort sorted_neurons = _sort_neurons(attr, gids, network) sort = "user defined sort" else: data = None if sort.lower() in ("firing_rate", "b2"): # get senders data = [[], []] for rec in lst_rec: info = nest.GetStatus([rec])[0] if str(info["model"]) == "spike_detector": data[0].extend(info["events"]["senders"]) data[1].extend(info["events"]["times"]) data = np.array(data).T sorted_neurons, attr = _sort_neurons(sort, gids, network, data=data, return_attr=True) # spikes plotting colors = palette(np.linspace(0, 1, num_group)) num_raster, num_detec, num_meter = 0, 0, 0 fignums = {} decim = [] if decimate is None: decim = [None for _ in range(num_group)] elif is_integer(decimate): decim = [decimate for _ in range(num_group)] elif nonstring_container(decimate): assert len(decimate) == num_group, "`decimate` should have one " +\ "entry per group in the population." decim = decimate else: raise AttributeError( "`decimate` must be either an int or a list of `int`.") # set labels if label is None: lst_labels = [None for _ in range(len(lst_rec))] else: if isinstance(label, str): lst_labels = [label] else: lst_labels = label if len(label) != len(lst_rec): _log_message( logger, "WARNING", 'Incorrect length for `label`: expecting {} but got ' '{}.\nIgnoring.'.format(len(lst_rec), len(label))) lst_labels = [None for _ in range(len(lst_rec))] # plot for rec, var, lbl in zip(lst_rec, record, lst_labels): info = nest.GetStatus([rec])[0] fnum = fignums[info["model"]] if info["model"] in fignums else None if info["model"] not in labels: labels[info["model"]] = [] lines[info["model"]] = [] if str(info["model"]) == "spike_detector": c = colors[num_raster] times, senders = info["events"]["times"], info["events"]["senders"] sorted_ids = sorted_neurons[senders] l = raster_plot(times, sorted_ids, color=c, show=False, limits=limits, sort=sort, fignum=fnum, decimate=decim[num_raster], sort_attribute=attr, network=network, transparent=transparent) num_raster += 1 if l: fig_raster = l[0].figure.number fignums['spike_detector'] = fig_raster labels["spike_detector"].append(lbl) lines["spike_detector"].extend(l) elif "detector" in str(info["model"]): c = colors[num_detec] times, senders = info["events"]["times"], info["events"]["senders"] sorted_ids = sorted_neurons[senders] l = raster_plot(times, sorted_ids, fignum=fnum, color=c, show=False, hist=hist, limits=limits) if l: fig_detect = l[0].figure.number num_detec += 1 fignums[info["model"]] = fig_detect labels[info["model"]].append(lbl) lines[info["model"]].extend(l) else: da_time = info["events"]["times"] fig = plt.figure(fnum) fignums[info["model"]] = fig.number lines_tmp, labels_tmp = [], [] if nonstring_container(var): axes = fig.axes if not axes: axes = _set_new_plot(fig.number, names=var)[1] labels_tmp = [lbl for _ in range(len(var))] for subvar in var: for ax in axes: if ax.name == subvar: da_subvar = info["events"][subvar] if isinstance(normalize, nngt.NeuralPop): da_subvar /= normalize[num_meter].size elif nonstring_container(normalize): da_subvar /= normalize[num_meter] elif normalize is not None: da_subvar /= normalize lines_tmp.extend(ax.plot(da_time, da_subvar)) ax.set_ylabel(subvar) ax.set_xlabel("time") if limits is not None: ax.set_xlim(limits[0], limits[1]) else: ax = fig.add_subplot(111) da_var = info["events"][var] lines_tmp.extend(ax.plot(da_time, da_var / normalize)) labels_tmp.append(lbl) ax.set_ylabel(var) ax.set_xlabel("time") labels[info["model"]].extend(labels_tmp) lines[info["model"]].extend(lines_tmp) num_meter += 1 for recorder in fignums: fig = plt.figure(fignums[recorder]) if title is not None: fig.suptitle(title) if label is not None: fig.legend(lines[recorder], labels[recorder]) if show: plt.show() return lines
def _get_edges(self, source_node=None, target_node=None): ''' Called by Graph.get_edges if source_node and target_node are not both integers. ''' g = self._graph edges = set() if source_node is not None: if target_node is None: if is_integer(source_node): if g.is_directed(): return [ tuple(e) for e in g.iter_out_edges(source_node) ] return [ tuple(e) if e[0] < e[1] else tuple(e[::-1]) for e in g.iter_all_edges(source_node) ] for s in source_node: if g.is_directed(): edges.update((tuple(e) for e in g.iter_out_edges(s))) else: for e in g.iter_all_edges(s): edges.add( tuple(e) if e[0] <= e[1] else tuple(e[::-1])) else: target_node = {target_node} if is_integer(target_node) \ else set(target_node) if is_integer(source_node): if g.is_directed(): return [ tuple(e) for e in g.get_out_edges(source_node) if e[1] in target_node ] return [ tuple(e) for e in g.get_all_edges(source_node) if e[0] in target_node or e[1] in target_node ] for s in source_node: if g.is_directed(): edges.update((tuple(e) for e in g.iter_out_edges(s) if e[1] in target_node)) else: for e in g.iter_all_edges(s): e = tuple(e) if e[0] <= e[1] else tuple(e[::-1]) if e[0] in target_node or e[1] in target_node: edges.add(e) return list(edges) if target_node is None: # return all edges return list(g.get_edges()) if is_integer(target_node): if g.is_directed(): return [tuple(e) for e in g.iter_in_edges(target_node)] return [ tuple(e) if e[0] <= e[1] else tuple(e[::-1]) for e in g.iter_all_edges(target_node) ] for t in target_node: if g.is_directed(): edges.update((tuple(e) for e in g.iter_in_edges(t))) else: for e in g.iter_all_edges(t): edges.add(tuple(e) if e[0] <= e[1] else tuple(e[::-1])) return list(edges)