Ejemplo n.º 1
0
    def get_with_index_array(self, variable_name, variable, item):
        if variable.scalar:
            if not (isinstance(item, slice) and item == slice(None)):
                raise IndexError(('Illegal index for variable %s, it is a '
                                  'scalar variable.') % variable_name)
            indices = np.array(0)
        else:
            indices = self.calc_indices(item)

        # For "normal" variables, we can directly access the underlying data
        # and use the usual slicing syntax. For subexpressions, however, we
        # have to evaluate code for the given indices
        if isinstance(variable, Subexpression):
            variables = Variables(None)
            variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                             dtype=variable.dtype,
                                             scalar=variable.scalar)
            if indices.shape ==  ():
                single_index = True
                indices = np.array([indices])
            else:
                single_index = False
            variables.add_array('_group_idx', unit=Unit(1),
                                size=len(indices), dtype=np.int32)
            variables['_group_idx'].set_value(indices)

            abstract_code = '_variable = ' + variable_name + '\n'
            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'group_variable_get',
                                            additional_variables=variables
            )
            result = codeobj()
            if single_index and not variable.scalar:
                return result[0]
            else:
                return result
        else:
            if variable.scalar:
                return variable.get_value()[0]
            else:
                # We are not going via code generation so we have to take care
                # of correct indexing (in particular for subgroups) explicitly
                var_index = self.variables.indices[variable_name]
                if var_index != '_idx':
                    indices = self.variables[var_index].get_value()[indices]
                return variable.get_value()[indices]
Ejemplo n.º 2
0
    def set_with_expression(self, varname, variable, item, code,
                            check_units=True, level=0, run_namespace=None):
        '''
        Sets a variable using a string expression. Is called by
        `VariableView.set_item` for statements such as
        ``S.var[:, :] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        varname : str
            The name of the variable to be set
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        item : `ndarray`
            The indices for the variable (in the context of this `group`).
        code : str
            The code that should be executed to set the variable values.
            Can contain references to indices, such as `i` or `j`
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the implicit
            namespace (if used, see `run_namespace`).
        run_namespace : dict-like, optional
            An additional namespace that is used for variable lookup (if not
            defined, the implicit namespace of local variables is used).
        '''
        indices = self.calc_indices(item)
        abstract_code = varname + ' = ' + code
        variables = Variables(None)
        variables.add_array('_group_idx', unit=Unit(1),
                            size=len(indices), dtype=np.int32)
        variables['_group_idx'].set_value(indices)

        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(self,
                                        abstract_code,
                                        'group_variable_set',
                                        additional_variables=variables,
                                        check_units=check_units,
                                        level=level+2,
                                        run_namespace=run_namespace)
        codeobj()
Ejemplo n.º 3
0
    def set_with_expression(self, group, varname, variable, item, code,
                            check_units=True, level=0):
        '''
        Sets a variable using a string expression. Is called by
        `VariableView.set_item` for statements such as
        ``S.var[:, :] = 'exp(-abs(i-j)/space_constant)*nS'``

        Parameters
        ----------
        group : `Group`
            The group providing the context for the indexing.
        varname : str
            The name of the variable to be set
        variable : `ArrayVariable`
            The `ArrayVariable` object for the variable to be set.
        item : `ndarray`
            The indices for the variable (in the context of this `group`).
        code : str
            The code that should be executed to set the variable values.
            Can contain references to indices, such as `i` or `j`
        check_units : bool, optional
            Whether to check the units of the expression.
        level : int, optional
            How much farther to go up in the stack to find the namespace.
        '''
        indices = group.calc_indices(item)
        abstract_code = varname + ' = ' + code
        namespace = get_local_namespace(level + 1)
        additional_namespace = ('implicit-namespace', namespace)
        variables = Variables(None)
        variables.add_array('_group_idx', unit=Unit(1),
                            size=len(indices), dtype=np.int32)
        variables['_group_idx'].set_value(indices)

        # TODO: Have an additional argument to avoid going through the index
        # array for situations where iterate_all could be used
        codeobj = create_runner_codeobj(group,
                                 abstract_code,
                                 'group_variable_set',
                                 additional_variables=variables,
                                 additional_namespace=additional_namespace,
                                 check_units=check_units)
        codeobj()
Ejemplo n.º 4
0
    def get_with_index_array(self, group, variable_name, variable, item):
        if variable.scalar:
            if not ((isinstance(item, slice) and item == slice(None)) or item == 0 or (hasattr(item, '__len__')
                                                                                           and len(item) == 0)):
                raise IndexError('Variable is a scalar variable.')
            indices = np.array([0])
        else:
            indices = group.calc_indices(item)

        # For "normal" variables, we can directly access the underlying data
        # and use the usual slicing syntax. For subexpressions, however, we
        # have to evaluate code for the given indices
        if isinstance(variable, Subexpression):
            variables = Variables(None)
            variables.add_auxiliary_variable('_variable', unit=variable.unit,
                                             dtype=variable.dtype,
                                             scalar=variable.scalar,
                                             is_bool=variable.is_bool)
            variables.add_array('_group_idx', unit=Unit(1),
                                size=len(indices), dtype=np.int32)
            variables['_group_idx'].set_value(indices)

            abstract_code = '_variable = ' + variable_name + '\n'
            codeobj = create_runner_codeobj(group,
                                            abstract_code,
                                            'group_variable_get',
                                            additional_variables=variables
            )
            return codeobj()
        else:
            # We are not going via code generation so we have to take care
            # of correct indexing (in particular for subgroups) explicitly
            var_index = group.variables.indices[variable_name]
            if var_index != '_idx':
                indices = group.variables[var_index].get_value()[indices]
            return variable.get_value()[indices]
Ejemplo n.º 5
0
class PoissonGroup(Group, SpikeSource):
    '''
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate. This string expression will be evaluated at every
        time step, it can therefore be time-dependent (e.g. refer to a
        `TimedArray`).
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.
    '''
    add_to_magic_network = True

    @check_units(rates=Hz)
    def __init__(self,
                 N,
                 rates,
                 dt=None,
                 clock=None,
                 when='thresholds',
                 order=0,
                 name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)
        self.namespace = None
        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        if isinstance(rates, basestring):
            self.variables.add_subexpression('rates', unit=Hz, expr=rates)
        else:
            self.variables.add_array('rates', size=N, unit=Hz)
        self._rates = rates

        self.start = 0
        self.stop = N

        self._refractory = False

        # To avoid a warning about the local variable rates, we set the real
        # threshold condition only after creating the object
        self.events = {'spike': 'False'}
        self.thresholder = {'spike': Thresholder(self)}
        self.events = {'spike': 'rand() < rates * dt'}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        if not isinstance(rates, basestring):
            self.rates = rates

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError(
                'Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        description = '{classname}({N}, rates={rates})'
        return description.format(classname=self.__class__.__name__,
                                  N=self.N,
                                  rates=repr(self._rates))
Ejemplo n.º 6
0
class EventMonitor(Group, CodeRunner):
    '''
    Record events from a `NeuronGroup` or another event source.

    The recorded events can be accessed in various ways:
    the attributes `~EventMonitor.i` and `~EventMonitor.t` store all the indices
    and event times, respectively. Alternatively, you can get a dictionary
    mapping neuron indices to event trains, by calling the `event_trains`
    method.

    Parameters
    ----------
    source : `NeuronGroup`
        The source of events to record.
    event : str
        The name of the event to record
    variables : str or sequence of str, optional
        Which variables to record at the time of the event (in addition to the
        index of the neuron). Can be the name of a variable or a list of names.
    record : bool, optional
        Whether or not to record each event in `i` and `t` (the `count` will
        always be recorded). Defaults to ``True``.
    when : str, optional
        When to record the events, by default records events in the same slot
        where the event is emitted.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to the order where the
        event is emitted + 1, i.e. it will be recorded directly afterwards.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_eventmonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    See Also
    --------
    SpikeMonitor
    '''
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self, source, event, variables=None, record=True,
                 when=None, order=None, name='eventmonitor*',
                 codeobj_class=None):
        #: The source we are recording from
        self.source = source
        #: Whether to record times and indices of events
        self.record = record

        if when is None:
            if order is not None:
                raise ValueError('Cannot specify order if when is not specified.')
            if hasattr(source, 'thresholder'):
                parent_obj = source.thresholder[event]
            else:
                parent_obj = source
            when = parent_obj.when
            order = parent_obj.order + 1
        elif order is None:
            order = 0

        #: The event that we are listening to
        self.event = event

        if variables is None:
            variables = {}
        elif isinstance(variables, basestring):
            variables = {variables}

        #: The additional variables that will be recorded
        self.record_variables = set(variables)

        for variable in variables:
            if variable not in source.variables:
                raise ValueError(("'%s' is not a variable of the recorded "
                                  "group" % variable))

        if self.record:
            self.record_variables |= {'i', 't'}

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in self.record_variables]
        code = '\n'.join(code)

        self.codeobj_class = codeobj_class

        # Since this now works for general events not only spikes, we have to
        # pass the information about which variable to use to the template,
        # it can not longer simply refer to "_spikespace"
        eventspace_name = '_{}space'.format(event)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        Nameable.__init__(self, name=name)

        self.variables = Variables(self)
        self.variables.add_reference(eventspace_name, source)

        for variable in self.record_variables:
            source_var = source.variables[variable]
            self.variables.add_reference('_source_%s' % variable,
                                         source, variable)
            self.variables.add_auxiliary_variable('_to_record_%s' % variable,
                                                   unit=source_var.unit,
                                                   dtype=source_var.dtype)
            self.variables.add_dynamic_array(variable, size=0,
                                             unit=source_var.unit,
                                             dtype=source_var.dtype,
                                             constant_size=False)
        self.variables.add_arange('_source_idx', size=len(source))
        self.variables.add_array('count', size=len(source), unit=Unit(1),
                                 dtype=np.int32, read_only=True,
                                 index='_source_idx')
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_array('N', unit=Unit(1), size=1, dtype=np.int32,
                                 read_only=True, scalar=True)

        record_variables = {varname: self.variables[varname]
                            for varname in self.record_variables}
        template_kwds = {'eventspace_variable': source.variables[eventspace_name],
                         'record_variables': record_variables,
                         'record': self.record}
        needed_variables = {eventspace_name} | self.record_variables
        CodeRunner.__init__(self, group=self, code=code, template='spikemonitor',
                            name=None,  # The name has already been initialized
                            clock=source.clock, when=when,
                            order=order, needed_variables=needed_variables,
                            template_kwds=template_kwds)

        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')

        self.add_dependency(source)
        self._enable_group_attributes()

    def resize(self, new_size):
        for variable in self.record_variables:
            self.variables[variable].resize(new_size)

    def __len__(self):
        return self.N

    def reinit(self):
        '''
        Clears all recorded spikes
        '''
        raise NotImplementedError()

    @property
    def it(self):
        '''
        Returns the pair (`i`, `t`).
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        return self.i, self.t

    @property
    def it_(self):
        '''
        Returns the pair (`i`, `t_`).
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')

        return self.i, self.t_

    def _values_dict(self, first_pos, sort_indices, used_indices, var):
        sorted_values = self.state(var, use_units=False)[sort_indices]
        dim = self.variables[var].unit.dim
        event_values = {}
        current_pos = 0  # position in the all_indices array
        for idx in xrange(len(self.source)):
            if current_pos < len(used_indices) and used_indices[current_pos] == idx:
                if current_pos < len(used_indices) - 1:
                    event_values[idx] = Quantity(sorted_values[
                                                 first_pos[current_pos]:
                                                 first_pos[current_pos + 1]],
                                                 dim=dim, copy=False)
                else:
                    event_values[idx] = Quantity(
                        sorted_values[first_pos[current_pos]:],
                        dim=dim, copy=False)
                current_pos += 1
            else:
                event_values[idx] = Quantity([], dim=dim)
        return event_values

    def values(self, var):
        '''
        Return a dictionary mapping neuron indices to arrays of variable values
        at the time of the events (sorted by time).
        Parameters
        ----------
        var : str
            The name of the variable.

        Returns
        -------
        values : dict
            Dictionary mapping each neuron index to an array of variable
            values at the time of the events

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, """dv/dt = 100*Hz : 1
        ...                       v_th : 1""", threshold='v>v_th', reset='v=0')
        >>> G.v_th = [0.5, 1]
        >>> mon = EventMonitor(G, event='spike', variables='v')
        >>> run(20*ms)
        >>> v_values = mon.values('v')
        >>> v_values[0]
        array([ 0.5,  0.5,  0.5,  0.5])
        >>> v_values[1]
        array([ 1.,  1.])
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        indices = self.i[:]
        sort_indices = np.argsort(indices)
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        return self._values_dict(first_pos, sort_indices, used_indices, var)

    def all_values(self):
        '''
        Return a dictionary mapping recorded variable names (including ``t``)
        to a dictionary mapping neuron indices to arrays of variable values at
        the time of the events (sorted by time). This is equivalent to (but more
        efficient than) calling `values` for each variable and storing the
        result in a dictionary.

        Returns
        -------
        all_values : dict
            Dictionary mapping variable names to dictionaries which themselves
            are mapping neuron indicies to arrays of variable values at the
            time of the events.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, """dv/dt = 100*Hz : 1
        ...                       v_th : 1""", threshold='v>v_th', reset='v=0')
        >>> G.v_th = [0.5, 1]
        >>> mon = EventMonitor(G, event='spike', variables='v')
        >>> run(20*ms)
        >>> all_values = mon.all_values()
        >>> all_values['t'][0]
        array([  4.9,   9.9,  14.9,  19.9]) * msecond
        >>> all_values['v'][0]
        array([ 0.5,  0.5,  0.5,  0.5])
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        indices = self.i[:]
        sort_indices = np.argsort(indices)
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        all_values_dict = {}
        for varname in self.record_variables - {'i'}:
            all_values_dict[varname] = self._values_dict(first_pos,
                                                         sort_indices,
                                                         used_indices,
                                                         varname)
        return all_values_dict

    def event_trains(self):
        '''
        Return a dictionary mapping event indices to arrays of event times.
        Equivalent to calling ``values('t')``.

        Returns
        -------
        event_trains : dict
            Dictionary that stores an array with the event times for each
            neuron index.

        See Also
        --------
        SpikeMonitor.spike_trains
        '''
        return self.values('t')

    @property
    def num_events(self):
        '''
        Returns the total number of recorded events.
        '''
        return self.N[:]

    def __repr__(self):
        description = '<{classname}, recording event "{event}" from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  event=self.event,
                                  source=self.group.name)
Ejemplo n.º 7
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    SpikeGeneratorGroup(N, indices, times, dt=None, clock=None,
                        period=0*second, when='thresholds', order=0,
                        sorted=False, name='spikegeneratorgroup*',
                        codeobj_class=None)

    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in ``indices``. Has to have the
        same length as ``indices``.
    period : `Quantity`, optional
        If this is specified, it will repeat spikes with this period. A
        period of 0s means not repeating spikes.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    sorted : bool, optional
        Whether the given indices and times are already sorted. Set to ``True``
        if your events are already sorted (first by spike time, then by index),
        this can save significant time at construction if your arrays contain
        large numbers of spikes. Defaults to ``False``.

    Notes
    -----
    * If `sorted` is set to ``True``, the given arrays will not be copied
      (only affects runtime mode)..
    '''
    @check_units(N=1, indices=1, times=second, period=second)
    def __init__(self,
                 N,
                 indices,
                 times,
                 dt=None,
                 clock=None,
                 period=0 * second,
                 when='thresholds',
                 order=0,
                 sorted=False,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        #: Array of spiking neuron indices.
        self._neuron_index = None
        #: Array of spiking neuron times.
        self._spike_time = None
        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        if N < 1 or int(N) != N:
            raise TypeError('N has to be an integer >=1.')
        N = int(
            N)  # Make sure that it is an integer, values such as 10.0 would
        # otherwise make weave compilation fail
        self.start = 0
        self.stop = N

        self.variables = Variables(self)
        self.variables.create_clock_variables(self._clock)

        indices, times = self._check_args(indices, times, period, N, sorted,
                                          self._clock.dt)

        self.variables.add_constant('N', value=N)
        self.variables.add_array('period',
                                 dimensions=second.dim,
                                 size=1,
                                 constant=True,
                                 read_only=True,
                                 scalar=True,
                                 dtype=self._clock.variables['t'].dtype)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices),
                                         dtype=np.int32,
                                         read_only=True,
                                         constant=True,
                                         index='spike_number',
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index',
                                         values=indices,
                                         size=len(indices),
                                         dtype=np.int32,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_dynamic_array(
            'spike_time',
            values=times,
            size=len(times),
            dimensions=second.dim,
            index='spike_number',
            read_only=True,
            constant=True,
            dtype=self._clock.variables['t'].dtype)
        self.variables.add_dynamic_array('_timebins',
                                         size=len(times),
                                         index='spike_number',
                                         read_only=True,
                                         constant=True,
                                         dtype=np.int32)
        self.variables.add_array('_period_bins',
                                 size=1,
                                 constant=True,
                                 read_only=True,
                                 scalar=True,
                                 dtype=np.int32)
        self.variables.add_array('_spikespace', size=N + 1, dtype=np.int32)
        self.variables.add_array('_lastindex',
                                 size=1,
                                 values=0,
                                 dtype=np.int32,
                                 read_only=True,
                                 scalar=True)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        CodeRunner.__init__(self,
                            self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)

    def _full_state(self):
        state = super(SpikeGeneratorGroup, self)._full_state()
        # Store the internal information we use to decide whether to rebuild
        # the time bins
        state['_previous_dt'] = self._previous_dt
        state['_spikes_changed'] = self._spikes_changed
        return state

    def _restore_from_full_state(self, state):
        state = state.copy()  # copy to avoid errors for multiple restores
        self._previous_dt = state.pop('_previous_dt')
        self._spikes_changed = state.pop('_spikes_changed')
        super(SpikeGeneratorGroup, self)._restore_from_full_state(state)

    def before_run(self, run_namespace):
        # Do some checks on the period vs. dt
        dt = self.dt_[:]  # make a copy
        period = self.period_
        if period < np.inf and period != 0:
            if period < dt:
                raise ValueError('The period of %s is %s, which is smaller '
                                 'than its dt of %s.' %
                                 (self.name, self.period[:], dt * second))

        if self._spikes_changed:
            current_t = self.variables['t'].get_value().item()
            timesteps = timestep(self._spike_time, dt)
            current_step = timestep(current_t, dt)
            in_the_past = np.nonzero(timesteps < current_step)[0]
            if len(in_the_past):
                logger.warn('The SpikeGeneratorGroup contains spike times '
                            'earlier than the start time of the current run '
                            '(t = {}), these spikes will be '
                            'ignored.'.format(str(current_t * second)),
                            name_suffix='ignored_spikes')
                self.variables['_lastindex'].set_value(in_the_past[-1] + 1)
            else:
                self.variables['_lastindex'].set_value(0)

        # Check that we don't have more than one spike per neuron in a time bin
        if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed:
            # We shift all the spikes by a tiny amount to make sure that spikes
            # at exact multiples of dt do not end up in the previous time bin
            # This shift has to be quite significant relative to machine
            # epsilon, we use 1e-3 of the dt here
            shift = 1e-3 * dt
            timebins = np.asarray(np.asarray(self._spike_time + shift) / dt,
                                  dtype=np.int32)
            # time is already in sorted order, so it's enough to check if the condition
            # that timebins[i]==timebins[i+1] and self._neuron_index[i]==self._neuron_index[i+1]
            # is ever both true
            if (np.logical_and(
                    np.diff(timebins) == 0,
                    np.diff(self._neuron_index) == 0)).any():
                raise ValueError('Using a dt of %s, some neurons of '
                                 'SpikeGeneratorGroup "%s" spike more than '
                                 'once during a time step.' %
                                 (str(self.dt), self.name))
            self.variables['_timebins'].set_value(timebins)
            period_bins = np.round(period / dt)
            max_int = np.iinfo(np.int32).max
            if period_bins > max_int:
                logger.warn(
                    'Periods longer than {} timesteps (={}) are not '
                    'supported, the period will therefore be '
                    'considered infinite. Set the period to 0*second '
                    'to avoid this '
                    'warning.'.format(max_int, str(max_int * dt * second)),
                    'spikegenerator_long_period')
                period = period_bins = 0
            if np.abs(period_bins * dt -
                      period) > period * np.finfo(dt.dtype).eps:
                raise NotImplementedError(
                    'The period of %s is %s, which is '
                    'not an integer multiple of its dt '
                    'of %s.' % (self.name, self.period[:], dt * second))

            self.variables['_period_bins'].set_value(period_bins)

            self._previous_dt = dt
            self._spikes_changed = False

        super(SpikeGeneratorGroup,
              self).before_run(run_namespace=run_namespace)

    @check_units(indices=1, times=second, period=second)
    def set_spikes(self, indices, times, period=0 * second, sorted=False):
        '''
        set_spikes(indices, times, period=0*second, sorted=False)

        Change the spikes that this group will generate.

        This can be used to set the input for a second run of a model based on
        the output of a first run (if the input for the second run is already
        known before the first run, then all the information should simply be
        included in the initial `SpikeGeneratorGroup` initializer call,
        instead).

        Parameters
        ----------
        indices : array of integers
            The indices of the spiking cells
        times : `Quantity`
            The spike times for the cells given in ``indices``. Has to have the
            same length as ``indices``.
        period : `Quantity`, optional
            If this is specified, it will repeat spikes with this period. A
            period of 0s means not repeating spikes.
        sorted : bool, optional
            Whether the given indices and times are already sorted. Set to
            ``True`` if your events are already sorted (first by spike time,
            then by index), this can save significant time at construction if
            your arrays contain large numbers of spikes. Defaults to ``False``.
        '''

        indices, times = self._check_args(indices, times, period, self.N,
                                          sorted, self.dt)

        self.variables['period'].set_value(period)
        self.variables['neuron_index'].resize(len(indices))
        self.variables['spike_time'].resize(len(indices))
        self.variables['spike_number'].resize(len(indices))
        self.variables['spike_number'].set_value(np.arange(len(indices)))
        self.variables['_timebins'].resize(len(indices))
        self.variables['neuron_index'].set_value(indices)
        self.variables['spike_time'].set_value(times)
        # _lastindex and _timebins will be set as part of before_run

    def _check_args(self, indices, times, period, N, sorted, dt):
        times = Quantity(times)
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))
        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period != 0 * second:
            period_bins = np.round(period / dt)
            # Note: we have to use the timestep function here, to use the same
            # binning as in the actual simulation
            max_bin = timestep(np.max(times), dt)
            if max_bin >= period_bins:
                raise ValueError('The period has to be greater than the '
                                 'maximum of the spike times')
        if len(times) and np.min(times) < 0 * second:
            raise ValueError('Spike times cannot be negative')
        if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N):
            raise ValueError('Indices have to lie in the interval [0, %d[' % N)

        times = np.asarray(times)
        indices = np.asarray(indices)
        if not sorted:
            # sort times and indices first by time, then by indices
            I = np.lexsort((indices, times))
            indices = indices[I]
            times = times[I]

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._neuron_index = indices
        self._spike_time = times
        self._spikes_changed = True

        return indices, times

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(
                    cls=self.__class__.__name__,
                    N=self.N,
                    l=self.variables['neuron_index'].size)
Ejemplo n.º 8
0
class Synapses(Group):
    '''
    Class representing synaptic connections. Creating a new `Synapses` object
    does by default not create any synapses -- you either have to provide
    the `connect` argument or call the `Synapses.connect` method for that.

    Parameters
    ----------

    source : `SpikeSource`
        The source of spikes, e.g. a `NeuronGroup`.
    target : `Group`, optional
        The target of the spikes, typically a `NeuronGroup`. If none is given,
        the same as `source`
    model : {`str`, `Equations`}, optional
        The model equations for the synapses.
    pre : {str, dict}, optional
        The code that will be executed after every pre-synaptic spike. Can be
        either a single (possibly multi-line) string, or a dictionary mapping
        pathway names to code strings. In the first case, the pathway will be
        called ``pre`` and made available as an attribute of the same name.
        In the latter case, the given names will be used as the
        pathway/attribute names. Each pathway has its own code and its own
        delays.
    post : {str, dict}, optional
        The code that will be executed after every post-synaptic spike. Same
        conventions as for `pre`, the default name for the pathway is ``post``.
    connect : {str, bool}. optional
        Determines whether any actual synapses are created. ``False`` (the
        default) means not to create any synapses, ``True`` means to create
        synapses between all source/target pairs. Also accepts a string
        expression that evaluates to ``True`` for every synapse that should
        be created, e.g. ``'i == j'`` for a one-to-one connectivity. See
        `Synapses.connect` for more details.
    delay : {`Quantity`, dict}, optional
        The delay for the "pre" pathway (same for all synapses) or a dictionary
        mapping pathway names to delays. If a delay is specified in this way
        for a pathway, it is stored as a single scalar value. It can still
        be changed afterwards, but only to a single scalar value. If you want
        to have delays that vary across synapses, do not use the keyword
        argument, but instead set the delays via the attribute of the pathway,
        e.g. ``S.pre.delay = ...`` (or ``S.delay = ...`` as an abbreviation),
        ``S.post.delay = ...``, etc.
    namespace : dict, optional
        A dictionary mapping identifier names to objects. If not given, the
        namespace will be filled in at the time of the call of `Network.run`,
        with either the values from the ``network`` argument of the
        `Network.run` method or from the local context, if no such argument is
        given.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to use to run code.
    clock : `Clock`, optional
        The clock to use.
    method : {str, `StateUpdateMethod`}, optional
        The numerical integration method to use. If none is given, an
        appropriate one is automatically determined.
    name : str, optional
        The name for this object. If none is given, a unique name of the form
        ``synapses``, ``synapses_1``, etc. will be automatically chosen.
    '''
    def __init__(self,
                 source,
                 target=None,
                 model=None,
                 pre=None,
                 post=None,
                 connect=False,
                 delay=None,
                 namespace=None,
                 dtype=None,
                 codeobj_class=None,
                 clock=None,
                 method=None,
                 name='synapses*'):
        self._N = 0
        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        self.source = weakref.proxy(source)
        if target is None:
            self.target = self.source
        else:
            self.target = weakref.proxy(target)

        ##### Prepare and validate equations
        if model is None:
            model = ''

        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({
            DIFFERENTIAL_EQUATION: ['event-driven'],
            SUBEXPRESSION: ['summed', 'scalar'],
            PARAMETER: ['constant', 'scalar']
        })

        # Add the lastupdate variable, needed for event-driven updates
        if 'lastupdate' in model._equations:
            raise SyntaxError('lastupdate is a reserved name.')
        model._equations['lastupdate'] = SingleEquation(
            PARAMETER, 'lastupdate', second)
        self._create_variables(model)

        # Separate the equations into event-driven equations,
        # continuously updated equations and summed variable updates
        event_driven = []
        continuous = []
        summed_updates = []
        for single_equation in model.itervalues():
            if 'event-driven' in single_equation.flags:
                event_driven.append(single_equation)
            elif 'summed' in single_equation.flags:
                summed_updates.append(single_equation)
            else:
                continuous.append(single_equation)

        if len(event_driven):
            self.event_driven = Equations(event_driven)
        else:
            self.event_driven = None

        self.equations = Equations(continuous)

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        #: Set of `Variable` objects that should be resized when the
        #: number of synapses changes
        self._registered_variables = set()

        for varname, var in self.variables.iteritems():
            if isinstance(var, DynamicArrayVariable):
                # Register the array with the `SynapticItemMapping` object so
                # it gets automatically resized
                self.register_variable(var)

        if delay is None:
            delay = {}

        if isinstance(delay, Quantity):
            delay = {'pre': delay}
        elif not isinstance(delay, collections.Mapping):
            raise TypeError('Delay argument has to be a quantity or a '
                            'dictionary, is type %s instead.' % type(delay))

        #: List of names of all updaters, e.g. ['pre', 'post']
        self._synaptic_updaters = []
        #: List of all `SynapticPathway` objects
        self._pathways = []
        for prepost, argument in zip(('pre', 'post'), (pre, post)):
            if not argument:
                continue
            if isinstance(argument, basestring):
                pathway_delay = delay.get(prepost, None)
                self._add_updater(argument, prepost, delay=pathway_delay)
            elif isinstance(argument, collections.Mapping):
                for key, value in argument.iteritems():
                    if not isinstance(key, basestring):
                        err_msg = ('Keys for the "{}" argument'
                                   'have to be strings, got '
                                   '{} instead.').format(prepost, type(key))
                        raise TypeError(err_msg)
                    pathway_delay = delay.get(key, None)
                    self._add_updater(value,
                                      prepost,
                                      objname=key,
                                      delay=pathway_delay)

        # Check whether any delays were specified for pathways that don't exist
        for pathway in delay:
            if not pathway in self._synaptic_updaters:
                raise ValueError(('Cannot set the delay for pathway '
                                  '"%s": unknown pathway.') % pathway)

        # If we have a pathway called "pre" (the most common use case), provide
        # direct access to its delay via a delay attribute (instead of having
        # to use pre.delay)
        if 'pre' in self._synaptic_updaters:
            self.variables.add_reference('delay', self.pre.variables['delay'])

        #: Performs numerical integration step
        self.state_updater = None

        # We only need a state update if we have differential equations
        if len(self.equations.diff_eq_names):
            self.state_updater = StateUpdater(self, method)
            self.contained_objects.append(self.state_updater)

        #: "Summed variable" mechanism -- sum over all synapses of a
        #: pre-/postsynaptic target
        self.summed_updaters = {}
        # We want to raise an error if the same variable is updated twice
        # using this mechanism. This could happen if the Synapses object
        # connected a NeuronGroup to itself since then all variables are
        # accessible as var_pre and var_post.
        summed_targets = set()
        for single_equation in summed_updates:
            varname = single_equation.varname
            if not (varname.endswith('_pre') or varname.endswith('_post')):
                raise ValueError(('The summed variable "%s" does not end '
                                  'in "_pre" or "_post".') % varname)
            if not varname in self.variables:
                raise ValueError(('The summed variable "%s" does not refer'
                                  'do any known variable in the '
                                  'target group.') % varname)
            if varname.endswith('_pre'):
                summed_target = self.source
                orig_varname = varname[:-4]
            else:
                summed_target = self.target
                orig_varname = varname[:-5]

            target_eq = getattr(summed_target, 'equations',
                                {}).get(orig_varname, None)
            if target_eq is None or target_eq.type != PARAMETER:
                raise ValueError(('The summed variable "%s" needs a '
                                  'corresponding parameter "%s" in the '
                                  'target group.') % (varname, orig_varname))

            fail_for_dimension_mismatch(
                self.variables['_summed_' + varname].unit,
                self.variables[varname].unit, ('Summed variables need to have '
                                               'the same units in Synapses '
                                               'and the target group'))
            if self.variables[varname] in summed_targets:
                raise ValueError(('The target variable "%s" is already '
                                  'updated by another summed '
                                  'variable') % orig_varname)
            summed_targets.add(self.variables[varname])
            updater = SummedVariableUpdater(single_equation.expr, varname,
                                            self, summed_target)
            self.summed_updaters[varname] = updater
            self.contained_objects.append(updater)

        # Do an initial connect, if requested
        if not isinstance(connect, (bool, basestring)):
            raise TypeError(
                ('"connect" keyword has to be a boolean value or a '
                 'string, is type %s instead.' % type(connect)))
        self._initial_connect = connect
        if not connect is False:
            self.connect(connect, level=1)

        # Activate name attribute access
        self._enable_group_attributes()

    def __len__(self):
        return self._N

    def before_run(self, run_namespace=None, level=0):
        self.lastupdate = self.clock.t
        super(Synapses, self).before_run(run_namespace, level=level + 1)

    def _add_updater(self, code, prepost, objname=None, delay=None):
        '''
        Add a new target updater. Users should call `add_pre` or `add_post`
        instead.

        Parameters
        ----------
        code : str
            The abstract code that should be executed on pre-/postsynaptic
            spikes.
        prepost : {'pre', 'post'}
            Whether the code is triggered by presynaptic or postsynaptic spikes
        objname : str, optional
            A name for the object, see `SynapticPathway` for more details.
        delay : `Quantity`, optional
            A scalar delay (same delay for all synapses) for this pathway. If
            not given, delays are expected to vary between synapses.

        Returns
        -------
        objname : str
            The final name for the object. Equals `objname` if it was explicitly
            given (and did not end in a wildcard character).

        '''
        if prepost == 'pre':
            spike_group, group_name = self.source, 'Source'
        elif prepost == 'post':
            spike_group, group_name = self.target, 'Target'
        else:
            raise ValueError(('"prepost" argument has to be "pre" or "post", '
                              'is "%s".') % prepost)

        if not isinstance(spike_group, SpikeSource) or not hasattr(
                spike_group, 'clock'):
            raise TypeError(('%s has to be a SpikeSource with spikes and'
                             ' clock attribute. Is type %r instead') %
                            (group_name, type(spike_group)))

        updater = SynapticPathway(self, code, prepost, objname, delay)
        objname = updater.objname
        if hasattr(self, objname):
            raise ValueError(
                ('Cannot add updater with name "{name}", synapses '
                 'object already has an attribute with this '
                 'name.').format(name=objname))

        setattr(self, objname, updater)
        self._synaptic_updaters.append(objname)
        self._pathways.append(updater)
        self.contained_objects.append(updater)
        return objname

    def _create_variables(self, equations, user_dtype=None):
        '''
        Create the variables dictionary for this `Synapses`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)

        # Standard variables always present
        self.variables.add_dynamic_array('_synaptic_pre',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=True)
        self.variables.add_dynamic_array('_synaptic_post',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=True)

        self.variables.add_reference('i',
                                     self.source.variables['i'],
                                     index='_presynaptic_idx')
        self.variables.add_reference('j',
                                     self.target.variables['i'],
                                     index='_postsynaptic_idx')

        if '_offset' in self.target.variables:
            target_offset = self.target.variables['_offset'].get_value()
        else:
            target_offset = 0
        if '_offset' in self.source.variables:
            source_offset = self.source.variables['_offset'].get_value()
        else:
            source_offset = 0
        self.variables.add_array('N_incoming',
                                 size=len(self.target) + target_offset,
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 constant=True,
                                 read_only=True,
                                 index='_postsynaptic_idx')
        self.variables.add_array('N_outgoing',
                                 size=len(self.source) + source_offset,
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 constant=True,
                                 read_only=True,
                                 index='_presynaptic_idx')

        # We have to make a distinction here between the indices
        # and the arrays (even though they refer to the same object)
        # the synaptic propagation template would otherwise overwrite
        # synaptic_post in its namespace with the value of the
        # postsynaptic index, leading to errors for the next
        # propagation.
        self.variables.add_reference('_presynaptic_idx',
                                     self.variables['_synaptic_pre'])
        self.variables.add_reference('_postsynaptic_idx',
                                     self.variables['_synaptic_post'])

        # Add the standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_attribute_variable('N',
                                              Unit(1),
                                              self,
                                              '_N',
                                              constant=True)

        for eq in equations.itervalues():
            dtype = get_dtype(eq, user_dtype)
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = 'constant' in eq.flags
                scalar = 'scalar' in eq.flags
                if scalar:
                    self.variables.add_array(eq.varname,
                                             size=1,
                                             unit=eq.unit,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=True,
                                             index='0')
                else:
                    # We are dealing with dynamic arrays here, code generation
                    # shouldn't directly access the specifier.array attribute but
                    # use specifier.get_value() to get a reference to the underlying
                    # array
                    self.variables.add_dynamic_array(eq.varname,
                                                     size=0,
                                                     unit=eq.unit,
                                                     dtype=dtype,
                                                     constant=constant)
            elif eq.type == SUBEXPRESSION:
                if 'summed' in eq.flags:
                    # Give a special name to the subexpression for summed
                    # variables to avoid confusion with the pre/postsynaptic
                    # target variable
                    varname = '_summed_' + eq.varname
                else:
                    varname = eq.varname
                self.variables.add_subexpression(varname,
                                                 unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 scalar='scalar' in eq.flags,
                                                 dtype=dtype)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Stochastic variables
        for xi in equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Add all the pre and post variables with _pre and _post suffixes
        for name, var in getattr(self.source, 'variables', {}).iteritems():
            index = '0' if var.scalar else '_presynaptic_idx'
            self.variables.add_reference(name + '_pre', var, index=index)
        for name, var in getattr(self.target, 'variables', {}).iteritems():
            index = '0' if var.scalar else '_postsynaptic_idx'
            self.variables.add_reference(name + '_post', var, index=index)
            # Also add all the post variables without a suffix -- note that a
            # reference will never overwrite the name of an existing name
            self.variables.add_reference(name, var, index=index)

        # Check scalar subexpressions
        for eq in equations.itervalues():
            if eq.type == SUBEXPRESSION and 'scalar' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Scalar subexpression %s refers '
                                 'to non-scalar variable %s.') %
                                (eq.varname, identifier))

    def connect(self,
                pre_or_cond,
                post=None,
                p=1.,
                n=1,
                namespace=None,
                level=0):
        '''
        Add synapses. The first argument can be either a presynaptic index
        (int or array) or a condition for synapse creation in the form of a
        string that evaluates to a boolean value (or directly a boolean value).
        If it is given as an index, also `post` has to be present. A string
        condition will be evaluated for all pre-/postsynaptic indices, which
        can be referred to as `i` and `j`.

        Parameters
        ----------
        pre_or_cond : {int, ndarray of int, bool, str}
            The presynaptic neurons (in the form of an index or an array of
            indices) or a boolean value or a string that evaluates to a
            boolean value. If it is an index, then also `post` has to be
            given.
        post_neurons : {int, ndarray of int), optional
            GroupIndices of neurons from the target group. Non-optional if one or
            more presynaptic indices have been given.
        p : float, optional
            The probability to create `n` synapses wherever the condition
            given as `pre_or_cond` evaluates to true or for the given
            pre/post indices.
        n : int, optional
            The number of synapses to create per pre/post connection pair.
            Defaults to 1.
        namespace : dict-like, optional
            A namespace that will be used in addition to the group-specific
            namespaces (if defined). If not specified, the locals
            and globals around the run function will be used.
        level : int, optional
            How deep to go up the stack frame to look for the locals/global
            (see `namespace` argument).

        Examples
        --------
        >>> from brian2 import *
        >>> import numpy as np
        >>> G = NeuronGroup(10, 'dv/dt = -v / tau : 1', threshold='v>1', reset='v=0')
        >>> S = Synapses(G, G, 'w:1', pre='v+=w')
        >>> S.connect('i != j') # all-to-all but no self-connections
        >>> S.connect(0, 0) # connect neuron 0 to itself
        >>> S.connect(np.array([1, 2]), np.array([2, 1])) # connect 1->2 and 2->1
        >>> S.connect(True) # connect all-to-all
        >>> S.connect('i != j', p=0.1)  # Connect neurons with 10% probability, exclude self-connections
        >>> S.connect('i == j', n=2)  # Connect all neurons to themselves with 2 synapses
        '''
        if not isinstance(pre_or_cond, (bool, basestring)):
            pre_or_cond = np.asarray(pre_or_cond)
            if not np.issubdtype(pre_or_cond.dtype, np.int):
                raise TypeError(
                    ('Presynaptic indices have to be given as '
                     'integers, are type %s instead.') % pre_or_cond.dtype)

            post = np.asarray(post)
            if not np.issubdtype(post.dtype, np.int):
                raise TypeError(('Presynaptic indices can only be combined '
                                 'with postsynaptic integer indices))'))
            if isinstance(n, basestring):
                raise TypeError(
                    ('Indices cannot be combined with a string'
                     'expression for n. Either use an array/scalar '
                     'for n, or a string expression for the '
                     'connections'))
            i, j, n = np.broadcast_arrays(pre_or_cond, post, n)
            if i.ndim > 1:
                raise ValueError('Can only use 1-dimensional indices')
            self._add_synapses(i,
                               j,
                               n,
                               p,
                               namespace=namespace,
                               level=level + 1)
        elif isinstance(pre_or_cond, (basestring, bool)):
            if pre_or_cond is False:
                return  # nothing to do...
            elif pre_or_cond is True:
                # TODO: This should not be handled with the general mechanism
                pre_or_cond = 'True'
            if post is not None:
                raise ValueError('Cannot give a postsynaptic index when '
                                 'using a string expression')
            if not isinstance(n, (int, basestring)):
                raise TypeError(
                    'n has to be an integer or a string evaluating '
                    'to an integer, is type %s instead.' % type(n))
            if not isinstance(p, (float, basestring)):
                raise TypeError('p has to be a float or a string evaluating '
                                'to an float, is type %s instead.' % type(n))
            self._add_synapses(None,
                               None,
                               n,
                               p,
                               condition=pre_or_cond,
                               namespace=namespace,
                               level=level + 1)
        else:
            raise TypeError(('First argument has to be an index or a '
                             'string, is %s instead.') % type(pre_or_cond))

    def _resize(self, number):
        if not isinstance(number, int):
            raise TypeError(('Expected an integer number got {} '
                             'instead').format(type(number)))
        if number < self._N:
            raise ValueError(('Cannot reduce number of synapses, '
                              '{} < {}').format(number, len(self)))

        for variable in self._registered_variables:
            variable.resize(number)

        self._N = number

    def register_variable(self, variable):
        '''
        Register a `DynamicArray` to be automatically resized when the size of
        the indices change. Called automatically when a `SynapticArrayVariable`
        specifier is created.
        '''
        if not hasattr(variable, 'resize'):
            raise TypeError(('Variable of type {} does not have a resize '
                             'method, cannot register it with the synaptic '
                             'indices.').format(type(variable)))
        self._registered_variables.add(variable)

    def unregister_variable(self, variable):
        '''
        Unregister a `DynamicArray` from the automatic resizing mechanism.
        '''
        self._registered_variables.remove(variable)

    def _add_synapses(self,
                      sources,
                      targets,
                      n,
                      p,
                      condition=None,
                      namespace=None,
                      level=0):

        if condition is None:
            sources = np.atleast_1d(sources).astype(np.int32)
            targets = np.atleast_1d(targets).astype(np.int32)
            n = np.atleast_1d(n)
            p = np.atleast_1d(p)
            if not len(p) == 1 or p != 1:
                use_connections = np.random.rand(len(sources)) < p
                sources = sources[use_connections]
                targets = targets[use_connections]
                n = n[use_connections]
            sources = sources.repeat(n)
            targets = targets.repeat(n)
            new_synapses = len(sources)

            old_N = len(self)
            new_N = old_N + new_synapses
            self._resize(new_N)

            # Deal with subgroups
            if '_sub_idx' in self.source.variables:
                real_sources = self.source.variables['_sub_idx'].get_value(
                )[sources]
            else:
                real_sources = sources
            if '_sub_idx' in self.target.variables:
                real_targets = self.target.variables['_sub_idx'].get_value(
                )[targets]
            else:
                real_targets = targets
            self.variables['_synaptic_pre'].get_value(
            )[old_N:new_N] = real_sources
            self.variables['_synaptic_post'].get_value(
            )[old_N:new_N] = real_targets

            self.variables['N_outgoing'].get_value()[:] += np.bincount(
                real_sources, minlength=self.variables['N_outgoing'].size)
            self.variables['N_incoming'].get_value()[:] += np.bincount(
                real_targets, minlength=self.variables['N_incoming'].size)
        else:
            abstract_code = '_pre_idx = _all_pre \n'
            abstract_code += '_post_idx = _all_post \n'
            abstract_code += '_cond = ' + condition + '\n'
            abstract_code += '_n = ' + str(n) + '\n'
            abstract_code += '_p = ' + str(p)
            # This overwrites 'i' and 'j' in the synapses' variables dictionary
            # This is necessary because in the context of synapse creation, i
            # and j do not correspond to the sources/targets of the existing
            # synapses but to all the possible sources/targets
            variables = Variables(None)
            # Will be set in the template
            variables.add_auxiliary_variable('_i', unit=Unit(1))
            variables.add_auxiliary_variable('_j', unit=Unit(1))
            # Make sure that variables have the correct type in the code
            variables.add_auxiliary_variable('_pre_idx',
                                             unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_post_idx',
                                             unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_cond',
                                             unit=Unit(1),
                                             dtype=np.bool)
            variables.add_auxiliary_variable('_n',
                                             unit=Unit(1),
                                             dtype=np.int32)
            variables.add_auxiliary_variable('_p', unit=Unit(1))

            if '_sub_idx' in self.source.variables:
                variables.add_reference('_all_pre',
                                        self.source.variables['_sub_idx'])
            else:
                variables.add_reference('_all_pre', self.source.variables['i'])

            if '_sub_idx' in self.target.variables:
                variables.add_reference('_all_post',
                                        self.target.variables['_sub_idx'])
            else:
                variables.add_reference('_all_post',
                                        self.target.variables['i'])

            variable_indices = defaultdict(lambda: '_idx')
            for varname in self.variables:
                if self.variables.indices[varname] == '_presynaptic_idx':
                    variable_indices[varname] = '_all_pre'
                elif self.variables.indices[varname] == '_postsynaptic_idx':
                    variable_indices[varname] = '_all_post'
            variable_indices['_all_pre'] = '_i'
            variable_indices['_all_post'] = '_j'
            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'synapses_create',
                                            variable_indices=variable_indices,
                                            additional_variables=variables,
                                            check_units=False,
                                            run_namespace=namespace,
                                            level=level + 1)
            codeobj()

    def calc_indices(self, index):
        '''
        Returns synaptic indices for `index`, which can be a tuple of indices
        (including arrays and slices), a single index or a string.

        '''
        if (not isinstance(index, (tuple, basestring)) and isinstance(
                index, (int, np.ndarray, slice, collections.Sequence))):
            index = (index, slice(None), slice(None))
        if isinstance(index, tuple):
            if len(index) == 2:  # two indices (pre- and postsynaptic cell)
                index = (index[0], index[1], slice(None))
            elif len(index) > 3:
                raise IndexError('Need 1, 2 or 3 indices, got %d.' %
                                 len(index))

            I, J, K = index

            pre_synapses = find_synapses(
                I, self.variables['_synaptic_pre'].get_value() -
                self.source.start)
            post_synapses = find_synapses(
                J, self.variables['_synaptic_post'].get_value() -
                self.target.start)
            matching_synapses = np.intersect1d(pre_synapses,
                                               post_synapses,
                                               assume_unique=True)

            if isinstance(K, slice) and K == slice(None):
                return matching_synapses
            elif isinstance(K, (int, slice)):
                test_k = slice_to_test(K)
            else:
                raise NotImplementedError(('Indexing synapses with arrays not'
                                           'implemented yet'))

            # We want to access the raw arrays here, not go through the Variable
            pre_neurons = self.variables['_synaptic_pre'].get_value(
            )[pre_synapses]
            post_neurons = self.variables['_synaptic_post'].get_value(
            )[post_synapses]
            synapse_numbers = _synapse_numbers(pre_neurons, post_neurons)
            return np.intersect1d(matching_synapses,
                                  np.flatnonzero(test_k(synapse_numbers)),
                                  assume_unique=True)
        else:
            raise IndexError(
                'Unsupported index type {itype}'.format(itype=type(index)))
