Ejemplo n.º 1
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.º 2
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.º 3
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.º 4
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.º 5
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.º 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 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 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.º 9
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.º 10
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.º 11
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.º 12
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.º 13
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.º 14
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.º 15
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.º 16
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.º 17
0
class Subgroup(Group, SpikeSource):
    """
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    """
    def __init__(self, source, start, stop, name=None):
        # A Subgroup should never be constructed from another Subgroup
        # Instead, use Subgroup(source.source,
        #                       start + source.start, stop + source.start)
        assert not isinstance(source, Subgroup)
        self.source = weakproxy_with_fallback(source)

        # Store a reference to the source's equations (if any)
        self.equations = None
        if hasattr(self.source, 'equations'):
            self.equations = weakproxy_with_fallback(self.source.equations)

        if name is None:
            name = f"{source.name}_subgroup*"
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        Group.__init__(self,
                       clock=source._clock,
                       when='thresholds',
                       order=source.order+1, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        self.events = self.source.events

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        if self.start > 0:
            self.variables.add_constant('_offset', value=self.start)
            self.variables.add_reference('_source_i', source, 'i')
            self.variables.add_subexpression('i',
                                             dtype=source.variables['i'].dtype,
                                             expr='_source_i - _offset',
                                             index='_idx')
        else:
            # no need to calculate anything if this is a subgroup starting at 0
            self.variables.add_reference('i', source)

        self.variables.add_constant('N', value=self._N)
        self.variables.add_constant('_source_N', value=len(source))
        # add references for all variables in the original group
        self.variables.add_references(source, list(source.variables.keys()))

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        # special indexing for subgroups
        self._indices = Indexing(self, self.variables['_sub_idx'])

        # Deal with special indices
        for key, value in self.source.variables.indices.items():
            if value == '0':
                self.variables.indices[key] = '0'
            elif value == '_idx':
                continue  # nothing to do, already uses _sub_idx correctly
            else:
                raise ValueError(f"Do not know how to deal with variable '{key}' "
                                 f"using index '{value}' in a subgroup.")

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    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.source, self.start + start, self.start + stop)

    def __repr__(self):
        classname = self.__class__.__name__
        return (f"<{classname} {self.name!r} of {self.source.name!r} "
                f"from {self.start} to {self.stop}>")