Ejemplo n.º 9
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.

    TODO: all internal variables (u_minus etc) could be inserted in the SpatialNeuron.
    '''
    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)
        CodeRunner.__init__(self,
                            group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        n = len(group)  # total number of compartments
        segments = self.number_branches(group.morphology)
        self.variables = Variables(self, default_index='_segment_idx')
        self.variables.add_reference('N', group)
        self.variables.add_arange('_compartment_idx', size=n)
        self.variables.add_arange('_segment_idx', size=segments)
        self.variables.add_arange('_segment_root_idx', size=segments + 1)
        self.variables.add_arange('_P_idx', size=(segments + 1)**2)

        self.variables.add_array('_invr',
                                 unit=siemens,
                                 size=n,
                                 constant=True,
                                 index='_compartment_idx')
        self.variables.add_array('_P',
                                 unit=Unit(1),
                                 size=(segments + 1)**2,
                                 constant=True,
                                 index='_P_idx')
        self.variables.add_array('_B',
                                 unit=Unit(1),
                                 size=segments + 1,
                                 constant=True,
                                 index='_segment_root_idx')
        self.variables.add_array('_morph_i',
                                 unit=Unit(1),
                                 size=segments,
                                 dtype=np.int32,
                                 constant=True)
        self.variables.add_array('_morph_parent_i',
                                 unit=Unit(1),
                                 size=segments,
                                 dtype=np.int32,
                                 constant=True)
        self.variables.add_array('_starts',
                                 unit=Unit(1),
                                 size=segments,
                                 dtype=np.int32,
                                 constant=True)
        self.variables.add_array('_ends',
                                 unit=Unit(1),
                                 size=segments,
                                 dtype=np.int32,
                                 constant=True)
        self.variables.add_array('_invr0',
                                 unit=siemens,
                                 size=segments,
                                 constant=True)
        self.variables.add_array('_invrn',
                                 unit=siemens,
                                 size=segments,
                                 constant=True)
        self._enable_group_attributes()

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(segments, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(segments, dtype=np.int32)
        self._temp_starts = np.zeros(segments, dtype=np.int32)
        self._temp_ends = np.zeros(segments, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace=None, level=0):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(
                self.group,
                '',  # no code,
                'spatialneuron_prepare',
                name=self.name + '_spatialneuron_prepare',
                check_units=False,
                additional_variables=self.variables,
                run_namespace=run_namespace,
                level=level + 1)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.warn(
                    ('SpatialNeuron will use numpy to do the numerical '
                     'integration -- this will be very slow. Either '
                     'switch to a different code generation target '
                     '(e.g. weave or cython) or install scipy.'),
                    once=True)
        CodeRunner.before_run(self, run_namespace, level=level + 1)

    def _pre_calc_iteration(self, morphology, counter=0):
        self._temp_morph_i[counter] = morphology.index + 1
        self._temp_morph_parent_i[counter] = morphology.parent + 1
        self._temp_starts[counter] = morphology._origin
        self._temp_ends[counter] = morphology._origin + len(morphology.x) - 1
        total_count = 1
        for child in morphology.children:
            total_count += self._pre_calc_iteration(child,
                                                    counter + total_count)
        return total_count

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches
Ejemplo n.º 10
0
class Clock(VariableOwner):
    '''
    An object that holds the simulation time and the time step.
    
    Parameters
    ----------
    dt : float
        The time step of the simulation as a float
    name : str, optional
        An explicit name, if not specified gives an automatically generated name

    Notes
    -----
    Clocks are run in the same `Network.run` iteration if `~Clock.t` is the
    same. The condition for two
    clocks to be considered as having the same time is
    ``abs(t1-t2)<epsilon*abs(t1)``, a standard test for equality of floating
    point values. The value of ``epsilon`` is ``1e-14``.
    '''
    def __init__(self, dt, name='clock*'):
        # We need a name right away because some devices (e.g. cpp_standalone)
        # need a name for the object when creating the variables
        Nameable.__init__(self, name=name)
        self._old_dt = None
        self.variables = Variables(self)
        self.variables.add_array('timestep',
                                 unit=Unit(1),
                                 size=1,
                                 dtype=np.uint64,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_array('t',
                                 unit=second,
                                 size=1,
                                 dtype=np.double,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_array('dt',
                                 unit=second,
                                 size=1,
                                 values=float(dt),
                                 dtype=np.float,
                                 read_only=True,
                                 constant=True,
                                 scalar=True)
        self.variables.add_constant('N', unit=Unit(1), value=1)
        self._enable_group_attributes()
        self.dt = dt
        logger.diagnostic("Created clock {name} with dt={dt}".format(
            name=self.name, dt=self.dt))

    @check_units(t=second)
    def _set_t_update_dt(self, target_t=0 * second):
        new_dt = self.dt_
        old_dt = self._old_dt
        target_t = float(target_t)
        if old_dt is not None and new_dt != old_dt:
            self._old_dt = None
            # Only allow a new dt which allows to correctly set the new time step
            if target_t != self.t_:
                old_t = np.uint64(np.round(target_t / old_dt)) * old_dt
                new_t = np.uint64(np.round(target_t / new_dt)) * new_dt
                error_t = target_t
            else:
                old_t = np.uint64(np.round(self.t_ / old_dt)) * old_dt
                new_t = np.uint64(np.round(self.t_ / new_dt)) * new_dt
                error_t = self.t_
            if abs(new_t - old_t) > self.epsilon:
                raise ValueError(('Cannot set dt from {old} to {new}, the '
                                  'time {t} is not a multiple of '
                                  '{new}').format(old=old_dt * second,
                                                  new=new_dt * second,
                                                  t=error_t * second))

        new_i = np.uint64(np.round(target_t / new_dt))
        new_t = new_i * new_dt
        if (new_t == target_t
                or np.abs(new_t - target_t) <= self.epsilon * np.abs(new_t)):
            new_timestep = new_i
        else:
            new_timestep = np.uint64(np.ceil(target_t / new_dt))
        # Since these attributes are read-only for normal users, we have to
        # update them via the variables object directly
        self.variables['timestep'].set_value(new_timestep)
        self.variables['t'].set_value(new_timestep * new_dt)
        logger.diagnostic("Setting Clock {name} to t={t}, dt={dt}".format(
            name=self.name, t=self.t, dt=self.dt))

    def __repr__(self):
        return 'Clock(dt=%r, name=%r)' % (self.dt, self.name)

    def _get_dt_(self):
        return self.variables['dt'].get_value().item()

    @check_units(dt_=1)
    def _set_dt_(self, dt_):
        self._old_dt = self._get_dt_()
        self.variables['dt'].set_value(dt_)

    @check_units(dt=second)
    def _set_dt(self, dt):
        self._set_dt_(float(dt))

    dt = property(
        fget=lambda self: Quantity(self.dt_, dim=second.dim),
        fset=_set_dt,
        doc='''The time step of the simulation in seconds.''',
    )
    dt_ = property(
        fget=_get_dt_,
        fset=_set_dt_,
        doc='''The time step of the simulation as a float (in seconds)''')

    @check_units(start=second, end=second)
    def set_interval(self, start, end):
        '''
        set_interval(self, start, end)
        
        Set the start and end time of the simulation.
        
        Sets the start and end value of the clock precisely if
        possible (using epsilon) or rounding up if not. This assures that
        multiple calls to `Network.run` will not re-run the same time step.      
        '''
        self._set_t_update_dt(target_t=start)
        end = float(end)
        i_end = np.uint64(np.round(end / self.dt_))
        t_end = i_end * self.dt_
        if t_end == end or np.abs(t_end - end) <= self.epsilon * np.abs(t_end):
            self._i_end = i_end
        else:
            self._i_end = np.uint64(np.ceil(end / self.dt_))

    epsilon = 1e-14
Ejemplo n.º 11
0
class PoissonGroup(Group, SpikeSource):
    '''
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate. This string expression will be evaluated at every
        time step, it can therefore be time-dependent (e.g. refer to a
        `TimedArray`).
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.
    '''
    add_to_magic_network = True

    @check_units(rates=Hz)
    def __init__(self, N, rates, dt=None, clock=None, when='thresholds',
                 order=0, namespace=None, name='poissongroup*',
                 codeobj_class=None):

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        if isinstance(rates, basestring):
            self.variables.add_subexpression('rates', dimensions=Hz.dim,
                                             expr=rates)
        else:
            self.variables.add_array('rates', size=N, dimensions=Hz.dim)
        self._rates = rates

        self.start = 0
        self.stop = N

        self._refractory = False

        self.events = {'spike': 'rand() < rates * dt'}
        self.thresholder = {'spike': Thresholder(self)}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        if not isinstance(rates, basestring):
            self.rates = rates

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError('Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    def before_run(self, run_namespace=None):
        rates_var = self.variables['rates']
        if isinstance(rates_var, Subexpression):
            # Check that the units of the expression make sense
            expr = rates_var.expr
            identifiers = get_identifiers(expr)
            variables = self.resolve_all(identifiers,
                                         run_namespace,
                                         user_identifiers=identifiers)
            unit = parse_expression_dimensions(rates_var.expr, variables)
            fail_for_dimension_mismatch(unit, Hz, "The expression provided for "
                                                  "PoissonGroup's 'rates' "
                                                  "argument, has to have units "
                                                  "of Hz")
        super(PoissonGroup, self).before_run(run_namespace)

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        description = '{classname}({N}, rates={rates})'
        return description.format(classname=self.__class__.__name__,
                                  N=self.N, rates=repr(self._rates))
Ejemplo n.º 12
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    namespace: dict, optional
        A dictionary mapping variable/function names to the respective objects.
        If no `namespace` is given, the "implicit" namespace, consisting of
        the local and global namespace surrounding the creation of the class,
        is used.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_scalar_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    clock : Clock, optional
        The update clock to be used, or defaultclock if not specified.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of `Scheduler.when` take these values). The `Scheduler.order`
    attribute is set to 0 initially, but this can be modified using the
    attributes `state_updater`, `thresholder` and `resetter`.    
    '''
    def __init__(self, N, model, method=None,
                 threshold=None,
                 reset=None,
                 refractory=False,
                 namespace=None,
                 dtype=None,
                 clock=None, name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError("First NeuronGroup argument should be size, not equations.")
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " + str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({DIFFERENTIAL_EQUATION: ('unless refractory'),
                           PARAMETER: ('constant')})

        # add refractoriness
        if refractory is not False:
            model = add_refractoriness(model)
        self.equations = model
        uses_refractoriness = len(model) and any(['unless refractory' in eq.flags
                                                  for eq in model.itervalues()
                                                  if eq.type == DIFFERENTIAL_EQUATION])

        logger.debug("Creating NeuronGroup of size {self._N}, "
                     "equations {self.equations}.".format(self=self))

        # Setup the namespace
        self.namespace = create_namespace(namespace)

        # Setup variables
        self._create_variables(dtype)

        # All of the following will be created in before_run
        
        #: The threshold condition
        self.threshold = threshold
        
        #: The reset statement(s)
        self.reset = reset

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn('Model equations use the "unless refractory" flag but '
                        'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method
        
        #: Performs thresholding step, sets the value of `spikes`
        self.thresholder = None
        if self.threshold is not None:
            self.thresholder = Thresholder(self)
            

        #: Resets neurons which have spiked (`spikes`)
        self.resetter = None
        if self.reset is not None:
            self.resetter = Resetter(self)

        # We try to run a before_run already now. This might fail because of an
        # incomplete namespace but if the namespace is already complete we
        # can spot unit errors in the equation already here.
        try:
            self.before_run(None)
        except KeyError:
            pass

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method)

        # Creation of contained_objects that do the work
        self.contained_objects.append(self.state_updater)
        if self.thresholder is not None:
            self.contained_objects.append(self.thresholder)
        if self.resetter is not None:
            self.contained_objects.append(self.resetter)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-np.inf*second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()


    def __len__(self):
        '''
        Return number of neurons in the group.
        '''
        return self.N

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError('Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    def runner(self, code, when=None, name=None):
        '''
        Returns a `CodeRunner` that runs abstract code in the groups namespace
        
        Parameters
        ----------
        code : str
            The abstract code to run.
        when : `Scheduler`, optional
            When to run, by default in the 'start' slot with the same clock as
            the group.
        name : str, optional
            A unique name, if non is given the name of the group appended with
            'runner', 'runner_1', etc. will be used. If a name is given
            explicitly, it will be used as given (i.e. the group name will not
            be prepended automatically).
        '''
        if when is None:  # TODO: make this better with default values
            when = Scheduler(clock=self.clock)
        else:
            raise NotImplementedError

        if name is None:
            name = self.name + '_runner*'

        runner = GroupCodeRunner(self, self.language.template_state_update,
                                 code=code, name=name, when=when)
        return runner

    def _create_variables(self, dtype=None):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', Unit(1), self._N)

        if dtype is None:
            dtype = defaultdict(lambda: brian_prefs['core.default_scalar_dtype'])
        elif isinstance(dtype, np.dtype):
            dtype = defaultdict(lambda: dtype)
        elif not hasattr(dtype, '__getitem__'):
            raise TypeError(('Cannot use type %s as dtype '
                             'specification') % type(dtype))

        # Standard variables always present
        self.variables.add_array('_spikespace', unit=Unit(1), size=self._N+1,
                                 dtype=np.int32, constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i', size=self._N, constant=True,
                                  read_only=True)

        for eq in self.equations.itervalues():
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = ('constant' in eq.flags)
                self.variables.add_array(eq.varname, size=self._N,
                                         unit=eq.unit, dtype=dtype[eq.varname],
                                         constant=constant, is_bool=eq.is_bool)
            elif eq.type == STATIC_EQUATION:
                self.variables.add_subexpression(eq.varname, unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 is_bool=eq.is_bool)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

    def before_run(self, namespace):
    
        # Update the namespace information in the variables in case the
        # namespace was not specified explicitly defined at creation time
        # Note that values in the explicit namespace might still change
        # between runs, but the Subexpression stores a reference to 
        # self.namespace so these changes are taken into account automatically
        if not self.namespace.is_explicit:
            for var in self.variables.itervalues():
                if isinstance(var, Subexpression):
                    var.additional_namespace = namespace

        # Check units
        self.equations.check_units(self.namespace, self.variables,
                                   namespace)
    
    def _repr_html_(self):
        text = [r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))
        text.append(r'<b>Integration method:</b><br>')
        text.append(sympy.latex(self.state_updater.method)+'<br>')
        if self.threshold is not None:
            text.append(r'<b>Threshold condition:</b><br>')
            text.append('<code>%s</code><br>' % str(self.threshold))
            text.append('')
        if self.reset is not None:
            text.append(r'<b>Reset statement:</b><br>')            
            text.append(r'<code>%s</code><br>' % str(self.reset))
            text.append('')
                    
        return '\n'.join(text)
Ejemplo n.º 13
0
class SpatialStateUpdater(CodeRunner, Group):
    """
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.
    """

    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)

        compartments = group.flat_morphology.n
        sections = group.flat_morphology.sections

        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code="""_gtot = gtot__private
                                    _I0 = I0__private""",
                            clock=clock,
                            when='groups',
                            order=order,
                            name=f"{group.name}_spatialstateupdater*",
                            check_units=False,
                            template_kwds={'number_sections': sections})

        self.variables = Variables(self, default_index='_section_idx')
        self.variables.add_reference('N', group)
        # One value per compartment
        self.variables.add_arange('_compartment_idx', size=compartments)
        self.variables.add_array('_invr', dimensions=siemens.dim,
                                 size=compartments, constant=True,
                                 index='_compartment_idx')
        # one value per section
        self.variables.add_arange('_section_idx', size=sections)
        self.variables.add_array('_P_parent', size=sections,
                                 constant=True)  # elements below diagonal
        self.variables.add_arrays(['_morph_idxchild', '_morph_parent_i',
                                   '_starts', '_ends'], size=sections,
                                  dtype=np.int32, constant=True)
        self.variables.add_arrays(['_invr0', '_invrn'], dimensions=siemens.dim,
                                  size=sections, constant=True)
        # one value per section + 1 value for the root
        self.variables.add_arange('_section_root_idx', size=sections+1)
        self.variables.add_array('_P_diag', size=sections+1,
                                 constant=True, index='_section_root_idx')
        self.variables.add_array('_B', size=sections+1,
                                 constant=True, index='_section_root_idx')
        self.variables.add_array('_morph_children_num',
                                 size=sections+1, dtype=np.int32,
                                 constant=True, index='_section_root_idx')
        # 2D matrices of size (sections + 1) x max children per section
        self.variables.add_arange('_morph_children_idx',
                                  size=len(group.flat_morphology.morph_children))
        self.variables.add_array('_P_children',
                                 size=len(group.flat_morphology.morph_children),
                                 index='_morph_children_idx',
                                 constant=True)  # elements above diagonal
        self.variables.add_array('_morph_children',
                                 size=len(group.flat_morphology.morph_children),
                                 dtype=np.int32, constant=True,
                                 index='_morph_children_idx')
        self._enable_group_attributes()

        self._morph_parent_i = group.flat_morphology.morph_parent_i
        self._morph_children_num = group.flat_morphology.morph_children_num
        self._morph_children = group.flat_morphology.morph_children
        self._morph_idxchild = group.flat_morphology.morph_idxchild
        self._starts = group.flat_morphology.starts
        self._ends = group.flat_morphology.ends

    def before_run(self, run_namespace):
        super(SpatialStateUpdater, self).before_run(run_namespace)
        # Raise a warning if the slow pure numpy version is used
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if type(self.code_objects[0]) == NumpyCodeObject:
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.info(('SpatialNeuron will use numpy to do the numerical '
                             'integration -- this will be very slow. Either '
                             'switch to a different code generation target '
                             '(e.g. cython) or install scipy.'),
                            once=True)
Ejemplo n.º 14
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.

    TODO: all internal variables (u_minus etc) could be inserted in the SpatialNeuron.
    '''
    def __init__(self, group, method):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self._isprepared = False
        CodeRunner.__init__(self,
                            group,
                            'spatialstateupdate',
                            when=(group.clock, 'groups', 1),
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        self.abstract_code = '''
        _gtot = gtot__private
        _I0 = I0__private
        '''
        N = len(self.group)
        self.ab_star = zeros((3, N))
        self.ab_plus = zeros((3, N))
        self.ab_minus = zeros((3, N))
        self.b_plus = zeros(N)
        self.b_minus = zeros(N)
        self.v_star = zeros(N)
        self.u_plus = zeros(N)
        self.u_minus = zeros(N)
        self.variables = Variables(self)
        # These 5 variables are constant after prepare()
        self.variables.add_array('ab_star',
                                 Unit(1),
                                 3 * N,
                                 values=self.ab_star.flatten(),
                                 dtype=self.ab_star.dtype)
        self.variables.add_array('ab_plus',
                                 Unit(1),
                                 3 * N,
                                 values=self.ab_plus.flatten(),
                                 dtype=self.ab_plus.dtype)
        self.variables.add_array('ab_minus',
                                 Unit(1),
                                 3 * N,
                                 values=self.ab_minus.flatten(),
                                 dtype=self.ab_minus.dtype)
        self.variables.add_array('b_plus',
                                 Unit(1),
                                 N,
                                 values=self.b_plus,
                                 dtype=self.b_plus.dtype)
        self.variables.add_array('b_minus',
                                 Unit(1),
                                 N,
                                 values=self.b_minus,
                                 dtype=self.b_minus.dtype)
        # These 3 variables change every time step
        self.variables.add_array('v_star',
                                 Unit(1),
                                 N,
                                 values=self.v_star,
                                 dtype=self.v_star.dtype)
        self.variables.add_array('u_plus',
                                 Unit(1),
                                 N,
                                 values=self.u_plus,
                                 dtype=self.u_plus.dtype)
        self.variables.add_array('u_minus',
                                 Unit(1),
                                 N,
                                 values=self.u_minus,
                                 dtype=self.u_minus.dtype)

    def before_run(self, run_namespace=None, level=0):
        if not self._isprepared:  # this is done only once even if there are multiple runs
            self.prepare()
            self._isprepared = True
        CodeRunner.before_run(self, run_namespace, level=level + 1)

    def run(self):
        CodeRunner.run(self)
        # Solve the linear system connecting branches
        self.P[:] = 0
        self.B[:] = 0
        self.fill_matrix(self.group.morphology)
        self.V = solve(
            self.P, self.B)  # This code could be generated at initialization
        # Calculate solutions by linear combination
        self.linear_combination(self.group.morphology)

    def prepare(self):
        '''
        Preparation of data structures.
        See the relevant document.
        '''
        # Correction for soma (a bit of a hack), so that it has negligible axial resistance
        if self.group.morphology.type == 'soma':
            self.group.length[0] = self.group.diameter[0] * 0.01
        # Inverse axial resistance
        self.invr = zeros(len(self.group)) * siemens
        self.invr[1:] = (pi / (2 * self.group.Ri) *
                         (self.group.diameter[:-1] * self.group.diameter[1:]) /
                         (self.group.length[:-1] + self.group.length[1:]))
        # Note: this would give nan for the soma
        self.cut_branches(self.group.morphology)

        # Linear systems
        # The particular solution
        '''a[i,j]=ab[u+i-j,j]'''  # u is the number of upper diagonals = 1
        self.ab_star[0, 1:] = self.invr[1:] / self.group.area[:-1]
        self.ab_star[2, :-1] = self.invr[1:] / self.group.area[1:]
        self.ab_star[1, :] = (-(self.group.Cm / self.group.clock.dt) -
                              self.invr / self.group.area)
        self.ab_star[1, :-1] -= self.invr[1:] / self.group.area[:-1]
        # Homogeneous solutions
        self.ab_plus[:] = self.ab_star
        self.ab_minus[:] = self.ab_star

        # Boundary conditions
        self.boundary_conditions(self.group.morphology)

        # Linear system for connecting branches
        n = 1 + self.number_branches(
            self.group.morphology)  # number of nodes (2 for the root)
        self.P = zeros((n, n))  # matrix
        self.B = zeros(n)  # vector RHS
        self.V = zeros(n)  # solution = voltages at nodes

        self.variables['ab_star'].set_value(self.ab_star.flatten())
        self.variables['ab_plus'].set_value(self.ab_plus.flatten())
        self.variables['ab_minus'].set_value(self.ab_minus.flatten())
        self.variables['b_plus'].set_value(self.b_plus)
        self.variables['b_minus'].set_value(self.b_minus)

    def cut_branches(self, morphology):
        '''
        Recursively cut the branches by setting zero axial resistances.
        '''
        self.invr[morphology._origin] = 0
        for kid in (morphology.children):
            self.cut_branches(kid)

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches

    def boundary_conditions(self, morphology):
        '''
        Recursively sets the boundary conditions in the linear systems.
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        # Inverse axial resistances at the ends: r0 and rn
        morphology.invr0 = float(pi / (2 * self.group.Ri) *
                                 self.group.diameter[first]**2 /
                                 self.group.length[first])
        morphology.invrn = float(pi / (2 * self.group.Ri) *
                                 self.group.diameter[last]**2 /
                                 self.group.length[last])
        # Correction for boundary conditions
        self.ab_star[1, first] -= float(
            morphology.invr0 /
            self.group.area[first])  # because of units problems
        self.ab_star[1,
                     last] -= float(morphology.invrn / self.group.area[last])
        self.ab_plus[1, first] -= float(
            morphology.invr0 /
            self.group.area[first])  # because of units problems
        self.ab_plus[1,
                     last] -= float(morphology.invrn / self.group.area[last])
        self.ab_minus[1, first] -= float(
            morphology.invr0 /
            self.group.area[first])  # because of units problems
        self.ab_minus[1,
                      last] -= float(morphology.invrn / self.group.area[last])
        # RHS for homogeneous solutions
        self.b_plus[last] = -float(morphology.invrn / self.group.area[last])
        self.b_minus[first] = -float(morphology.invr0 / self.group.area[first])
        # Recursive call
        for kid in (morphology.children):
            self.boundary_conditions(kid)

    # ### The two methods below should be written in C
    #### In each one there is a static function, plus a call for each segment
    #### Code for the latter could be generated at initialization
    def linear_combination(self, morphology):
        '''
        Calculates solutions by linear combination
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        i = morphology.index + 1
        i_parent = morphology.parent + 1
        v_star = self.variables['v_star'].get_value()
        u_minus = self.variables['u_minus'].get_value()
        u_plus = self.variables['u_plus'].get_value()
        self.group.v_[first:last +
                      1] = (v_star[first:last + 1] +
                            self.V[i_parent] * u_minus[first:last + 1] +
                            self.V[i] * u_plus[first:last + 1])
        # Recursive call
        for kid in (morphology.children):
            self.linear_combination(kid)

    def fill_matrix(self, morphology):
        '''
        Recursively fills the matrix of the linear system that connects
        branches together.
        Apparently this is quick.
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        i = morphology.index + 1
        i_parent = morphology.parent + 1
        v_star = self.variables['v_star'].get_value()
        u_minus = self.variables['u_minus'].get_value()
        u_plus = self.variables['u_plus'].get_value()
        # Towards parent
        if i == 1:  # first branch, sealed end
            self.P[0, 0] = u_minus[first] - 1
            self.P[0, 1] = u_plus[first]
            self.B[0] = -v_star[first]
        else:
            self.P[i_parent,
                   i_parent] += (1 - u_minus[first]) * morphology.invr0
            self.P[i_parent, i] -= u_plus[first] * morphology.invr0
            self.B[i_parent] += v_star[first] * morphology.invr0
        # Towards children
        self.P[i, i] = (1 - u_plus[last]) * morphology.invrn
        self.P[i, i_parent] = -u_minus[last] * morphology.invrn
        self.B[i] = v_star[last] * morphology.invrn
        # Recursive call
        for kid in (morphology.children):
            self.fill_matrix(kid)
Ejemplo n.º 15
0
class PoissonGroup(Group, SpikeSource):
    """
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate. This string expression will be evaluated at every
        time step, it can therefore be time-dependent (e.g. refer to a
        `TimedArray`).
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
        See :ref:`scheduling` for possible values.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.
    """
    add_to_magic_network = True

    @check_units(rates=Hz)
    def __init__(self,
                 N,
                 rates,
                 dt=None,
                 clock=None,
                 when='thresholds',
                 order=0,
                 namespace=None,
                 name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       namespace=namespace,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N + 1, dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        if isinstance(rates, str):
            self.variables.add_subexpression('rates',
                                             dimensions=Hz.dim,
                                             expr=rates)
        else:
            self.variables.add_array('rates', size=N, dimensions=Hz.dim)
        self._rates = rates

        self.start = 0
        self.stop = N

        self._refractory = False

        self.events = {'spike': 'rand() < rates * dt'}
        self.thresholder = {'spike': Thresholder(self)}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        if not isinstance(rates, str):
            self.rates = rates

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError(
                "Subgroups can only be constructed using slicing syntax")
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError("Subgroups have to be contiguous")
        if start >= stop:
            raise IndexError(
                f"Illegal start/end values for subgroup, {int(start)}>={int(stop)}"
            )

        return Subgroup(self, start, stop)

    def before_run(self, run_namespace=None):
        rates_var = self.variables['rates']
        if isinstance(rates_var, Subexpression):
            # Check that the units of the expression make sense
            expr = rates_var.expr
            identifiers = get_identifiers(expr)
            variables = self.resolve_all(identifiers,
                                         run_namespace,
                                         user_identifiers=identifiers)
            unit = parse_expression_dimensions(rates_var.expr, variables)
            fail_for_dimension_mismatch(
                unit, Hz, "The expression provided for "
                "PoissonGroup's 'rates' "
                "argument, has to have units "
                "of Hz")
        super(PoissonGroup, self).before_run(run_namespace)

    @property
    def spikes(self):
        """
        The spikes returned by the most recent thresholding operation.
        """
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        classname = self.__class__.__name__
        return f"{classname}({self.N}, rates={self._rates!r})"
Ejemplo n.º 16
0
class StateMonitor(Group, CodeRunner):
    """
    Record values of state variables during a run
    
    To extract recorded values after a run, use the ``t`` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where ``indices`` are the array indices which were recorded. When indexing
    the `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in ``source``, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : bool, sequence of ints
        Which indices to record, nothing is recorded for ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices.
    dt : `Quantity`, optional
        The time step to be used for the monitor. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the ``dt`` argument
        is specified, the clock of the `source` will be used.
    when : str, optional
        At which point during a time step the values should be recorded.
        Defaults to ``'start'``. See :ref:`scheduling` for possible values.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = '''
        dV/dt = (2-V)/(10*ms) : 1
        '''
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    Notes
    -----

    Since this monitor by default records in the ``'start'`` time slot,
    recordings of the membrane potential in integrate-and-fire models may look
    unexpected: the recorded membrane potential trace will never be above
    threshold in an integrate-and-fire model, because the reset statement will
    have been applied already. Set the ``when`` keyword to a different value if
    this is not what you want.

    Note that ``record=True`` only works in runtime mode for synaptic variables.
    This is because the actual array of indices has to be calculated and this is
    not possible in standalone mode, where the synapses have not been created
    yet at this stage. Consider using an explicit array of indices instead,
    i.e. something like ``record=np.arange(n_synapses)``.
    """
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, variables, record, dt=None, clock=None,
                 when='start', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            try:
                record = np.arange(len(source), dtype=np.int32)
            except NotImplementedError:
                # In standalone mode, this is not possible for synaptic
                # variables because the number of synapses is not defined yet
                raise NotImplementedError("Cannot determine the actual "
                                          "indices to record for record=True. "
                                          "This can occur for example in "
                                          "standalone mode when trying to "
                                          "record a synaptic variable. "
                                          "Consider providing an explicit "
                                          "array of indices for the record "
                                          "argument.")
        elif record is False:
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)

        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        if not self.record_all:
            try:
                if len(record) and (np.max(record) >= len(source) or np.min(record) < 0):
                    # Check whether the values in record make sense
                    error_message = (f"The indices to record from contain values outside of the range "
                                     f"[0, {len(source)-1}] allowed for the group '{source.name}'")
                    raise IndexError(error_message)
            except NotImplementedError:
                logger.warn("Cannot check whether the indices to record from are valid. This can happen "
                            "in standalone mode when recording from synapses that have been created with "
                            "a connection pattern. You can avoid this situation by using synaptic indices "
                            "in the connect call.", name_suffix='cannot_check_statemonitor_indices')

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = [f'_to_record_{v} = _source_{v}'
                for v in variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, dimensions=second.dim,
                                         constant=False,
                                         dtype=self._clock.variables['t'].dtype)
        self.variables.add_array('N', dtype=np.int32, size=1, scalar=True,
                                 read_only=True)
        self.variables.add_array('_indices', size=len(self.record),
                                 dtype=self.record.dtype, constant=True,
                                 read_only=True, values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference(f'_source_{varname}',
                                         source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array(varname,
                                             size=(0, len(self.record)),
                                             resize_along_first=True,
                                             dimensions=var.dim,
                                             dtype=var.dtype,
                                             constant=False,
                                             read_only=True)

        for varname in variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable(f"_to_record_{varname}",
                                                  dimensions=var.dim,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname, self.variables[varname])
                                        for varname in variables])
        recorded_names = [varname for varname in variables]

        self.needed_variables = recorded_names
        self.template_kwds = {'_recorded_variables': self.recorded_variables}
        self.written_readonly_vars = {self.variables[varname]
                                      for varname in self.record_variables}
        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['N'].set_value(new_size)
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.signedinteger):
            return StateMonitorView(self, item)
        elif isinstance(item, Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.signedinteger):
                raise TypeError("Index has to be an integer or a sequence "
                                "of integers")
            return StateMonitorView(self, item)
        elif hasattr(item, '_indices'):
            # objects that support the indexing interface will return absolute
            # indices but here we need relative ones
            # TODO: How to we prevent the use of completely unrelated objects here?
            source_offset = getattr(self.source, '_offset', 0)
            return StateMonitorView(self, item._indices() - source_offset)
        else:
            raise TypeError(f'Cannot use object of type {type(item)} as an index')

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            var_dim = self.variables[item].dim
            return Quantity(self.variables[item].get_value().T,
                            dim=var_dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables[item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        classname = self.__class__.__name__
        variables = repr(self.record_variables)
        return f"<{classname}, recording {variables} from '{self.source.name}'>"

    def record_single_timestep(self):
        """
        Records a single time step. Useful for recording the values at the end
        of the simulation -- otherwise a `StateMonitor` will not record the
        last simulated values since its ``when`` attribute defaults to
        ``'start'``, i.e. the last recording is at the *beginning* of the last
        time step.

        Notes
        -----
        This function will only work if the `StateMonitor` has been already run,
        but a run with a length of ``0*ms`` does suffice.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(1, 'dv/dt = -v/(5*ms) : 1')
        >>> G.v = 1
        >>> mon = StateMonitor(G, 'v', record=True)
        >>> run(0.5*ms)
        >>> print(np.array_str(mon.v[:], precision=3))
        [[ 1.     0.98   0.961  0.942  0.923]]
        >>> print(mon.t[:])
        [   0.  100.  200.  300.  400.] us
        >>> print(np.array_str(G.v[:], precision=3))  # last value had not been recorded
        [ 0.905]
        >>> mon.record_single_timestep()
        >>> print(mon.t[:])
        [   0.  100.  200.  300.  400.  500.] us
        >>> print(np.array_str(mon.v[:], precision=3))
        [[ 1.     0.98   0.961  0.942  0.923  0.905]]
        """
        if self.codeobj is None:
            raise TypeError("Can only record a single time step after the "
                            "network has been run once.")
        self.codeobj()
Ejemplo n.º 17
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    namespace: dict, optional
        A dictionary mapping variable/function names to the respective objects.
        If no `namespace` is given, the "implicit" namespace, consisting of
        the local and global namespace surrounding the creation of the class,
        is used.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    clock : Clock, optional
        The update clock to be used, or defaultclock if not specified.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of `Scheduler.when` take these values). The `Scheduler.order`
    attribute is set to 0 initially, but this can be modified using the
    attributes `state_updater`, `thresholder` and `resetter`.    
    '''
    def __init__(self,
                 N,
                 model,
                 method=None,
                 threshold=None,
                 reset=None,
                 refractory=False,
                 namespace=None,
                 dtype=None,
                 clock=None,
                 name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError(
                    "First NeuronGroup argument should be size, not equations."
                )
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " +
                             str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({
            DIFFERENTIAL_EQUATION: ('unless refractory', ),
            PARAMETER: ('constant', 'scalar'),
            SUBEXPRESSION: ('scalar', )
        })

        # add refractoriness
        if refractory is not False:
            model = add_refractoriness(model)
        self.equations = model
        uses_refractoriness = len(model) and any([
            'unless refractory' in eq.flags
            for eq in model.itervalues() if eq.type == DIFFERENTIAL_EQUATION
        ])

        logger.debug("Creating NeuronGroup of size {self._N}, "
                     "equations {self.equations}.".format(self=self))

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        # Setup variables
        self._create_variables(dtype)

        # All of the following will be created in before_run

        #: The threshold condition
        self.threshold = threshold

        #: The reset statement(s)
        self.reset = reset

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn(
                'Model equations use the "unless refractory" flag but '
                'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method

        #: Performs thresholding step, sets the value of `spikes`
        self.thresholder = None
        if self.threshold is not None:
            self.thresholder = Thresholder(self)

        #: Resets neurons which have spiked (`spikes`)
        self.resetter = None
        if self.reset is not None:
            self.resetter = Resetter(self)

        # We try to run a before_run already now. This might fail because of an
        # incomplete namespace but if the namespace is already complete we
        # can spot unit errors in the equation already here.
        try:
            self.before_run(None)
        except KeyError:
            pass

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method)

        # Creation of contained_objects that do the work
        self.contained_objects.append(self.state_updater)
        if self.thresholder is not None:
            self.contained_objects.append(self.thresholder)
        if self.resetter is not None:
            self.contained_objects.append(self.resetter)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-np.inf * second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()

    def __len__(self):
        '''
        Return number of neurons in the group.
        '''
        return self.N

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError(
                'Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    def _create_variables(self, user_dtype=None):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', Unit(1), self._N)

        # Standard variables always present
        self.variables.add_array('_spikespace',
                                 unit=Unit(1),
                                 size=self._N + 1,
                                 dtype=np.int32,
                                 constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i',
                                  size=self._N,
                                  constant=True,
                                  read_only=True)

        for eq in self.equations.itervalues():
            dtype = get_dtype(eq, user_dtype)

            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = 'constant' in eq.flags
                scalar = 'scalar' in eq.flags
                size = 1 if scalar else self._N
                index = '0' if scalar else None
                self.variables.add_array(eq.varname,
                                         size=size,
                                         unit=eq.unit,
                                         dtype=dtype,
                                         constant=constant,
                                         scalar=scalar,
                                         index=index)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname,
                                                 unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='scalar' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        for eq in self.equations.itervalues():
            if eq.type == DIFFERENTIAL_EQUATION and 'unless refractory' in eq.flags:
                not_refractory_var = self.variables['not_refractory']
                self.variables[eq.varname].set_conditional_write(
                    not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Check scalar subexpressions
        for eq in self.equations.itervalues():
            if eq.type == SUBEXPRESSION and 'scalar' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Scalar subexpression %s refers '
                                 'to non-scalar variable %s.') %
                                (eq.varname, identifier))

    def before_run(self, run_namespace=None, level=0):
        # Check units
        self.equations.check_units(self,
                                   run_namespace=run_namespace,
                                   level=level + 1)

    def _repr_html_(self):
        text = [
            r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)
        ]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))
        text.append(r'<b>Integration method:</b><br>')
        text.append(sympy.latex(self.state_updater.method) + '<br>')
        if self.threshold is not None:
            text.append(r'<b>Threshold condition:</b><br>')
            text.append('<code>%s</code><br>' % str(self.threshold))
            text.append('')
        if self.reset is not None:
            text.append(r'<b>Reset statement:</b><br>')
            text.append(r'<code>%s</code><br>' % str(self.reset))
            text.append('')

        return '\n'.join(text)
Ejemplo n.º 18
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    SpikeGeneratorGroup(N, indices, times, dt=None, clock=None,
                        period=1e100*second, when='thresholds', order=0,
                        sorted=False, name='spikegeneratorgroup*',
                        codeobj_class=None)

    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in ``indices``. Has to have the
        same length as ``indices``.
    period : `Quantity`, optional
        If this is specified, it will repeat spikes with this period.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    sorted : bool, optional
        Whether the given indices and times are already sorted. Set to ``True``
        if your events are already sorted (first by spike time, then by index),
        this can save significant time at construction if your arrays contain
        large numbers of spikes. Defaults to ``False``.

    Notes
    -----
    * In a time step, `SpikeGeneratorGroup` emits all spikes that happened
      at :math:`t-dt < t_{spike} \leq t`. This might lead to unexpected
      or missing spikes if you change the time step dt between runs.
    * `SpikeGeneratorGroup` does not currently raise any warning if a neuron
      spikes more that once during a time step, but other code (e.g. for
      synaptic propagation) might assume that neurons only spike once per
      time step and will therefore not work properly.
    * If `sorted` is set to ``True``, the given arrays will not be copied
      (only affects runtime mode)..
    '''
    @check_units(N=1, indices=1, times=second, period=second)
    def __init__(self,
                 N,
                 indices,
                 times,
                 dt=None,
                 clock=None,
                 period=1e100 * second,
                 when='thresholds',
                 order=0,
                 sorted=False,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):

        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when=when,
                       order=order,
                       name=name)

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        times = Quantity(times)
        if N < 1 or int(N) != N:
            raise TypeError('N has to be an integer >=1.')
        N = int(
            N)  # Make sure that it is an integer, values such as 10.0 would
        # otherwise make weave compilation fail
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))
        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError(
                'The period has to be greater than the maximum of '
                'the spike times')
        if len(times) and np.min(times) < 0 * second:
            raise ValueError('Spike times cannot be negative')
        if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N):
            raise ValueError('Indices have to lie in the interval [0, %d[' % N)

        self.start = 0
        self.stop = N

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._spike_time = times
        self._neuron_index = indices

        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_array('period',
                                 unit=second,
                                 size=1,
                                 constant=True,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         read_only=True,
                                         constant=True,
                                         index='spike_number',
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index',
                                         values=indices,
                                         size=len(indices),
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_dynamic_array('spike_time',
                                         values=times,
                                         size=len(times),
                                         unit=second,
                                         index='spike_number',
                                         read_only=True,
                                         constant=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_array('_lastindex',
                                 size=1,
                                 values=0,
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 read_only=True,
                                 scalar=True)
        self.variables.create_clock_variables(self._clock)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        CodeRunner.__init__(self,
                            self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)

    def before_run(self, run_namespace):
        # Do some checks on the period vs. dt
        dt = self.dt_[:]  # make a copy
        period = self.period_
        if period < np.inf:
            if period < dt:
                raise ValueError('The period of %s is %s, which is smaller '
                                 'than its dt of %s.' %
                                 (self.name, self.period, dt))
            if (abs(int(period / dt) * dt - period) >
                    period * np.finfo(dt.dtype).eps):
                raise NotImplementedError('The period of %s is %s, which is '
                                          'not an integer multiple of its dt '
                                          'of %s.' %
                                          (self.name, self.period, dt))

        # Check that we don't have more than one spike per neuron in a time bin
        if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed:
            # We shift all the spikes by a tiny amount to make sure that spikes
            # at exact multiples of dt do not end up in the previous time bin
            # This shift has to be quite significant relative to machine
            # epsilon, we use 1e-3 of the dt here
            shift = 1e-3 * dt
            timebins = np.asarray(np.asarray(self._spike_time + shift) / dt,
                                  dtype=np.int32)
            index_timebins = np.rec.fromarrays([self._neuron_index, timebins],
                                               names=['i', 't'])
            if not len(np.unique(index_timebins)) == len(timebins):
                raise ValueError('Using a dt of %s, some neurons of '
                                 'SpikeGeneratorGroup "%s" spike more than '
                                 'once during a time step.' %
                                 (str(self.dt), self.name))
            self._previous_dt = dt
            self._spikes_changed = False

        super(SpikeGeneratorGroup,
              self).before_run(run_namespace=run_namespace)

    @check_units(indices=1, times=second, period=second)
    def set_spikes(self, indices, times, period=1e100 * second, sorted=False):
        '''
        set_spikes(indices, times, period=1e100*second, sorted=False)

        Change the spikes that this group will generate.

        This can be used to set the input for a second run of a model based on
        the output of a first run (if the input for the second run is already
        known before the first run, then all the information should simply be
        included in the initial `SpikeGeneratorGroup` initializer call,
        instead).

        Parameters
        ----------
        indices : array of integers
            The indices of the spiking cells
        times : `Quantity`
            The spike times for the cells given in ``indices``. Has to have the
            same length as ``indices``.
        period : `Quantity`, optional
            If this is specified, it will repeat spikes with this period.
        sorted : bool, optional
            Whether the given indices and times are already sorted. Set to
            ``True`` if your events are already sorted (first by spike time,
            then by index), this can save significant time at construction if
            your arrays contain large numbers of spikes. Defaults to ``False``.
        '''
        times = Quantity(times)
        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))

        if period < 0 * second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError(
                'The period has to be greater than the maximum of '
                'the spike times')

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables['period'].set_value(period)
        self.variables['neuron_index'].resize(len(indices))
        self.variables['spike_time'].resize(len(indices))
        self.variables['spike_number'].resize(len(indices))
        self.variables['spike_number'].set_value(np.arange(len(indices)))
        self.variables['neuron_index'].set_value(indices)
        self.variables['spike_time'].set_value(times)
        self.variables['_lastindex'].set_value(0)

        # Update the internal variables used in `SpikeGeneratorGroup.before_run`
        self._neuron_index = indices
        self._spike_time = times
        self._spikes_changed = True

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(
                    cls=self.__class__.__name__,
                    N=self.N,
                    l=self.variables['neuron_index'].size)
Ejemplo n.º 19
0
class PoissonGroup(Group, SpikeSource):
    '''
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.

    '''
    add_to_magic_network = True

    @check_units(rates=Hz)
    def __init__(self, N, rates, dt=None, clock=None, when='thresholds',
                 order=0, name='poissongroup*', codeobj_class=None):

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)
        self.variables.create_clock_variables(self._clock)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N

        self._refractory = False

        # To avoid a warning about the local variable rates, we set the real
        # threshold condition only after creating the object
        self.events = {'spike': 'False'}
        self.thresholder = {'spike': Thresholder(self)}
        self.events = {'spike': 'rand() < rates * dt'}
        self.contained_objects.append(self.thresholder['spike'])

        self._enable_group_attributes()

        # Here we want to use the local namespace, but at the level where the
        # constructor was called
        self.rates.set_item(slice(None), rates, level=2)


    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __len__(self):
        return self.N

    def __repr__(self):
        description = '{classname}({N}, rates=<...>)'
        return description.format(classname=self.__class__.__name__,
                                  N=self.N)