Ejemplo n.º 18
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.º 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. 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.º 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',
                                 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.º 21
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    '''
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        # Store a reference to the source's equations (if any)
        self.equations = None
        if hasattr(self.source, 'equations'):
            self.equations = weakproxy_with_fallback(self.source.equations)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        Group.__init__(self,
                       clock=source._clock,
                       when='thresholds',
                       order=source.order+1, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        self.events = self.source.events

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        if self.start > 0:
            self.variables.add_constant('_offset', value=self.start)
            self.variables.add_reference('_source_i', source, 'i')
            self.variables.add_subexpression('i',
                                             dtype=source.variables['i'].dtype,
                                             expr='_source_i - _offset',
                                             index='_idx')
        else:
            # no need to calculate anything if this is a subgroup starting at 0
            self.variables.add_reference('i', source)

        self.variables.add_constant('N', value=self._N)
        self.variables.add_constant('_source_N', value=len(source))
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        # special indexing for subgroups
        self._indices = Indexing(self, self.variables['_sub_idx'])

        for key, value in self.source.variables.indices.iteritems():
            if value not in ('_idx', '0'):
                raise ValueError(('Do not know how to deal with variable %s '
                                  'using  index %s in a subgroup') % (key,
                                                                      value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    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.source, self.start + start, self.start + stop)

    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 22
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.º 23
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    '''
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        # Store a reference to the source's equations (if any)
        self.equations = None
        if hasattr(self.source, 'equations'):
            self.equations = weakproxy_with_fallback(self.source.equations)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        Group.__init__(self,
                       clock=source._clock,
                       when='thresholds',
                       order=source.order+1, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        self.events = self.source.events

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        if self.start > 0:
            self.variables.add_constant('_offset', value=self.start)
            self.variables.add_reference('_source_i', source, 'i')
            self.variables.add_subexpression('i',
                                             dtype=source.variables['i'].dtype,
                                             expr='_source_i - _offset',
                                             index='_idx')
        else:
            # no need to calculate anything if this is a subgroup starting at 0
            self.variables.add_reference('i', source)

        self.variables.add_constant('N', value=self._N)
        self.variables.add_constant('_source_N', value=len(source))
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        # special indexing for subgroups
        self._indices = Indexing(self, self.variables['_sub_idx'])

        # Deal with special indices
        for key, value in self.source.variables.indices.iteritems():
            if value == '0':
                self.variables.indices[key] = '0'
            elif value == '_idx':
                continue  # nothing to do, already uses _sub_idx correctly
            else:
                raise ValueError(('Do not know how to deal with variable %s '
                                  'using  index %s in a subgroup') % (key,
                                                                      value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    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.source, self.start + start, self.start + stop)

    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 24
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)
Ejemplo n.º 25
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    
    Notes
    -----
    
    Functions differently to Brian 1.x subgroup in that:
    
    * It works for any spike source
    * You need to keep a reference to it
    * It makes a copy of the spikes, and there is no direct support for
      subgroups in `Connection` (or rather `Synapses`)
    
    TODO: Group state variable access
    '''
    def __init__(self, source, start, stop, name=None):
        self.source = weakref.proxy(source)
        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        schedule = Scheduler(clock=source.clock,
                             when='thresholds',
                             order=source.order + 1)
        Group.__init__(self, when=schedule, name=name)
        self._N = stop - start
        self.start = start
        self.stop = stop

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        self.variables.add_constant('_offset', unit=Unit(1), value=self.start)
        self.variables.add_reference('_source_i', source.variables['i'])
        self.variables.add_subexpression('i',
                                         unit=Unit(1),
                                         dtype=source.variables['i'].dtype,
                                         expr='_source_i - _offset')
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        # add references for all variables in the original group
        self.variables.add_references(source.variables)

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx',
                                  size=self._N,
                                  start=self.start,
                                  index='_idx')

        for key, value in self.source.variables.indices.iteritems():
            if value != '_idx':
                raise ValueError(('Do not how to deal with variable %s using '
                                  'index %s in a subgroup') % (key, value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    def __len__(self):
        return self._N

    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 26
0
class Subgroup(Group, SpikeSource):
    '''
    Subgroup of any `Group`
    
    Parameters
    ----------
    source : SpikeSource
        The source object to subgroup.
    start, stop : int
        Select only spikes with indices from ``start`` to ``stop-1``.
    name : str, optional
        A unique name for the group, or use ``source.name+'_subgroup_0'``, etc.
    
    Notes
    -----
    
    Functions differently to Brian 1.x subgroup in that:
    
    * It works for any spike source
    * You need to keep a reference to it
    * It makes a copy of the spikes, and there is no direct support for
      subgroups in `Connection` (or rather `Synapses`)
    '''
    def __init__(self, source, start, stop, name=None):
        # First check if the source is itself a Subgroup
        # If so, then make this a Subgroup of the original Group
        if isinstance(source, Subgroup):
            source = source.source
            start = start + source.start
            stop = stop + source.start
            self.source = source
        else:
            self.source = weakproxy_with_fallback(source)

        if name is None:
            name = source.name + '_subgroup*'
        # We want to update the spikes attribute after it has been updated
        # by the parent, we do this in slot 'thresholds' with an order
        # one higher than the parent order to ensure it takes place after the
        # parent threshold operation
        schedule = Scheduler(clock=source.clock, when='thresholds',
                             order=source.order+1)
        Group.__init__(self, when=schedule, name=name)
        self._N = stop-start
        self.start = start
        self.stop = stop

        # All the variables have to go via the _sub_idx to refer to the
        # appropriate values in the source group
        self.variables = Variables(self, default_index='_sub_idx')

        # overwrite the meaning of N and i
        self.variables.add_constant('_offset', unit=Unit(1), value=self.start)
        self.variables.add_reference('_source_i', source, 'i')
        self.variables.add_subexpression('i', unit=Unit(1),
                                         dtype=source.variables['i'].dtype,
                                         expr='_source_i - _offset')
        self.variables.add_constant('N', unit=Unit(1), value=self._N)
        # add references for all variables in the original group
        self.variables.add_references(source, source.variables.keys())

        # Only the variable _sub_idx itself is stored in the subgroup
        # and needs the normal index for this group
        self.variables.add_arange('_sub_idx', size=self._N, start=self.start,
                                  index='_idx')

        for key, value in self.source.variables.indices.iteritems():
            if value not in ('_idx', '0'):
                raise ValueError(('Do not know how to deal with variable %s '
                                  'using  index %s in a subgroup') % (key,
                                                                      value))

        self.namespace = self.source.namespace
        self.codeobj_class = self.source.codeobj_class

        self._enable_group_attributes()

    spikes = property(lambda self: self.source.spikes)

    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.source, self.start + start, self.start + stop)

    def __len__(self):
        return self._N
        
    def __repr__(self):
        description = '<{classname} {name} of {source} from {start} to {end}>'
        return description.format(classname=self.__class__.__name__,
                                  name=repr(self.name),
                                  source=repr(self.source.name),
                                  start=self.start,
                                  end=self.stop)
Ejemplo n.º 27
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.º 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 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.º 30
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.º 31
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.º 32
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.º 33
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.º 34
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)