Ejemplo n.º 20
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.
    '''

    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)

        compartments = group.flat_morphology.n
        sections = group.flat_morphology.sections

        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False,
                            template_kwds={'number_sections': sections})

        self.variables = Variables(self, default_index='_section_idx')
        self.variables.add_reference('N', group)
        # One value per compartment
        self.variables.add_arange('_compartment_idx', size=compartments)
        self.variables.add_array('_invr', unit=siemens, size=compartments,
                                 constant=True, index='_compartment_idx')
        # one value per section
        self.variables.add_arange('_section_idx', size=sections)
        self.variables.add_array('_P_parent', unit=Unit(1), size=sections,
                                 constant=True)  # elements below diagonal
        self.variables.add_arrays(['_morph_idxchild', '_morph_parent_i',
                                   '_starts', '_ends'], unit=Unit(1),
                                  size=sections, dtype=np.int32, constant=True)
        self.variables.add_arrays(['_invr0', '_invrn'], unit=siemens,
                                  size=sections, constant=True)
        # one value per section + 1 value for the root
        self.variables.add_arange('_section_root_idx', size=sections+1)
        self.variables.add_array('_P_diag', unit=Unit(1), size=sections+1,
                                 constant=True, index='_section_root_idx')
        self.variables.add_array('_B', unit=Unit(1), size=sections+1,
                                 constant=True, index='_section_root_idx')
        self.variables.add_array('_morph_children_num', unit=Unit(1),
                                 size=sections+1, dtype=np.int32,
                                 constant=True, index='_section_root_idx')
        # 2D matrices of size (sections + 1) x max children per section
        self.variables.add_arange('_morph_children_idx',
                                  size=len(group.flat_morphology.morph_children))
        self.variables.add_array('_P_children', unit=Unit(1),
                                 size=len(group.flat_morphology.morph_children),
                                 index='_morph_children_idx',
                                 constant=True)  # elements above diagonal
        self.variables.add_array('_morph_children', unit=Unit(1),
                                 size=len(group.flat_morphology.morph_children),
                                 dtype=np.int32, constant=True,
                                 index='_morph_children_idx')
        self._enable_group_attributes()

        self._morph_parent_i = group.flat_morphology.morph_parent_i
        self._morph_children_num = group.flat_morphology.morph_children_num
        self._morph_children = group.flat_morphology.morph_children
        self._morph_idxchild = group.flat_morphology.morph_idxchild
        self._starts = group.flat_morphology.starts
        self._ends = group.flat_morphology.ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(self.group,
                                                          '', # no code,
                                                          'spatialneuron_prepare',
                                                          name=self.name+'_spatialneuron_prepare',
                                                          check_units=False,
                                                          additional_variables=self.variables,
                                                          run_namespace=run_namespace)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.info(('SpatialNeuron will use numpy to do the numerical '
                             'integration -- this will be very slow. Either '
                             'switch to a different code generation target '
                             '(e.g. weave or cython) or install scipy.'),
                            once=True)
        CodeRunner.before_run(self, run_namespace)
Ejemplo n.º 21
0
class SpikeMonitor(Group, CodeRunner):
    '''
    Record spikes from a `NeuronGroup` or other spike source
    
    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    record : bool
        Whether or not to record each spike in `i` and `t` (the `count` will
        always be recorded).
    when : str, optional
        When to record the spikes, by default records spikes in the slot
        ``'end'``.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_spikemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self,
                 source,
                 record=True,
                 when='end',
                 order=0,
                 name='spikemonitor*',
                 codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            code='',
                            template='spikemonitor',
                            name=name,
                            clock=source.clock,
                            when=when,
                            order=order)

        self.add_dependency(source)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('i',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_arange('_source_i', size=len(source))
        self.variables.add_array('_count',
                                 size=len(source),
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 read_only=True,
                                 index='_source_i')
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)
        self.variables.create_clock_variables(self._clock, prefix='_clock_')
        self._enable_group_attributes()

    @property
    def _N(self):
        return len(self.variables['t'].get_value())

    def resize(self, new_size):
        self.variables['i'].resize(new_size)
        self.variables['t'].resize(new_size)

    def __len__(self):
        return self._N

    def reinit(self):
        '''
        Clears all recorded spikes
        '''
        raise NotImplementedError()

    # TODO: Maybe there's a more elegant solution for the count attribute?
    @property
    def count(self):
        return self.variables['_count'].get_value().copy()

    @property
    def it(self):
        '''
        Returns the pair (`i`, `t`).
        '''
        return self.i, self.t

    @property
    def it_(self):
        '''
        Returns the pair (`i`, `t_`).
        '''
        return self.i, self.t_

    @property
    def num_spikes(self):
        '''
        Returns the total number of recorded spikes
        '''
        return self._N

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.group.name)
Ejemplo n.º 22
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use the ``t`` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where ``indices`` are the array indices which were recorded. When indexing
    the `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in ``source``, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints, optional
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices. Defaults to ``None``.
    dt : `Quantity`, optional
        The time step to be used for the monitor. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the ``dt`` argument
        is specified, the clock of the `source` will be used.
    when : str, optional
        At which point during a time step the values should be recorded.
        Defaults to ``'start'``.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    Notes
    -----

    Since this monitor by default records in the ``'start'`` time slot,
    recordings of the membrane potential in integrate-and-fire models may look
    unexpected: the recorded membrane potential trace will never be above
    threshold in an integrate-and-fire model, because the reset statement will
    have been applied already. Set the ``when`` keyword to a different value if
    this is not what you want.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, variables, record=None, dt=None, clock=None,
                 when='start', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            logger.warn(('The StateMonitor set up to record the variable(s) '
                         '{vars} of "{source}" is not recording any value. '
                         'Did you forget to provide the record '
                         'argument?').format(vars=', '.join('"%s"' % var
                                                            for var in variables),
                                             source=self.source.name),
                        once=True)
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False, constant_size=False)
        self.variables.add_attribute_variable('N', unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self, attribute='_N')
        self.variables.add_array('_indices', size=len(self.record),
                                 unit=Unit(1), dtype=self.record.dtype,
                                 constant=True, read_only=True,
                                 values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference('_source_%s' % varname,
                                         source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array(varname,
                                             size=(0, len(self.record)),
                                             resize_along_first=True,
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname, self.variables[varname])
                                        for varname in variables])
        recorded_names = [varname for varname in variables]

        self.needed_variables = recorded_names
        self.template_kwds = {'_recorded_variables': self.recorded_variables}
        self._enable_group_attributes()

    @property
    def _N(self):
        return self.variables['t'].get_value().shape[0]

    def __len__(self):
        return self._N

    def resize(self, new_size):
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        elif hasattr(item, '_indices'):
            # objects that support the indexing interface will return absolute
            # indices but here we need relative ones
            # TODO: How to we prevent the use of completely unrelated objects here?
            source_offset = getattr(self.source, '_offset', 0)
            return StateMonitorView(self, item._indices() - source_offset)
        else:
            raise TypeError('Cannot use object of type %s as an index'
                            % type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables[item].get_value().T,
                            dim=unit.dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables[item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)

    def record_single_timestep(self):
        '''
        Records a single time step. Useful for recording the values at the end
        of the simulation -- otherwise a `StateMonitor` will not record the
        last simulated values since its ``when`` attribute defaults to
        ``'start'``, i.e. the last recording is at the *beginning* of the last
        time step.

        Notes
        -----
        This function will only work if the `StateMonitor` has been already run,
        but a run with a length of ``0*ms`` does suffice.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(1, 'dv/dt = -v/(5*ms) : 1')
        >>> G.v = 1
        >>> mon = StateMonitor(G, 'v', record=True)
        >>> run(0.5*ms)
        >>> mon.v
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635]])
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.]) * usecond
        >>> G.v[:]  # last value had not been recorded
        array([ 0.90483742])
        >>> mon.record_single_timestep()
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.,  500.]) * usecond
        >>> mon.v[:]
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635,
                 0.90483742]])
        '''
        if self.codeobj is None:
            raise TypeError('Can only record a single time step after the '
                            'network has been run once.')
        self.codeobj()
Ejemplo n.º 23
0
    def _add_synapses(self, sources, targets, n, p, condition=None,
                      namespace=None, level=0):

        if condition is None:
            variables = Variables(self)

            sources = np.atleast_1d(sources).astype(np.int32)
            targets = np.atleast_1d(targets).astype(np.int32)
            n = np.atleast_1d(n)
            p = np.atleast_1d(p)

            if not len(p) == 1 or p != 1:
                use_connections = np.random.rand(len(sources)) < p
                sources = sources[use_connections]
                targets = targets[use_connections]
                n = n[use_connections]
            sources = sources.repeat(n)
            targets = targets.repeat(n)

            variables.add_array('sources', Unit(1), len(sources), dtype=np.int32,
                                values=sources)
            variables.add_array('targets', Unit(1), len(targets), dtype=np.int32,
                                values=targets)
            # These definitions are important to get the types right in C++
            variables.add_auxiliary_variable('_real_sources', Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_real_targets', Unit(1), dtype=np.int32)
            abstract_code = ''
            if '_offset' in self.source.variables:
                variables.add_reference('_source_offset', self.source, '_offset')
                abstract_code += '_real_sources = sources + _source_offset\n'
            else:
                abstract_code += '_real_sources = sources\n'
            if '_offset' in self.target.variables:
                variables.add_reference('_target_offset', self.target, '_offset')
                abstract_code += '_real_targets = targets + _target_offset\n'
            else:
                abstract_code += '_real_targets = targets'

            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'synapses_create_array',
                                            additional_variables=variables,
                                            check_units=False,
                                            run_namespace=namespace,
                                            level=level+1)
            codeobj()
        else:
            abstract_code = '_pre_idx = _all_pre \n'
            abstract_code += '_post_idx = _all_post \n'
            abstract_code += '_cond = ' + condition + '\n'
            abstract_code += '_n = ' + str(n) + '\n'
            abstract_code += '_p = ' + str(p)
            # This overwrites 'i' and 'j' in the synapses' variables dictionary
            # This is necessary because in the context of synapse creation, i
            # and j do not correspond to the sources/targets of the existing
            # synapses but to all the possible sources/targets
            variables = Variables(None)
            # Will be set in the template
            variables.add_auxiliary_variable('_i', unit=Unit(1))
            variables.add_auxiliary_variable('_j', unit=Unit(1))
            # Make sure that variables have the correct type in the code
            variables.add_auxiliary_variable('_pre_idx', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_post_idx', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool)
            variables.add_auxiliary_variable('_n', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_p', unit=Unit(1))

            if '_sub_idx' in self.source.variables:
                variables.add_reference('_all_pre', self.source, '_sub_idx')
            else:
                variables.add_reference('_all_pre', self.source, 'i')

            if '_sub_idx' in self.target.variables:
                variables.add_reference('_all_post', self.target, '_sub_idx')
            else:
                variables.add_reference('_all_post', self.target, 'i')

            variable_indices = defaultdict(lambda: '_idx')
            for varname in self.variables:
                if self.variables.indices[varname] == '_presynaptic_idx':
                    variable_indices[varname] = '_all_pre'
                elif self.variables.indices[varname] == '_postsynaptic_idx':
                    variable_indices[varname] = '_all_post'
            variable_indices['_all_pre'] = '_i'
            variable_indices['_all_post'] = '_j'
            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'synapses_create',
                                            variable_indices=variable_indices,
                                            additional_variables=variables,
                                            check_units=False,
                                            run_namespace=namespace,
                                            level=level+1)
            codeobj()
Ejemplo n.º 24
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.

    TODO: all internal variables (u_minus etc) could be inserted in the SpatialNeuron.
    '''

    def __init__(self, group, method):
        # group is the neuron (a group of compartments) 
        self.method_choice = method
        self._isprepared = False
        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            when=(group.clock, 'groups', 1),
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        self.abstract_code = '''
        _gtot = gtot__private
        _I0 = I0__private
        '''
        N = len(self.group)
        self.ab_star = zeros((3, N))
        self.ab_plus = zeros((3, N))
        self.ab_minus = zeros((3, N))
        self.b_plus = zeros(N)
        self.b_minus = zeros(N)
        self.v_star = zeros(N)
        self.u_plus = zeros(N)
        self.u_minus = zeros(N)
        self.variables = Variables(self)
        # These 5 variables are constant after prepare()
        self.variables.add_array('ab_star', Unit(1), 3 * N,
                                 values=self.ab_star.flatten(),
                                 dtype=self.ab_star.dtype)
        self.variables.add_array('ab_plus', Unit(1), 3 * N,
                                 values=self.ab_plus.flatten(),
                                 dtype=self.ab_plus.dtype)
        self.variables.add_array('ab_minus', Unit(1), 3 * N,
                                 values=self.ab_minus.flatten(),
                                 dtype=self.ab_minus.dtype)
        self.variables.add_array('b_plus', Unit(1), N, values=self.b_plus,
                                 dtype=self.b_plus.dtype)
        self.variables.add_array('b_minus', Unit(1), N, values=self.b_minus,
                                 dtype=self.b_minus.dtype)
        # These 3 variables change every time step
        self.variables.add_array('v_star', Unit(1), N, values=self.v_star,
                                 dtype=self.v_star.dtype)
        self.variables.add_array('u_plus', Unit(1), N, values=self.u_plus,
                                 dtype=self.u_plus.dtype)
        self.variables.add_array('u_minus', Unit(1), N, values=self.u_minus,
                                 dtype=self.u_minus.dtype)

    def before_run(self, run_namespace=None, level=0):
        if not self._isprepared:  # this is done only once even if there are multiple runs
            self.prepare()
            self._isprepared = True
        CodeRunner.before_run(self, run_namespace, level=level + 1)

    def run(self):
        CodeRunner.run(self)
        # Solve the linear system connecting branches
        self.P[:] = 0
        self.B[:] = 0
        self.fill_matrix(self.group.morphology)
        self.V = solve(self.P, self.B)  # This code could be generated at initialization
        # Calculate solutions by linear combination
        self.linear_combination(self.group.morphology)

    def prepare(self):
        '''
        Preparation of data structures.
        See the relevant document.
        '''
        # Correction for soma (a bit of a hack), so that it has negligible axial resistance
        if self.group.morphology.type == 'soma':
            self.group.length[0] = self.group.diameter[0] * 0.01
        # Inverse axial resistance
        self.invr = zeros(len(self.group)) * siemens
        self.invr[1:] = (pi / (2 * self.group.Ri) * (self.group.diameter[:-1] *
                                                     self.group.diameter[1:]) /
                         (self.group.length[:-1] + self.group.length[1:]))
        # Note: this would give nan for the soma
        self.cut_branches(self.group.morphology)

        # Linear systems
        # The particular solution
        '''a[i,j]=ab[u+i-j,j]'''  # u is the number of upper diagonals = 1
        self.ab_star[0, 1:] = self.invr[1:] / self.group.area[:-1]
        self.ab_star[2, :-1] = self.invr[1:] / self.group.area[1:]
        self.ab_star[1, :] = (-(self.group.Cm / self.group.clock.dt) -
                              self.invr / self.group.area)
        self.ab_star[1, :-1] -= self.invr[1:] / self.group.area[:-1]
        # Homogeneous solutions
        self.ab_plus[:] = self.ab_star
        self.ab_minus[:] = self.ab_star

        # Boundary conditions
        self.boundary_conditions(self.group.morphology)

        # Linear system for connecting branches
        n = 1 + self.number_branches(self.group.morphology)  # number of nodes (2 for the root)
        self.P = zeros((n, n))  # matrix
        self.B = zeros(n)  # vector RHS
        self.V = zeros(n)  # solution = voltages at nodes

        self.variables['ab_star'].set_value(self.ab_star.flatten())
        self.variables['ab_plus'].set_value(self.ab_plus.flatten())
        self.variables['ab_minus'].set_value(self.ab_minus.flatten())
        self.variables['b_plus'].set_value(self.b_plus)
        self.variables['b_minus'].set_value(self.b_minus)

    def cut_branches(self, morphology):
        '''
        Recursively cut the branches by setting zero axial resistances.
        '''
        self.invr[morphology._origin] = 0
        for kid in (morphology.children):
            self.cut_branches(kid)

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches

    def boundary_conditions(self, morphology):
        '''
        Recursively sets the boundary conditions in the linear systems.
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        # Inverse axial resistances at the ends: r0 and rn
        morphology.invr0 = float(pi / (2 * self.group.Ri) *
                                 self.group.diameter[first] ** 2 /
                                 self.group.length[first])
        morphology.invrn = float(pi / (2 * self.group.Ri) *
                                 self.group.diameter[last] ** 2 /
                                 self.group.length[last])
        # Correction for boundary conditions
        self.ab_star[1, first] -= float(morphology.invr0 / self.group.area[first])  # because of units problems
        self.ab_star[1, last] -= float(morphology.invrn / self.group.area[last])
        self.ab_plus[1, first] -= float(morphology.invr0 / self.group.area[first])  # because of units problems
        self.ab_plus[1, last] -= float(morphology.invrn / self.group.area[last])
        self.ab_minus[1, first] -= float(morphology.invr0 / self.group.area[first])  # because of units problems
        self.ab_minus[1, last] -= float(morphology.invrn / self.group.area[last])
        # RHS for homogeneous solutions
        self.b_plus[last] = -float(morphology.invrn / self.group.area[last])
        self.b_minus[first] = -float(morphology.invr0 / self.group.area[first])
        # Recursive call
        for kid in (morphology.children):
            self.boundary_conditions(kid)

    # ### The two methods below should be written in C
    #### In each one there is a static function, plus a call for each segment
    #### Code for the latter could be generated at initialization
    def linear_combination(self, morphology):
        '''
        Calculates solutions by linear combination
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        i = morphology.index + 1
        i_parent = morphology.parent + 1
        v_star = self.variables['v_star'].get_value()
        u_minus = self.variables['u_minus'].get_value()
        u_plus = self.variables['u_plus'].get_value()
        self.group.v_[first:last + 1] = (v_star[first:last + 1] +
                                         self.V[i_parent] * u_minus[first:last + 1]
                                         + self.V[i] * u_plus[first:last + 1])
        # Recursive call
        for kid in (morphology.children):
            self.linear_combination(kid)

    def fill_matrix(self, morphology):
        '''
        Recursively fills the matrix of the linear system that connects
        branches together.
        Apparently this is quick.
        '''
        first = morphology._origin  # first compartment
        last = first + len(morphology.x) - 1  # last compartment
        i = morphology.index + 1
        i_parent = morphology.parent + 1
        v_star = self.variables['v_star'].get_value()
        u_minus = self.variables['u_minus'].get_value()
        u_plus = self.variables['u_plus'].get_value()
        # Towards parent
        if i == 1:  # first branch, sealed end
            self.P[0, 0] = u_minus[first] - 1
            self.P[0, 1] = u_plus[first]
            self.B[0] = -v_star[first]
        else:
            self.P[i_parent, i_parent] += (1 - u_minus[first]) * morphology.invr0
            self.P[i_parent, i] -= u_plus[first] * morphology.invr0
            self.B[i_parent] += v_star[first] * morphology.invr0
        # Towards children
        self.P[i, i] = (1 - u_plus[last]) * morphology.invrn
        self.P[i, i_parent] = -u_minus[last] * morphology.invrn
        self.B[i] = v_star[last] * morphology.invrn
        # Recursive call
        for kid in (morphology.children):
            self.fill_matrix(kid)
Ejemplo n.º 25
0
class PopulationRateMonitor(Group, CodeRunner):
    '''
    Record instantaneous firing rates, averaged across neurons from a
    `NeuronGroup` or other spike source.

    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_ratemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    Notes
    -----
    Currently, this monitor can only monitor the instantaneous firing rates at
    each time step of the source clock. Any binning/smoothing of the firing
    rates has to be done manually afterwards.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, name='ratemonitor*',
                 codeobj_class=None):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='ratemonitor',
                            clock=source.clock, when='end', order=0, name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate', size=0, unit=hertz)
        self.variables.add_dynamic_array('t', size=0, unit=second)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N', unit=Unit(1), dtype=np.int32, size=1,
                                 scalar=True, read_only=True)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['N'].set_value(new_size)
        self.variables['rate'].resize(new_size)
        self.variables['t'].resize(new_size)

    def reinit(self):
        '''
        Clears all recorded rates
        '''
        raise NotImplementedError()

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.source.name)
Ejemplo n.º 26
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.

    TODO: all internal variables (u_minus etc) could be inserted in the SpatialNeuron.
    '''

    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments) 
        self.method_choice = method
        self.group = weakref.proxy(group)
        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        n = len(group) # total number of compartments
        segments = self.number_branches(group.morphology)
        self.variables = Variables(self, default_index='_segment_idx')
        self.variables.add_reference('N', group)
        self.variables.add_arange('_compartment_idx', size=n)
        self.variables.add_arange('_segment_idx', size=segments)
        self.variables.add_arange('_segment_root_idx', size=segments+1)
        self.variables.add_arange('_P_idx', size=(segments+1)**2)

        self.variables.add_array('_invr', unit=siemens, size=n, constant=True,
                                 index='_compartment_idx')
        self.variables.add_array('_P', unit=Unit(1), size=(segments+1)**2,
                                 constant=True, index='_P_idx')
        self.variables.add_array('_B', unit=Unit(1), size=segments+1,
                                 constant=True, index='_segment_root_idx')
        self.variables.add_array('_morph_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_morph_parent_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_starts', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_ends', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_invr0', unit=siemens, size=segments,
                                 constant=True)
        self.variables.add_array('_invrn', unit=siemens, size=segments,
                                 constant=True)
        self._enable_group_attributes()

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(segments, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(segments, dtype=np.int32)
        self._temp_starts = np.zeros(segments, dtype=np.int32)
        self._temp_ends = np.zeros(segments, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace=None, level=0):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(self.group,
                                                          '', # no code,
                                                          'spatialneuron_prepare',
                                                          name=self.name+'_spatialneuron_prepare',
                                                          check_units=False,
                                                          additional_variables=self.variables,
                                                          run_namespace=run_namespace,
                                                          level=level+1)
        self._prepare_codeobj()
        CodeRunner.before_run(self, run_namespace, level=level + 1)

    def _pre_calc_iteration(self, morphology, counter=0):
        self._temp_morph_i[counter] = morphology.index + 1
        self._temp_morph_parent_i[counter] = morphology.parent + 1
        self._temp_starts[counter] = morphology._origin
        self._temp_ends[counter] = morphology._origin + len(morphology.x) - 1
        total_count = 1
        for child in morphology.children:
            total_count += self._pre_calc_iteration(child, counter+total_count)
        return total_count

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches
Ejemplo n.º 27
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use `t` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where `indices` are the array indices which were recorded. When indexing the
    `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in `source`, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices.
    when : `Scheduler`, optional
        When to record the spikes, by default uses the clock of the source
        and records spikes in the slot 'end'.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    '''
    def __init__(self,
                 source,
                 variables,
                 record=None,
                 when=None,
                 name='statemonitor*',
                 codeobj_class=None):
        self.source = weakref.proxy(source)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            record = np.array([], dtype=np.int32)
        elif isinstance(record, int):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)

        #: The array of recorded indices
        self.indices = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = %s' % (v, v) for v in self.record_variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self,
                            group=self,
                            template='statemonitor',
                            code=code,
                            name=name,
                            when=scheduler,
                            check_units=False)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant=False,
                                         constant_size=False)
        if scheduler.clock is source.clock:
            self.variables.add_reference('_clock_t', source.variables['t'])
        else:
            self.variables.add_attribute_variable('_clock_t',
                                                  unit=second,
                                                  obj=scheduler.clock,
                                                  attribute='t_')
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self,
                                              attribute='_N')
        self.variables.add_array('_indices',
                                 size=len(self.indices),
                                 unit=Unit(1),
                                 dtype=self.indices.dtype,
                                 constant=True,
                                 read_only=True)
        self.variables['_indices'].set_value(self.indices)

        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.indices) > 1:
                logger.warn(('Variable %s is a scalar variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference(varname, var, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source.variables[index])
            # For subexpressions, we also need all referred variables (if they
            # are not already present, e.g. the t as _clock_t
            if isinstance(var, Subexpression):
                for subexpr_varname in var.identifiers:
                    if subexpr_varname in source.variables:
                        source_var = source.variables[subexpr_varname]
                        index = source.variables.indices[subexpr_varname]
                        if index != '_idx' and index not in variables:
                            self.variables.add_reference(
                                index, source.variables[index])
                        if not source_var in self.variables.values():
                            source_index = source.variables.indices[
                                subexpr_varname]
                            # `translate_subexpression` will later replace
                            # the name used in the original subexpression with
                            # _source_varname
                            self.variables.add_reference('_source_' +
                                                         subexpr_varname,
                                                         source_var,
                                                         index=source_index)
            self.variables.add_dynamic_array('_recorded_' + varname,
                                             size=(0, len(self.indices)),
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in self.record_variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([
            (varname, self.variables['_recorded_' + varname])
            for varname in self.record_variables
        ])
        recorded_names = [
            '_recorded_' + varname for varname in self.record_variables
        ]

        self.needed_variables = recorded_names
        self.template_kwds = template_kwds = {
            '_recorded_variables': self.recorded_variables
        }
        self._N = 0
        self._enable_group_attributes()

    def __len__(self):
        return self._N

    def resize(self, new_size):
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))
        self._N = new_size

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        else:
            raise TypeError('Cannot use object of type %s as an index' %
                            type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables['_recorded_' + item].get_value().T,
                            dim=unit.dim,
                            copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables['_recorded_' + item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)
Ejemplo n.º 28
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.
    '''
    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)

        compartments = len(group)  # total number of compartments
        branches = self.number_branches(group.morphology)

        CodeRunner.__init__(self,
                            group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False,
                            template_kwds={'number_branches': branches})

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(branches, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(branches, dtype=np.int32)
        # for the following: a smaller array of size no_segments x max_no_children would suffice...
        self._temp_morph_children = np.zeros((branches + 1, branches),
                                             dtype=np.int32)
        # children count per branch: determines the no of actually used elements of the array above
        self._temp_morph_children_num = np.zeros(branches + 1, dtype=np.int32)
        # each branch is child of exactly one parent (and we say the first branch i=1 is child of branch i=0)
        # here we store the indices j-1->k of morph_children_i[i,k] = j
        self._temp_morph_idxchild = np.zeros(branches, dtype=np.int32)
        self._temp_starts = np.zeros(branches, dtype=np.int32)
        self._temp_ends = np.zeros(branches, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        # flattened and reduce children indices
        max_children = max(self._temp_morph_children_num)
        self._temp_morph_children = self._temp_morph_children[:, :
                                                              max_children].reshape(
                                                                  -1)

        self.variables = Variables(self, default_index='_branch_idx')
        self.variables.add_reference('N', group)
        # One value per compartment
        self.variables.add_arange('_compartment_idx', size=compartments)
        self.variables.add_array('_invr',
                                 unit=siemens,
                                 size=compartments,
                                 constant=True,
                                 index='_compartment_idx')
        # one value per branch
        self.variables.add_arange('_branch_idx', size=branches)
        self.variables.add_array('_P_parent',
                                 unit=Unit(1),
                                 size=branches,
                                 constant=True)  # elements below diagonal
        self.variables.add_array('_morph_idxchild',
                                 unit=Unit(1),
                                 size=branches,
                                 dtype=np.int32,
                                 constant=True)
        self.variables.add_arrays(
            ['_morph_i', '_morph_parent_i', '_starts', '_ends'],
            unit=Unit(1),
            size=branches,
            dtype=np.int32,
            constant=True)
        self.variables.add_arrays(['_invr0', '_invrn'],
                                  unit=siemens,
                                  size=branches,
                                  constant=True)
        # one value per branch + 1 value for the root
        self.variables.add_arange('_branch_root_idx', size=branches + 1)
        self.variables.add_array('_P_diag',
                                 unit=Unit(1),
                                 size=branches + 1,
                                 constant=True,
                                 index='_branch_root_idx')
        self.variables.add_array('_B',
                                 unit=Unit(1),
                                 size=branches + 1,
                                 constant=True,
                                 index='_branch_root_idx')
        self.variables.add_arange('_morph_children_num_idx', size=branches + 1)
        self.variables.add_array('_morph_children_num',
                                 unit=Unit(1),
                                 size=branches + 1,
                                 dtype=np.int32,
                                 constant=True,
                                 index='_morph_children_num_idx')
        # 2D matrices of size (branches + 1) x max children per branch
        # Note that this data structure wastes space if the number of children
        # per branch is very different. In practice, however, this should not
        # matter much, since branches will normally have 0, 1, or 2 children
        # (e.g. SWC files on neuromporh are strictly binary trees)
        self.variables.add_array('_P_children',
                                 unit=Unit(1),
                                 size=(branches + 1) * max_children,
                                 constant=True)  # elements above diagonal
        self.variables.add_arange('_morph_children_idx',
                                  size=(branches + 1) * max_children)
        self.variables.add_array('_morph_children',
                                 unit=Unit(1),
                                 size=(branches + 1) * max_children,
                                 dtype=np.int32,
                                 constant=True,
                                 index='_morph_children_idx')
        self._enable_group_attributes()

        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._morph_children_num = self._temp_morph_children_num
        self._morph_children = self._temp_morph_children
        self._morph_idxchild = self._temp_morph_idxchild
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(
                self.group,
                '',  # no code,
                'spatialneuron_prepare',
                name=self.name + '_spatialneuron_prepare',
                check_units=False,
                additional_variables=self.variables,
                run_namespace=run_namespace)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.info(
                    ('SpatialNeuron will use numpy to do the numerical '
                     'integration -- this will be very slow. Either '
                     'switch to a different code generation target '
                     '(e.g. weave or cython) or install scipy.'),
                    once=True)
        CodeRunner.before_run(self, run_namespace)

    def _pre_calc_iteration(self, morphology, counter=0):
        self._temp_morph_i[counter] = morphology.index + 1
        self._temp_morph_parent_i[counter] = morphology.parent + 1

        # add to parent's children list
        if counter > 0:
            parent_i = self._temp_morph_parent_i[counter]
            child_num = self._temp_morph_children_num[parent_i]
            self._temp_morph_children[parent_i, child_num] = counter + 1
            self._temp_morph_children_num[
                parent_i] += 1  # increase parent's children count
            self._temp_morph_idxchild[counter] = child_num
        else:
            self._temp_morph_children_num[0] = 1
            self._temp_morph_children[0, 0] = 1
            self._temp_morph_idxchild[0] = 0

        self._temp_starts[counter] = morphology._origin
        self._temp_ends[counter] = morphology._origin + len(morphology.x) - 1
        total_count = 1
        for child in morphology.children:
            total_count += self._pre_calc_iteration(child,
                                                    counter + total_count)
        return total_count

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches
Ejemplo n.º 29
0
class Clock(VariableOwner):
    '''
    An object that holds the simulation time and the time step.
    
    Parameters
    ----------
    dt : float
        The time step of the simulation as a float
    name : str, optional
        An explicit name, if not specified gives an automatically generated name

    Notes
    -----
    Clocks are run in the same `Network.run` iteration if `~Clock.t` is the
    same. The condition for two
    clocks to be considered as having the same time is
    ``abs(t1-t2)<epsilon*abs(t1)``, a standard test for equality of floating
    point values. The value of ``epsilon`` is ``1e-14``.
    '''

    def __init__(self, dt, name='clock*'):
        # We need a name right away because some devices (e.g. cpp_standalone)
        # need a name for the object when creating the variables
        Nameable.__init__(self, name=name)
        self._old_dt = None
        self.variables = Variables(self)
        self.variables.add_array('timestep', unit=Unit(1), size=1,
                                 dtype=np.uint64, read_only=True, scalar=True)
        self.variables.add_array('t', unit=second, size=1,
                                 dtype=np.double, read_only=True, scalar=True)
        self.variables.add_array('dt', unit=second, size=1, values=float(dt),
                                 dtype=np.float, read_only=True, constant=True,
                                 scalar=True)
        self.variables.add_constant('N', unit=Unit(1), value=1)
        self._enable_group_attributes()
        self.dt = dt
        logger.diagnostic("Created clock {name} with dt={dt}".format(name=self.name,
                                                                     dt=self.dt))

    @check_units(t=second)
    def _set_t_update_dt(self, target_t=0*second):
        new_dt = self.dt_
        old_dt = self._old_dt
        target_t = float(target_t)
        if old_dt is not None and new_dt != old_dt:
            self._old_dt = None
            # Only allow a new dt which allows to correctly set the new time step
            check_dt(new_dt, old_dt, target_t)

        new_timestep = self._calc_timestep(target_t)
        # Since these attributes are read-only for normal users, we have to
        # update them via the variables object directly
        self.variables['timestep'].set_value(new_timestep)
        self.variables['t'].set_value(new_timestep * new_dt)
        logger.diagnostic("Setting Clock {name} to t={t}, dt={dt}".format(name=self.name,
                                                                          t=self.t,
                                                                          dt=self.dt))

    def _calc_timestep(self, target_t):
        '''
        Calculate the integer time step for the target time. If it cannot be
        exactly represented (up to 0.01% of dt), round up.

        Parameters
        ----------
        target_t : float
            The target time in seconds

        Returns
        -------
        timestep : int
            The target time in integers (based on dt)
        '''
        new_i = np.uint64(np.round(target_t / self.dt_))
        new_t = new_i * self.dt_
        if (new_t == target_t or
                        np.abs(new_t - target_t)/self.dt_ <= Clock.epsilon_dt):
            new_timestep = new_i
        else:
            new_timestep = np.uint64(np.ceil(target_t / self.dt_))
        return new_timestep

    def __repr__(self):
        return 'Clock(dt=%r, name=%r)' % (self.dt, self.name)

    def _get_dt_(self):
        return self.variables['dt'].get_value().item()

    @check_units(dt_=1)
    def _set_dt_(self, dt_):
        self._old_dt = self._get_dt_()
        self.variables['dt'].set_value(dt_)

    @check_units(dt=second)
    def _set_dt(self, dt):
        self._set_dt_(float(dt))

    dt = property(fget=lambda self: Quantity(self.dt_, dim=second.dim),
                  fset=_set_dt,
                  doc='''The time step of the simulation in seconds.''',
                  )
    dt_ = property(fget=_get_dt_, fset=_set_dt_,
                   doc='''The time step of the simulation as a float (in seconds)''')

    @check_units(start=second, end=second)
    def set_interval(self, start, end):
        '''
        set_interval(self, start, end)

        Set the start and end time of the simulation.

        Sets the start and end value of the clock precisely if
        possible (using epsilon) or rounding up if not. This assures that
        multiple calls to `Network.run` will not re-run the same time step.      
        '''
        self._set_t_update_dt(target_t=start)
        end = float(end)
        self._i_end = self._calc_timestep(end)
        if self._i_end > 2**40:
            logger.warn('The end time of the simulation has been set to {}, '
                        'which based on the dt value of {} means that {} '
                        'time steps will be simulated. This can lead to '
                        'numerical problems, e.g. the times t will not '
                        'correspond to exact multiples of '
                        'dt.'.format(str(end*second),
                                     str(self.dt),
                                     self._i_end),
                        'many_timesteps')

    #: The relative difference for times (in terms of dt) so that they are
    #: considered identical.
    epsilon_dt = 1e-4
Ejemplo n.º 30
0
class PopulationRateMonitor(Group, CodeRunner):
    '''
    Record instantaneous firing rates, averaged across neurons from a
    `NeuronGroup` or other spike source.

    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_ratemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    Notes
    -----
    Currently, this monitor can only monitor the instantaneous firing rates at
    each time step of the source clock. Any binning/smoothing of the firing
    rates has to be done manually afterwards.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, name='ratemonitor*',
                 codeobj_class=None):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='ratemonitor',
                            clock=source.clock, when='end', order=0, name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate', size=0, unit=hertz,
                                         constant_size=False)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant_size=False)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N', unit=Unit(1), dtype=np.int32, size=1,
                                 scalar=True, read_only=True)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['N'].set_value(new_size)
        self.variables['rate'].resize(new_size)
        self.variables['t'].resize(new_size)

    def reinit(self):
        '''
        Clears all recorded rates
        '''
        raise NotImplementedError()

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.source.name)
Ejemplo n.º 31
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.
    '''

    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)

        compartments = len(group) # total number of compartments
        branches = self.number_branches(group.morphology)

        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False,
                            template_kwds={'number_branches': branches})

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(branches, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(branches, dtype=np.int32)
        # for the following: a smaller array of size no_segments x max_no_children would suffice...
        self._temp_morph_children = np.zeros((branches+1, branches), dtype=np.int32)
        # children count per branch: determines the no of actually used elements of the array above
        self._temp_morph_children_num = np.zeros(branches+1, dtype=np.int32)
        # each branch is child of exactly one parent (and we say the first branch i=1 is child of branch i=0)
        # here we store the indices j-1->k of morph_children_i[i,k] = j 
        self._temp_morph_idxchild = np.zeros(branches, dtype=np.int32)
        self._temp_starts = np.zeros(branches, dtype=np.int32)
        self._temp_ends = np.zeros(branches, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        # flattened and reduce children indices
        max_children = max(self._temp_morph_children_num)
        self._temp_morph_children = self._temp_morph_children[:,:max_children].reshape(-1)
        
        self.variables = Variables(self, default_index='_branch_idx')
        self.variables.add_reference('N', group)
        # One value per compartment
        self.variables.add_arange('_compartment_idx', size=compartments)
        self.variables.add_array('_invr', unit=siemens, size=compartments,
                                 constant=True, index='_compartment_idx')
        # one value per branch
        self.variables.add_arange('_branch_idx', size=branches)
        self.variables.add_array('_P_parent', unit=Unit(1), size=branches,
                                 constant=True) # elements below diagonal
        self.variables.add_array('_morph_idxchild', unit=Unit(1), size=branches,
                                 dtype=np.int32, constant=True)
        self.variables.add_arrays(['_morph_i', '_morph_parent_i',
                                   '_starts', '_ends'], unit=Unit(1),
                                  size=branches, dtype=np.int32, constant=True)
        self.variables.add_arrays(['_invr0', '_invrn'], unit=siemens,
                                  size=branches, constant=True)
        # one value per branch + 1 value for the root
        self.variables.add_arange('_branch_root_idx', size=branches+1)
        self.variables.add_array('_P_diag', unit=Unit(1), size=branches+1,
                                 constant=True, index='_branch_root_idx')
        self.variables.add_array('_B', unit=Unit(1), size=branches+1,
                                 constant=True, index='_branch_root_idx')
        self.variables.add_arange('_morph_children_num_idx', size=branches+1)
        self.variables.add_array('_morph_children_num', unit=Unit(1),
                                 size=branches+1, dtype=np.int32, constant=True,
                                 index='_morph_children_num_idx')
        # 2D matrices of size (branches + 1) x max children per branch
        # Note that this data structure wastes space if the number of children
        # per branch is very different. In practice, however, this should not
        # matter much, since branches will normally have 0, 1, or 2 children
        # (e.g. SWC files on neuromporh are strictly binary trees)
        self.variables.add_array('_P_children', unit=Unit(1),
                                 size=(branches+1)*max_children,
                                 constant=True)  # elements above diagonal
        self.variables.add_arange('_morph_children_idx',
                                  size=(branches+1)*max_children)
        self.variables.add_array('_morph_children', unit=Unit(1),
                                 size=(branches+1)*max_children,
                                 dtype=np.int32, constant=True,
                                 index='_morph_children_idx')
        self._enable_group_attributes()
        
        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._morph_children_num = self._temp_morph_children_num
        self._morph_children = self._temp_morph_children
        self._morph_idxchild = self._temp_morph_idxchild
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(self.group,
                                                          '', # no code,
                                                          'spatialneuron_prepare',
                                                          name=self.name+'_spatialneuron_prepare',
                                                          check_units=False,
                                                          additional_variables=self.variables,
                                                          run_namespace=run_namespace)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.info(('SpatialNeuron will use numpy to do the numerical '
                             'integration -- this will be very slow. Either '
                             'switch to a different code generation target '
                             '(e.g. weave or cython) or install scipy.'),
                            once=True)
        CodeRunner.before_run(self, run_namespace)

    def _pre_calc_iteration(self, morphology, counter=0):
        self._temp_morph_i[counter] = morphology.index + 1
        self._temp_morph_parent_i[counter] = morphology.parent + 1
        
        # add to parent's children list
        if counter>0:
            parent_i = self._temp_morph_parent_i[counter]
            child_num = self._temp_morph_children_num[parent_i]
            self._temp_morph_children[parent_i, child_num] = counter+1
            self._temp_morph_children_num[parent_i] += 1 # increase parent's children count
            self._temp_morph_idxchild[counter] = child_num
        else:
            self._temp_morph_children_num[0] = 1
            self._temp_morph_children[0, 0] = 1
            self._temp_morph_idxchild[0] = 0
        
        self._temp_starts[counter] = morphology._origin
        self._temp_ends[counter] = morphology._origin + len(morphology.x) - 1
        total_count = 1
        for child in morphology.children:
            total_count += self._pre_calc_iteration(child, counter+total_count)
        return total_count

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches
Ejemplo n.º 32
0
class SynapticPathway(CodeRunner, Group):
    '''
    The `CodeRunner` that applies the pre/post statement(s) to the state
    variables of synapses where the pre-/postsynaptic group spiked in this
    time step.

    Parameters
    ----------

    synapses : `Synapses`
        Reference to the main `Synapses` object
    prepost : {'pre', 'post'}
        Whether this object should react to pre- or postsynaptic spikes
    objname : str, optional
        The name to use for the object, will be appendend to the name of
        `synapses` to create a name in the sense of `Nameable`. The `synapses`
        object should allow access to this object via
        ``synapses.getattr(objname)``. It has to use the actual `objname`
        attribute instead of relying on the provided argument, since the name
        may have changed to become unique. If ``None`` is provided (the
        default), ``prepost+'*'`` will be used (see `Nameable` for an
        explanation of the wildcard operator).
    delay : `Quantity`, optional
        A scalar delay (same delay for all synapses) for this pathway. If
        not given, delays are expected to vary between synapses.
    '''
    def __init__(self, synapses, code, prepost, objname=None, delay=None):
        self.code = code
        self.prepost = prepost
        if prepost == 'pre':
            self.source = synapses.source
            self.target = synapses.target
            self.synapse_sources = synapses.variables['_synaptic_pre']
        elif prepost == 'post':
            self.source = synapses.target
            self.target = synapses.source
            self.synapse_sources = synapses.variables['_synaptic_post']
        else:
            raise ValueError('prepost argument has to be either "pre" or '
                             '"post"')
        self.synapses = synapses

        if objname is None:
            objname = prepost + '*'

        CodeRunner.__init__(self,
                            synapses,
                            'synapses',
                            code=code,
                            when=(synapses.clock, 'synapses'),
                            name=synapses.name + '_' + objname,
                            template_kwds={'pathway': self})

        self._pushspikes_codeobj = None

        self.spikes_start = self.source.start
        self.spikes_stop = self.source.stop

        self.spiking_synapses = []
        self.variables = Variables(self)
        self.variables.add_attribute_variable('_spiking_synapses',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='spiking_synapses',
                                              constant=False,
                                              scalar=False)
        self.variables.add_reference('_spikespace',
                                     self.source.variables['_spikespace'])
        self.variables.add_reference('N', synapses.variables['N'])
        if delay is None:  # variable delays
            self.variables.add_dynamic_array('delay',
                                             unit=second,
                                             size=synapses._N,
                                             constant=True,
                                             constant_size=True)
            # Register the object with the `SynapticIndex` object so it gets
            # automatically resized
            synapses.register_variable(self.variables['delay'])
        else:
            if not isinstance(delay, Quantity):
                raise TypeError(('Cannot set the delay for pathway "%s": '
                                 'expected a quantity, got %s instead.') %
                                (objname, type(delay)))
            if delay.size != 1:
                raise TypeError(
                    ('Cannot set the delay for pathway "%s": '
                     'expected a scalar quantity, got a '
                     'quantity with shape %s instead.') % str(delay.shape))
            fail_for_dimension_mismatch(delay, second, ('Delay has to be '
                                                        'specified in units '
                                                        'of seconds'))
            self.variables.add_array('delay',
                                     unit=second,
                                     size=1,
                                     constant=True,
                                     scalar=True)
            self.variables['delay'].set_value(delay)

        self._delays = self.variables['delay']

        # Re-extract the last part of the name from the full name
        self.objname = self.name[len(synapses.name) + 1:]

        #: The simulation dt (necessary for the delays)
        self.dt = self.synapses.clock.dt_

        #: The `SpikeQueue`
        self.queue = None

        #: The `CodeObject` initalising the `SpikeQueue` at the begin of a run
        self._initialise_queue_codeobj = None

        self.namespace = synapses.namespace
        # Enable access to the delay attribute via the specifier
        self._enable_group_attributes()

    def __len__(self):
        return self.N_

    def update_abstract_code(self, run_namespace=None, level=0):
        if self.synapses.event_driven is not None:
            event_driven_update = independent(self.synapses.event_driven,
                                              self.group.variables)
            # TODO: Any way to do this more elegantly?
            event_driven_update = re.sub(r'\bdt\b', '(t - lastupdate)',
                                         event_driven_update)

            self.abstract_code = event_driven_update + '\n'
        else:
            self.abstract_code = ''

        self.abstract_code += self.code + '\n'
        self.abstract_code += 'lastupdate = t\n'

    def before_run(self, run_namespace=None, level=0):
        # execute code to initalize the spike queue
        if self._initialise_queue_codeobj is None:
            self._initialise_queue_codeobj = create_runner_codeobj(
                self,
                '',  # no code,
                'synapses_initialise_queue',
                name=self.name + '_initialise_queue',
                check_units=False,
                additional_variables=self.variables,
                run_namespace=run_namespace,
                level=level + 1)
        self._initialise_queue_codeobj()
        CodeRunner.before_run(self, run_namespace, level=level + 1)

        # we insert rather than replace because CodeRunner puts a CodeObject in updaters already
        if self._pushspikes_codeobj is None:
            self._pushspikes_codeobj = create_runner_codeobj(
                self,
                '',  # no code
                'synapses_push_spikes',
                name=self.name + '_push_spikes',
                check_units=False,
                additional_variables=self.variables,
                run_namespace=run_namespace,
                level=level + 1)

        self._code_objects.insert(0, weakref.proxy(self._pushspikes_codeobj))

    def initialise_queue(self):
        if self.queue is None:
            self.queue = get_device().spike_queue(self.source.start,
                                                  self.source.stop)

        # Update the dt (might have changed between runs)
        self.dt = self.synapses.clock.dt_

        self.queue.prepare(self._delays.get_value(), self.dt,
                           self.synapse_sources.get_value())

    def push_spikes(self):
        # Push new spikes into the queue
        spikes = self.source.spikes
        if len(spikes):
            self.queue.push(spikes)
        # Get the spikes
        self.spiking_synapses = self.queue.peek()
        # Advance the spike queue
        self.queue.advance()
Ejemplo n.º 33
0
class PoissonGroup(Group, SpikeSource):
    '''
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate
    clock : Clock, optional
        The update clock to be used, or defaultclock if not specified.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.

    '''
    @check_units(rates=Hz)
    def __init__(self, N, rates, clock=None, name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N
        self.namespace = create_namespace(None)

        self.threshold = 'rand() < rates * dt'
        self._refractory = False
        self.thresholder = Thresholder(self)
        self.contained_objects.append(self.thresholder)

        self._enable_group_attributes()
        # Set the rates according to the argument (make sure to use the correct
        # namespace)
        self.rates.set_item(slice(None), rates, level=2)

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __len__(self):
        return self.N

    def __repr__(self):
        description = '{classname}({N}, rates=<...>)'
        return description.format(classname=self.__class__.__name__,
                                  N=self.N)
Ejemplo n.º 34
0
class Clock(VariableOwner):
    '''
    An object that holds the simulation time and the time step.
    
    Parameters
    ----------
    dt : float
        The time step of the simulation as a float
    name : str, optional
        An explicit name, if not specified gives an automatically generated name

    Notes
    -----
    Clocks are run in the same `Network.run` iteration if `~Clock.t` is the
    same. The condition for two
    clocks to be considered as having the same time is
    ``abs(t1-t2)<epsilon*abs(t1)``, a standard test for equality of floating
    point values. The value of ``epsilon`` is ``1e-14``.
    '''
    def __init__(self, dt, name='clock*'):
        # We need a name right away because some devices (e.g. cpp_standalone)
        # need a name for the object when creating the variables
        Nameable.__init__(self, name=name)
        self._old_dt = None
        self.variables = Variables(self)
        self.variables.add_array('timestep',
                                 size=1,
                                 dtype=np.int64,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_array('t',
                                 dimensions=second.dim,
                                 size=1,
                                 dtype=np.double,
                                 read_only=True,
                                 scalar=True)
        self.variables.add_array('dt',
                                 dimensions=second.dim,
                                 size=1,
                                 values=float(dt),
                                 dtype=np.float,
                                 read_only=True,
                                 constant=True,
                                 scalar=True)
        self.variables.add_constant('N', value=1)
        self._enable_group_attributes()
        self.dt = dt
        logger.diagnostic("Created clock {name} with dt={dt}".format(
            name=self.name, dt=self.dt))

    @check_units(t=second)
    def _set_t_update_dt(self, target_t=0 * second):
        new_dt = self.dt_
        old_dt = self._old_dt
        target_t = float(target_t)
        if old_dt is not None and new_dt != old_dt:
            self._old_dt = None
            # Only allow a new dt which allows to correctly set the new time step
            check_dt(new_dt, old_dt, target_t)

        new_timestep = self._calc_timestep(target_t)
        # Since these attributes are read-only for normal users, we have to
        # update them via the variables object directly
        self.variables['timestep'].set_value(new_timestep)
        self.variables['t'].set_value(new_timestep * new_dt)
        logger.diagnostic("Setting Clock {name} to t={t}, dt={dt}".format(
            name=self.name, t=self.t, dt=self.dt))

    def _calc_timestep(self, target_t):
        '''
        Calculate the integer time step for the target time. If it cannot be
        exactly represented (up to 0.01% of dt), round up.

        Parameters
        ----------
        target_t : float
            The target time in seconds

        Returns
        -------
        timestep : int
            The target time in integers (based on dt)
        '''
        new_i = np.int64(np.round(target_t / self.dt_))
        new_t = new_i * self.dt_
        if (new_t == target_t
                or np.abs(new_t - target_t) / self.dt_ <= Clock.epsilon_dt):
            new_timestep = new_i
        else:
            new_timestep = np.int64(np.ceil(target_t / self.dt_))
        return new_timestep

    def __repr__(self):
        return 'Clock(dt=%r, name=%r)' % (self.dt, self.name)

    def _get_dt_(self):
        return self.variables['dt'].get_value().item()

    @check_units(dt_=1)
    def _set_dt_(self, dt_):
        self._old_dt = self._get_dt_()
        self.variables['dt'].set_value(dt_)

    @check_units(dt=second)
    def _set_dt(self, dt):
        self._set_dt_(float(dt))

    dt = property(
        fget=lambda self: Quantity(self.dt_, dim=second.dim),
        fset=_set_dt,
        doc='''The time step of the simulation in seconds.''',
    )
    dt_ = property(
        fget=_get_dt_,
        fset=_set_dt_,
        doc='''The time step of the simulation as a float (in seconds)''')

    @check_units(start=second, end=second)
    def set_interval(self, start, end):
        '''
        set_interval(self, start, end)

        Set the start and end time of the simulation.

        Sets the start and end value of the clock precisely if
        possible (using epsilon) or rounding up if not. This assures that
        multiple calls to `Network.run` will not re-run the same time step.      
        '''
        self._set_t_update_dt(target_t=start)
        end = float(end)
        self._i_end = self._calc_timestep(end)
        if self._i_end > 2**40:
            logger.warn(
                'The end time of the simulation has been set to {}, '
                'which based on the dt value of {} means that {} '
                'time steps will be simulated. This can lead to '
                'numerical problems, e.g. the times t will not '
                'correspond to exact multiples of '
                'dt.'.format(str(end * second), str(self.dt), self._i_end),
                'many_timesteps')

    #: The relative difference for times (in terms of dt) so that they are
    #: considered identical.
    epsilon_dt = 1e-4
Ejemplo n.º 35
0
class Synapses(Group):
    '''
    Class representing synaptic connections. Creating a new `Synapses` object
    does by default not create any synapses -- you either have to provide
    the `connect` argument or call the `Synapses.connect` method for that.

    Parameters
    ----------

    source : `SpikeSource`
        The source of spikes, e.g. a `NeuronGroup`.
    target : `Group`, optional
        The target of the spikes, typically a `NeuronGroup`. If none is given,
        the same as `source`
    model : {`str`, `Equations`}, optional
        The model equations for the synapses.
    pre : {str, dict}, optional
        The code that will be executed after every pre-synaptic spike. Can be
        either a single (possibly multi-line) string, or a dictionary mapping
        pathway names to code strings. In the first case, the pathway will be
        called ``pre`` and made available as an attribute of the same name.
        In the latter case, the given names will be used as the
        pathway/attribute names. Each pathway has its own code and its own
        delays.
    post : {str, dict}, optional
        The code that will be executed after every post-synaptic spike. Same
        conventions as for `pre`, the default name for the pathway is ``post``.
    connect : {str, bool}. optional
        Determines whether any actual synapses are created. ``False`` (the
        default) means not to create any synapses, ``True`` means to create
        synapses between all source/target pairs. Also accepts a string
        expression that evaluates to ``True`` for every synapse that should
        be created, e.g. ``'i == j'`` for a one-to-one connectivity. See
        `Synapses.connect` for more details.
    delay : {`Quantity`, dict}, optional
        The delay for the "pre" pathway (same for all synapses) or a dictionary
        mapping pathway names to delays. If a delay is specified in this way
        for a pathway, it is stored as a single scalar value. It can still
        be changed afterwards, but only to a single scalar value. If you want
        to have delays that vary across synapses, do not use the keyword
        argument, but instead set the delays via the attribute of the pathway,
        e.g. ``S.pre.delay = ...`` (or ``S.delay = ...`` as an abbreviation),
        ``S.post.delay = ...``, etc.
    namespace : dict, optional
        A dictionary mapping identifier names to objects. If not given, the
        namespace will be filled in at the time of the call of `Network.run`,
        with either the values from the ``network`` argument of the
        `Network.run` method or from the local context, if no such argument is
        given.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to use to run code.
    clock : `Clock`, optional
        The clock to use.
    method : {str, `StateUpdateMethod`}, optional
        The numerical integration method to use. If none is given, an
        appropriate one is automatically determined.
    name : str, optional
        The name for this object. If none is given, a unique name of the form
        ``synapses``, ``synapses_1``, etc. will be automatically chosen.
    '''
    def __init__(self, source, target=None, model=None, pre=None, post=None,
                 connect=False, delay=None, namespace=None, dtype=None,
                 codeobj_class=None,
                 clock=None, method=None, name='synapses*'):
        self._N = 0
        Group.__init__(self, when=clock, name=name)
        
        self.codeobj_class = codeobj_class

        self.source = weakref.proxy(source)
        if target is None:
            self.target = self.source
        else:
            self.target = weakref.proxy(target)
            
        ##### Prepare and validate equations
        if model is None:
            model = ''

        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({DIFFERENTIAL_EQUATION: ['event-driven'],
                           SUBEXPRESSION: ['summed', 'scalar'],
                           PARAMETER: ['constant', 'scalar']})

        # Add the lastupdate variable, needed for event-driven updates
        if 'lastupdate' in model._equations:
            raise SyntaxError('lastupdate is a reserved name.')
        model._equations['lastupdate'] = SingleEquation(PARAMETER,
                                                        'lastupdate',
                                                        second)
        self._create_variables(model)

        # Separate the equations into event-driven equations,
        # continuously updated equations and summed variable updates
        event_driven = []
        continuous = []
        summed_updates = []
        for single_equation in model.itervalues():
            if 'event-driven' in single_equation.flags:
                event_driven.append(single_equation)
            elif 'summed' in single_equation.flags:
                summed_updates.append(single_equation)
            else:
                continuous.append(single_equation)

        if len(event_driven):
            self.event_driven = Equations(event_driven)
        else:
            self.event_driven = None

        self.equations = Equations(continuous)

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        #: Set of `Variable` objects that should be resized when the
        #: number of synapses changes
        self._registered_variables = set()

        for varname, var in self.variables.iteritems():
            if isinstance(var, DynamicArrayVariable):
                # Register the array with the `SynapticItemMapping` object so
                # it gets automatically resized
                self.register_variable(var)

        if delay is None:
            delay = {}

        if isinstance(delay, Quantity):
            delay = {'pre': delay}
        elif not isinstance(delay, collections.Mapping):
            raise TypeError('Delay argument has to be a quantity or a '
                            'dictionary, is type %s instead.' % type(delay))

        #: List of names of all updaters, e.g. ['pre', 'post']
        self._synaptic_updaters = []
        #: List of all `SynapticPathway` objects
        self._pathways = []
        for prepost, argument in zip(('pre', 'post'), (pre, post)):
            if not argument:
                continue
            if isinstance(argument, basestring):
                pathway_delay = delay.get(prepost, None)
                self._add_updater(argument, prepost, delay=pathway_delay)
            elif isinstance(argument, collections.Mapping):
                for key, value in argument.iteritems():
                    if not isinstance(key, basestring):
                        err_msg = ('Keys for the "{}" argument'
                                   'have to be strings, got '
                                   '{} instead.').format(prepost, type(key))
                        raise TypeError(err_msg)
                    pathway_delay = delay.get(key, None)
                    self._add_updater(value, prepost, objname=key,
                                      delay=pathway_delay)

        # Check whether any delays were specified for pathways that don't exist
        for pathway in delay:
            if not pathway in self._synaptic_updaters:
                raise ValueError(('Cannot set the delay for pathway '
                                  '"%s": unknown pathway.') % pathway)

        # If we have a pathway called "pre" (the most common use case), provide
        # direct access to its delay via a delay attribute (instead of having
        # to use pre.delay)
        if 'pre' in self._synaptic_updaters:
            self.variables.add_reference('delay', self.pre.variables['delay'])

        #: Performs numerical integration step
        self.state_updater = None

        # We only need a state update if we have differential equations
        if len(self.equations.diff_eq_names):
            self.state_updater = StateUpdater(self, method)
            self.contained_objects.append(self.state_updater)

        #: "Summed variable" mechanism -- sum over all synapses of a
        #: pre-/postsynaptic target
        self.summed_updaters = {}
        # We want to raise an error if the same variable is updated twice
        # using this mechanism. This could happen if the Synapses object
        # connected a NeuronGroup to itself since then all variables are
        # accessible as var_pre and var_post.
        summed_targets = set()
        for single_equation in summed_updates:
            varname = single_equation.varname
            if not (varname.endswith('_pre') or varname.endswith('_post')):
                raise ValueError(('The summed variable "%s" does not end '
                                  'in "_pre" or "_post".') % varname)
            if not varname in self.variables:
                raise ValueError(('The summed variable "%s" does not refer'
                                  'do any known variable in the '
                                  'target group.') % varname)
            if varname.endswith('_pre'):
                summed_target = self.source
                orig_varname = varname[:-4]
            else:
                summed_target = self.target
                orig_varname = varname[:-5]

            target_eq = getattr(summed_target, 'equations', {}).get(orig_varname, None)
            if target_eq is None or target_eq.type != PARAMETER:
                raise ValueError(('The summed variable "%s" needs a '
                                  'corresponding parameter "%s" in the '
                                  'target group.') % (varname,
                                                      orig_varname))

            fail_for_dimension_mismatch(self.variables['_summed_'+varname].unit,
                                        self.variables[varname].unit,
                                        ('Summed variables need to have '
                                         'the same units in Synapses '
                                         'and the target group'))
            if self.variables[varname] in summed_targets:
                raise ValueError(('The target variable "%s" is already '
                                  'updated by another summed '
                                  'variable') % orig_varname)
            summed_targets.add(self.variables[varname])
            updater = SummedVariableUpdater(single_equation.expr,
                                            varname, self, summed_target)
            self.summed_updaters[varname] = updater
            self.contained_objects.append(updater)

        # Do an initial connect, if requested
        if not isinstance(connect, (bool, basestring)):
            raise TypeError(('"connect" keyword has to be a boolean value or a '
                             'string, is type %s instead.' % type(connect)))
        self._initial_connect = connect
        if not connect is False:
            self.connect(connect, level=1)

        # Activate name attribute access
        self._enable_group_attributes()

    def __len__(self):
        return self._N

    def before_run(self, run_namespace=None, level=0):
        self.lastupdate = self.clock.t
        super(Synapses, self).before_run(run_namespace, level=level+1)

    def _add_updater(self, code, prepost, objname=None, delay=None):
        '''
        Add a new target updater. Users should call `add_pre` or `add_post`
        instead.

        Parameters
        ----------
        code : str
            The abstract code that should be executed on pre-/postsynaptic
            spikes.
        prepost : {'pre', 'post'}
            Whether the code is triggered by presynaptic or postsynaptic spikes
        objname : str, optional
            A name for the object, see `SynapticPathway` for more details.
        delay : `Quantity`, optional
            A scalar delay (same delay for all synapses) for this pathway. If
            not given, delays are expected to vary between synapses.

        Returns
        -------
        objname : str
            The final name for the object. Equals `objname` if it was explicitly
            given (and did not end in a wildcard character).

        '''
        if prepost == 'pre':
            spike_group, group_name = self.source, 'Source'
        elif prepost == 'post':
            spike_group, group_name = self.target, 'Target'
        else:
            raise ValueError(('"prepost" argument has to be "pre" or "post", '
                              'is "%s".') % prepost)

        if not isinstance(spike_group, SpikeSource) or not hasattr(spike_group, 'clock'):
            raise TypeError(('%s has to be a SpikeSource with spikes and'
                             ' clock attribute. Is type %r instead')
                            % (group_name, type(spike_group)))

        updater = SynapticPathway(self, code, prepost, objname, delay)
        objname = updater.objname
        if hasattr(self, objname):
            raise ValueError(('Cannot add updater with name "{name}", synapses '
                              'object already has an attribute with this '
                              'name.').format(name=objname))

        setattr(self, objname, updater)
        self._synaptic_updaters.append(objname)
        self._pathways.append(updater)
        self.contained_objects.append(updater)
        return objname

    def _create_variables(self, equations, user_dtype=None):
        '''
        Create the variables dictionary for this `Synapses`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)

        # Standard variables always present
        self.variables.add_dynamic_array('_synaptic_pre', size=0, unit=Unit(1),
                                         dtype=np.int32, constant_size=True)
        self.variables.add_dynamic_array('_synaptic_post', size=0, unit=Unit(1),
                                         dtype=np.int32, constant_size=True)

        self.variables.add_reference('i', self.source.variables['i'],
                                     index='_presynaptic_idx')
        self.variables.add_reference('j', self.target.variables['i'],
                                     index='_postsynaptic_idx')

        if '_offset' in self.target.variables:
            target_offset = self.target.variables['_offset'].get_value()
        else:
            target_offset = 0
        if '_offset' in self.source.variables:
            source_offset = self.source.variables['_offset'].get_value()
        else:
            source_offset = 0
        self.variables.add_array('N_incoming', size=len(self.target)+target_offset,
                                 unit=Unit(1), dtype=np.int32,
                                 constant=True,  read_only=True,
                                 index='_postsynaptic_idx')
        self.variables.add_array('N_outgoing', size=len(self.source)+source_offset,
                                 unit=Unit(1), dtype=np.int32,
                                 constant=True,  read_only=True,
                                 index='_presynaptic_idx')

        # We have to make a distinction here between the indices
        # and the arrays (even though they refer to the same object)
        # the synaptic propagation template would otherwise overwrite
        # synaptic_post in its namespace with the value of the
        # postsynaptic index, leading to errors for the next
        # propagation.
        self.variables.add_reference('_presynaptic_idx',
                                     self.variables['_synaptic_pre'])
        self.variables.add_reference('_postsynaptic_idx',
                                     self.variables['_synaptic_post'])

        # Add the standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_attribute_variable('N', Unit(1), self, '_N',
                                              constant=True)

        for eq in equations.itervalues():
            dtype = get_dtype(eq, user_dtype)
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                constant = 'constant' in eq.flags
                scalar = 'scalar' in eq.flags
                if scalar:
                    self.variables.add_array(eq.varname, size=1,
                                             unit=eq.unit,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=True,
                                             index='0')
                else:
                    # We are dealing with dynamic arrays here, code generation
                    # shouldn't directly access the specifier.array attribute but
                    # use specifier.get_value() to get a reference to the underlying
                    # array
                    self.variables.add_dynamic_array(eq.varname, size=0,
                                                     unit=eq.unit,
                                                     dtype=dtype,
                                                     constant=constant)
            elif eq.type == SUBEXPRESSION:
                if 'summed' in eq.flags:
                    # Give a special name to the subexpression for summed
                    # variables to avoid confusion with the pre/postsynaptic
                    # target variable
                    varname = '_summed_'+eq.varname
                else:
                    varname = eq.varname
                self.variables.add_subexpression(varname, unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 scalar='scalar' in eq.flags,
                                                 dtype=dtype)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Stochastic variables
        for xi in equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Add all the pre and post variables with _pre and _post suffixes
        for name, var in getattr(self.source, 'variables', {}).iteritems():
            index = '0' if var.scalar else '_presynaptic_idx'
            self.variables.add_reference(name + '_pre', var,
                                         index=index)
        for name, var in getattr(self.target, 'variables', {}).iteritems():
            index = '0' if var.scalar else '_postsynaptic_idx'
            self.variables.add_reference(name + '_post', var,
                                         index=index)
            # Also add all the post variables without a suffix -- note that a
            # reference will never overwrite the name of an existing name
            self.variables.add_reference(name, var, index=index)

        # Check scalar subexpressions
        for eq in equations.itervalues():
            if eq.type == SUBEXPRESSION and 'scalar' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(('Scalar subexpression %s refers '
                                               'to non-scalar variable %s.')
                                              % (eq.varname, identifier))

    def connect(self, pre_or_cond, post=None, p=1., n=1, namespace=None,
                level=0):
        '''
        Add synapses. The first argument can be either a presynaptic index
        (int or array) or a condition for synapse creation in the form of a
        string that evaluates to a boolean value (or directly a boolean value).
        If it is given as an index, also `post` has to be present. A string
        condition will be evaluated for all pre-/postsynaptic indices, which
        can be referred to as `i` and `j`.

        Parameters
        ----------
        pre_or_cond : {int, ndarray of int, bool, str}
            The presynaptic neurons (in the form of an index or an array of
            indices) or a boolean value or a string that evaluates to a
            boolean value. If it is an index, then also `post` has to be
            given.
        post_neurons : {int, ndarray of int), optional
            GroupIndices of neurons from the target group. Non-optional if one or
            more presynaptic indices have been given.
        p : float, optional
            The probability to create `n` synapses wherever the condition
            given as `pre_or_cond` evaluates to true or for the given
            pre/post indices.
        n : int, optional
            The number of synapses to create per pre/post connection pair.
            Defaults to 1.
        namespace : dict-like, optional
            A namespace that will be used in addition to the group-specific
            namespaces (if defined). If not specified, the locals
            and globals around the run function will be used.
        level : int, optional
            How deep to go up the stack frame to look for the locals/global
            (see `namespace` argument).

        Examples
        --------
        >>> from brian2 import *
        >>> import numpy as np
        >>> G = NeuronGroup(10, 'dv/dt = -v / tau : 1', threshold='v>1', reset='v=0')
        >>> S = Synapses(G, G, 'w:1', pre='v+=w')
        >>> S.connect('i != j') # all-to-all but no self-connections
        >>> S.connect(0, 0) # connect neuron 0 to itself
        >>> S.connect(np.array([1, 2]), np.array([2, 1])) # connect 1->2 and 2->1
        >>> S.connect(True) # connect all-to-all
        >>> S.connect('i != j', p=0.1)  # Connect neurons with 10% probability, exclude self-connections
        >>> S.connect('i == j', n=2)  # Connect all neurons to themselves with 2 synapses
        '''
        if not isinstance(pre_or_cond, (bool, basestring)):
            pre_or_cond = np.asarray(pre_or_cond)
            if not np.issubdtype(pre_or_cond.dtype, np.int):
                raise TypeError(('Presynaptic indices have to be given as '
                                 'integers, are type %s instead.') % pre_or_cond.dtype)

            post = np.asarray(post)
            if not np.issubdtype(post.dtype, np.int):
                raise TypeError(('Presynaptic indices can only be combined '
                                 'with postsynaptic integer indices))'))
            if isinstance(n, basestring):
                raise TypeError(('Indices cannot be combined with a string'
                                 'expression for n. Either use an array/scalar '
                                 'for n, or a string expression for the '
                                 'connections'))
            i, j, n = np.broadcast_arrays(pre_or_cond, post, n)
            if i.ndim > 1:
                raise ValueError('Can only use 1-dimensional indices')
            self._add_synapses(i, j, n, p, namespace=namespace, level=level+1)
        elif isinstance(pre_or_cond, (basestring, bool)):
            if pre_or_cond is False:
                return  # nothing to do...
            elif pre_or_cond is True:
                # TODO: This should not be handled with the general mechanism
                pre_or_cond = 'True'
            if post is not None:
                raise ValueError('Cannot give a postsynaptic index when '
                                 'using a string expression')
            if not isinstance(n, (int, basestring)):
                raise TypeError('n has to be an integer or a string evaluating '
                                'to an integer, is type %s instead.' % type(n))
            if not isinstance(p, (float, basestring)):
                raise TypeError('p has to be a float or a string evaluating '
                                'to an float, is type %s instead.' % type(n))
            self._add_synapses(None, None, n, p, condition=pre_or_cond,
                               namespace=namespace, level=level+1)
        else:
            raise TypeError(('First argument has to be an index or a '
                             'string, is %s instead.') % type(pre_or_cond))

    def _resize(self, number):
        if not isinstance(number, int):
            raise TypeError(('Expected an integer number got {} '
                             'instead').format(type(number)))
        if number < self._N:
            raise ValueError(('Cannot reduce number of synapses, '
                              '{} < {}').format(number, len(self)))

        for variable in self._registered_variables:
            variable.resize(number)

        self._N = number

    def register_variable(self, variable):
        '''
        Register a `DynamicArray` to be automatically resized when the size of
        the indices change. Called automatically when a `SynapticArrayVariable`
        specifier is created.
        '''
        if not hasattr(variable, 'resize'):
            raise TypeError(('Variable of type {} does not have a resize '
                             'method, cannot register it with the synaptic '
                             'indices.').format(type(variable)))
        self._registered_variables.add(variable)

    def unregister_variable(self, variable):
        '''
        Unregister a `DynamicArray` from the automatic resizing mechanism.
        '''
        self._registered_variables.remove(variable)

    def _add_synapses(self, sources, targets, n, p, condition=None,
                      namespace=None, level=0):

        if condition is None:
            sources = np.atleast_1d(sources).astype(np.int32)
            targets = np.atleast_1d(targets).astype(np.int32)
            n = np.atleast_1d(n)
            p = np.atleast_1d(p)
            if not len(p) == 1 or p != 1:
                use_connections = np.random.rand(len(sources)) < p
                sources = sources[use_connections]
                targets = targets[use_connections]
                n = n[use_connections]
            sources = sources.repeat(n)
            targets = targets.repeat(n)
            new_synapses = len(sources)

            old_N = len(self)
            new_N = old_N + new_synapses
            self._resize(new_N)

            # Deal with subgroups
            if '_sub_idx' in self.source.variables:
                real_sources = self.source.variables['_sub_idx'].get_value()[sources]
            else:
                real_sources = sources
            if '_sub_idx' in self.target.variables:
                real_targets = self.target.variables['_sub_idx'].get_value()[targets]
            else:
                real_targets = targets
            self.variables['_synaptic_pre'].get_value()[old_N:new_N] = real_sources
            self.variables['_synaptic_post'].get_value()[old_N:new_N] = real_targets

            self.variables['N_outgoing'].get_value()[:] += np.bincount(real_sources,
                                                                       minlength=self.variables['N_outgoing'].size)
            self.variables['N_incoming'].get_value()[:] += np.bincount(real_targets,
                                                                       minlength=self.variables['N_incoming'].size)
        else:
            abstract_code = '_pre_idx = _all_pre \n'
            abstract_code += '_post_idx = _all_post \n'
            abstract_code += '_cond = ' + condition + '\n'
            abstract_code += '_n = ' + str(n) + '\n'
            abstract_code += '_p = ' + str(p)
            # This overwrites 'i' and 'j' in the synapses' variables dictionary
            # This is necessary because in the context of synapse creation, i
            # and j do not correspond to the sources/targets of the existing
            # synapses but to all the possible sources/targets
            variables = Variables(None)
            # Will be set in the template
            variables.add_auxiliary_variable('_i', unit=Unit(1))
            variables.add_auxiliary_variable('_j', unit=Unit(1))
            # Make sure that variables have the correct type in the code
            variables.add_auxiliary_variable('_pre_idx', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_post_idx', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_cond', unit=Unit(1), dtype=np.bool)
            variables.add_auxiliary_variable('_n', unit=Unit(1), dtype=np.int32)
            variables.add_auxiliary_variable('_p', unit=Unit(1))

            if '_sub_idx' in self.source.variables:
                variables.add_reference('_all_pre', self.source.variables['_sub_idx'])
            else:
                variables.add_reference('_all_pre', self.source.variables['i'])

            if '_sub_idx' in self.target.variables:
                variables.add_reference('_all_post', self.target.variables['_sub_idx'])
            else:
                variables.add_reference('_all_post', self.target.variables['i'])

            variable_indices = defaultdict(lambda: '_idx')
            for varname in self.variables:
                if self.variables.indices[varname] == '_presynaptic_idx':
                    variable_indices[varname] = '_all_pre'
                elif self.variables.indices[varname] == '_postsynaptic_idx':
                    variable_indices[varname] = '_all_post'
            variable_indices['_all_pre'] = '_i'
            variable_indices['_all_post'] = '_j'
            codeobj = create_runner_codeobj(self,
                                            abstract_code,
                                            'synapses_create',
                                            variable_indices=variable_indices,
                                            additional_variables=variables,
                                            check_units=False,
                                            run_namespace=namespace,
                                            level=level+1)
            codeobj()


    def calc_indices(self, index):
        '''
        Returns synaptic indices for `index`, which can be a tuple of indices
        (including arrays and slices), a single index or a string.

        '''
        if (not isinstance(index, (tuple, basestring)) and
                isinstance(index, (int, np.ndarray, slice,
                                   collections.Sequence))):
            index = (index, slice(None), slice(None))
        if isinstance(index, tuple):
            if len(index) == 2:  # two indices (pre- and postsynaptic cell)
                index = (index[0], index[1], slice(None))
            elif len(index) > 3:
                raise IndexError('Need 1, 2 or 3 indices, got %d.' % len(index))

            I, J, K = index

            pre_synapses = find_synapses(I, self.variables['_synaptic_pre'].get_value() - self.source.start)
            post_synapses = find_synapses(J, self.variables['_synaptic_post'].get_value() - self.target.start)
            matching_synapses = np.intersect1d(pre_synapses, post_synapses,
                                               assume_unique=True)

            if isinstance(K, slice) and K == slice(None):
                return matching_synapses
            elif isinstance(K, (int, slice)):
                test_k = slice_to_test(K)
            else:
                raise NotImplementedError(('Indexing synapses with arrays not'
                                           'implemented yet'))

            # We want to access the raw arrays here, not go through the Variable
            pre_neurons = self.variables['_synaptic_pre'].get_value()[pre_synapses]
            post_neurons = self.variables['_synaptic_post'].get_value()[post_synapses]
            synapse_numbers = _synapse_numbers(pre_neurons,
                                               post_neurons)
            return np.intersect1d(matching_synapses,
                                  np.flatnonzero(test_k(synapse_numbers)),
                                  assume_unique=True)
        else:
            raise IndexError('Unsupported index type {itype}'.format(itype=type(index)))
Ejemplo n.º 36
0
class SpikeMonitor(Group, CodeRunner):
    '''
    Record spikes from a `NeuronGroup` or other spike source
    
    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    record : bool
        Whether or not to record each spike in `i` and `t` (the `count` will
        always be recorded).
    when : `Scheduler`, optional
        When to record the spikes, by default uses the clock of the source
        and records spikes in the slot 'end'.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_spikemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, record=True, when=None, name='spikemonitor*',
                 codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source =source

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, template='spikemonitor',
                            name=name, when=scheduler)

        self.add_dependency(source)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_clock_variables(scheduler.clock, prefix='_clock_')
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('i', size=0, unit=Unit(1),
                                         dtype=np.int32, constant_size=False)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant_size=False)
        self.variables.add_array('_count', size=len(source), unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N', unit=Unit(1), obj=self,
                                              attribute='_N', dtype=np.int32)

        self._enable_group_attributes()

    @property
    def _N(self):
        return len(self.variables['t'].get_value())

    def resize(self, new_size):
        self.variables['i'].resize(new_size)
        self.variables['t'].resize(new_size)

    def __len__(self):
        return self._N

    def reinit(self):
        '''
        Clears all recorded spikes
        '''
        raise NotImplementedError()

    # TODO: Maybe there's a more elegant solution for the count attribute?
    @property
    def count(self):
        return self.variables['_count'].get_value().copy()

    @property
    def it(self):
        '''
        Returns the pair (`i`, `t`).
        '''
        return self.i, self.t

    @property
    def it_(self):
        '''
        Returns the pair (`i`, `t_`).
        '''
        return self.i, self.t_

    @property
    def num_spikes(self):
        '''
        Returns the total number of recorded spikes
        '''
        return self._N

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.group.name)
Ejemplo n.º 37
0
class SynapticPathway(CodeRunner, Group):
    '''
    The `CodeRunner` that applies the pre/post statement(s) to the state
    variables of synapses where the pre-/postsynaptic group spiked in this
    time step.

    Parameters
    ----------

    synapses : `Synapses`
        Reference to the main `Synapses` object
    prepost : {'pre', 'post'}
        Whether this object should react to pre- or postsynaptic spikes
    objname : str, optional
        The name to use for the object, will be appendend to the name of
        `synapses` to create a name in the sense of `Nameable`. The `synapses`
        object should allow access to this object via
        ``synapses.getattr(objname)``. It has to use the actual `objname`
        attribute instead of relying on the provided argument, since the name
        may have changed to become unique. If ``None`` is provided (the
        default), ``prepost+'*'`` will be used (see `Nameable` for an
        explanation of the wildcard operator).
    delay : `Quantity`, optional
        A scalar delay (same delay for all synapses) for this pathway. If
        not given, delays are expected to vary between synapses.
    '''
    def __init__(self, synapses, code, prepost, objname=None,
                 delay=None):
        self.code = code
        self.prepost = prepost
        if prepost == 'pre':
            self.source = synapses.source
            self.target = synapses.target
            self.synapse_sources = synapses.variables['_synaptic_pre']
        elif prepost == 'post':
            self.source = synapses.target
            self.target = synapses.source
            self.synapse_sources = synapses.variables['_synaptic_post']
        else:
            raise ValueError('prepost argument has to be either "pre" or '
                             '"post"')
        self.synapses = synapses

        if objname is None:
            objname = prepost + '*'

        CodeRunner.__init__(self, synapses,
                            'synapses',
                            code=code,
                            when=(synapses.clock, 'synapses'),
                            name=synapses.name + '_' + objname,
                            template_kwds={'pathway': self})

        self._pushspikes_codeobj = None

        self.spikes_start = self.source.start
        self.spikes_stop = self.source.stop

        self.spiking_synapses = []
        self.variables = Variables(self)
        self.variables.add_attribute_variable('_spiking_synapses', unit=Unit(1),
                                              obj=self,
                                              attribute='spiking_synapses',
                                              constant=False,
                                              scalar=False)
        self.variables.add_reference('_spikespace',
                                     self.source.variables['_spikespace'])
        self.variables.add_reference('N', synapses.variables['N'])
        if delay is None:  # variable delays
            self.variables.add_dynamic_array('delay', unit=second,
                                             size=synapses._N, constant=True,
                                             constant_size=True)
            # Register the object with the `SynapticIndex` object so it gets
            # automatically resized
            synapses.register_variable(self.variables['delay'])
        else:
            if not isinstance(delay, Quantity):
                raise TypeError(('Cannot set the delay for pathway "%s": '
                                 'expected a quantity, got %s instead.') % (objname,
                                                                            type(delay)))
            if delay.size != 1:
                raise TypeError(('Cannot set the delay for pathway "%s": '
                                 'expected a scalar quantity, got a '
                                 'quantity with shape %s instead.') % str(delay.shape))
            fail_for_dimension_mismatch(delay, second, ('Delay has to be '
                                                        'specified in units '
                                                        'of seconds'))
            self.variables.add_array('delay', unit=second, size=1,
                                     constant=True, scalar=True)
            self.variables['delay'].set_value(delay)

        self._delays = self.variables['delay']

        # Re-extract the last part of the name from the full name
        self.objname = self.name[len(synapses.name) + 1:]

        #: The simulation dt (necessary for the delays)
        self.dt = self.synapses.clock.dt_

        #: The `SpikeQueue`
        self.queue = None

        #: The `CodeObject` initalising the `SpikeQueue` at the begin of a run
        self._initialise_queue_codeobj = None

        self.namespace = synapses.namespace
        # Enable access to the delay attribute via the specifier
        self._enable_group_attributes()

    def __len__(self):
        return self.N_

    def update_abstract_code(self, run_namespace=None, level=0):
        if self.synapses.event_driven is not None:
            event_driven_update = independent(self.synapses.event_driven,
                                              self.group.variables)
            # TODO: Any way to do this more elegantly?
            event_driven_update = re.sub(r'\bdt\b', '(t - lastupdate)',
                                         event_driven_update)

            self.abstract_code = event_driven_update + '\n'
        else:
            self.abstract_code = ''

        self.abstract_code += self.code + '\n'
        self.abstract_code += 'lastupdate = t\n'

    def before_run(self, run_namespace=None, level=0):
        # execute code to initalize the spike queue
        if self._initialise_queue_codeobj is None:
            self._initialise_queue_codeobj = create_runner_codeobj(self,
                                                                   '', # no code,
                                                                   'synapses_initialise_queue',
                                                                   name=self.name+'_initialise_queue',
                                                                   check_units=False,
                                                                   additional_variables=self.variables,
                                                                   run_namespace=run_namespace,
                                                                   level=level+1)
        self._initialise_queue_codeobj()
        CodeRunner.before_run(self, run_namespace, level=level+1)

        # we insert rather than replace because CodeRunner puts a CodeObject in updaters already
        if self._pushspikes_codeobj is None:
            self._pushspikes_codeobj = create_runner_codeobj(self,
                                                             '', # no code
                                                             'synapses_push_spikes',
                                                             name=self.name+'_push_spikes',
                                                             check_units=False,
                                                             additional_variables=self.variables,
                                                             run_namespace=run_namespace,
                                                             level=level+1)

        self._code_objects.insert(0, weakref.proxy(self._pushspikes_codeobj))

    def initialise_queue(self):
        if self.queue is None:
            self.queue = get_device().spike_queue(self.source.start, self.source.stop)

        # Update the dt (might have changed between runs)
        self.dt = self.synapses.clock.dt_

        self.queue.prepare(self._delays.get_value(), self.dt,
                           self.synapse_sources.get_value())

    def push_spikes(self):
        # Push new spikes into the queue
        spikes = self.source.spikes
        if len(spikes):
            self.queue.push(spikes)
        # Get the spikes
        self.spiking_synapses = self.queue.peek()
        # Advance the spike queue
        self.queue.advance()
Ejemplo n.º 38
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    SpikeGeneratorGroup(N, indices, times, dt=None, clock=None,
                        period=0*second, when='thresholds', order=0,
                        sorted=False, name='spikegeneratorgroup*',
                        codeobj_class=None)

    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in ``indices``. Has to have the
        same length as ``indices``.
    period : `Quantity`, optional
        If this is specified, it will repeat spikes with this period. A
        period of 0s means not repeating spikes.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    sorted : bool, optional
        Whether the given indices and times are already sorted. Set to ``True``
        if your events are already sorted (first by spike time, then by index),
        this can save significant time at construction if your arrays contain
        large numbers of spikes. Defaults to ``False``.

    Notes
    -----
    * If `sorted` is set to ``True``, the given arrays will not be copied
      (only affects runtime mode)..
    '''

    @check_units(N=1, indices=1, times=second, period=second)
    def __init__(self, N, indices, times, dt=None, clock=None,
                 period=0*second, when='thresholds', order=0, sorted=False,
                 name='spikegeneratorgroup*', codeobj_class=None):

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order, name=name)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        #: Array of spiking neuron indices.
        self._neuron_index = None
        #: Array of spiking neuron times.
        self._spike_time = None
        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        if N < 1 or int(N) != N:
            raise TypeError('N has to be an integer >=1.')
        N = int(N)  # Make sure that it is an integer, values such as 10.0 would
                    # otherwise make weave compilation fail
        self.start = 0
        self.stop = N

        self.variables = Variables(self)
        self.variables.create_clock_variables(self._clock)

        indices, times = self._check_args(indices, times, period, N, sorted,
                                          self._clock.dt)

        self.variables.add_constant('N', value=N)
        self.variables.add_array('period', dimensions=second.dim, size=1,
                                 constant=True, read_only=True, scalar=True,
                                 dtype=self._clock.variables['t'].dtype)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices),
                                         dtype=np.int32, read_only=True,
                                         constant=True, index='spike_number',
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index', values=indices,
                                         size=len(indices),
                                         dtype=np.int32, index='spike_number',
                                         read_only=True, constant=True)
        self.variables.add_dynamic_array('spike_time', values=times, size=len(times),
                                         dimensions=second.dim, index='spike_number',
                                         read_only=True, constant=True,
                                         dtype=self._clock.variables['t'].dtype)
        self.variables.add_dynamic_array('_timebins', size=len(times),
                                         index='spike_number',
                                         read_only=True, constant=True,
                                         dtype=np.int32)
        self.variables.add_array('_period_bins', size=1, constant=True,
                                 read_only=True, scalar=True,
                                 dtype=np.int32)
        self.variables.add_array('_spikespace', size=N+1, dtype=np.int32)
        self.variables.add_array('_lastindex', size=1, values=0, dtype=np.int32,
                                 read_only=True, scalar=True)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        CodeRunner.__init__(self, self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)

    def before_run(self, run_namespace):
        # Do some checks on the period vs. dt
        dt = self.dt_[:]  # make a copy
        period = self.period_
        if period < np.inf and period != 0:
            if period < dt:
                raise ValueError('The period of %s is %s, which is smaller '
                                 'than its dt of %s.' % (self.name,
                                                         self.period[:],
                                                         dt*second))

        if self._spikes_changed:
            current_t = self.variables['t'].get_value().item()
            timesteps = timestep(self._spike_time, dt)
            current_step = timestep(current_t, dt)
            in_the_past = np.nonzero(timesteps < current_step)[0]
            if len(in_the_past):
                logger.warn('The SpikeGeneratorGroup contains spike times '
                            'earlier than the start time of the current run '
                            '(t = {}), these spikes will be '
                            'ignored.'.format(str(current_t*second)),
                            name_suffix='ignored_spikes')
                self.variables['_lastindex'].set_value(in_the_past[-1] + 1)
            else:
                self.variables['_lastindex'].set_value(0)

        # Check that we don't have more than one spike per neuron in a time bin
        if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed:
            # We shift all the spikes by a tiny amount to make sure that spikes
            # at exact multiples of dt do not end up in the previous time bin
            # This shift has to be quite significant relative to machine
            # epsilon, we use 1e-3 of the dt here
            shift = 1e-3*dt
            timebins = np.asarray(np.asarray(self._spike_time + shift)/dt,
                                  dtype=np.int32)
            # time is already in sorted order, so it's enough to check if the condition
            # that timebins[i]==timebins[i+1] and self._neuron_index[i]==self._neuron_index[i+1]
            # is ever both true
            if (np.logical_and(np.diff(timebins)==0, np.diff(self._neuron_index)==0)).any():
                raise ValueError('Using a dt of %s, some neurons of '
                                 'SpikeGeneratorGroup "%s" spike more than '
                                 'once during a time step.' % (str(self.dt),
                                                               self.name))
            self.variables['_timebins'].set_value(timebins)
            period_bins = np.round(period / dt)
            max_int = np.iinfo(np.int32).max
            if period_bins > max_int:
                logger.warn('Periods longer than {} timesteps (={}) are not '
                            'supported, the period will therefore be '
                            'considered infinite. Set the period to 0*second '
                            'to avoid this '
                            'warning.'.format(max_int, str(max_int*dt*second)),
                            'spikegenerator_long_period')
                period = period_bins = 0
            if np.abs(period_bins * dt - period) > period * np.finfo(dt.dtype).eps:
                raise NotImplementedError('The period of %s is %s, which is '
                                          'not an integer multiple of its dt '
                                          'of %s.' % (self.name,
                                                      self.period[:],
                                                      dt * second))

            self.variables['_period_bins'].set_value(period_bins)

            self._previous_dt = dt
            self._spikes_changed = False

        super(SpikeGeneratorGroup, self).before_run(run_namespace=run_namespace)

    @check_units(indices=1, times=second, period=second)
    def set_spikes(self, indices, times, period=0*second, sorted=False):
        '''
        set_spikes(indices, times, period=0*second, sorted=False)

        Change the spikes that this group will generate.

        This can be used to set the input for a second run of a model based on
        the output of a first run (if the input for the second run is already
        known before the first run, then all the information should simply be
        included in the initial `SpikeGeneratorGroup` initializer call,
        instead).

        Parameters
        ----------
        indices : array of integers
            The indices of the spiking cells
        times : `Quantity`
            The spike times for the cells given in ``indices``. Has to have the
            same length as ``indices``.
        period : `Quantity`, optional
            If this is specified, it will repeat spikes with this period. A
            period of 0s means not repeating spikes.
        sorted : bool, optional
            Whether the given indices and times are already sorted. Set to
            ``True`` if your events are already sorted (first by spike time,
            then by index), this can save significant time at construction if
            your arrays contain large numbers of spikes. Defaults to ``False``.
        '''

        indices, times = self._check_args(indices, times, period, self.N,
                                          sorted, self.dt)

        self.variables['period'].set_value(period)
        self.variables['neuron_index'].resize(len(indices))
        self.variables['spike_time'].resize(len(indices))
        self.variables['spike_number'].resize(len(indices))
        self.variables['spike_number'].set_value(np.arange(len(indices)))
        self.variables['_timebins'].resize(len(indices))
        self.variables['neuron_index'].set_value(indices)
        self.variables['spike_time'].set_value(times)
        # _lastindex and _timebins will be set as part of before_run

    def _check_args(self, indices, times, period, N, sorted, dt):
        times = Quantity(times)
        if len(indices) != len(times):
            raise ValueError(('Length of the indices and times array must '
                              'match, but %d != %d') % (len(indices),
                                                        len(times)))
        if period < 0*second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period != 0*second:
            period_bins = np.round(period / dt)
            # Note: we have to use the timestep function here, to use the same
            # binning as in the actual simulation
            max_bin = timestep(np.max(times), dt)
            if max_bin >= period_bins:
                raise ValueError('The period has to be greater than the '
                                 'maximum of the spike times')
        if len(times) and np.min(times) < 0*second:
            raise ValueError('Spike times cannot be negative')
        if len(indices) and (np.min(indices) < 0 or np.max(indices) >= N):
            raise ValueError('Indices have to lie in the interval [0, %d[' % N)

        times = np.asarray(times)
        indices = np.asarray(indices)
        if not sorted:
            # sort times and indices first by time, then by indices
            I = np.lexsort((indices, times))
            indices = indices[I]
            times = times[I]

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._neuron_index = indices
        self._spike_time = times
        self._spikes_changed = True

        return indices, times

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(cls=self.__class__.__name__,
                                                   N=self.N,
                                                   l=self.variables['neuron_index'].size)
Ejemplo n.º 39
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    namespace: dict, optional
        A dictionary mapping variable/function names to the respective objects.
        If no `namespace` is given, the "implicit" namespace, consisting of
        the local and global namespace surrounding the creation of the class,
        is used.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of their `when` attribute take these values). The `order`
    attribute will be passed down to the contained objects but can be set
    individually by setting the `order` attribute of the `state_updater`,
    `thresholder` and `resetter` attributes, respectively.
    '''
    add_to_magic_network = True

    def __init__(self,
                 N,
                 model,
                 method=('linear', 'euler', 'milstein'),
                 threshold=None,
                 reset=None,
                 refractory=False,
                 namespace=None,
                 dtype=None,
                 dt=None,
                 clock=None,
                 order=0,
                 name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when='start',
                       order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError(
                    "First NeuronGroup argument should be size, not equations."
                )
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " +
                             str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({
            DIFFERENTIAL_EQUATION: ('unless refractory', ),
            PARAMETER: ('constant', 'shared', 'linked'),
            SUBEXPRESSION: ('shared', )
        })

        # add refractoriness
        if refractory is not False:
            model = add_refractoriness(model)
        self.equations = model
        uses_refractoriness = len(model) and any([
            'unless refractory' in eq.flags
            for eq in model.itervalues() if eq.type == DIFFERENTIAL_EQUATION
        ])
        self._linked_variables = set()
        logger.debug("Creating NeuronGroup of size {self._N}, "
                     "equations {self.equations}.".format(self=self))

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        # Setup variables
        self._create_variables(dtype)

        # All of the following will be created in before_run

        #: The threshold condition
        self.threshold = threshold

        #: The reset statement(s)
        self.reset = reset

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn(
                'Model equations use the "unless refractory" flag but '
                'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method

        #: Performs thresholding step, sets the value of `spikes`
        self.thresholder = None
        if self.threshold is not None:
            self.thresholder = Thresholder(self)

        #: Resets neurons which have spiked (`spikes`)
        self.resetter = None
        if self.reset is not None:
            self.resetter = Resetter(self)

        # We try to run a before_run already now. This might fail because of an
        # incomplete namespace but if the namespace is already complete we
        # can spot unit errors in the equation already here.
        try:
            self.before_run(None)
        except KeyError:
            pass

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method)

        # Creation of contained_objects that do the work
        self.contained_objects.append(self.state_updater)
        if self.thresholder is not None:
            self.contained_objects.append(self.thresholder)
        if self.resetter is not None:
            self.contained_objects.append(self.resetter)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-np.inf * second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()

    def __len__(self):
        '''
        Return number of neurons in the group.
        '''
        return self.N

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def state(self, name, use_units=True, level=0):
        try:
            return Group.state(self,
                               name,
                               use_units=use_units,
                               level=level + 1)
        except KeyError as ex:
            if name in self._linked_variables:
                raise TypeError(('Link target for variable %s has not been '
                                 'set.') % name)
            else:
                raise ex

    def __setattr__(self, key, value):
        # attribute access is switched off until this attribute is created by
        # _enable_group_attributes
        if not hasattr(
                self,
                '_group_attribute_access_active') or key in self.__dict__:
            object.__setattr__(self, key, value)
        elif key in self._linked_variables:
            if not isinstance(value, LinkedVariable):
                raise ValueError(
                    ('Cannot set a linked variable directly, link '
                     'it to another variable using "linked_var".'))
            linked_var = value.variable

            if isinstance(linked_var, DynamicArrayVariable):
                raise NotImplementedError(('Linking to variable %s is not '
                                           'supported, can only link to '
                                           'state variables of fixed '
                                           'size.') % linked_var.name)

            eq = self.equations[key]
            if eq.unit != linked_var.unit:
                raise DimensionMismatchError(
                    ('Unit of variable %s does not '
                     'match its link target %s') % (key, linked_var.name))

            if not isinstance(linked_var, Subexpression):
                var_length = len(linked_var)
            else:
                var_length = len(linked_var.owner)

            if value.index is not None:
                try:
                    index_array = np.asarray(value.index)
                    if not np.issubsctype(index_array.dtype, np.int):
                        raise TypeError()
                except TypeError:
                    raise TypeError(('The index for a linked variable has '
                                     'to be an integer array'))
                size = len(index_array)
                source_index = value.group.variables.indices[value.name]
                if source_index not in ('_idx', '0'):
                    # we are indexing into an already indexed variable,
                    # calculate the indexing into the target variable
                    index_array = value.group.variables[
                        source_index].get_value()[index_array]

                if not index_array.ndim == 1 or size != len(self):
                    raise TypeError(
                        ('Index array for linked variable %s '
                         'has to be a one-dimensional array of '
                         'length %d, but has shape '
                         '%s') % (key, len(self), str(index_array.shape)))
                if min(index_array) < 0 or max(index_array) >= var_length:
                    raise ValueError('Index array for linked variable %s '
                                     'contains values outside of the valid '
                                     'range [0, %d[' % (key, var_length))
                self.variables.add_array('_%s_indices' % key,
                                         unit=Unit(1),
                                         size=size,
                                         dtype=index_array.dtype,
                                         constant=True,
                                         read_only=True,
                                         values=index_array)
                index = '_%s_indices' % key
            else:
                if linked_var.scalar or (var_length == 1 and self._N != 1):
                    index = '0'
                else:
                    index = value.group.variables.indices[value.name]
                    if index == '_idx':
                        target_length = var_length
                    else:
                        target_length = len(value.group.variables[index])
                        # we need a name for the index that does not clash with
                        # other names and a reference to the index
                        new_index = '_' + value.name + '_index_' + index
                        self.variables.add_reference(new_index, value.group,
                                                     index)
                        index = new_index

                    if len(self) != target_length:
                        raise ValueError(
                            ('Cannot link variable %s to %s, the size of '
                             'the target group does not match '
                             '(%d != %d). You can provide an indexing '
                             'scheme with the "index" keyword to link '
                             'groups with different sizes') %
                            (key, linked_var.name, len(self), target_length))

            self.variables.add_reference(key,
                                         value.group,
                                         value.name,
                                         index=index)
            log_msg = ('Setting {target}.{targetvar} as a link to '
                       '{source}.{sourcevar}').format(
                           target=self.name,
                           targetvar=key,
                           source=value.variable.owner.name,
                           sourcevar=value.variable.name)
            if index is not None:
                log_msg += '(using "{index}" as index variable)'.format(
                    index=index)
            logger.debug(log_msg)
        else:
            if isinstance(value, LinkedVariable):
                raise TypeError(
                    ('Cannot link variable %s, it has to be marked '
                     'as a linked variable with "(linked)" in the '
                     'model equations.') % key)
            else:
                Group.__setattr__(self, key, value, level=1)

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError(
                'Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    def _create_variables(self, user_dtype=None):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_constant('N', Unit(1), self._N)

        # Standard variables always present
        self.variables.add_array('_spikespace',
                                 unit=Unit(1),
                                 size=self._N + 1,
                                 dtype=np.int32,
                                 constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i',
                                  size=self._N,
                                  constant=True,
                                  read_only=True)
        # Add the clock variables
        self.variables.create_clock_variables(self._clock)

        for eq in self.equations.itervalues():
            dtype = get_dtype(eq, user_dtype)

            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                if 'linked' in eq.flags:
                    # 'linked' cannot be combined with other flags
                    if not len(eq.flags) == 1:
                        raise SyntaxError(('The "linked" flag cannot be '
                                           'combined with other flags'))
                    self._linked_variables.add(eq.varname)
                else:
                    constant = 'constant' in eq.flags
                    shared = 'shared' in eq.flags
                    size = 1 if shared else self._N
                    index = '0' if shared else None
                    self.variables.add_array(eq.varname,
                                             size=size,
                                             unit=eq.unit,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=shared,
                                             index=index)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname,
                                                 unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='shared' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        for eq in self.equations.itervalues():
            if eq.type == DIFFERENTIAL_EQUATION and 'unless refractory' in eq.flags:
                not_refractory_var = self.variables['not_refractory']
                self.variables[eq.varname].set_conditional_write(
                    not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Check scalar subexpressions
        for eq in self.equations.itervalues():
            if eq.type == SUBEXPRESSION and 'shared' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Shared subexpression %s refers '
                                 'to non-shared variable %s.') %
                                (eq.varname, identifier))

    def before_run(self, run_namespace=None, level=0):
        # Check units
        self.equations.check_units(self,
                                   run_namespace=run_namespace,
                                   level=level + 1)

    def _repr_html_(self):
        text = [
            r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)
        ]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))
        text.append(r'<b>Integration method:</b><br>')
        text.append(sympy.latex(self.state_updater.method) + '<br>')
        if self.threshold is not None:
            text.append(r'<b>Threshold condition:</b><br>')
            text.append('<code>%s</code><br>' % str(self.threshold))
            text.append('')
        if self.reset is not None:
            text.append(r'<b>Reset statement:</b><br>')
            text.append(r'<code>%s</code><br>' % str(self.reset))
            text.append('')

        return '\n'.join(text)
Ejemplo n.º 40
0
class EventMonitor(Group, CodeRunner):
    '''
    Record events from a `NeuronGroup` or another event source.

    The recorded events can be accessed in various ways:
    the attributes `~EventMonitor.i` and `~EventMonitor.t` store all the indices
    and event times, respectively. Alternatively, you can get a dictionary
    mapping neuron indices to event trains, by calling the `event_trains`
    method.

    Parameters
    ----------
    source : `NeuronGroup`, `SpikeSource`
        The source of events to record.
    event : str
        The name of the event to record
    variables : str or sequence of str, optional
        Which variables to record at the time of the event (in addition to the
        index of the neuron). Can be the name of a variable or a list of names.
    record : bool, optional
        Whether or not to record each event in `i` and `t` (the `count` will
        always be recorded). Defaults to ``True``.
    when : str, optional
        When to record the events, by default records events in the same slot
        where the event is emitted.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to the order where the
        event is emitted + 1, i.e. it will be recorded directly afterwards.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_eventmonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    See Also
    --------
    SpikeMonitor
    '''
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self, source, event, variables=None, record=True,
                 when=None, order=None, name='eventmonitor*',
                 codeobj_class=None):
        if not isinstance(source, SpikeSource):
            raise TypeError(('%s can only monitor groups producing spikes '
                             '(such as NeuronGroup), but the given argument '
                             'is of type %s.') % (self.__class__.__name__,
                                                  type(source)))
        #: The source we are recording from
        self.source = source
        #: Whether to record times and indices of events
        self.record = record

        if when is None:
            if order is not None:
                raise ValueError('Cannot specify order if when is not specified.')
            if hasattr(source, 'thresholder'):
                parent_obj = source.thresholder[event]
            else:
                parent_obj = source
            when = parent_obj.when
            order = parent_obj.order + 1
        elif order is None:
            order = 0

        #: The event that we are listening to
        self.event = event

        if variables is None:
            variables = {}
        elif isinstance(variables, basestring):
            variables = {variables}

        #: The additional variables that will be recorded
        self.record_variables = set(variables)

        for variable in variables:
            if variable not in source.variables:
                raise ValueError(("'%s' is not a variable of the recorded "
                                  "group" % variable))

        if self.record:
            self.record_variables |= {'i', 't'}

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in self.record_variables]
        code = '\n'.join(code)

        self.codeobj_class = codeobj_class

        # Since this now works for general events not only spikes, we have to
        # pass the information about which variable to use to the template,
        # it can not longer simply refer to "_spikespace"
        eventspace_name = '_{}space'.format(event)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        Nameable.__init__(self, name=name)

        self.variables = Variables(self)
        self.variables.add_reference(eventspace_name, source)

        for variable in self.record_variables:
            source_var = source.variables[variable]
            self.variables.add_reference('_source_%s' % variable,
                                         source, variable)
            self.variables.add_auxiliary_variable('_to_record_%s' % variable,
                                                   unit=source_var.unit,
                                                   dtype=source_var.dtype)
            self.variables.add_dynamic_array(variable, size=0,
                                             unit=source_var.unit,
                                             dtype=source_var.dtype,
                                             read_only=True)
        self.variables.add_arange('_source_idx', size=len(source))
        self.variables.add_array('count', size=len(source), unit=Unit(1),
                                 dtype=np.int32, read_only=True,
                                 index='_source_idx')
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_array('N', unit=Unit(1), size=1, dtype=np.int32,
                                 read_only=True, scalar=True)

        record_variables = {varname: self.variables[varname]
                            for varname in self.record_variables}
        template_kwds = {'eventspace_variable': source.variables[eventspace_name],
                         'record_variables': record_variables,
                         'record': self.record}
        needed_variables = {eventspace_name} | self.record_variables
        CodeRunner.__init__(self, group=self, code=code, template='spikemonitor',
                            name=None,  # The name has already been initialized
                            clock=source.clock, when=when,
                            order=order, needed_variables=needed_variables,
                            template_kwds=template_kwds)

        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')

        self.add_dependency(source)
        self._enable_group_attributes()

    def resize(self, new_size):
        # Note that this does not set N, this has to be done in the template
        # since we use a restricted pointer to access it (which promises that
        # we only change the value through this pointer)
        for variable in self.record_variables:
            self.variables[variable].resize(new_size)

    def reinit(self):
        '''
        Clears all recorded spikes
        '''
        raise NotImplementedError()

    @property
    def it(self):
        '''
        Returns the pair (`i`, `t`).
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        return self.i, self.t

    @property
    def it_(self):
        '''
        Returns the pair (`i`, `t_`).
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')

        return self.i, self.t_

    def _values_dict(self, first_pos, sort_indices, used_indices, var):
        sorted_values = self.state(var, use_units=False)[sort_indices]
        dim = self.variables[var].unit.dim
        event_values = {}
        current_pos = 0  # position in the all_indices array
        for idx in xrange(len(self.source)):
            if current_pos < len(used_indices) and used_indices[current_pos] == idx:
                if current_pos < len(used_indices) - 1:
                    event_values[idx] = Quantity(sorted_values[
                                                 first_pos[current_pos]:
                                                 first_pos[current_pos + 1]],
                                                 dim=dim, copy=False)
                else:
                    event_values[idx] = Quantity(
                        sorted_values[first_pos[current_pos]:],
                        dim=dim, copy=False)
                current_pos += 1
            else:
                event_values[idx] = Quantity([], dim=dim)
        return event_values

    def values(self, var):
        '''
        Return a dictionary mapping neuron indices to arrays of variable values
        at the time of the events (sorted by time).
        Parameters
        ----------
        var : str
            The name of the variable.

        Returns
        -------
        values : dict
            Dictionary mapping each neuron index to an array of variable
            values at the time of the events

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, """dv/dt = 100*Hz : 1
        ...                       v_th : 1""", threshold='v>v_th', reset='v=0')
        >>> G.v_th = [0.5, 1]
        >>> mon = EventMonitor(G, event='spike', variables='v')
        >>> run(20*ms)
        >>> v_values = mon.values('v')
        >>> v_values[0]
        array([ 0.5,  0.5,  0.5,  0.5])
        >>> v_values[1]
        array([ 1.,  1.])
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        indices = self.i[:]
        # We have to make sure that the sort is stable, otherwise our spike
        # times do not necessarily remain sorted.
        sort_indices = np.argsort(indices, kind='mergesort')
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        return self._values_dict(first_pos, sort_indices, used_indices, var)

    def all_values(self):
        '''
        Return a dictionary mapping recorded variable names (including ``t``)
        to a dictionary mapping neuron indices to arrays of variable values at
        the time of the events (sorted by time). This is equivalent to (but more
        efficient than) calling `values` for each variable and storing the
        result in a dictionary.

        Returns
        -------
        all_values : dict
            Dictionary mapping variable names to dictionaries which themselves
            are mapping neuron indicies to arrays of variable values at the
            time of the events.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, """dv/dt = 100*Hz : 1
        ...                       v_th : 1""", threshold='v>v_th', reset='v=0')
        >>> G.v_th = [0.5, 1]
        >>> mon = EventMonitor(G, event='spike', variables='v')
        >>> run(20*ms)
        >>> all_values = mon.all_values()
        >>> all_values['t'][0]
        array([  4.9,   9.9,  14.9,  19.9]) * msecond
        >>> all_values['v'][0]
        array([ 0.5,  0.5,  0.5,  0.5])
        '''
        if not self.record:
            raise AttributeError('Indices and times have not been recorded.'
                                 'Set the record argument to True to record '
                                 'them.')
        indices = self.i[:]
        sort_indices = np.argsort(indices)
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        all_values_dict = {}
        for varname in self.record_variables - {'i'}:
            all_values_dict[varname] = self._values_dict(first_pos,
                                                         sort_indices,
                                                         used_indices,
                                                         varname)
        return all_values_dict

    def event_trains(self):
        '''
        Return a dictionary mapping event indices to arrays of event times.
        Equivalent to calling ``values('t')``.

        Returns
        -------
        event_trains : dict
            Dictionary that stores an array with the event times for each
            neuron index.

        See Also
        --------
        SpikeMonitor.spike_trains
        '''
        return self.values('t')

    @property
    def num_events(self):
        '''
        Returns the total number of recorded events.
        '''
        return self.N[:]

    def __repr__(self):
        description = '<{classname}, recording event "{event}" from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  event=self.event,
                                  source=self.group.name)
Ejemplo n.º 41
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in `indices`. Has to have the
        same length as `indices`.
    when : `Scheduler`
        When to update this group

    Notes
    -----
    * In a time step, `SpikeGeneratorGroup` emits all spikes that happened
      at :math:`t-dt < t_{spike} \leq t`. This might lead to unexpected
      or missing spikes if you change the timestep dt between runs.
    * `SpikeGeneratorGroup` does not currently raise any warning if a neuron
      spikes more that once during a timestep, but other code (e.g. for
      synaptic propagation) might assume that neurons only spike once per
      timestep and will therefore not work properly.
    '''
    @check_units(N=1, indices=1, times=second)
    def __init__(self,
                 N,
                 indices,
                 times,
                 when=None,
                 name='spikegeneratorgroup*',
                 codeobj_class=None):
        if when is None:
            when = Scheduler(when='thresholds')
        Group.__init__(self, when=when, name=name)

        self.codeobj_class = codeobj_class

        if N < 1 or int(N) != N:
            raise ValueError('N has to be an integer >=1.')

        if len(indices) != len(times):
            raise ValueError(
                ('Length of the indices and times array must '
                 'match, but %d != %d') % (len(indices), len(times)))

        self.start = 0
        self.stop = N

        # sort times and indices first by time, then by indices
        rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
        rec.sort()
        times = np.ascontiguousarray(rec.t)
        indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_arange('i', N)
        self.variables.add_arange('spike_number', len(indices))
        self.variables.add_array('neuron_index',
                                 values=indices,
                                 size=len(indices),
                                 unit=Unit(1),
                                 dtype=np.int32,
                                 index='spike_number',
                                 read_only=True)
        self.variables.add_array('spike_time',
                                 values=times,
                                 size=len(times),
                                 unit=second,
                                 index='spike_number',
                                 read_only=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)
        # Activate name attribute access
        self._enable_group_attributes()

        CodeRunner.__init__(self, self, 'spikegenerator', when=when, name=None)

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __len__(self):
        return self.N

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(
                    cls=self.__class__.__name__,
                    N=self.N,
                    l=self.variables['neuron_index'].size)
Ejemplo n.º 42
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    events : dict, optional
        User-defined events in addition to the "spike" event defined by the
        ``threshold``. Has to be a mapping of strings (the event name) to
         strings (the condition) that will be checked.
    namespace: dict, optional
        A dictionary mapping variable/function names to the respective objects.
        If no `namespace` is given, the "implicit" namespace, consisting of
        the local and global namespace surrounding the creation of the class,
        is used.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of their `when` attribute take these values). The `order`
    attribute will be passed down to the contained objects but can be set
    individually by setting the `order` attribute of the `state_updater`,
    `thresholder` and `resetter` attributes, respectively.
    '''
    add_to_magic_network = True

    def __init__(self, N, model,
                 method=('linear', 'euler', 'heun'),
                 threshold=None,
                 reset=None,
                 refractory=False,
                 events=None,
                 namespace=None,
                 dtype=None,
                 dt=None,
                 clock=None,
                 order=0,
                 name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self, dt=dt, clock=clock, when='start', order=order,
                       name=name)

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError("First NeuronGroup argument should be size, not equations.")
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " + str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({DIFFERENTIAL_EQUATION: ('unless refractory',),
                           PARAMETER: ('constant', 'shared', 'linked'),
                           SUBEXPRESSION: ('shared',)})

        # add refractoriness
        if refractory is not False:
            model = add_refractoriness(model)
        self.equations = model
        uses_refractoriness = len(model) and any(['unless refractory' in eq.flags
                                                  for eq in model.itervalues()
                                                  if eq.type == DIFFERENTIAL_EQUATION])
        self._linked_variables = set()
        logger.debug("Creating NeuronGroup of size {self._N}, "
                     "equations {self.equations}.".format(self=self))

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        # All of the following will be created in before_run

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn('Model equations use the "unless refractory" flag but '
                        'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method

        if events is None:
            events = {}

        if threshold is not None:
            if 'spike' in events:
                raise ValueError(("The NeuronGroup defines both a threshold "
                                  "and a 'spike' event"))
            events['spike'] = threshold

        # Setup variables
        # Since we have to create _spikespace and possibly other "eventspace"
        # variables, we pass the supported events
        self._create_variables(dtype, events=events.keys())

        #: Events supported by this group
        self.events = events

        #: Code that is triggered on events (e.g. reset)
        self.event_codes = {}

        #: Checks the spike threshold (or abitrary user-defined events)
        self.thresholder = {}

        #: Reset neurons which have spiked (or perform arbitrary actions for
        #: user-defined events)
        self.resetter = {}

        for event_name in events.iterkeys():
            if not isinstance(event_name, basestring):
                raise TypeError(('Keys in the "events" dictionary have to be '
                                 'strings, not type %s.') % type(event_name))
            if not _valid_event_name(event_name):
                raise TypeError(("The name '%s' cannot be used as an event "
                                 "name.") % event_name)
            # By default, user-defined events are checked after the threshold
            when = 'thresholds' if event_name == 'spike' else 'after_thresholds'
            # creating a Thresholder will take care of checking the validity
            # of the condition
            thresholder = Thresholder(self, event=event_name, when=when)
            self.thresholder[event_name] = thresholder
            self.contained_objects.append(thresholder)

        if reset is not None:
            self.run_on_event('spike', reset, when='resets')

        # We try to run a before_run already now. This might fail because of an
        # incomplete namespace but if the namespace is already complete we
        # can spot unit errors in the equation already here.
        try:
            self.before_run(None)
        except KeyError:
            pass

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method)

        # Creation of contained_objects that do the work
        self.contained_objects.append(self.state_updater)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-np.inf*second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def state(self, name, use_units=True, level=0):
        try:
            return Group.state(self, name, use_units=use_units, level=level+1)
        except KeyError as ex:
            if name in self._linked_variables:
                raise TypeError(('Link target for variable %s has not been '
                                 'set.') % name)
            else:
                raise ex

    def run_on_event(self, event, code, when='after_resets', order=None):
        '''
        Run code triggered by a custom-defined event (see `NeuronGroup`
        documentation for the specification of events).The created `Resetter`
        object will be automatically added to the group, it therefore does not
        need to be added to the network manually. However, a reference to the
        object will be returned, which can be used to later remove it from the
        group or to set it to inactive.

        Parameters
        ----------
        event : str
            The name of the event that should trigger the code
        code : str
            The code that should be executed
        when : str, optional
            The scheduling slot that should be used to execute the code.
            Defaults to `'after_resets'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.

        Returns
        -------
        obj : `Resetter`
            A reference to the object that will be run.
        '''
        if event not in self.events:
            error_message = "Unknown event '%s'." % event
            if event == 'spike':
                error_message += ' Did you forget to define a threshold?'
            raise ValueError(error_message)
        if event in self.resetter:
            raise ValueError(("Cannot add code for event '%s', code for this "
                              "event has already been added.") % event)
        self.event_codes[event] = code
        resetter = Resetter(self, when=when, order=order, event=event)
        self.resetter[event] = resetter
        self.contained_objects.append(resetter)

        return resetter

    def set_event_schedule(self, event, when='after_thresholds', order=None):
        '''
        Change the scheduling slot for checking the condition of an event.

        Parameters
        ----------
        event : str
            The name of the event for which the scheduling should be changed
        when : str, optional
            The scheduling slot that should be used to check the condition.
            Defaults to `'after_thresholds'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.
        '''
        if event not in self.thresholder:
            raise ValueError("Unknown event '%s'." % event)
        order = order if order is not None else self.order
        self.thresholder[event].when = when
        self.thresholder[event].order = order

    def __setattr__(self, key, value):
        # attribute access is switched off until this attribute is created by
        # _enable_group_attributes
        if not hasattr(self, '_group_attribute_access_active') or key in self.__dict__:
            object.__setattr__(self, key, value)
        elif key in self._linked_variables:
            if not isinstance(value, LinkedVariable):
                raise ValueError(('Cannot set a linked variable directly, link '
                                  'it to another variable using "linked_var".'))
            linked_var = value.variable
            
            if isinstance(linked_var, DynamicArrayVariable):
                raise NotImplementedError(('Linking to variable %s is not '
                                           'supported, can only link to '
                                           'state variables of fixed '
                                           'size.') % linked_var.name)

            eq = self.equations[key]
            if eq.unit != linked_var.unit:
                raise DimensionMismatchError(('Unit of variable %s does not '
                                              'match its link target %s') % (key,
                                                                             linked_var.name))

            if not isinstance(linked_var, Subexpression):
                var_length = len(linked_var)
            else:
                var_length = len(linked_var.owner)

            if value.index is not None:
                try:
                    index_array = np.asarray(value.index)
                    if not np.issubsctype(index_array.dtype, np.int):
                        raise TypeError()
                except TypeError:
                    raise TypeError(('The index for a linked variable has '
                                     'to be an integer array'))
                size = len(index_array)
                source_index = value.group.variables.indices[value.name]
                if source_index not in ('_idx', '0'):
                    # we are indexing into an already indexed variable,
                    # calculate the indexing into the target variable
                    index_array = value.group.variables[source_index].get_value()[index_array]

                if not index_array.ndim == 1 or size != len(self):
                    raise TypeError(('Index array for linked variable %s '
                                     'has to be a one-dimensional array of '
                                     'length %d, but has shape '
                                     '%s') % (key,
                                              len(self),
                                              str(index_array.shape)))
                if min(index_array) < 0 or max(index_array) >= var_length:
                    raise ValueError('Index array for linked variable %s '
                                     'contains values outside of the valid '
                                     'range [0, %d[' % (key,
                                                        var_length))
                self.variables.add_array('_%s_indices' % key, unit=Unit(1),
                                         size=size, dtype=index_array.dtype,
                                         constant=True, read_only=True,
                                         values=index_array)
                index = '_%s_indices' % key
            else:
                if linked_var.scalar or (var_length == 1 and self._N != 1):
                    index = '0'
                else:
                    index = value.group.variables.indices[value.name]
                    if index == '_idx':
                        target_length = var_length
                    else:
                        target_length = len(value.group.variables[index])
                        # we need a name for the index that does not clash with
                        # other names and a reference to the index
                        new_index = '_' + value.name + '_index_' + index
                        self.variables.add_reference(new_index,
                                                     value.group,
                                                     index)
                        index = new_index

                    if len(self) != target_length:
                        raise ValueError(('Cannot link variable %s to %s, the size of '
                                          'the target group does not match '
                                          '(%d != %d). You can provide an indexing '
                                          'scheme with the "index" keyword to link '
                                          'groups with different sizes') % (key,
                                                           linked_var.name,
                                                           len(self),
                                                           target_length))

            self.variables.add_reference(key,
                                         value.group,
                                         value.name,
                                         index=index)
            log_msg = ('Setting {target}.{targetvar} as a link to '
                       '{source}.{sourcevar}').format(target=self.name,
                                                      targetvar=key,
                                                      source=value.variable.owner.name,
                                                      sourcevar=value.variable.name)
            if index is not None:
                log_msg += '(using "{index}" as index variable)'.format(index=index)
            logger.debug(log_msg)
        else:
            if isinstance(value, LinkedVariable):
                raise TypeError(('Cannot link variable %s, it has to be marked '
                                 'as a linked variable with "(linked)" in the '
                                 'model equations.') % key)
            else:
                Group.__setattr__(self, key, value, level=1)

    def __getitem__(self, item):
        if not isinstance(item, slice):
            raise TypeError('Subgroups can only be constructed using slicing syntax')
        start, stop, step = item.indices(self._N)
        if step != 1:
            raise IndexError('Subgroups have to be contiguous')
        if start >= stop:
            raise IndexError('Illegal start/end values for subgroup, %d>=%d' %
                             (start, stop))

        return Subgroup(self, start, stop)

    def _create_variables(self, user_dtype, events):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_constant('N', Unit(1), self._N)

        # Standard variables always present
        for event in events:
            self.variables.add_array('_{}space'.format(event), unit=Unit(1),
                                     size=self._N+1, dtype=np.int32,
                                     constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i', size=self._N, constant=True,
                                  read_only=True)
        # Add the clock variables
        self.variables.create_clock_variables(self._clock)

        for eq in self.equations.itervalues():
            dtype = get_dtype(eq, user_dtype)

            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                if 'linked' in eq.flags:
                    # 'linked' cannot be combined with other flags
                    if not len(eq.flags) == 1:
                        raise SyntaxError(('The "linked" flag cannot be '
                                           'combined with other flags'))
                    self._linked_variables.add(eq.varname)
                else:
                    constant = 'constant' in eq.flags
                    shared = 'shared' in eq.flags
                    size = 1 if shared else self._N
                    self.variables.add_array(eq.varname, size=size,
                                             unit=eq.unit, dtype=dtype,
                                             constant=constant,
                                             scalar=shared)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname, unit=eq.unit,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='shared' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        for eq in self.equations.itervalues():
            if eq.type == DIFFERENTIAL_EQUATION and 'unless refractory' in eq.flags:
                not_refractory_var = self.variables['not_refractory']
                self.variables[eq.varname].set_conditional_write(not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(xi, unit=second**-0.5)

        # Check scalar subexpressions
        for eq in self.equations.itervalues():
            if eq.type == SUBEXPRESSION and 'shared' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(('Shared subexpression %s refers '
                                               'to non-shared variable %s.')
                                              % (eq.varname, identifier))

    def before_run(self, run_namespace=None):
        # Check units
        self.equations.check_units(self, run_namespace=run_namespace)

    def _repr_html_(self):
        text = [r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))

        if 'spike' in self.events:
            threshold, reset = self.events['spike']
        else:
            threshold = reset = None
        if threshold is not None:
            text.append(r'<b>Threshold condition:</b><br>')
            text.append('<code>%s</code><br>' % str(threshold))
            text.append('')
        if reset is not None:
            text.append(r'<b>Reset statement(s):</b><br>')
            text.append(r'<code>%s</code><br>' % str(reset))
            text.append('')
        for event, (condition, statements) in self.events.iteritems():
            if event != 'spike':  # we dealt with this already above
                text.append(r'<b>Condition for event "%s"</b><br>' % event)
                text.append('<code>%s</code><br>' % str(condition))
                text.append('')
                if statements is not None:
                    text.append(r'<b>Executed statement(s):</b><br>')
                    text.append(r'<code>%s</code><br>' % str(statements))
                    text.append('')

        return '\n'.join(text)
Ejemplo n.º 43
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use `t` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where `indices` are the array indices which were recorded. When indexing the
    `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in `source`, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices.
    when : `Scheduler`, optional
        When to record the spikes, by default uses the clock of the source
        and records spikes in the slot 'end'.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    '''
    def __init__(self, source, variables, record=None, when=None,
                 name='statemonitor*', codeobj_class=None):
        self.source = weakref.proxy(source)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            record = np.array([], dtype=np.int32)
        elif isinstance(record, int):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.indices = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = %s' % (v, v)
                for v in self.record_variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            when=scheduler,
                            check_units=False)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False, constant_size=False)
        if scheduler.clock is source.clock:
            self.variables.add_reference('_clock_t', source.variables['t'])
        else:
            self.variables.add_attribute_variable('_clock_t', unit=second,
                                                  obj=scheduler.clock,
                                                  attribute='t_')
        self.variables.add_attribute_variable('N', unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self, attribute='_N')
        self.variables.add_array('_indices', size=len(self.indices),
                                 unit=Unit(1), dtype=self.indices.dtype,
                                 constant=True, read_only=True)
        self.variables['_indices'].set_value(self.indices)

        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.indices) > 1:
                logger.warn(('Variable %s is a scalar variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference(varname, var,
                                         index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source.variables[index])
            # For subexpressions, we also need all referred variables (if they
            # are not already present, e.g. the t as _clock_t
            if isinstance(var, Subexpression):
                for subexpr_varname in var.identifiers:
                    if subexpr_varname in source.variables:
                        source_var = source.variables[subexpr_varname]
                        index = source.variables.indices[subexpr_varname]
                        if index != '_idx' and index not in variables:
                            self.variables.add_reference(index,
                                                         source.variables[index])
                        if not source_var in self.variables.values():
                            source_index = source.variables.indices[subexpr_varname]
                            # `translate_subexpression` will later replace
                            # the name used in the original subexpression with
                            # _source_varname
                            self.variables.add_reference('_source_'+subexpr_varname,
                                                         source_var,
                                                         index=source_index)
            self.variables.add_dynamic_array('_recorded_' + varname,
                                             size=(0, len(self.indices)),
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in self.record_variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname,
                                         self.variables['_recorded_'+varname])
                                        for varname in self.record_variables])
        recorded_names = ['_recorded_'+varname
                          for varname in self.record_variables]

        self.needed_variables = recorded_names
        self.template_kwds = template_kwds={'_recorded_variables':
                                            self.recorded_variables}
        self._N = 0
        self._enable_group_attributes()

    def __len__(self):
        return self._N

    def resize(self, new_size):
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))
        self._N = new_size

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        else:
            raise TypeError('Cannot use object of type %s as an index'
                            % type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables['_recorded_'+item].get_value().T,
                            dim=unit.dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables['_recorded_'+item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)
Ejemplo n.º 44
0
class Clock(VariableOwner):
    '''
    An object that holds the simulation time and the time step.
    
    Parameters
    ----------
    dt : float
        The time step of the simulation as a float
    name : str, optional
        An explicit name, if not specified gives an automatically generated name

    Notes
    -----
    Clocks are run in the same `Network.run` iteration if `~Clock.t` is the
    same. The condition for two
    clocks to be considered as having the same time is
    ``abs(t1-t2)<epsilon*abs(t1)``, a standard test for equality of floating
    point values. The value of ``epsilon`` is ``1e-14``.
    '''

    def __init__(self, dt, name='clock*'):
        # We need a name right away because some devices (e.g. cpp_standalone)
        # need a name for the object when creating the variables
        Nameable.__init__(self, name=name)
        #: Note that right after a change of dt, this
        #: will not equal the new dt (which is stored in `Clock._new_dt`). Call
        #: `Clock._set_t_update_t` to update the internal clock representation.
        self._new_dt = None
        self.variables = Variables(self)
        self.variables.add_array('timestep', unit=Unit(1), size=1,
                                 dtype=np.uint64, read_only=True, scalar=True)
        self.variables.add_array('t', unit=second, size=1,
                                 dtype=np.double, read_only=True, scalar=True)
        self.variables.add_array('dt', unit=second, size=1, values=float(dt),
                                 dtype=np.float, read_only=True, constant=True,
                                 scalar=True)
        self.variables.add_constant('N', unit=Unit(1), value=1)
        self._enable_group_attributes()
        self.dt = dt
        logger.diagnostic("Created clock {name} with dt={dt}".format(name=self.name,
                                                                     dt=self.dt))

    @check_units(t=second)
    def _set_t_update_dt(self, target_t=0*second):
        new_dt = self._new_dt if self._new_dt is not None else self.dt_
        old_dt = self.variables['dt'].get_value().item()
        target_t = float(target_t)
        if new_dt != old_dt:
            self._new_dt = None  # i.e.: i is up-to-date for the dt
            # Only allow a new dt which allows to correctly set the new time step
            if target_t != self.t_:
                old_t = np.uint64(np.round(target_t / old_dt)) * old_dt
                new_t = np.uint64(np.round(target_t / new_dt)) * new_dt
                error_t = target_t
            else:
                old_t = np.uint64(np.round(self.t_ / old_dt)) * old_dt
                new_t = np.uint64(np.round(self.t_ / new_dt)) * new_dt
                error_t = self.t_
            if abs(new_t - old_t) > self.epsilon:
                raise ValueError(('Cannot set dt from {old} to {new}, the '
                                  'time {t} is not a multiple of '
                                  '{new}').format(old=old_dt*second,
                                                  new=new_dt*second,
                                                  t=error_t*second))
            self.variables['dt'].set_value(new_dt)

        new_i = np.uint64(np.round(target_t/new_dt))
        new_t = new_i*new_dt
        if new_t==target_t or np.abs(new_t-target_t)<=self.epsilon*np.abs(new_t):
            new_timestep = new_i
        else:
            new_timestep = np.uint64(np.ceil(target_t/new_dt))
        # Since these attributes are read-only for normal users, we have to
        # update them via the variables object directly
        self.variables['timestep'].set_value(new_timestep)
        self.variables['t'].set_value(new_timestep * new_dt)
        logger.diagnostic("Setting Clock {name} to t={t}, dt={dt}".format(name=self.name,
                                                                          t=self.t,
                                                                          dt=self.dt))

    def __repr__(self):
        return 'Clock(dt=%r, name=%r)' % (self.dt, self.name)

    def _get_dt_(self):
        if self._new_dt is None:
            return self.variables['dt'].get_value().item()
        else:
            return self._new_dt

    @check_units(dt_=1)
    def _set_dt_(self, dt_):
        self._new_dt = dt_

    @check_units(dt=second)
    def _set_dt(self, dt):
        self._new_dt = float(dt)

    dt = property(fget=lambda self: Quantity(self.dt_, dim=second.dim),
                  fset=_set_dt,
                  doc='''The time step of the simulation in seconds.''',
                  )
    dt_ = property(fget=_get_dt_, fset=_set_dt_,
                   doc='''The time step of the simulation as a float (in seconds)''')

    @check_units(start=second, end=second)
    def set_interval(self, start, end):
        '''
        set_interval(self, start, end)
        
        Set the start and end time of the simulation.
        
        Sets the start and end value of the clock precisely if
        possible (using epsilon) or rounding up if not. This assures that
        multiple calls to `Network.run` will not re-run the same time step.      
        '''
        self._set_t_update_dt(target_t=start)
        end = float(end)
        i_end = np.uint64(np.round(end/self.dt_))
        t_end = i_end*self.dt_
        if t_end==end or np.abs(t_end-end)<=self.epsilon*np.abs(t_end):
            self._i_end = i_end
        else:
            self._i_end = np.uint64(np.ceil(end/self.dt_))

    epsilon = 1e-14
Ejemplo n.º 45
0
class SpikeMonitor(Group, CodeRunner):
    '''
    Record spikes from a `NeuronGroup` or other spike source
    
    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    record : bool
        Whether or not to record each spike in `i` and `t` (the `count` will
        always be recorded).
    when : `Scheduler`, optional
        When to record the spikes, by default uses the clock of the source
        and records spikes in the slot 'end'.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_spikemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    '''
    def __init__(self,
                 source,
                 record=True,
                 when=None,
                 name='spikemonitor*',
                 codeobj_class=None):
        self.record = bool(record)
        #: The source we are recording from
        self.source = source

        # run by default on source clock at the end
        scheduler = Scheduler(when)
        if not scheduler.defined_clock:
            scheduler.clock = source.clock
        if not scheduler.defined_when:
            scheduler.when = 'end'

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            template='spikemonitor',
                            name=name,
                            when=scheduler)

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))

        self.variables = Variables(self)
        self.variables.add_clock_variables(scheduler.clock, prefix='_clock_')
        self.variables.add_reference('_spikespace',
                                     source.variables['_spikespace'])
        self.variables.add_dynamic_array('i',
                                         size=0,
                                         unit=Unit(1),
                                         dtype=np.int32,
                                         constant_size=False)
        self.variables.add_dynamic_array('t',
                                         size=0,
                                         unit=second,
                                         constant_size=False)
        self.variables.add_array('_count',
                                 size=len(source),
                                 unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_attribute_variable('N',
                                              unit=Unit(1),
                                              obj=self,
                                              attribute='_N',
                                              dtype=np.int32)
        self._N = 0

        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['i'].resize(new_size)
        self.variables['t'].resize(new_size)
        self._N = new_size

    def __len__(self):
        return self._N

    def reinit(self):
        '''
        Clears all recorded spikes
        '''
        raise NotImplementedError()

    # TODO: Maybe there's a more elegant solution for the count attribute?
    @property
    def count(self):
        return self.variables['_count'].get_value().copy()

    @property
    def it(self):
        '''
        Returns the pair (`i`, `t`).
        '''
        return self.i, self.t

    @property
    def it_(self):
        '''
        Returns the pair (`i`, `t_`).
        '''
        return self.i, self.t_

    @property
    def num_spikes(self):
        '''
        Returns the total number of recorded spikes
        '''
        return self._N

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.group.name)
Ejemplo n.º 46
0
class PopulationRateMonitor(Group, CodeRunner):
    """
    Record instantaneous firing rates, averaged across neurons from a
    `NeuronGroup` or other spike source.

    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_ratemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dtype : dtype, optional
        The dtype to use to store the ``rate`` variable. Defaults to
        `~numpy.float64`, i.e. double precision.
    Notes
    -----
    Currently, this monitor can only monitor the instantaneous firing rates at
    each time step of the source clock. Any binning/smoothing of the firing
    rates has to be done manually afterwards.
    """
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self,
                 source,
                 name='ratemonitor*',
                 codeobj_class=None,
                 dtype=np.float64):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self,
                            group=self,
                            code='',
                            template='ratemonitor',
                            clock=source.clock,
                            when='end',
                            order=0,
                            name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', start)
        self.variables.add_constant('_source_stop', stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate',
                                         size=0,
                                         dimensions=hertz.dim,
                                         read_only=True,
                                         dtype=dtype)
        self.variables.add_dynamic_array(
            't',
            size=0,
            dimensions=second.dim,
            read_only=True,
            dtype=self._clock.variables['t'].dtype)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N',
                                 dtype=np.int32,
                                 size=1,
                                 scalar=True,
                                 read_only=True)
        self.variables.create_clock_variables(self._clock, prefix='_clock_')
        self._enable_group_attributes()

    def resize(self, new_size):
        # Note that this does not set N, this has to be done in the template
        # since we use a restricted pointer to access it (which promises that
        # we only change the value through this pointer)
        self.variables['rate'].resize(new_size)
        self.variables['t'].resize(new_size)

    def reinit(self):
        """
        Clears all recorded rates
        """
        raise NotImplementedError()

    @check_units(width=second)
    def smooth_rate(self, window='gaussian', width=None):
        """
        smooth_rate(self, window='gaussian', width=None)

        Return a smooth version of the population rate.

        Parameters
        ----------
        window : str, ndarray
            The window to use for smoothing. Can be a string to chose a
            predefined window(``'flat'`` for a rectangular, and ``'gaussian'``
            for a Gaussian-shaped window). In this case the width of the window
            is determined by the ``width`` argument. Note that for the Gaussian
            window, the ``width`` parameter specifies the standard deviation of
            the Gaussian, the width of the actual window is ``4*width + dt``
            (rounded to the nearest dt). For the flat window, the width is
            rounded to the nearest odd multiple of dt to avoid shifting the rate
            in time.
            Alternatively, an arbitrary window can be given as a numpy array
            (with an odd number of elements). In this case, the width in units
            of time depends on the ``dt`` of the simulation, and no ``width``
            argument can be specified. The given window will be automatically
            normalized to a sum of 1.
        width : `Quantity`, optional
            The width of the ``window`` in seconds (for a predefined window).

        Returns
        -------
        rate : `Quantity`
            The population rate in Hz, smoothed with the given window. Note that
            the rates are smoothed and not re-binned, i.e. the length of the
            returned array is the same as the length of the ``rate`` attribute
            and can be plotted against the `PopulationRateMonitor` 's ``t``
            attribute.
        """
        if width is None and isinstance(window, str):
            raise TypeError("Need a width when using a predefined window.")
        if width is not None and not isinstance(window, str):
            raise TypeError("Can only specify a width for a predefined window")

        if isinstance(window, str):
            if window == 'gaussian':
                width_dt = int(np.round(2 * width / self.clock.dt))
                # Rounding only for the size of the window, not for the standard
                # deviation of the Gaussian
                window = np.exp(-np.arange(-width_dt, width_dt + 1)**2 * 1. /
                                (2 * (width / self.clock.dt)**2))
            elif window == 'flat':
                width_dt = int(width / 2 / self.clock.dt) * 2 + 1
                used_width = width_dt * self.clock.dt
                if abs(used_width - width) > 1e-6 * self.clock.dt:
                    logger.info(f'width adjusted from {width} to {used_width}',
                                'adjusted_width',
                                once=True)
                window = np.ones(width_dt)
            else:
                raise NotImplementedError(
                    f'Unknown pre-defined window "{window}"')
        else:
            try:
                window = np.asarray(window)
            except TypeError:
                raise TypeError(f"Cannot use a window of type {type(window)}")
            if window.ndim != 1:
                raise TypeError("The provided window has to be "
                                "one-dimensional.")
            if len(window) % 2 != 1:
                raise TypeError("The window has to have an odd number of "
                                "values.")
        return Quantity(np.convolve(self.rate_,
                                    window * 1. / sum(window),
                                    mode='same'),
                        dim=hertz.dim)

    def __repr__(self):
        classname = self.__class__.__name__
        return f"<{classname}, recording {self.source.name}>"
Ejemplo n.º 47
0
class EventMonitor(Group, CodeRunner):
    """
    Record events from a `NeuronGroup` or another event source.

    The recorded events can be accessed in various ways:
    the attributes `~EventMonitor.i` and `~EventMonitor.t` store all the indices
    and event times, respectively. Alternatively, you can get a dictionary
    mapping neuron indices to event trains, by calling the `event_trains`
    method.

    Parameters
    ----------
    source : `NeuronGroup`, `SpikeSource`
        The source of events to record.
    event : str
        The name of the event to record
    variables : str or sequence of str, optional
        Which variables to record at the time of the event (in addition to the
        index of the neuron). Can be the name of a variable or a list of names.
    record : bool, optional
        Whether or not to record each event in `i` and `t` (the `count` will
        always be recorded). Defaults to ``True``.
    when : str, optional
        When to record the events, by default records events in the same slot
        where the event is emitted. See :ref:`scheduling` for possible values.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to the order where the
        event is emitted + 1, i.e. it will be recorded directly afterwards.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_eventmonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    See Also
    --------
    SpikeMonitor
    """
    invalidates_magic_network = False
    add_to_magic_network = True

    def __init__(self,
                 source,
                 event,
                 variables=None,
                 record=True,
                 when=None,
                 order=None,
                 name='eventmonitor*',
                 codeobj_class=None):
        if not isinstance(source, SpikeSource):
            raise TypeError(
                f"{self.__class__.__name__} can only monitor groups "
                f"producing spikes (such as NeuronGroup), but the given "
                f"argument is of type {type(source)}.")
        #: The source we are recording from
        self.source = source
        #: Whether to record times and indices of events
        self.record = record
        #: The array of event counts (length = size of target group)
        self.count = None
        del self.count  # this is handled by the Variable mechanism

        if event not in source.events:
            if event == 'spike':
                threshold_text = " Did you forget to set a 'threshold'?"
            else:
                threshold_text = ''
            raise ValueError(
                f"Recorded group '{source.name}' does not define an event "
                f"'{event}'.{threshold_text}")
        if when is None:
            if order is not None:
                raise ValueError(
                    "Cannot specify order if when is not specified.")
            # TODO: Would be nicer if there was a common way of accessing the
            #       relevant object for NeuronGroup and SpikeGeneratorGroup
            if hasattr(source, 'thresholder'):
                parent_obj = source.thresholder[event]
            else:
                parent_obj = source
            when = parent_obj.when
            order = parent_obj.order + 1
        elif order is None:
            order = 0

        #: The event that we are listening to
        self.event = event

        if variables is None:
            variables = {}
        elif isinstance(variables, str):
            variables = {variables}

        #: The additional variables that will be recorded
        self.record_variables = set(variables)

        for variable in variables:
            if variable not in source.variables:
                raise ValueError(
                    f"'{variable}' is not a variable of the recorded group")

        if self.record:
            self.record_variables |= {'i', 't'}

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = [
            f'_to_record_{v} = _source_{v}'
            for v in sorted(self.record_variables)
        ]
        code = '\n'.join(code)

        self.codeobj_class = codeobj_class

        # Since this now works for general events not only spikes, we have to
        # pass the information about which variable to use to the template,
        # it can not longer simply refer to "_spikespace"
        eventspace_name = f'_{event}space'

        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        source_N = getattr(source, '_source_N', len(source))

        Nameable.__init__(self, name=name)

        self.variables = Variables(self)
        self.variables.add_reference(eventspace_name, source)

        for variable in self.record_variables:
            source_var = source.variables[variable]
            self.variables.add_reference(f'_source_{variable}', source,
                                         variable)
            self.variables.add_auxiliary_variable(f'_to_record_{variable}',
                                                  dimensions=source_var.dim,
                                                  dtype=source_var.dtype)
            self.variables.add_dynamic_array(variable,
                                             size=0,
                                             dimensions=source_var.dim,
                                             dtype=source_var.dtype,
                                             read_only=True)
        self.variables.add_arange('_source_idx', size=len(source))
        self.variables.add_array('count',
                                 size=len(source),
                                 dtype=np.int32,
                                 read_only=True,
                                 index='_source_idx')
        self.variables.add_constant('_source_start', start)
        self.variables.add_constant('_source_stop', stop)
        self.variables.add_constant('_source_N', source_N)
        self.variables.add_array('N',
                                 size=1,
                                 dtype=np.int32,
                                 read_only=True,
                                 scalar=True)

        record_variables = {
            varname: self.variables[varname]
            for varname in self.record_variables
        }
        template_kwds = {
            'eventspace_variable': source.variables[eventspace_name],
            'record_variables': record_variables,
            'record': self.record
        }
        needed_variables = {eventspace_name} | self.record_variables
        CodeRunner.__init__(
            self,
            group=self,
            code=code,
            template='spikemonitor',
            name=None,  # The name has already been initialized
            clock=source.clock,
            when=when,
            order=order,
            needed_variables=needed_variables,
            template_kwds=template_kwds)

        self.variables.create_clock_variables(self._clock, prefix='_clock_')
        self.add_dependency(source)
        self.written_readonly_vars = {
            self.variables[varname]
            for varname in self.record_variables
        }
        self._enable_group_attributes()

    def resize(self, new_size):
        # Note that this does not set N, this has to be done in the template
        # since we use a restricted pointer to access it (which promises that
        # we only change the value through this pointer)
        for variable in self.record_variables:
            self.variables[variable].resize(new_size)

    def reinit(self):
        """
        Clears all recorded spikes
        """
        raise NotImplementedError()

    @property
    def it(self):
        """
        Returns the pair (`i`, `t`).
        """
        if not self.record:
            raise AttributeError("Indices and times have not been recorded."
                                 "Set the record argument to True to record "
                                 "them.")
        return self.i, self.t

    @property
    def it_(self):
        """
        Returns the pair (`i`, `t_`).
        """
        if not self.record:
            raise AttributeError("Indices and times have not been recorded."
                                 "Set the record argument to True to record "
                                 "them.")

        return self.i, self.t_

    def _values_dict(self, first_pos, sort_indices, used_indices, var):
        sorted_values = self.state(var, use_units=False)[sort_indices]
        dim = self.variables[var].dim
        event_values = {}
        current_pos = 0  # position in the all_indices array
        for idx in range(len(self.source)):
            if current_pos < len(
                    used_indices) and used_indices[current_pos] == idx:
                if current_pos < len(used_indices) - 1:
                    event_values[idx] = Quantity(sorted_values[
                        first_pos[current_pos]:first_pos[current_pos + 1]],
                                                 dim=dim,
                                                 copy=False)
                else:
                    event_values[idx] = Quantity(
                        sorted_values[first_pos[current_pos]:],
                        dim=dim,
                        copy=False)
                current_pos += 1
            else:
                event_values[idx] = Quantity([], dim=dim)
        return event_values

    def values(self, var):
        """
        Return a dictionary mapping neuron indices to arrays of variable values
        at the time of the events (sorted by time).

        Parameters
        ----------
        var : str
            The name of the variable.

        Returns
        -------
        values : dict
            Dictionary mapping each neuron index to an array of variable
            values at the time of the events

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, '''counter1 : integer
        ...                       counter2 : integer
        ...                       max_value : integer''',
        ...                    threshold='counter1 >= max_value',
        ...                    reset='counter1 = 0')
        >>> G.run_regularly('counter1 += 1; counter2 += 1')  # doctest: +ELLIPSIS
        CodeRunner(...)
        >>> G.max_value = [50, 100]
        >>> mon = EventMonitor(G, event='spike', variables='counter2')
        >>> run(10*ms)
        >>> counter2_values = mon.values('counter2')
        >>> print(counter2_values[0])
        [ 50 100]
        >>> print(counter2_values[1])
        [100]
        """
        if not self.record:
            raise AttributeError("Indices and times have not been recorded."
                                 "Set the record argument to True to record "
                                 "them.")
        indices = self.i[:]
        # We have to make sure that the sort is stable, otherwise our spike
        # times do not necessarily remain sorted.
        sort_indices = np.argsort(indices, kind='mergesort')
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        return self._values_dict(first_pos, sort_indices, used_indices, var)

    def all_values(self):
        """
        Return a dictionary mapping recorded variable names (including ``t``)
        to a dictionary mapping neuron indices to arrays of variable values at
        the time of the events (sorted by time). This is equivalent to (but more
        efficient than) calling `values` for each variable and storing the
        result in a dictionary.

        Returns
        -------
        all_values : dict
            Dictionary mapping variable names to dictionaries which themselves
            are mapping neuron indicies to arrays of variable values at the
            time of the events.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(2, '''counter1 : integer
        ...                       counter2 : integer
        ...                       max_value : integer''',
        ...                    threshold='counter1 >= max_value',
        ...                    reset='counter1 = 0')
        >>> G.run_regularly('counter1 += 1; counter2 += 1')  # doctest: +ELLIPSIS
        CodeRunner(...)
        >>> G.max_value = [50, 100]
        >>> mon = EventMonitor(G, event='spike', variables='counter2')
        >>> run(10*ms)
        >>> all_values = mon.all_values()
        >>> print(all_values['counter2'][0])
        [ 50 100]
        >>> print(all_values['t'][1])
        [ 9.9] ms
        """
        if not self.record:
            raise AttributeError("Indices and times have not been recorded."
                                 "Set the record argument to True to record "
                                 "them.")
        indices = self.i[:]
        sort_indices = np.argsort(indices)
        used_indices, first_pos = np.unique(self.i[:][sort_indices],
                                            return_index=True)
        all_values_dict = {}
        for varname in self.record_variables - {'i'}:
            all_values_dict[varname] = self._values_dict(
                first_pos, sort_indices, used_indices, varname)
        return all_values_dict

    def event_trains(self):
        """
        Return a dictionary mapping neuron indices to arrays of event times.
        Equivalent to calling ``values('t')``.

        Returns
        -------
        event_trains : dict
            Dictionary that stores an array with the event times for each
            neuron index.

        See Also
        --------
        SpikeMonitor.spike_trains
        """
        return self.values('t')

    @property
    def num_events(self):
        """
        Returns the total number of recorded events.
        """
        return self.N[:]

    def __repr__(self):
        classname = self.__class__.__name__
        return (f"<{classname}, recording event '{self.event}' from "
                f"'{self.group.name}'>")
Ejemplo n.º 48
0
class NeuronGroup(Group, SpikeSource):
    '''
    A group of neurons.

    
    Parameters
    ----------
    N : int
        Number of neurons in the group.
    model : (str, `Equations`)
        The differential equations defining the group
    method : (str, function), optional
        The numerical integration method. Either a string with the name of a
        registered method (e.g. "euler") or a function that receives an
        `Equations` object and returns the corresponding abstract code. If no
        method is specified, a suitable method will be chosen automatically.
    threshold : str, optional
        The condition which produces spikes. Should be a single line boolean
        expression.
    reset : str, optional
        The (possibly multi-line) string with the code to execute on reset.
    refractory : {str, `Quantity`}, optional
        Either the length of the refractory period (e.g. ``2*ms``), a string
        expression that evaluates to the length of the refractory period
        after each spike (e.g. ``'(1 + rand())*ms'``), or a string expression
        evaluating to a boolean value, given the condition under which the
        neuron stays refractory after a spike (e.g. ``'v > -20*mV'``)
    events : dict, optional
        User-defined events in addition to the "spike" event defined by the
        ``threshold``. Has to be a mapping of strings (the event name) to
        strings (the condition) that will be checked.
    namespace: dict, optional
        A dictionary mapping identifier names to objects. If not given, the
        namespace will be filled in at the time of the call of `Network.run`,
        with either the values from the ``namespace`` argument of the
        `Network.run` method or from the local context, if no such argument is
        given.
    dtype : (`dtype`, `dict`), optional
        The `numpy.dtype` that will be used to store the values, or a
        dictionary specifying the type for variable names. If a value is not
        provided for a variable (or no value is provided at all), the preference
        setting `core.default_float_dtype` is used.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the group, otherwise use ``neurongroup_0``, etc.
        
    Notes
    -----
    `NeuronGroup` contains a `StateUpdater`, `Thresholder` and `Resetter`, and
    these are run at the 'groups', 'thresholds' and 'resets' slots (i.e. the
    values of their `when` attribute take these values). The `order`
    attribute will be passed down to the contained objects but can be set
    individually by setting the `order` attribute of the `state_updater`,
    `thresholder` and `resetter` attributes, respectively.
    '''
    add_to_magic_network = True

    def __init__(self,
                 N,
                 model,
                 method=('exact', 'euler', 'heun'),
                 method_options=None,
                 threshold=None,
                 reset=None,
                 refractory=False,
                 events=None,
                 namespace=None,
                 dtype=None,
                 dt=None,
                 clock=None,
                 order=0,
                 name='neurongroup*',
                 codeobj_class=None):
        Group.__init__(self,
                       dt=dt,
                       clock=clock,
                       when='start',
                       order=order,
                       name=name)
        if dtype is None:
            dtype = {}
        if isinstance(dtype, collections.MutableMapping):
            dtype['lastspike'] = self._clock.variables['t'].dtype

        self.codeobj_class = codeobj_class

        try:
            self._N = N = int(N)
        except ValueError:
            if isinstance(N, str):
                raise TypeError(
                    "First NeuronGroup argument should be size, not equations."
                )
            raise
        if N < 1:
            raise ValueError("NeuronGroup size should be at least 1, was " +
                             str(N))

        self.start = 0
        self.stop = self._N

        ##### Prepare and validate equations
        if isinstance(model, basestring):
            model = Equations(model)
        if not isinstance(model, Equations):
            raise TypeError(('model has to be a string or an Equations '
                             'object, is "%s" instead.') % type(model))

        # Check flags
        model.check_flags({
            DIFFERENTIAL_EQUATION: ('unless refractory', ),
            PARAMETER: ('constant', 'shared', 'linked'),
            SUBEXPRESSION: ('shared', 'constant over dt')
        })

        # add refractoriness
        #: The original equations as specified by the user (i.e. without
        #: the multiplied `int(not_refractory)` term for equations marked as
        #: `(unless refractory)`)
        self.user_equations = model
        if refractory is not False:
            model = add_refractoriness(model)
        uses_refractoriness = len(model) and any([
            'unless refractory' in eq.flags
            for eq in model.values() if eq.type == DIFFERENTIAL_EQUATION
        ])

        # Separate subexpressions depending whether they are considered to be
        # constant over a time step or not
        model, constant_over_dt = extract_constant_subexpressions(model)
        self.equations = model

        self._linked_variables = set()
        logger.diagnostic("Creating NeuronGroup of size {self._N}, "
                          "equations {self.equations}.".format(self=self))

        if namespace is None:
            namespace = {}
        #: The group-specific namespace
        self.namespace = namespace

        # All of the following will be created in before_run

        #: The refractory condition or timespan
        self._refractory = refractory
        if uses_refractoriness and refractory is False:
            logger.warn(
                'Model equations use the "unless refractory" flag but '
                'no refractory keyword was given.', 'no_refractory')

        #: The state update method selected by the user
        self.method_choice = method

        if events is None:
            events = {}

        if threshold is not None:
            if 'spike' in events:
                raise ValueError(("The NeuronGroup defines both a threshold "
                                  "and a 'spike' event"))
            events['spike'] = threshold

        # Setup variables
        # Since we have to create _spikespace and possibly other "eventspace"
        # variables, we pass the supported events
        self._create_variables(dtype, events=list(events.keys()))

        #: Events supported by this group
        self.events = events

        #: Code that is triggered on events (e.g. reset)
        self.event_codes = {}

        #: Checks the spike threshold (or abitrary user-defined events)
        self.thresholder = {}

        #: Reset neurons which have spiked (or perform arbitrary actions for
        #: user-defined events)
        self.resetter = {}

        for event_name in events.keys():
            if not isinstance(event_name, basestring):
                raise TypeError(('Keys in the "events" dictionary have to be '
                                 'strings, not type %s.') % type(event_name))
            if not _valid_event_name(event_name):
                raise TypeError(("The name '%s' cannot be used as an event "
                                 "name.") % event_name)
            # By default, user-defined events are checked after the threshold
            when = 'thresholds' if event_name == 'spike' else 'after_thresholds'
            # creating a Thresholder will take care of checking the validity
            # of the condition
            thresholder = Thresholder(self, event=event_name, when=when)
            self.thresholder[event_name] = thresholder
            self.contained_objects.append(thresholder)

        if reset is not None:
            self.run_on_event('spike', reset, when='resets')

        #: Performs numerical integration step
        self.state_updater = StateUpdater(self, method, method_options)
        self.contained_objects.append(self.state_updater)

        #: Update the "constant over a time step" subexpressions
        self.subexpression_updater = None
        if len(constant_over_dt):
            self.subexpression_updater = SubexpressionUpdater(
                self, constant_over_dt)
            self.contained_objects.append(self.subexpression_updater)

        if refractory is not False:
            # Set the refractoriness information
            self.variables['lastspike'].set_value(-1e4 * second)
            self.variables['not_refractory'].set_value(True)

        # Activate name attribute access
        self._enable_group_attributes()

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def state(self, name, use_units=True, level=0):
        try:
            return Group.state(self,
                               name,
                               use_units=use_units,
                               level=level + 1)
        except KeyError as ex:
            if name in self._linked_variables:
                raise TypeError(('Link target for variable %s has not been '
                                 'set.') % name)
            else:
                raise ex

    def run_on_event(self, event, code, when='after_resets', order=None):
        '''
        Run code triggered by a custom-defined event (see `NeuronGroup`
        documentation for the specification of events).The created `Resetter`
        object will be automatically added to the group, it therefore does not
        need to be added to the network manually. However, a reference to the
        object will be returned, which can be used to later remove it from the
        group or to set it to inactive.

        Parameters
        ----------
        event : str
            The name of the event that should trigger the code
        code : str
            The code that should be executed
        when : str, optional
            The scheduling slot that should be used to execute the code.
            Defaults to `'after_resets'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.

        Returns
        -------
        obj : `Resetter`
            A reference to the object that will be run.
        '''
        if event not in self.events:
            error_message = "Unknown event '%s'." % event
            if event == 'spike':
                error_message += ' Did you forget to define a threshold?'
            raise ValueError(error_message)
        if event in self.resetter:
            raise ValueError(("Cannot add code for event '%s', code for this "
                              "event has already been added.") % event)
        self.event_codes[event] = code
        resetter = Resetter(self, when=when, order=order, event=event)
        self.resetter[event] = resetter
        self.contained_objects.append(resetter)

        return resetter

    def set_event_schedule(self, event, when='after_thresholds', order=None):
        '''
        Change the scheduling slot for checking the condition of an event.

        Parameters
        ----------
        event : str
            The name of the event for which the scheduling should be changed
        when : str, optional
            The scheduling slot that should be used to check the condition.
            Defaults to `'after_thresholds'`.
        order : int, optional
            The order for operations in the same scheduling slot. Defaults to
            the order of the `NeuronGroup`.
        '''
        if event not in self.thresholder:
            raise ValueError("Unknown event '%s'." % event)
        order = order if order is not None else self.order
        self.thresholder[event].when = when
        self.thresholder[event].order = order

    def __setattr__(self, key, value):
        # attribute access is switched off until this attribute is created by
        # _enable_group_attributes
        if not hasattr(
                self,
                '_group_attribute_access_active') or key in self.__dict__:
            object.__setattr__(self, key, value)
        elif key in self._linked_variables:
            if not isinstance(value, LinkedVariable):
                raise ValueError(
                    ('Cannot set a linked variable directly, link '
                     'it to another variable using "linked_var".'))
            linked_var = value.variable

            if isinstance(linked_var, DynamicArrayVariable):
                raise NotImplementedError(('Linking to variable %s is not '
                                           'supported, can only link to '
                                           'state variables of fixed '
                                           'size.') % linked_var.name)

            eq = self.equations[key]
            if eq.dim is not linked_var.dim:
                raise DimensionMismatchError(
                    ('Unit of variable %s does not '
                     'match its link target %s') % (key, linked_var.name))

            if not isinstance(linked_var, Subexpression):
                var_length = len(linked_var)
            else:
                var_length = len(linked_var.owner)

            if value.index is not None:
                try:
                    index_array = np.asarray(value.index)
                    if not np.issubsctype(index_array.dtype, np.int):
                        raise TypeError()
                except TypeError:
                    raise TypeError(('The index for a linked variable has '
                                     'to be an integer array'))
                size = len(index_array)
                source_index = value.group.variables.indices[value.name]
                if source_index not in ('_idx', '0'):
                    # we are indexing into an already indexed variable,
                    # calculate the indexing into the target variable
                    index_array = value.group.variables[
                        source_index].get_value()[index_array]

                if not index_array.ndim == 1 or size != len(self):
                    raise TypeError(
                        ('Index array for linked variable %s '
                         'has to be a one-dimensional array of '
                         'length %d, but has shape '
                         '%s') % (key, len(self), str(index_array.shape)))
                if min(index_array) < 0 or max(index_array) >= var_length:
                    raise ValueError('Index array for linked variable %s '
                                     'contains values outside of the valid '
                                     'range [0, %d[' % (key, var_length))
                self.variables.add_array('_%s_indices' % key,
                                         size=size,
                                         dtype=index_array.dtype,
                                         constant=True,
                                         read_only=True,
                                         values=index_array)
                index = '_%s_indices' % key
            else:
                if linked_var.scalar or (var_length == 1 and self._N != 1):
                    index = '0'
                else:
                    index = value.group.variables.indices[value.name]
                    if index == '_idx':
                        target_length = var_length
                    else:
                        target_length = len(value.group.variables[index])
                        # we need a name for the index that does not clash with
                        # other names and a reference to the index
                        new_index = '_' + value.name + '_index_' + index
                        self.variables.add_reference(new_index, value.group,
                                                     index)
                        index = new_index

                    if len(self) != target_length:
                        raise ValueError(
                            ('Cannot link variable %s to %s, the size of '
                             'the target group does not match '
                             '(%d != %d). You can provide an indexing '
                             'scheme with the "index" keyword to link '
                             'groups with different sizes') %
                            (key, linked_var.name, len(self), target_length))

            self.variables.add_reference(key,
                                         value.group,
                                         value.name,
                                         index=index)
            log_msg = ('Setting {target}.{targetvar} as a link to '
                       '{source}.{sourcevar}').format(
                           target=self.name,
                           targetvar=key,
                           source=value.variable.owner.name,
                           sourcevar=value.variable.name)
            if index is not None:
                log_msg += '(using "{index}" as index variable)'.format(
                    index=index)
            logger.diagnostic(log_msg)
        else:
            if isinstance(value, LinkedVariable):
                raise TypeError(
                    ('Cannot link variable %s, it has to be marked '
                     'as a linked variable with "(linked)" in the '
                     'model equations.') % key)
            else:
                Group.__setattr__(self, key, value, level=1)

    def __getitem__(self, item):
        start, stop = to_start_stop(item, self._N)

        return Subgroup(self, start, stop)

    def _create_variables(self, user_dtype, events):
        '''
        Create the variables dictionary for this `NeuronGroup`, containing
        entries for the equation variables and some standard entries.
        '''
        self.variables = Variables(self)
        self.variables.add_constant('N', self._N)

        # Standard variables always present
        for event in events:
            self.variables.add_array('_{}space'.format(event),
                                     size=self._N + 1,
                                     dtype=np.int32,
                                     constant=False)
        # Add the special variable "i" which can be used to refer to the neuron index
        self.variables.add_arange('i',
                                  size=self._N,
                                  constant=True,
                                  read_only=True)
        # Add the clock variables
        self.variables.create_clock_variables(self._clock)

        for eq in self.equations.values():
            dtype = get_dtype(eq, user_dtype)
            check_identifier_pre_post(eq.varname)
            if eq.type in (DIFFERENTIAL_EQUATION, PARAMETER):
                if 'linked' in eq.flags:
                    # 'linked' cannot be combined with other flags
                    if not len(eq.flags) == 1:
                        raise SyntaxError(('The "linked" flag cannot be '
                                           'combined with other flags'))
                    self._linked_variables.add(eq.varname)
                else:
                    constant = 'constant' in eq.flags
                    shared = 'shared' in eq.flags
                    size = 1 if shared else self._N
                    self.variables.add_array(eq.varname,
                                             size=size,
                                             dimensions=eq.dim,
                                             dtype=dtype,
                                             constant=constant,
                                             scalar=shared)
            elif eq.type == SUBEXPRESSION:
                self.variables.add_subexpression(eq.varname,
                                                 dimensions=eq.dim,
                                                 expr=str(eq.expr),
                                                 dtype=dtype,
                                                 scalar='shared' in eq.flags)
            else:
                raise AssertionError('Unknown type of equation: ' + eq.eq_type)

        # Add the conditional-write attribute for variables with the
        # "unless refractory" flag
        if self._refractory is not False:
            for eq in self.equations.values():
                if (eq.type == DIFFERENTIAL_EQUATION
                        and 'unless refractory' in eq.flags):
                    not_refractory_var = self.variables['not_refractory']
                    var = self.variables[eq.varname]
                    var.set_conditional_write(not_refractory_var)

        # Stochastic variables
        for xi in self.equations.stochastic_variables:
            self.variables.add_auxiliary_variable(
                xi, dimensions=(second**-0.5).dim)

        # Check scalar subexpressions
        for eq in self.equations.values():
            if eq.type == SUBEXPRESSION and 'shared' in eq.flags:
                var = self.variables[eq.varname]
                for identifier in var.identifiers:
                    if identifier in self.variables:
                        if not self.variables[identifier].scalar:
                            raise SyntaxError(
                                ('Shared subexpression %s refers '
                                 'to non-shared variable %s.') %
                                (eq.varname, identifier))

    def before_run(self, run_namespace=None):
        # Check units
        self.equations.check_units(self, run_namespace=run_namespace)
        # Check that subexpressions that refer to stateful functions are labeled
        # as "constant over dt"
        check_subexpressions(self, self.equations, run_namespace)
        super(NeuronGroup, self).before_run(run_namespace=run_namespace)

    def _repr_html_(self):
        text = [
            r'NeuronGroup "%s" with %d neurons.<br>' % (self.name, self._N)
        ]
        text.append(r'<b>Model:</b><nr>')
        text.append(sympy.latex(self.equations))

        def add_event_to_text(event):
            if event == 'spike':
                event_header = 'Spiking behaviour'
                event_condition = 'Threshold condition'
                event_code = 'Reset statement(s)'
            else:
                event_header = 'Event "%s"' % event
                event_condition = 'Event condition'
                event_code = 'Executed statement(s)'
            condition = self.events[event]
            text.append(
                r'<b>%s:</b><ul style="list-style-type: none; margin-top: 0px;">'
                % event_header)
            text.append(r'<li><i>%s: </i>' % event_condition)
            text.append('<code>%s</code></li>' % str(condition))
            statements = self.event_codes.get(event, None)
            if statements is not None:
                text.append(r'<li><i>%s:</i>' % event_code)
                if '\n' in str(statements):
                    text.append('</br>')
                text.append(r'<code>%s</code></li>' % str(statements))
            text.append('</ul>')

        if 'spike' in self.events:
            add_event_to_text('spike')
        for event in self.events:
            if event != 'spike':
                add_event_to_text(event)

        return '\n'.join(text)
Ejemplo n.º 49
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.
    '''
    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments)
        self.method_choice = method
        self.group = weakref.proxy(group)

        compartments = group.flat_morphology.n
        sections = group.flat_morphology.sections

        CodeRunner.__init__(self,
                            group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False,
                            template_kwds={'number_sections': sections})

        self.variables = Variables(self, default_index='_section_idx')
        self.variables.add_reference('N', group)
        # One value per compartment
        self.variables.add_arange('_compartment_idx', size=compartments)
        self.variables.add_array('_invr',
                                 dimensions=siemens.dim,
                                 size=compartments,
                                 constant=True,
                                 index='_compartment_idx')
        # one value per section
        self.variables.add_arange('_section_idx', size=sections)
        self.variables.add_array('_P_parent', size=sections,
                                 constant=True)  # elements below diagonal
        self.variables.add_arrays(
            ['_morph_idxchild', '_morph_parent_i', '_starts', '_ends'],
            size=sections,
            dtype=np.int32,
            constant=True)
        self.variables.add_arrays(['_invr0', '_invrn'],
                                  dimensions=siemens.dim,
                                  size=sections,
                                  constant=True)
        # one value per section + 1 value for the root
        self.variables.add_arange('_section_root_idx', size=sections + 1)
        self.variables.add_array('_P_diag',
                                 size=sections + 1,
                                 constant=True,
                                 index='_section_root_idx')
        self.variables.add_array('_B',
                                 size=sections + 1,
                                 constant=True,
                                 index='_section_root_idx')
        self.variables.add_array('_morph_children_num',
                                 size=sections + 1,
                                 dtype=np.int32,
                                 constant=True,
                                 index='_section_root_idx')
        # 2D matrices of size (sections + 1) x max children per section
        self.variables.add_arange('_morph_children_idx',
                                  size=len(
                                      group.flat_morphology.morph_children))
        self.variables.add_array('_P_children',
                                 size=len(
                                     group.flat_morphology.morph_children),
                                 index='_morph_children_idx',
                                 constant=True)  # elements above diagonal
        self.variables.add_array('_morph_children',
                                 size=len(
                                     group.flat_morphology.morph_children),
                                 dtype=np.int32,
                                 constant=True,
                                 index='_morph_children_idx')
        self._enable_group_attributes()

        self._morph_parent_i = group.flat_morphology.morph_parent_i
        self._morph_children_num = group.flat_morphology.morph_children_num
        self._morph_children = group.flat_morphology.morph_children
        self._morph_idxchild = group.flat_morphology.morph_idxchild
        self._starts = group.flat_morphology.starts
        self._ends = group.flat_morphology.ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(
                self.group,
                '',  # no code,
                'spatialneuron_prepare',
                name=self.name + '_spatialneuron_prepare',
                check_units=False,
                additional_variables=self.variables,
                run_namespace=run_namespace)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.info(
                    ('SpatialNeuron will use numpy to do the numerical '
                     'integration -- this will be very slow. Either '
                     'switch to a different code generation target '
                     '(e.g. weave or cython) or install scipy.'),
                    once=True)
        CodeRunner.before_run(self, run_namespace)
Ejemplo n.º 50
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use the ``t`` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where ``indices`` are the array indices which were recorded. When indexing
    the `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in ``source``, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints, optional
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices. Defaults to ``None``.
    dt : `Quantity`, optional
        The time step to be used for the monitor. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the ``dt`` argument
        is specified, the clock of the `source` will be used.
    when : str, optional
        At which point during a time step the values should be recorded.
        Defaults to ``'start'``.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    Notes
    -----

    Since this monitor by default records in the ``'start'`` time slot,
    recordings of the membrane potential in integrate-and-fire models may look
    unexpected: the recorded membrane potential trace will never be above
    threshold in an integrate-and-fire model, because the reset statement will
    have been applied already. Set the ``when`` keyword to a different value if
    this is not what you want.

    Note that ``record=True`` only works in runtime mode for synaptic variables.
    This is because the actual array of indices has to be calculated and this is
    not possible in standalone mode, where the synapses have not been created
    yet at this stage. Consider using an explicit array of indices instead,
    i.e. something like ``record=np.arange(n_synapses)``.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, variables, record=None, dt=None, clock=None,
                 when='start', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            try:
                record = np.arange(len(source), dtype=np.int32)
            except NotImplementedError:
                # In standalone mode, this is not possible for synaptic
                # variables because the number of synapses is not defined yet
                raise NotImplementedError(('Cannot determine the actual '
                                           'indices to record for record=True. '
                                           'This can occur for example in '
                                           'standalone mode when trying to '
                                           'record a synaptic variable. '
                                           'Consider providing an explicit '
                                           'array of indices for the record '
                                           'argument.'))
        elif record is None or record is False:
            logger.warn(('The StateMonitor set up to record the variable(s) '
                         '{vars} of "{source}" is not recording any value. '
                         'Did you forget to provide the record '
                         'argument?').format(vars=', '.join('"%s"' % var
                                                            for var in variables),
                                             source=self.source.name),
                        once=True)
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = _source_%s' % (v, v)
                for v in variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False)
        self.variables.add_array('N', unit=Unit(1), dtype=np.int32,
                                 size=1, scalar=True, read_only=True)
        self.variables.add_array('_indices', size=len(self.record),
                                 unit=Unit(1), dtype=self.record.dtype,
                                 constant=True, read_only=True,
                                 values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference('_source_%s' % varname,
                                         source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array(varname,
                                             size=(0, len(self.record)),
                                             resize_along_first=True,
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False)

        for varname in variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname, self.variables[varname])
                                        for varname in variables])
        recorded_names = [varname for varname in variables]

        self.needed_variables = recorded_names
        self.template_kwds = {'_recorded_variables': self.recorded_variables}
        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['N'].set_value(new_size)
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        elif hasattr(item, '_indices'):
            # objects that support the indexing interface will return absolute
            # indices but here we need relative ones
            # TODO: How to we prevent the use of completely unrelated objects here?
            source_offset = getattr(self.source, '_offset', 0)
            return StateMonitorView(self, item._indices() - source_offset)
        else:
            raise TypeError('Cannot use object of type %s as an index'
                            % type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables[item].get_value().T,
                            dim=unit.dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables[item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)

    def record_single_timestep(self):
        '''
        Records a single time step. Useful for recording the values at the end
        of the simulation -- otherwise a `StateMonitor` will not record the
        last simulated values since its ``when`` attribute defaults to
        ``'start'``, i.e. the last recording is at the *beginning* of the last
        time step.

        Notes
        -----
        This function will only work if the `StateMonitor` has been already run,
        but a run with a length of ``0*ms`` does suffice.

        Examples
        --------
        >>> from brian2 import *
        >>> G = NeuronGroup(1, 'dv/dt = -v/(5*ms) : 1')
        >>> G.v = 1
        >>> mon = StateMonitor(G, 'v', record=True)
        >>> run(0.5*ms)
        >>> mon.v
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635]])
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.]) * usecond
        >>> G.v[:]  # last value had not been recorded
        array([ 0.90483742])
        >>> mon.record_single_timestep()
        >>> mon.t[:]
        array([   0.,  100.,  200.,  300.,  400.,  500.]) * usecond
        >>> mon.v[:]
        array([[ 1.        ,  0.98019867,  0.96078944,  0.94176453,  0.92311635,
                 0.90483742]])
        '''
        if self.codeobj is None:
            raise TypeError('Can only record a single time step after the '
                            'network has been run once.')
        self.codeobj()
Ejemplo n.º 51
0
class SpatialStateUpdater(CodeRunner, Group):
    '''
    The `CodeRunner` that updates the state variables of a `SpatialNeuron`
    at every timestep.

    TODO: all internal variables (u_minus etc) could be inserted in the SpatialNeuron.
    '''

    def __init__(self, group, method, clock, order=0):
        # group is the neuron (a group of compartments) 
        self.method_choice = method
        self.group = weakref.proxy(group)
        CodeRunner.__init__(self, group,
                            'spatialstateupdate',
                            code='''_gtot = gtot__private
                                    _I0 = I0__private''',
                            clock=clock,
                            when='groups',
                            order=order,
                            name=group.name + '_spatialstateupdater*',
                            check_units=False)
        n = len(group) # total number of compartments
        segments = self.number_branches(group.morphology)
        self.variables = Variables(self, default_index='_segment_idx')
        self.variables.add_reference('N', group)
        self.variables.add_arange('_compartment_idx', size=n)
        self.variables.add_arange('_segment_idx', size=segments)
        self.variables.add_arange('_segment_root_idx', size=segments+1)
        self.variables.add_arange('_P_idx', size=(segments+1)**2)

        self.variables.add_array('_invr', unit=siemens, size=n, constant=True,
                                 index='_compartment_idx')
        self.variables.add_array('_P', unit=Unit(1), size=(segments+1)**2,
                                 constant=True, index='_P_idx')
        self.variables.add_array('_B', unit=Unit(1), size=segments+1,
                                 constant=True, index='_segment_root_idx')
        self.variables.add_array('_morph_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_morph_parent_i', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_starts', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_ends', unit=Unit(1), size=segments,
                                 dtype=np.int32, constant=True)
        self.variables.add_array('_invr0', unit=siemens, size=segments,
                                 constant=True)
        self.variables.add_array('_invrn', unit=siemens, size=segments,
                                 constant=True)
        self._enable_group_attributes()

        # The morphology is considered fixed (length etc. can still be changed,
        # though)
        # Traverse it once to get a flattened representation
        self._temp_morph_i = np.zeros(segments, dtype=np.int32)
        self._temp_morph_parent_i = np.zeros(segments, dtype=np.int32)
        self._temp_starts = np.zeros(segments, dtype=np.int32)
        self._temp_ends = np.zeros(segments, dtype=np.int32)
        self._pre_calc_iteration(self.group.morphology)
        self._morph_i = self._temp_morph_i
        self._morph_parent_i = self._temp_morph_parent_i
        self._starts = self._temp_starts
        self._ends = self._temp_ends
        self._prepare_codeobj = None

    def before_run(self, run_namespace):
        # execute code to initalize the data structures
        if self._prepare_codeobj is None:
            self._prepare_codeobj = create_runner_codeobj(self.group,
                                                          '', # no code,
                                                          'spatialneuron_prepare',
                                                          name=self.name+'_spatialneuron_prepare',
                                                          check_units=False,
                                                          additional_variables=self.variables,
                                                          run_namespace=run_namespace)
        self._prepare_codeobj()
        # Raise a warning if the slow pure numpy version is used
        # For simplicity, we check which CodeObject class the _prepare_codeobj
        # is using, this will be the same as the main state updater
        from brian2.codegen.runtime.numpy_rt.numpy_rt import NumpyCodeObject
        if isinstance(self._prepare_codeobj, NumpyCodeObject):
            # If numpy is used, raise a warning if scipy is not present
            try:
                import scipy
            except ImportError:
                logger.warn(('SpatialNeuron will use numpy to do the numerical '
                             'integration -- this will be very slow. Either '
                             'switch to a different code generation target '
                             '(e.g. weave or cython) or install scipy.'),
                            once=True)
        CodeRunner.before_run(self, run_namespace)

    def _pre_calc_iteration(self, morphology, counter=0):
        self._temp_morph_i[counter] = morphology.index + 1
        self._temp_morph_parent_i[counter] = morphology.parent + 1
        self._temp_starts[counter] = morphology._origin
        self._temp_ends[counter] = morphology._origin + len(morphology.x) - 1
        total_count = 1
        for child in morphology.children:
            total_count += self._pre_calc_iteration(child, counter+total_count)
        return total_count

    def number_branches(self, morphology, n=0, parent=-1):
        '''
        Recursively number the branches and return their total number.
        n is the index number of the current branch.
        parent is the index number of the parent branch.
        '''
        morphology.index = n
        morphology.parent = parent
        nbranches = 1
        for kid in (morphology.children):
            nbranches += self.number_branches(kid, n + nbranches, n)
        return nbranches
Ejemplo n.º 52
0
class SpikeGeneratorGroup(Group, CodeRunner, SpikeSource):
    '''
    SpikeGeneratorGroup(N, indices, times, dt=None, clock=None, period=1e100*second, when='thresholds', order=0, sorted=False, name='spikegeneratorgroup*', codeobj_class=None)

    A group emitting spikes at given times.

    Parameters
    ----------
    N : int
        The number of "neurons" in this group
    indices : array of integers
        The indices of the spiking cells
    times : `Quantity`
        The spike times for the cells given in ``indices``. Has to have the
        same length as ``indices``.
    period : `Quantity`, optional
        If this is specified, it will repeat spikes with this period.
    dt : `Quantity`, optional
        The time step to be used for the simulation. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the `defaultclock` will be used.
    when : str, optional
        When to run within a time step, defaults to the ``'thresholds'`` slot.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    sorted : bool, optional
        Whether the given indices and times are already sorted. Set to ``True``
        if your events are already sorted (first by spike time, then by index),
        this can save significant time at construction if your arrays contain
        large numbers of spikes. Defaults to ``False``.

    Notes
    -----
    * In a time step, `SpikeGeneratorGroup` emits all spikes that happened
      at :math:`t-dt < t_{spike} \leq t`. This might lead to unexpected
      or missing spikes if you change the time step dt between runs.
    * `SpikeGeneratorGroup` does not currently raise any warning if a neuron
      spikes more that once during a time step, but other code (e.g. for
      synaptic propagation) might assume that neurons only spike once per
      time step and will therefore not work properly.
    * If `sorted` is set to ``True``, the given arrays will not be copied
      (only affects runtime mode)..
    '''

    @check_units(N=1, indices=1, times=second, period=second)
    def __init__(self, N, indices, times, dt=None, clock=None,
                 period=1e100*second, when='thresholds', order=0, sorted=False,
                 name='spikegeneratorgroup*', codeobj_class=None):

        Group.__init__(self, dt=dt, clock=clock, when=when, order=order, name=name)

        # Let other objects know that we emit spikes events
        self.events = {'spike': None}

        self.codeobj_class = codeobj_class

        if N < 1 or int(N) != N:
            raise ValueError('N has to be an integer >=1.')

        if len(indices) != len(times):
            raise ValueError(('Length of the indices and times array must '
                              'match, but %d != %d') % (len(indices),
                                                        len(times)))

        if period < 0*second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError('The period has to be greater than the maximum of '
                             'the spike times')

        self.start = 0
        self.stop = N

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables = Variables(self)

        # We store the indices and times also directly in the Python object,
        # this way we can use them for checks in `before_run` even in standalone
        # TODO: Remove this when the checks in `before_run` have been moved to the template
        self._spike_time = times
        self._neuron_index = indices

        # standard variables
        self.variables.add_constant('N', unit=Unit(1), value=N)
        self.variables.add_array('period', unit=second, size=1,
                                 constant=True, read_only=True, scalar=True)
        self.variables.add_arange('i', N)
        self.variables.add_dynamic_array('spike_number',
                                         values=np.arange(len(indices)),
                                         size=len(indices), unit=Unit(1),
                                         dtype=np.int32, read_only=True,
                                         constant=True,
                                         constant_size=True,
                                         unique=True)
        self.variables.add_dynamic_array('neuron_index', values=indices,
                                         size=len(indices), unit=Unit(1),
                                         dtype=np.int32, index='spike_number',
                                         read_only=True, constant=True,
                                         constant_size=True)
        self.variables.add_dynamic_array('spike_time', values=times, size=len(times),
                                         unit=second, index='spike_number',
                                         read_only=True, constant=True,
                                         constant_size=True)
        self.variables.add_array('_spikespace', size=N+1, unit=Unit(1),
                                 dtype=np.int32)
        self.variables.add_array('_lastindex', size=1, values=0, unit=Unit(1),
                                 dtype=np.int32, read_only=True, scalar=True)
        self.variables.create_clock_variables(self._clock)

        #: Remember the dt we used the last time when we checked the spike bins
        #: to not repeat the work for multiple runs with the same dt
        self._previous_dt = None

        #: "Dirty flag" that will be set when spikes are changed after the
        #: `before_run` check
        self._spikes_changed = True

        CodeRunner.__init__(self, self,
                            code='',
                            template='spikegenerator',
                            clock=self._clock,
                            when=when,
                            order=order,
                            name=None)

        # Activate name attribute access
        self._enable_group_attributes()

        self.variables['period'].set_value(period)

    def before_run(self, run_namespace=None, level=0):
        # Do some checks on the period vs. dt
        dt = self.dt_[:]  # make a copy
        period = self.period_
        if period < np.inf:
            if period < dt:
                raise ValueError('The period of %s is %s, which is smaller '
                                 'than its dt of %s.' % (self.name,
                                                         self.period,
                                                         dt))
            if (abs(int(period/dt)*dt - period)
                    > period * np.finfo(dt.dtype).eps):
                raise NotImplementedError('The period of %s is %s, which is '
                                          'not an integer multiple of its dt '
                                          'of %s.' % (self.name,
                                                      self.period,
                                                      dt))

        # Check that we don't have more than one spike per neuron in a time bin
        if self._previous_dt is None or dt != self._previous_dt or self._spikes_changed:
            # We shift all the spikes by a tiny amount to make sure that spikes
            # at exact multiples of dt do not end up in the previous time bin
            # This shift has to be quite significant relative to machine
            # epsilon, we use 1e-3 of the dt here
            shift = 1e-3*dt
            timebins = np.asarray(np.asarray(self._spike_time + shift)/dt,
                                  dtype=np.int32)
            index_timebins = np.rec.fromarrays([self._neuron_index,
                                                timebins], names=['i', 't'])
            if not len(np.unique(index_timebins)) == len(timebins):
                raise ValueError('Using a dt of %s, some neurons of '
                                 'SpikeGeneratorGroup "%s" spike more than '
                                 'once during a time step.' % (str(self.dt),
                                                               self.name))
            self._previous_dt = dt
            self._spikes_changed = False

        super(SpikeGeneratorGroup, self).before_run(run_namespace=run_namespace,
                                                    level=level+1)

    @check_units(indices=1, times=second, period=second)
    def set_spikes(self, indices, times, period=1e100*second, sorted=False):
        '''
        set_spikes(indices, times, period=1e100*second, sorted=False)

        Change the spikes that this group will generate.

        This can be used to set the input for a second run of a model based on
        the output of a first run (if the input for the second run is already
        known before the first run, then all the information should simply be
        included in the initial `SpikeGeneratorGroup` initializer call,
        instead).

        Parameters
        ----------
        indices : array of integers
            The indices of the spiking cells
        times : `Quantity`
            The spike times for the cells given in ``indices``. Has to have the
            same length as ``indices``.
        period : `Quantity`, optional
            If this is specified, it will repeat spikes with this period.
        sorted : bool, optional
            Whether the given indices and times are already sorted. Set to
            ``True`` if your events are already sorted (first by spike time,
            then by index), this can save significant time at construction if
            your arrays contain large numbers of spikes. Defaults to ``False``.
        '''
        if len(indices) != len(times):
            raise ValueError(('Length of the indices and times array must '
                              'match, but %d != %d') % (len(indices),
                                                        len(times)))

        if period < 0*second:
            raise ValueError('The period cannot be negative.')
        elif len(times) and period <= np.max(times):
            raise ValueError('The period has to be greater than the maximum of '
                             'the spike times')

        if not sorted:
            # sort times and indices first by time, then by indices
            rec = np.rec.fromarrays([times, indices], names=['t', 'i'])
            rec.sort()
            times = np.ascontiguousarray(rec.t)
            indices = np.ascontiguousarray(rec.i)

        self.variables['period'].set_value(period)
        self.variables['neuron_index'].resize(len(indices))
        self.variables['spike_time'].resize(len(indices))
        self.variables['spike_number'].resize(len(indices))
        self.variables['spike_number'].set_value(np.arange(len(indices)))
        self.variables['neuron_index'].set_value(indices)
        self.variables['spike_time'].set_value(times)
        self.variables['_lastindex'].set_value(0)

        # Update the internal variables used in `SpikeGeneratorGroup.before_run`
        self._neuron_index = indices
        self._spike_time = times
        self._spikes_changed = True

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __repr__(self):
        return ('{cls}({N}, indices=<length {l} array>, '
                'times=<length {l} array>').format(cls=self.__class__.__name__,
                                                   N=self.N,
                                                   l=self.variables['neuron_index'].size)
Ejemplo n.º 53
0
class PopulationRateMonitor(Group, CodeRunner):
    '''
    Record instantaneous firing rates, averaged across neurons from a
    `NeuronGroup` or other spike source.

    Parameters
    ----------
    source : (`NeuronGroup`, `SpikeSource`)
        The source of spikes to record.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'_ratemonitor_0'``, etc.
    codeobj_class : class, optional
        The `CodeObject` class to run code with.

    Notes
    -----
    Currently, this monitor can only monitor the instantaneous firing rates at
    each time step of the source clock. Any binning/smoothing of the firing
    rates has to be done manually afterwards.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, name='ratemonitor*',
                 codeobj_class=None):

        #: The group we are recording from
        self.source = source

        self.codeobj_class = codeobj_class
        CodeRunner.__init__(self, group=self, code='', template='ratemonitor',
                            clock=source.clock, when='end', order=0, name=name)

        self.add_dependency(source)

        self.variables = Variables(self)
        # Handle subgroups correctly
        start = getattr(source, 'start', 0)
        stop = getattr(source, 'stop', len(source))
        self.variables.add_constant('_source_start', Unit(1), start)
        self.variables.add_constant('_source_stop', Unit(1), stop)
        self.variables.add_reference('_spikespace', source)
        self.variables.add_dynamic_array('rate', size=0, unit=hertz,
                                         read_only=True)
        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         read_only=True)
        self.variables.add_reference('_num_source_neurons', source, 'N')
        self.variables.add_array('N', unit=Unit(1), dtype=np.int32, size=1,
                                 scalar=True, read_only=True)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        self._enable_group_attributes()

    def resize(self, new_size):
        self.variables['N'].set_value(new_size)
        self.variables['rate'].resize(new_size)
        self.variables['t'].resize(new_size)

    def reinit(self):
        '''
        Clears all recorded rates
        '''
        raise NotImplementedError()

    @check_units(width=second)
    def smooth_rate(self, window='gaussian', width=None):
        '''
        smooth_rate(self, window='gaussian', width=None)

        Return a smooth version of the population rate.

        Parameters
        ----------
        window : str, ndarray
            The window to use for smoothing. Can be a string to chose a
            predefined window(``'flat'`` for a rectangular, and ``'gaussian'``
            for a Gaussian-shaped window). In this case the width of the window
            is determined by the ``width`` argument. Note that for the Gaussian
            window, the ``width`` parameter specifies the standard deviation of
            the Gaussian, the width of the actual window is ``4*width + dt``
            (rounded to the nearest dt). For the flat window, the width is
            rounded to the nearest odd multiple of dt to avoid shifting the rate
            in time.
            Alternatively, an arbitrary window can be given as a numpy array
            (with an odd number of elements). In this case, the width in units
            of time depends on the ``dt`` of the simulation, and no ``width``
            argument can be specified. The given window will be automatically
            normalized to a sum of 1.
        width : `Quantity`, optional
            The width of the ``window`` in seconds (for a predefined window).

        Returns
        -------
        rate : `Quantity`
            The population rate in Hz, smoothed with the given window. Note that
            the rates are smoothed and not re-binned, i.e. the length of the
            returned array is the same as the length of the ``rate`` attribute
            and can be plotted against the `PopulationRateMonitor` 's ``t``
            attribute.
        '''
        if width is None and isinstance(window, basestring):
            raise TypeError('Need a width when using a predefined window.')
        if width is not None and not isinstance(window, basestring):
            raise TypeError('Can only specify a width for a predefined window')

        if isinstance(window, basestring):
            if window == 'gaussian':
                width_dt = int(np.round(2*width / self.clock.dt))
                # Rounding only for the size of the window, not for the standard
                # deviation of the Gaussian
                window = np.exp(-np.arange(-width_dt,
                                           width_dt + 1)**2 *
                                1. / (2 * (width/self.clock.dt) ** 2))
            elif window == 'flat':
                width_dt = int(width / 2 / self.clock.dt)*2 + 1
                used_width = width_dt * self.clock.dt
                if abs(used_width - width) > 1e-6*self.clock.dt:
                    logger.info('width adjusted from %s to %s' % (width, used_width),
                                'adjusted_width', once=True)
                window = np.ones(width_dt)
            else:
                raise NotImplementedError('Unknown pre-defined window "%s"' % window)
        else:
            try:
                window = np.asarray(window)
            except TypeError:
                raise TypeError('Cannot use a window of type %s' % type(window))
            if window.ndim != 1:
                raise TypeError('The provided window has to be '
                                'one-dimensional.')
            if len(window) % 2 != 1:
                raise TypeError('The window has to have an odd number of '
                                'values.')
        return Quantity(np.convolve(self.rate_,
                                    window * 1. / sum(window),
                                    mode='same'), dim=hertz.dim)

    def __repr__(self):
        description = '<{classname}, recording {source}>'
        return description.format(classname=self.__class__.__name__,
                                  source=self.source.name)
Ejemplo n.º 54
0
class StateMonitor(Group, CodeRunner):
    '''
    Record values of state variables during a run
    
    To extract recorded values after a run, use `t` attribute for the
    array of times at which values were recorded, and variable name attribute
    for the values. The values will have shape ``(len(indices), len(t))``,
    where `indices` are the array indices which were recorded. When indexing the
    `StateMonitor` directly, the returned object can be used to get the
    recorded values for the specified indices, i.e. the indexing semantic
    refers to the indices in `source`, not to the relative indices of the
    recorded values. For example, when recording only neurons with even numbers,
    `mon[[0, 2]].v` will return the values for neurons 0 and 2, whereas
    `mon.v[[0, 2]]` will return the values for the first and third *recorded*
    neurons, i.e. for neurons 0 and 4.

    Parameters
    ----------
    source : `Group`
        Which object to record values from.
    variables : str, sequence of str, True
        Which variables to record, or ``True`` to record all variables
        (note that this may use a great deal of memory).
    record : None, False, True, sequence of ints
        Which indices to record, nothing is recorded for ``None`` or ``False``,
        everything is recorded for ``True`` (warning: may use a great deal of
        memory), or a specified subset of indices.
    dt : `Quantity`, optional
        The time step to be used for the monitor. Cannot be combined with
        the `clock` argument.
    clock : `Clock`, optional
        The update clock to be used. If neither a clock, nor the `dt` argument
        is specified, the clock of the `source` will be used.
    when : str, optional
        At which point during a time step the values should be recorded.
        Defaults to ``'end'``.
    order : int, optional
        The priority of of this group for operations occurring at the same time
        step and in the same scheduling slot. Defaults to 0.
    name : str, optional
        A unique name for the object, otherwise will use
        ``source.name+'statemonitor_0'``, etc.
    codeobj_class : `CodeObject`, optional
        The `CodeObject` class to create.

    Examples
    --------
    
    Record all variables, first 5 indices::
    
        eqs = """
        dV/dt = (2-V)/(10*ms) : 1
        """
        threshold = 'V>1'
        reset = 'V = 0'
        G = NeuronGroup(100, eqs, threshold=threshold, reset=reset)
        G.V = rand(len(G))
        M = StateMonitor(G, True, record=range(5))
        run(100*ms)
        plot(M.t, M.V.T)
        show()

    Notes
    -----

    Since this monitor by default records in the ``'end'`` time slot, recordings
    of the membrane potential in integrate-and-fire models may look unexpected:
    the recording is done *after* application of the reset statement, i.e the
    recorded membrane potential trace will never be above threshold. Set the
    `when` keyword to a different value if this is not what you want.
    '''
    invalidates_magic_network = False
    add_to_magic_network = True
    def __init__(self, source, variables, record=None, dt=None, clock=None,
                 when='end', order=0, name='statemonitor*', codeobj_class=None):
        self.source = source
        # Make the monitor use the explicitly defined namespace of its source
        # group (if it exists)
        self.namespace = getattr(source, 'namespace', None)
        self.codeobj_class = codeobj_class

        # run by default on source clock at the end
        if dt is None and clock is None:
            clock = source.clock

        # variables should always be a list of strings
        if variables is True:
            variables = source.equations.names
        elif isinstance(variables, str):
            variables = [variables]
        #: The variables to record
        self.record_variables = variables

        # record should always be an array of ints
        self.record_all = False
        if hasattr(record, '_indices'):
            # The ._indices method always returns absolute indices
            # If the source is already a subgroup of another group, we therefore
            # have to shift the indices to become relative to the subgroup
            record = record._indices() - getattr(source, '_offset', 0)
        if record is True:
            self.record_all = True
            record = np.arange(len(source), dtype=np.int32)
        elif record is None or record is False:
            record = np.array([], dtype=np.int32)
        elif isinstance(record, numbers.Number):
            record = np.array([record], dtype=np.int32)
        else:
            record = np.asarray(record, dtype=np.int32)
            
        #: The array of recorded indices
        self.record = record
        self.n_indices = len(record)

        # Some dummy code so that code generation takes care of the indexing
        # and subexpressions
        code = ['_to_record_%s = %s' % (v, v)
                for v in self.record_variables]
        code = '\n'.join(code)

        CodeRunner.__init__(self, group=self, template='statemonitor',
                            code=code, name=name,
                            clock=clock,
                            dt=dt,
                            when=when,
                            order=order,
                            check_units=False)

        self.add_dependency(source)

        # Setup variables
        self.variables = Variables(self)

        self.variables.add_dynamic_array('t', size=0, unit=second,
                                         constant=False, constant_size=False)
        self.variables.add_attribute_variable('N', unit=Unit(1),
                                              dtype=np.int32,
                                              obj=self, attribute='_N')
        self.variables.add_array('_indices', size=len(self.record),
                                 unit=Unit(1), dtype=self.record.dtype,
                                 constant=True, read_only=True,
                                 values=self.record)
        self.variables.create_clock_variables(self._clock,
                                              prefix='_clock_')
        for varname in variables:
            var = source.variables[varname]
            if var.scalar and len(self.record) > 1:
                logger.warn(('Variable %s is a shared variable but it will be '
                             'recorded once for every target.' % varname),
                            once=True)
            index = source.variables.indices[varname]
            self.variables.add_reference(varname, source, varname, index=index)
            if not index in ('_idx', '0') and index not in variables:
                self.variables.add_reference(index, source)
            self.variables.add_dynamic_array('_recorded_' + varname,
                                             size=(0, len(self.record)),
                                             unit=var.unit,
                                             dtype=var.dtype,
                                             constant=False,
                                             constant_size=False)

        for varname in self.record_variables:
            var = self.source.variables[varname]
            self.variables.add_auxiliary_variable('_to_record_' + varname,
                                                  unit=var.unit,
                                                  dtype=var.dtype,
                                                  scalar=var.scalar)

        self.recorded_variables = dict([(varname,
                                         self.variables['_recorded_'+varname])
                                        for varname in self.record_variables])
        recorded_names = ['_recorded_'+varname
                          for varname in self.record_variables]

        self.needed_variables = recorded_names
        self.template_kwds = template_kwds={'_recorded_variables':
                                            self.recorded_variables}
        self._enable_group_attributes()

    @property
    def _N(self):
        return self.variables['t'].get_value().shape[0]

    def __len__(self):
        return self._N

    def resize(self, new_size):
        self.variables['t'].resize(new_size)

        for var in self.recorded_variables.values():
            var.resize((new_size, self.n_indices))

    def reinit(self):
        raise NotImplementedError()

    def __getitem__(self, item):
        dtype = get_dtype(item)
        if np.issubdtype(dtype, np.int):
            return StateMonitorView(self, item)
        elif isinstance(item, collections.Sequence):
            index_array = np.array(item)
            if not np.issubdtype(index_array.dtype, np.int):
                raise TypeError('Index has to be an integer or a sequence '
                                'of integers')
            return StateMonitorView(self, item)
        elif hasattr(item, '_indices'):
            # objects that support the indexing interface will return absolute
            # indices but here we need relative ones
            # TODO: How to we prevent the use of completely unrelated objects here?
            source_offset = getattr(self.source, '_offset', 0)
            return StateMonitorView(self, item._indices() - source_offset)
        else:
            raise TypeError('Cannot use object of type %s as an index'
                            % type(item))

    def __getattr__(self, item):
        # We do this because __setattr__ and __getattr__ are not active until
        # _group_attribute_access_active attribute is set, and if it is set,
        # then __getattr__ will not be called. Therefore, if getattr is called
        # with this name, it is because it hasn't been set yet and so this
        # method should raise an AttributeError to agree that it hasn't been
        # called yet.
        if item == '_group_attribute_access_active':
            raise AttributeError
        if not hasattr(self, '_group_attribute_access_active'):
            raise AttributeError
        if item in self.record_variables:
            unit = self.variables[item].unit
            return Quantity(self.variables['_recorded_'+item].get_value().T,
                            dim=unit.dim, copy=True)
        elif item.endswith('_') and item[:-1] in self.record_variables:
            return self.variables['_recorded_'+item[:-1]].get_value().T
        else:
            return Group.__getattr__(self, item)

    def __repr__(self):
        description = '<{classname}, recording {variables} from {source}>'
        return description.format(classname=self.__class__.__name__,
                                  variables=repr(self.record_variables),
                                  source=self.source.name)
Ejemplo n.º 55
0
class PoissonGroup(Group, SpikeSource):
    '''
    Poisson spike source
    
    Parameters
    ----------
    N : int
        Number of neurons
    rates : `Quantity`, str
        Single rate, array of rates of length N, or a string expression
        evaluating to a rate
    clock : Clock, optional
        The update clock to be used, or defaultclock if not specified.
    name : str, optional
        Unique name, or use poissongroup, poissongroup_1, etc.

    '''
    @check_units(rates=Hz)
    def __init__(self,
                 N,
                 rates,
                 clock=None,
                 name='poissongroup*',
                 codeobj_class=None):

        Group.__init__(self, when=clock, name=name)

        self.codeobj_class = codeobj_class

        self._N = N = int(N)

        # TODO: In principle, it would be nice to support Poisson groups with
        # refactoriness, but we can't currently, since the refractoriness
        # information is reset in the state updater which we are not using
        # We could either use a specific template or simply not bother and make
        # users write their own NeuronGroup (with threshold rand() < rates*dt)
        # for more complex use cases.

        self.variables = Variables(self)
        # standard variables
        self.variables.add_clock_variables(self.clock)
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        self.variables.add_arange('i', self._N, constant=True, read_only=True)
        self.variables.add_array('_spikespace',
                                 size=N + 1,
                                 unit=Unit(1),
                                 dtype=np.int32)

        # The firing rates
        self.variables.add_array('rates', size=N, unit=Hz)

        self.start = 0
        self.stop = N

        self._refractory = False

        #
        self._enable_group_attributes()
        # To avoid a warning about the local variable rates, we set the real
        # threshold condition only after creating the object
        self.threshold = 'False'
        self.thresholder = Thresholder(self)
        self.threshold = 'rand() < rates * dt'
        self.contained_objects.append(self.thresholder)

        # Here we want to use the local namespace, but at the level where the
        # constructor was called
        self.rates.set_item(slice(None), rates, level=2)

    @property
    def spikes(self):
        '''
        The spikes returned by the most recent thresholding operation.
        '''
        # Note that we have to directly access the ArrayVariable object here
        # instead of using the Group mechanism by accessing self._spikespace
        # Using the latter would cut _spikespace to the length of the group
        spikespace = self.variables['_spikespace'].get_value()
        return spikespace[:spikespace[-1]]

    def __len__(self):
        return self.N

    def __repr__(self):
        description = '{classname}({N}, rates=<...>)'
        return description.format(classname=self.__class__.__name__, N=self.N)