Пример #1
0
 def _latex(self, *args):
     equations = []
     t = sympy.Symbol('t')
     for eq in self._equations.itervalues():
         # do not use SingleEquations._latex here as we want nice alignment
         varname = sympy.Symbol(eq.varname)
         if eq.type == DIFFERENTIAL_EQUATION:
             lhs = r'\frac{\mathrm{d}' + sympy.latex(
                 varname) + r'}{\mathrm{d}t}'
         else:
             # Normal equation or parameter
             lhs = varname
         if not eq.type == PARAMETER:
             rhs = str_to_sympy(eq.expr.code)
         if len(eq.flags):
             flag_str = ', flags: ' + ', '.join(eq.flags)
         else:
             flag_str = ''
         if eq.type == PARAMETER:
             eq_latex = r'%s &&& \text{(unit: $%s$%s)}' % (
                 sympy.latex(lhs), sympy.latex(get_unit(eq.dim)), flag_str)
         else:
             eq_latex = r'%s &= %s && \text{(unit of $%s$: $%s$%s)}' % (
                 sympy.latex(lhs), sympy.latex(rhs), sympy.latex(varname),
                 sympy.latex(get_unit(eq.dim)), flag_str)
         equations.append(eq_latex)
     return r'\begin{align*}' + (r'\\' +
                                 '\n').join(equations) + r'\end{align*}'
Пример #2
0
def check_dimensions(expression, dimensions, variables):
    '''
    Compares the physical dimensions of an expression to expected dimensions in
    a given namespace.

    Parameters
    ----------
    expression : str
        The expression to evaluate.
    dimensions : `Dimension`
        The expected physical dimensions for the `expression`.
    variables : dict
        Dictionary of all variables (including external constants) used in
        the `expression`.

    Raises
    ------
    KeyError
        In case on of the identifiers cannot be resolved.
    DimensionMismatchError
        If an unit mismatch occurs during the evaluation.
    '''
    expr_dims = parse_expression_dimensions(expression, variables)
    err_msg = ('Expression {expr} does not have the '
               'expected unit {expected}').format(expr=expression.strip(),
                                                  expected=repr(get_unit(dimensions)))
    fail_for_dimension_mismatch(expr_dims, dimensions, err_msg)
Пример #3
0
def check_dimensions(expression, dimensions, variables):
    '''
    Compares the physical dimensions of an expression to expected dimensions in
    a given namespace.

    Parameters
    ----------
    expression : str
        The expression to evaluate.
    dimensions : `Dimension`
        The expected physical dimensions for the `expression`.
    variables : dict
        Dictionary of all variables (including external constants) used in
        the `expression`.

    Raises
    ------
    KeyError
        In case on of the identifiers cannot be resolved.
    DimensionMismatchError
        If an unit mismatch occurs during the evaluation.
    '''
    expr_dims = parse_expression_dimensions(expression, variables)
    err_msg = ('Expression {expr} does not have the '
               'expected unit {expected}').format(expr=expression.strip(),
                                                  expected=repr(get_unit(dimensions)))
    fail_for_dimension_mismatch(expr_dims, dimensions, err_msg)
Пример #4
0
 def _latex(self, *args):        
     equations = []
     t = sympy.Symbol('t')
     for eq in self._equations.itervalues():
         # do not use SingleEquations._latex here as we want nice alignment
         varname = sympy.Symbol(eq.varname)
         if eq.type == DIFFERENTIAL_EQUATION:
             lhs = r'\frac{\mathrm{d}' + sympy.latex(varname) + r'}{\mathrm{d}t}'
         else:
             # Normal equation or parameter
             lhs = varname
         if not eq.type == PARAMETER:
             rhs = str_to_sympy(eq.expr.code)
         if len(eq.flags):
             flag_str = ', flags: ' + ', '.join(eq.flags)
         else:
             flag_str = ''
         if eq.type == PARAMETER:
             eq_latex = r'%s &&& \text{(unit: $%s$%s)}' % (sympy.latex(lhs),                                 
                                                           sympy.latex(get_unit(eq.dim)),
                                                           flag_str)
         else:
             eq_latex = r'%s &= %s && \text{(unit of $%s$: $%s$%s)}' % (sympy.latex(lhs),
                                                                        sympy.latex(rhs),
                                                                        sympy.latex(varname),
                                                                        sympy.latex(get_unit(eq.dim)),
                                                                        flag_str)
         equations.append(eq_latex)
     return r'\begin{align*}' + (r'\\' + '\n').join(equations) + r'\end{align*}'
Пример #5
0
def test_get_unit():
    '''
    Test get_unit and get_unit_fast
    '''
    values = [3 * mV, np.array([1, 2]) * mV, np.arange(12).reshape(4, 3) * mV]
    for value in values:
        assert get_unit(value) == volt
        assert_quantity(get_unit_fast(value), 1, volt)
Пример #6
0
def test_get_unit():
    '''
    Test get_unit and get_unit_fast
    '''
    values = [3 * mV, np.array([1, 2]) * mV,
              np.arange(12).reshape(4, 3) * mV]
    for value in values:
        assert get_unit(value) == volt
        assert_quantity(get_unit_fast(value), 1, volt)
Пример #7
0
def test_get_unit():
    '''
    Test get_unit and get_unit_fast
    '''
    values = [3 * mV, np.array([1, 2]) * mV,
              np.arange(12).reshape(4, 3) * mV]
    for value in values:
        unit = get_unit(value)
        assert isinstance(unit, Unit)
        assert unit == volt
        assert_quantity(get_unit_fast(value), 1, volt)

    values = [3 * amp/metre**2, np.array([1, 2]) * amp/metre**2,
              np.arange(12).reshape(4, 3) * amp/metre**2]
    for value in values:
        unit = get_unit(value)
        assert isinstance(unit, Unit)
        assert unit == amp/metre**2
        assert float(unit) == 1.
        assert_quantity(get_unit_fast(value), 1, amp/metre**2)
Пример #8
0
def test_get_unit():
    '''
    Test get_unit and get_unit_fast
    '''
    values = [3 * mV, np.array([1, 2]) * mV,
              np.arange(12).reshape(4, 3) * mV]
    for value in values:
        unit = get_unit(value)
        assert isinstance(unit, Unit)
        assert unit == volt
        assert_quantity(get_unit_fast(value), 1, volt)

    values = [3 * amp/metre**2, np.array([1, 2]) * amp/metre**2,
              np.arange(12).reshape(4, 3) * amp/metre**2]
    for value in values:
        unit = get_unit(value)
        assert isinstance(unit, Unit)
        assert unit == amp/metre**2
        assert float(unit) == 1.
        assert_quantity(get_unit_fast(value), 1, amp/metre**2)
Пример #9
0
def test_get_unit():
    '''
    Test get_unit
    '''
    values = [(volt.dim, volt), (mV.dim, volt),
              ((amp / metre**2).dim, amp / metre**2)]
    for dim, expected_unit in values:
        unit = get_unit(dim)
        assert isinstance(unit, Unit)
        assert unit == expected_unit
        assert float(unit) == 1.
Пример #10
0
def test_get_unit():
    '''
    Test get_unit
    '''
    values = [(volt.dim, volt),
              (mV.dim, volt),
              ((amp/metre**2).dim, amp/metre**2)]
    for dim, expected_unit in values:
        unit = get_unit(dim)
        assert isinstance(unit, Unit)
        assert unit == expected_unit
        assert float(unit) == 1.
Пример #11
0
    def __repr__(self):
        s = '<' + self.type + ' ' + self.varname

        if not self.expr is None:
            s += ': ' + self.expr.code

        s += ' (Unit: ' + str(get_unit(self.dim))

        if len(self.flags):
            s += ', flags: ' + ', '.join(self.flags)

        s += ')>'
        return s
Пример #12
0
    def __init__(self,
                 target,
                 target_var,
                 N,
                 rate,
                 weight,
                 when='synapses',
                 order=0):
        if target_var not in target.variables:
            raise KeyError('%s is not a variable of %s' %
                           (target_var, target.name))

        if isinstance(weight, basestring):
            weight = '(%s)' % weight
        else:
            weight_unit = get_unit(weight)
            weight = repr(weight)
            target_unit = target.variables[target_var].unit
            # This will be checked automatically in the abstract code as well
            # but doing an explicit check here allows for a clearer error
            # message
            if not have_same_dimensions(weight_unit, target_unit):
                raise DimensionMismatchError(
                    ('The provided weight does not '
                     'have the same unit as the '
                     'target variable "%s"') % target_var, weight_unit.dim,
                    target_unit.dim)

        binomial_sampling = BinomialFunction(N,
                                             rate * target.clock.dt,
                                             name='poissoninput_binomial*')

        code = '{targetvar} += {binomial}()*{weight}'.format(
            targetvar=target_var,
            binomial=binomial_sampling.name,
            weight=weight)
        self._stored_dt = target.dt_[:]  # make a copy
        # FIXME: we need an explicit reference here for on-the-fly subgroups
        # For example: PoissonInput(group[:N], ...)
        self._group = target
        CodeRunner.__init__(self,
                            group=target,
                            template='stateupdate',
                            code=code,
                            user_code='',
                            when=when,
                            order=order,
                            name='poissoninput*',
                            clock=target.clock)
        self.variables = Variables(self)
        self.variables._add_variable(binomial_sampling.name, binomial_sampling)
Пример #13
0
    def _init_2d(self):
        dimensions = self.dim
        unit = get_unit(dimensions)
        values = self.values
        dt = self.dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(i=1, t=second, result=unit)
        def timed_array_func(t, i):
            # We round according to the current defaultclock.dt
            K = _find_K(float(defaultclock.dt), dt)
            epsilon = dt / K
            time_step = np.clip(np.int_(np.round(np.asarray(t/epsilon)) / K),
                        0, len(values)-1)
            return Quantity(values[time_step, i], dim=dimensions)

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_

            K = _find_K(group_dt, dt)
            n_values = len(values)
            epsilon = dt / K
            def unitless_timed_array_func(t, i):
                timestep = np.clip(np.int_(np.round(t/epsilon) / K),
                                   0, n_values-1)
                return values[timestep, i]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation('numpy',
                                                        create_numpy_implementation)
        values_flat = self.values.astype(np.double,
                                       order='C',
                                       copy=False).ravel()
        namespace = lambda owner: {'%s_values' % self.name: values_flat}

        for target, (_, func_2d) in TimedArray.implementations.items():
            self.implementations.add_dynamic_implementation(target,
                                                            func_2d(self.values,
                                                                    self.dt,
                                                                    self.name),
                                                            namespace=namespace,
                                                            name=self.name)
Пример #14
0
    def __init__(self, values, dt, name=None):
        if name is None:
            name = '_timedarray*'
        Nameable.__init__(self, name)
        unit = get_unit(values)
        values = np.asarray(values)
        self.values = values
        dt = float(dt)
        self.dt = dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(t=second, result=unit)
        def timed_array_func(t):
            i = np.clip(np.int_(np.float_(t) / dt + 0.5), 0, len(values)-1)
            return values[i] * unit

        Function.__init__(self, pyfunc=timed_array_func)

        # Implementation for numpy, without units
        def unitless_timed_array_func(t):
            i = np.clip(np.int_(np.float_(t) / dt + 0.5), 0, len(values)-1)
            return values[i]
        unitless_timed_array_func._arg_units = [second]
        unitless_timed_array_func._return_unit = unit

        # Implementation for C++
        cpp_code = {'support_code': '''
        inline double _timedarray_%NAME%(const double t, const double _dt, const int _num_values, const double* _values)
        {
            int i = (int)(t/_dt + 0.5); // rounds to nearest int for positive values
            if(i<0)
                i = 0;
            if(i>=_num_values)
                i = _num_values-1;
            return _values[i];
        }
        '''.replace('%NAME%', self.name),
                                       'hashdefine_code': '''
        #define %NAME%(t) _timedarray_%NAME%(t, _%NAME%_dt, _%NAME%_num_values, _%NAME%_values)
        '''.replace('%NAME%', self.name)}
        namespace = {'_%s_dt' % self.name: self.dt,
                     '_%s_num_values' % self.name: len(self.values),
                     '_%s_values' % self.name: self.values}

        add_implementations(self, codes={'cpp': cpp_code,
                                         'numpy': unitless_timed_array_func},
                            namespaces={'cpp': namespace},
                            names={'cpp': self.name})
Пример #15
0
    def __str__(self):
        if self.type == DIFFERENTIAL_EQUATION:
            s = 'd' + self.varname + '/dt'
        else:
            s = self.varname

        if not self.expr is None:
            s += ' = ' + str(self.expr)

        s += ' : ' + str(get_unit(self.dim))

        if len(self.flags):
            s += ' (' + ', '.join(self.flags) + ')'

        return s
Пример #16
0
 def __init__(self, values, dt, name=None):
     if name is None:
         name = '_timedarray*'
     Nameable.__init__(self, name)
     unit = get_unit(values)
     self.unit = unit
     values = np.asarray(values, dtype=np.double)
     self.values = values
     dt = float(dt)
     self.dt = dt
     if values.ndim == 1:
         self._init_1d()
     elif values.ndim == 2:
         self._init_2d()
     else:
         raise NotImplementedError(('Only 1d and 2d arrays are supported '
                                    'for TimedArray'))
Пример #17
0
 def __init__(self, values, dt, name=None):
     if name is None:
         name = '_timedarray*'
     Nameable.__init__(self, name)
     unit = get_unit(values)
     self.unit = unit
     values = np.asarray(values, dtype=np.double)
     self.values = values
     dt = float(dt)
     self.dt = dt
     if values.ndim == 1:
         self._init_1d()
     elif values.ndim == 2:
         self._init_2d()
     else:
         raise NotImplementedError(('Only 1d and 2d arrays are supported '
                                    'for TimedArray'))
Пример #18
0
    def __init__(self, target, target_var, N, rate, weight, when='synapses',
                 order=0):
        if target_var not in target.variables:
            raise KeyError('%s is not a variable of %s' % (target_var, target.name))

        if isinstance(weight, basestring):
            weight = '(%s)' % weight
        else:
            weight_unit = get_unit(weight)
            weight = repr(weight)
            target_unit = target.variables[target_var].unit
            # This will be checked automatically in the abstract code as well
            # but doing an explicit check here allows for a clearer error
            # message
            if not have_same_dimensions(weight_unit, target_unit):
                raise DimensionMismatchError(('The provided weight does not '
                                              'have the same unit as the '
                                              'target variable "%s"') % target_var,
                                             weight_unit.dim,
                                             target_unit.dim)


        binomial_sampling = BinomialFunction(N, rate*target.clock.dt,
                                             name='poissoninput_binomial*')

        code = '{targetvar} += {binomial}()*{weight}'.format(targetvar=target_var,
                                                             binomial=binomial_sampling.name,
                                                             weight=weight)
        self._stored_dt = target.dt_[:]  # make a copy
        # FIXME: we need an explicit reference here for on-the-fly subgroups
        # For example: PoissonInput(group[:N], ...)
        self._group = target
        CodeRunner.__init__(self,
                            group=target,
                            template='stateupdate',
                            code=code,
                            user_code='',
                            when=when,
                            order=order,
                            name='poissoninput*',
                            clock=target.clock
                            )
        self.variables = Variables(self)
        self.variables._add_variable(binomial_sampling.name, binomial_sampling)
Пример #19
0
    def _repr_pretty_(self, p, cycle):
        '''
        Pretty printing for ipython.
        '''
        if cycle:
            # should never happen
            raise AssertionError('Cyclical call of SingleEquation._repr_pretty')

        if self.type == DIFFERENTIAL_EQUATION:
            p.text('d' + self.varname + '/dt')
        else:
            p.text(self.varname)

        if not self.expr is None:
            p.text(' = ')
            p.pretty(self.expr)

        p.text(' : ')
        p.pretty(get_unit(self.dim))

        if len(self.flags):
            p.text(' (' + ', '.join(self.flags) + ')')
Пример #20
0
    def _repr_pretty_(self, p, cycle):
        '''
        Pretty printing for ipython.
        '''
        if cycle:
            # should never happen
            raise AssertionError('Cyclical call of SingleEquation._repr_pretty')

        if self.type == DIFFERENTIAL_EQUATION:
            p.text('d' + self.varname + '/dt')
        else:
            p.text(self.varname)

        if not self.expr is None:
            p.text(' = ')
            p.pretty(self.expr)

        p.text(' : ')
        p.pretty(get_unit(self.dim))

        if len(self.flags):
            p.text(' (' + ', '.join(self.flags) + ')')
Пример #21
0
def dimensions_and_type_from_string(unit_string):
    '''
    Returns the physical dimensions that results from evaluating a string like
    "siemens / metre ** 2", allowing for the special string "1" to signify
    dimensionless units, the string "boolean" for a boolean and "integer" for
    an integer variable.

    Parameters
    ----------
    unit_string : str
        The string that should evaluate to a unit

    Returns
    -------
    d, type : (`Dimension`, {FLOAT, INTEGER or BOOL})
        The resulting physical dimensions and the type of the variable.

    Raises
    ------
    ValueError
        If the string cannot be evaluated to a unit.
    '''
    # Lazy import to avoid circular dependency
    from brian2.core.namespace import DEFAULT_UNITS
    global _base_units_with_alternatives
    global _base_units
    if _base_units_with_alternatives is None:
        base_units_for_dims = {}
        for unit_name, unit in reversed(DEFAULT_UNITS.items()):
            if float(unit) == 1.0 and repr(unit)[-1] not in ['2', '3']:
                if unit.dim in base_units_for_dims:
                    if unit_name not in base_units_for_dims[unit.dim]:
                        base_units_for_dims[unit.dim].append(unit_name)
                else:
                    base_units_for_dims[unit.dim] = [repr(unit)]
                    if unit_name != repr(unit):
                        base_units_for_dims[unit.dim].append(unit_name)
        alternatives = sorted([tuple(values) for values in base_units_for_dims.itervalues()])
        _base_units = dict([(v, DEFAULT_UNITS[v])
                            for values in alternatives for v in values])
        # Create a string that lists all allowed base units
        alternative_strings = []
        for units in alternatives:
            string = units[0]
            if len(units) > 1:
                string += ' ({other_units})'.format(other_units=', '.join(units[1:]))
            alternative_strings.append(string)
        _base_units_with_alternatives = ', '.join(alternative_strings)

    unit_string = unit_string.strip()

    # Special case: dimensionless unit
    if unit_string == '1':
        return DIMENSIONLESS, FLOAT

    # Another special case: boolean variable
    if unit_string == 'boolean':
        return DIMENSIONLESS, BOOLEAN
    if unit_string == 'bool':
        raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean "
                        "variable.")

    # Yet another special case: integer variable
    if unit_string == 'integer':
        return DIMENSIONLESS, INTEGER

    # Check first whether the expression only refers to base units
    identifiers = get_identifiers(unit_string)
    for identifier in identifiers:
        if identifier not in _base_units:
            if identifier in DEFAULT_UNITS:
                # A known unit, but not a base unit
                base_unit = get_unit(DEFAULT_UNITS[identifier].dim)
                if not repr(base_unit) in _base_units:
                    # Make sure that we don't suggest a unit that is not allowed
                    # (should not happen, normally)
                    base_unit = Unit(1, dim=base_unit.dim)
                raise ValueError(('Unit specification refers to '
                                  '"{identifier}", but this is not a base '
                                  'unit. Use "{base_unit}" '
                                  'instead.').format(identifier=identifier,
                                                     base_unit=repr(base_unit)))
            else:
                # Not a known unit
                raise ValueError(('Unit specification refers to '
                                  '"{identifier}", but this is not a base '
                                  'unit. The following base units are '
                                  'allowed: '
                                  '{allowed_units}.').format(identifier=identifier,
                                                             allowed_units=_base_units_with_alternatives))
    try:
        evaluated_unit = eval(unit_string, _base_units)
    except Exception as ex:
        raise ValueError(('Could not interpret "%s" as a unit specification: '
                          '%s') % (unit_string, ex))

    # Check whether the result is a unit
    if not isinstance(evaluated_unit, Unit):
        if isinstance(evaluated_unit, Quantity):
            raise ValueError(('"%s" does not evaluate to a unit but to a '
                              'quantity -- make sure to only use units, e.g. '
                              '"siemens/metre**2" and not "1 * siemens/metre**2"') %
                             unit_string)
        else:
            raise ValueError(('"%s" does not evaluate to a unit, the result '
                             'has type %s instead.' % (unit_string,
                                                       type(evaluated_unit))))

    # No error has been raised, all good
    return evaluated_unit.dim, FLOAT
Пример #22
0
    def _init_2d(self):
        dimensions = self.dim
        unit = get_unit(dimensions)
        values = self.values
        dt = self.dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(i=1, t=second, result=unit)
        def timed_array_func(t, i):
            # We round according to the current defaultclock.dt
            K = _find_K(float(defaultclock.dt), dt)
            epsilon = dt / K
            time_step = np.clip(np.int_(np.round(np.asarray(t/epsilon)) / K),
                        0, len(values)-1)
            return Quantity(values[time_step, i], dim=dimensions)

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_

            K = _find_K(group_dt, dt)
            n_values = len(values)
            epsilon = dt / K
            def unitless_timed_array_func(t, i):
                timestep = np.clip(np.int_(np.round(t/epsilon) / K),
                                   0, n_values-1)
                return values[timestep, i]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation('numpy',
                                                        create_numpy_implementation)


        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            support_code = '''
            static inline double %NAME%(const double t, const int i)
            {
                const double epsilon = %DT% / %K%;
                if (i < 0 || i >= %COLS%)
                    return NAN;
                int timestep = (int)((t/epsilon + 0.5)/%K%);
                if(timestep < 0)
                   timestep = 0;
                else if(timestep >= %ROWS%)
                    timestep = %ROWS%-1;
                return _namespace%NAME%_values[timestep*%COLS% + i];
            }
            '''
            support_code = replace(support_code, {'%NAME%': self.name,
                                                  '%DT%': '%.18f' % dt,
                                                  '%K%': str(K),
                                                  '%COLS%': str(self.values.shape[1]),
                                                  '%ROWS%': str(self.values.shape[0])})
            cpp_code = {'support_code': support_code}

            return cpp_code

        def create_cpp_namespace(owner):
            return {'%s_values' % self.name: self.values.astype(np.double,
                                                                order='C',
                                                                copy=False).ravel()}

        self.implementations.add_dynamic_implementation('cpp',
                                                        code=create_cpp_implementation,
                                                        namespace=create_cpp_namespace,
                                                        name=self.name)

        def create_cython_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            code = '''
            cdef double %NAME%(const double t, const int i):
                global _namespace%NAME%_values
                cdef double epsilon = %DT% / %K%
                if i < 0 or i >= %COLS%:
                    return _numpy.nan
                cdef int timestep = (int)((t/epsilon + 0.5)/%K%)
                if timestep < 0:
                   timestep = 0
                elif timestep >= %ROWS%:
                    timestep = %ROWS%-1
                return _namespace%NAME%_values[timestep*%COLS% + i]
            '''
            code = replace(code, {'%NAME%': self.name,
                                  '%DT%': '%.18f' % dt,
                                  '%K%': str(K),
                                  '%COLS%': str(self.values.shape[1]),
                                  '%ROWS%': str(self.values.shape[0])})

            return code

        def create_cython_namespace(owner):
            return {'%s_values' % self.name: self.values.astype(np.double,
                                                                order='C',
                                                                copy=False).ravel()}

        self.implementations.add_dynamic_implementation('cython',
                                                        code=create_cython_implementation,
                                                        namespace=create_cython_namespace,
                                                        name=self.name)
Пример #23
0
def create_runner_codeobj(group, code, template_name,
                          variable_indices=None,
                          name=None, check_units=True,
                          needed_variables=None,
                          additional_variables=None,
                          additional_namespace=None,
                          template_kwds=None):
    ''' Create a `CodeObject` for the execution of code in the context of a
    `Group`.

    Parameters
    ----------
    group : `Group`
        The group where the code is to be run
    code : str
        The code to be executed.
    template_name : str
        The name of the template to use for the code.
    variable_indices : dict-like, optional
        A mapping from `Variable` objects to index names (strings).  If none is
        given, uses the corresponding attribute of `group`.
    name : str, optional
        A name for this code object, will use ``group + '_codeobject*'`` if
        none is given.
    check_units : bool, optional
        Whether to check units in the statement. Defaults to ``True``.
    needed_variables: list of str, optional
        A list of variables that are neither present in the abstract code, nor
        in the ``USES_VARIABLES`` statement in the template. This is only
        rarely necessary, an example being a `StateMonitor` where the
        names of the variables are neither known to the template nor included
        in the abstract code statements.
    additional_variables : dict-like, optional
        A mapping of names to `Variable` objects, used in addition to the
        variables saved in `group`.
    additional_namespace : dict-like, optional
        A mapping from names to objects, used in addition to the namespace
        saved in `group`.
    template_kwds : dict, optional
        A dictionary of additional information that is passed to the template.
    '''
    logger.debug('Creating code object for abstract code:\n' + str(code))
    from brian2.devices import get_device
    device = get_device()

    if check_units:
        if isinstance(code, dict):
            for c in code.values():
                check_code_units(c, group,
                                 additional_variables=additional_variables,
                                 additional_namespace=additional_namespace)
        else:
            check_code_units(code, group,
                             additional_variables=additional_variables,
                             additional_namespace=additional_namespace)

    codeobj_class = device.code_object_class(group.codeobj_class)
    template = getattr(codeobj_class.templater, template_name)

    all_variables = dict(group.variables)
    if additional_variables is not None:
        all_variables.update(additional_variables)

    # Determine the identifiers that were used
    if isinstance(code, dict):
        used_known = set()
        unknown = set()
        for v in code.values():
            _, uk, u = analyse_identifiers(v, all_variables, recursive=True)
            used_known |= uk
            unknown |= u
    else:
        _, used_known, unknown = analyse_identifiers(code, all_variables,
                                                     recursive=True)

    logger.debug('Unknown identifiers in the abstract code: ' + str(unknown))

    # Only pass the variables that are actually used
    variables = {}
    for var in used_known:
        # Emit a warning if a variable is also present in the namespace
        if (var in group.namespace or (additional_namespace is not None and
                                       var in additional_namespace[1])):
            message = ('Variable {var} is present in the namespace but is also an'
                       ' internal variable of {name}, the internal variable will'
                       ' be used.'.format(var=var, name=group.name))
            logger.warn(message, 'create_runner_codeobj.resolution_conflict',
                        once=True)
        variables[var] = all_variables[var]

    resolved_namespace = group.namespace.resolve_all(unknown,
                                                     additional_namespace)

    for varname, value in resolved_namespace.iteritems():
        if isinstance(value, Function):
            variables[varname] = value
        else:
            unit = get_unit(value)
            # For the moment, only allow the use of scalar values
            array_value = np.asarray(value)
            if array_value.shape != ():
                raise TypeError('Name "%s" does not refer to a scalar value' % varname)
            variables[varname] = Constant(name=varname, unit=unit, value=value)

    # Add variables that are not in the abstract code, nor specified in the
    # template but nevertheless necessary
    if needed_variables is None:
        needed_variables = []
    for var in needed_variables:
        variables[var] = all_variables[var]

    # Also add the variables that the template needs
    for var in template.variables:
        try:
            variables[var] = all_variables[var]
        except KeyError as ex:
            # We abuse template.variables here to also store names of things
            # from the namespace (e.g. rand) that are needed
            # TODO: Improve all of this namespace/specifier handling
            if group is not None:
                # Try to find the name in the group's namespace
                variables[var] = group.namespace.resolve(var,
                                                         additional_namespace)
            else:
                raise ex

    # always add N, the number of neurons or synapses
    variables['N'] = all_variables['N']

    if name is None:
        if group is not None:
            name = '%s_%s_codeobject*' % (group.name, template_name)
        else:
            name = '%s_codeobject*' % template_name

    all_variable_indices = copy.copy(group.variables.indices)
    if additional_variables is not None:
        all_variable_indices.update(additional_variables.indices)
    if variable_indices is not None:
        all_variable_indices.update(variable_indices)

    # Add the indices needed by the variables
    varnames = variables.keys()
    for varname in varnames:
        var_index = all_variable_indices[varname]
        if var_index != '_idx':
            variables[var_index] = all_variables[var_index]

    return device.code_object(owner=group,
                              name=name,
                              abstract_code=code,
                              variables=variables,
                              template_name=template_name,
                              variable_indices=all_variable_indices,
                              template_kwds=template_kwds,
                              codeobj_class=group.codeobj_class)
Пример #24
0
    def __init__(self, values, dt, name=None):
        if name is None:
            name = '_timedarray*'
        Nameable.__init__(self, name)
        unit = get_unit(values)
        values = np.asarray(values)
        self.values = values
        dt = float(dt)
        self.dt = dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(t=second, result=unit)
        def timed_array_func(t):
            i = np.clip(np.int_(np.float_(t) / dt + 0.5), 0, len(values)-1)
            return values[i] * unit

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            epsilon = dt / K
            n_values = len(values)
            def unitless_timed_array_func(t):
                timestep = np.clip(np.int_(np.round(t/epsilon)) / K, 0, n_values-1)
                return values[timestep]
            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation('numpy',
                                                        create_numpy_implementation)

        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            cpp_code = {'support_code': '''
            inline double _timedarray_%NAME%(const double t, const int _num_values, const double* _values)
            {
                const double epsilon = %DT% / %K%;
                int i = (int)((t/epsilon + 0.5)/%K%); // rounds to nearest int for positive values
                if(i<0)
                    i = 0;
                if(i>=_num_values)
                    i = _num_values-1;
                return _values[i];
            }
            '''.replace('%NAME%', self.name).replace('%DT%', '%.18f' % dt).replace('%K%', str(K)),
                                           'hashdefine_code': '''
            #define %NAME%(t) _timedarray_%NAME%(t, _%NAME%_num_values, _%NAME%_values)
            '''.replace('%NAME%', self.name)}

            return cpp_code

        def create_cpp_namespace(owner):
            return {'_%s_num_values' % self.name: len(self.values),
                    '_%s_values' % self.name: self.values}

        self.implementations.add_dynamic_implementation('cpp',
                                                        create_cpp_implementation,
                                                        create_cpp_namespace,
                                                        name=self.name)
Пример #25
0
    def _resolve_external(self,
                          identifier,
                          run_namespace,
                          user_identifier=True,
                          internal_variable=None):
        '''
        Resolve an external identifier in the context of a `Group`. If the `Group`
        declares an explicit namespace, this namespace is used in addition to the
        standard namespace for units and functions. Additionally, the namespace in
        the `run_namespace` argument (i.e. the namespace provided to `Network.run`)
        is used.

        Parameters
        ----------
        identifier : str
            The name to resolve.
        group : `Group`
            The group that potentially defines an explicit namespace for looking up
            external names.
        run_namespace : dict
            A namespace (mapping from strings to objects), as provided as an
            argument to the `Network.run` function or returned by
            `get_local_namespace`.
        user_identifier : bool, optional
            Whether this is an identifier that was used by the user (and not
            something automatically generated that the user might not even
            know about). Will be used to determine whether to display a
            warning in the case of namespace clashes. Defaults to ``True``.
        internal_variable : `Variable`, optional
            The internal variable object that corresponds to this name (if any).
            This is used to give warnings if it also corresponds to a variable
            from an external namespace.
        '''
        # We save tuples of (namespace description, referred object) to
        # give meaningful warnings in case of duplicate definitions
        matches = []

        namespaces = OrderedDict()
        # Default namespaces (units and functions)
        namespaces['constants'] = DEFAULT_CONSTANTS
        namespaces['units'] = DEFAULT_UNITS
        namespaces['functions'] = DEFAULT_FUNCTIONS
        if getattr(self, 'namespace', None) is not None:
            namespaces['group-specific'] = self.namespace

        # explicit or implicit run namespace
        namespaces['run'] = run_namespace

        for description, namespace in namespaces.iteritems():
            if identifier in namespace:
                match = namespace[identifier]
                if ((isinstance(match, (numbers.Number, np.ndarray, np.number,
                                        Function, Variable)))
                        or (inspect.isfunction(match)
                            and hasattr(match, '_arg_units')
                            and hasattr(match, '_return_unit'))):
                    matches.append((description, match))

        if len(matches) == 0:
            # No match at all
            if internal_variable is not None:
                return None
            else:
                raise KeyError(('The identifier "%s" could not be resolved.') %
                               (identifier))

        elif len(matches) > 1:
            # Possibly, all matches refer to the same object
            first_obj = matches[0][1]
            found_mismatch = False
            for m in matches:
                if _same_value(m[1], first_obj):
                    continue
                if _same_function(m[1], first_obj):
                    continue
                try:
                    proxy = weakref.proxy(first_obj)
                    if m[1] is proxy:
                        continue
                except TypeError:
                    pass

                # Found a mismatch
                found_mismatch = True
                break

            if found_mismatch and user_identifier and internal_variable is None:
                _conflict_warning(
                    ('The name "%s" refers to different objects '
                     'in different namespaces used for resolving '
                     'names in the context of group "%s". '
                     'Will use the object from the %s namespace '
                     'with the value %s,') %
                    (identifier, getattr(self, 'name', '<unknown>'),
                     matches[0][0], _display_value(first_obj)), matches[1:])

        if internal_variable is not None and user_identifier:
            # Filter out matches that are identical (a typical case being an
            # externally defined "N" with the the number of neurons and a later
            # use of "N" in an expression (which refers to the internal variable
            # storing the number of neurons in the group)
            if isinstance(internal_variable, Constant):
                filtered_matches = []
                for match in matches:
                    if not _same_value(match[1], internal_variable):
                        filtered_matches.append(match)
            else:
                filtered_matches = matches
            if len(filtered_matches) == 0:
                pass  # Nothing to warn about
            else:
                warning_message = ('"{name}" is an internal variable of group '
                                   '"{group}", but also exists in the ')
                if len(matches) == 1:
                    warning_message += ('{namespace} namespace with the value '
                                        '{value}. ').format(
                                            namespace=filtered_matches[0][0],
                                            value=_display_value(
                                                filtered_matches[0][1]))
                else:
                    warning_message += ('following namespaces: '
                                        '{namespaces}. ').format(
                                            namespaces=' ,'.join(
                                                match[0]
                                                for match in filtered_matches))
                warning_message += 'The internal variable will be used.'
                logger.warn(warning_message.format(name=identifier,
                                                   group=self.name),
                            'Group.resolve.resolution_conflict',
                            once=True)

        if internal_variable is not None:
            return None  # We were only interested in the warnings above

        # use the first match (according to resolution order)
        resolved = matches[0][1]

        # Replace pure Python functions by a Functions object
        if callable(resolved) and not isinstance(resolved, Function):
            resolved = Function(resolved, stateless=False)

        if not isinstance(resolved, (Function, Variable)):
            # Wrap the value in a Constant object
            unit = get_unit(resolved)
            value = np.asarray(resolved)
            if value.shape != ():
                raise KeyError('Variable %s was found in the namespace, but is'
                               ' not a scalar value' % identifier)
            resolved = Constant(identifier, unit=unit, value=value)

        return resolved
Пример #26
0
def parse_expression_dimensions(expr, variables):
    '''
    Returns the unit value of an expression, and checks its validity
    
    Parameters
    ----------
    expr : str
        The expression to check.
    variables : dict
        Dictionary of all variables used in the `expr` (including `Constant`
        objects for external variables)
    
    Returns
    -------
    unit : Quantity
        The output unit of the expression
    
    Raises
    ------
    SyntaxError
        If the expression cannot be parsed, or if it uses ``a**b`` for ``b``
        anything other than a constant number.
    DimensionMismatchError
        If any part of the expression is dimensionally inconsistent.
    '''

    # If we are working on a string, convert to the top level node    
    if isinstance(expr, basestring):
        mod = ast.parse(expr, mode='eval')
        expr = mod.body
    if expr.__class__ is getattr(ast, 'NameConstant', None):
        # new class for True, False, None in Python 3.4
        value = expr.value
        if value is True or value is False:
            return DIMENSIONLESS
        else:
            raise ValueError('Do not know how to handle value %s' % value)
    if expr.__class__ is ast.Name:
        name = expr.id
        # Raise an error if a function is called as if it were a variable
        # (most of the time this happens for a TimedArray)
        if name in variables and isinstance(variables[name], Function):
            raise SyntaxError('%s was used like a variable/constant, but it is '
                              'a function.' % name)
        if name in variables:
            return variables[name].dim
        elif name in ['True', 'False']:
            return DIMENSIONLESS
        else:
            raise KeyError('Unknown identifier %s' % name)
    elif expr.__class__ is ast.Num:
        return DIMENSIONLESS
    elif expr.__class__ is ast.BoolOp:
        # check that the units are valid in each subexpression
        for node in expr.values:
            parse_expression_dimensions(node, variables)
        # but the result is a bool, so we just return 1 as the unit
        return DIMENSIONLESS
    elif expr.__class__ is ast.Compare:
        # check that the units are consistent in each subexpression
        subexprs = [expr.left]+expr.comparators
        subunits = []
        for node in subexprs:
            subunits.append(parse_expression_dimensions(node, variables))
        for left_dim, right_dim in zip(subunits[:-1], subunits[1:]):
            if not have_same_dimensions(left_dim, right_dim):
                msg = ('Comparison of expressions with different units. Expression '
                       '"{}" has unit ({}), while expression "{}" has units ({})').format(
                            NodeRenderer().render_node(expr.left), get_dimensions(left_dim),
                            NodeRenderer().render_node(expr.comparators[0]), get_dimensions(right_dim))
                raise DimensionMismatchError(msg)
        # but the result is a bool, so we just return 1 as the unit
        return DIMENSIONLESS
    elif expr.__class__ is ast.Call:
        if len(expr.keywords):
            raise ValueError("Keyword arguments not supported.")
        elif getattr(expr, 'starargs', None) is not None:
            raise ValueError("Variable number of arguments not supported")
        elif getattr(expr, 'kwargs', None) is not None:
            raise ValueError("Keyword arguments not supported")

        func = variables.get(expr.func.id, None)
        if func is None:
            raise SyntaxError('Unknown function %s' % expr.func.id)
        if not hasattr(func, '_arg_units') or not hasattr(func, '_return_unit'):
            raise ValueError(('Function %s does not specify how it '
                              'deals with units.') % expr.func.id)

        if len(func._arg_units) != len(expr.args):
            raise SyntaxError('Function %s was called with %d parameters, '
                              'needs %d.' % (expr.func.id,
                                             len(expr.args),
                                             len(func._arg_units)))

        for idx, (arg, expected_unit) in enumerate(zip(expr.args,
                                                       func._arg_units)):
            # A "None" in func._arg_units means: No matter what unit
            if expected_unit is None:
                continue
            elif expected_unit == bool:
                if not is_boolean_expression(arg, variables):
                    raise TypeError(('Argument number %d for function %s was '
                                     'expected to be a boolean value, but is '
                                     '"%s".') % (idx + 1, expr.func.id,
                                                 NodeRenderer().render_node(arg)))
            else:
                arg_unit = parse_expression_dimensions(arg, variables)
                if not have_same_dimensions(arg_unit, expected_unit):
                    msg = ('Argument number {} for function {} does not have the '
                           'correct units. Expression "{}" has units ({}), but '
                           'should be ({}).').format(
                        idx+1, expr.func.id,
                        NodeRenderer().render_node(arg),
                        get_dimensions(arg_unit), get_dimensions(expected_unit))
                    raise DimensionMismatchError(msg)

        if func._return_unit == bool:
            return DIMENSIONLESS
        elif isinstance(func._return_unit, (Unit, int)):
            # Function always returns the same unit
            return getattr(func._return_unit, 'dim', DIMENSIONLESS)
        else:
            # Function returns a unit that depends on the arguments
            arg_units = [parse_expression_dimensions(arg, variables)
                         for arg in expr.args]
            return func._return_unit(*arg_units).dim

    elif expr.__class__ is ast.BinOp:
        op = expr.op.__class__.__name__
        left_dim = parse_expression_dimensions(expr.left, variables)
        right_dim = parse_expression_dimensions(expr.right, variables)
        if op in ['Add', 'Sub', 'Mod']:
            # dimensions should be the same
            if left_dim is not right_dim:
                op_symbol = {'Add': '+', 'Sub': '-', 'Mod': '%'}.get(op)
                left_str = NodeRenderer().render_node(expr.left)
                right_str = NodeRenderer().render_node(expr.right)
                left_unit = repr(get_unit(left_dim))
                right_unit = repr(get_unit(right_dim))
                error_msg = ('Expression "{left} {op} {right}" uses '
                             'inconsistent units ("{left}" has unit '
                             '{left_unit}; "{right}" '
                             'has unit {right_unit})').format(left=left_str,
                                                             right=right_str,
                                                             op=op_symbol,
                                                             left_unit=left_unit,
                                                             right_unit=right_unit)
                raise DimensionMismatchError(error_msg)
            u = left_dim
        elif op == 'Mult':
            u = left_dim*right_dim
        elif op == 'Div':
            u = left_dim/right_dim
        elif op == 'FloorDiv':
            if not (left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS):
                raise SyntaxError('Floor division can only be used on '
                                  'dimensionless values.')
            u = DIMENSIONLESS
        elif op == 'Pow':
            if left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS:
                return DIMENSIONLESS
            n = _get_value_from_expression(expr.right, variables)
            u = left_dim**n
        else:
            raise SyntaxError("Unsupported operation "+op)
        return u
    elif expr.__class__ is ast.UnaryOp:
        op = expr.op.__class__.__name__
        # check validity of operand and get its unit
        u = parse_expression_dimensions(expr.operand, variables)
        if op == 'Not':
            return DIMENSIONLESS
        else:
            return u
    else:
        raise SyntaxError('Unsupported operation ' + str(expr.__class__))
Пример #27
0
 def __setattr__(self, name, val, level=0):
     # attribute access is switched off until this attribute is created by
     # _enable_group_attributes
     if not hasattr(self, '_group_attribute_access_active') or name in self.__dict__:
         object.__setattr__(self, name, val)
     elif (name in self.__getattribute__('__dict__') or
                 name in self.__getattribute__('__class__').__dict__):
         # Makes sure that classes can override the "variables" mechanism
         # with instance/class attributes and properties
         return object.__setattr__(self, name, val)
     elif name in self.variables:
         var = self.variables[name]
         if not isinstance(val, basestring):
             if var.dim is DIMENSIONLESS:
                 fail_for_dimension_mismatch(val, var.dim,
                                             ('%s should be set with a '
                                              'dimensionless value, but got '
                                              '{value}') % name,
                                             value=val)
             else:
                 fail_for_dimension_mismatch(val, var.dim,
                                             ('%s should be set with a '
                                              'value with units %r, but got '
                                              '{value}') % (name, get_unit(var.dim)),
                                             value=val)
         if var.read_only:
             raise TypeError('Variable %s is read-only.' % name)
         # Make the call X.var = ... equivalent to X.var[:] = ...
         var.get_addressable_value_with_unit(name, self).set_item(slice(None),
                                                                  val,
                                                                  level=level+1)
     elif len(name) and name[-1]=='_' and name[:-1] in self.variables:
         # no unit checking
         var = self.variables[name[:-1]]
         if var.read_only:
             raise TypeError('Variable %s is read-only.' % name[:-1])
         # Make the call X.var = ... equivalent to X.var[:] = ...
         var.get_addressable_value(name[:-1], self).set_item(slice(None),
                                                             val,
                                                             level=level+1)
     elif hasattr(self, name) or name.startswith('_'):
         object.__setattr__(self, name, val)
     else:
         # Try to suggest the correct name in case of a typo
         checker = SpellChecker([varname for varname, var in self.variables.iteritems()
                                 if not (varname.startswith('_') or var.read_only)])
         if name.endswith('_'):
             suffix = '_'
             name = name[:-1]
         else:
             suffix = ''
         error_msg = 'Could not find a state variable with name "%s".' % name
         suggestions = checker.suggest(name)
         if len(suggestions) == 1:
             suggestion, = suggestions
             error_msg += ' Did you mean to write "%s%s"?' % (suggestion,
                                                              suffix)
         elif len(suggestions) > 1:
             error_msg += (' Did you mean to write any of the following: %s ?' %
                           (', '.join(['"%s%s"' % (suggestion, suffix)
                                       for suggestion in suggestions])))
         error_msg += (' Use the add_attribute method if you intend to add '
                       'a new attribute to the object.')
         raise AttributeError(error_msg)
Пример #28
0
class SingleEquation(collections.Hashable, CacheKey):
    '''
    Class for internal use, encapsulates a single equation or parameter.

    .. note::
        This class should never be used directly, it is only useful as part of
        the `Equations` class.
    
    Parameters
    ----------
    type : {PARAMETER, DIFFERENTIAL_EQUATION, SUBEXPRESSION}
        The type of the equation.
    varname : str
        The variable that is defined by this equation.
    dimensions : `Dimension`
        The physical dimensions of the variable
    var_type : {FLOAT, INTEGER, BOOLEAN}
        The type of the variable (floating point value or boolean).
    expr : `Expression`, optional
        The expression defining the variable (or ``None`` for parameters).        
    flags: list of str, optional
        A list of flags that give additional information about this equation.
        What flags are possible depends on the type of the equation and the
        context.
    '''

    _cache_irrelevant_attributes = {'update_order'}

    def __init__(self,
                 type,
                 varname,
                 dimensions,
                 var_type=FLOAT,
                 expr=None,
                 flags=None):
        self.type = type
        self.varname = varname
        self.dim = get_dimensions(dimensions)
        self.var_type = var_type
        if dimensions is not DIMENSIONLESS:
            if var_type == BOOLEAN:
                raise TypeError(
                    'Boolean variables are necessarily dimensionless.')
            elif var_type == INTEGER:
                raise TypeError(
                    'Integer variables are necessarily dimensionless.')

        if type == DIFFERENTIAL_EQUATION:
            if var_type != FLOAT:
                raise TypeError(
                    'Differential equations can only define floating point variables'
                )
        self.expr = expr
        if flags is None:
            self.flags = []
        else:
            self.flags = flags

        # will be set later in the sort_subexpressions method of Equations
        self.update_order = -1

    unit = property(lambda self: get_unit(self.dim),
                    doc='The `Unit` of this equation.')

    identifiers = property(lambda self: self.expr.identifiers
                           if not self.expr is None else set([]),
                           doc='All identifiers in the RHS of this equation.')

    stochastic_variables = property(
        lambda self: set([
            variable for variable in self.identifiers
            if variable == 'xi' or variable.startswith('xi_')
        ]),
        doc='Stochastic variables in the RHS of this equation')

    def __eq__(self, other):
        if not isinstance(other, SingleEquation):
            return NotImplemented
        return self._state_tuple == other._state_tuple

    def __ne__(self, other):
        return not self == other

    def __hash__(self):
        return hash(self._state_tuple)

    def _latex(self, *args):
        if self.type == DIFFERENTIAL_EQUATION:
            return (r'\frac{\mathrm{d}' + sympy.latex(self.varname) +
                    r'}{\mathrm{d}t} = ' +
                    sympy.latex(str_to_sympy(self.expr.code)))
        elif self.type == SUBEXPRESSION:
            return (sympy.latex(self.varname) + ' = ' +
                    sympy.latex(str_to_sympy(self.expr.code)))
        elif self.type == PARAMETER:
            return sympy.latex(self.varname)

    def __str__(self):
        if self.type == DIFFERENTIAL_EQUATION:
            s = 'd' + self.varname + '/dt'
        else:
            s = self.varname

        if not self.expr is None:
            s += ' = ' + str(self.expr)

        s += ' : ' + get_unit_for_display(self.dim)

        if len(self.flags):
            s += ' (' + ', '.join(self.flags) + ')'

        return s

    def __repr__(self):
        s = '<' + self.type + ' ' + self.varname

        if not self.expr is None:
            s += ': ' + self.expr.code

        s += ' (Unit: ' + get_unit_for_display(self.dim)

        if len(self.flags):
            s += ', flags: ' + ', '.join(self.flags)

        s += ')>'
        return s

    def _repr_pretty_(self, p, cycle):
        '''
        Pretty printing for ipython.
        '''
        if cycle:
            # should never happen
            raise AssertionError(
                'Cyclical call of SingleEquation._repr_pretty')

        if self.type == DIFFERENTIAL_EQUATION:
            p.text('d' + self.varname + '/dt')
        else:
            p.text(self.varname)

        if not self.expr is None:
            p.text(' = ')
            p.pretty(self.expr)

        p.text(' : ')
        p.pretty(get_unit(self.dim))

        if len(self.flags):
            p.text(' (' + ', '.join(self.flags) + ')')

    def _repr_latex_(self):
        return '$' + sympy.latex(self) + '$'
Пример #29
0
def dimensions_and_type_from_string(unit_string):
    '''
    Returns the physical dimensions that results from evaluating a string like
    "siemens / metre ** 2", allowing for the special string "1" to signify
    dimensionless units, the string "boolean" for a boolean and "integer" for
    an integer variable.

    Parameters
    ----------
    unit_string : str
        The string that should evaluate to a unit

    Returns
    -------
    d, type : (`Dimension`, {FLOAT, INTEGER or BOOL})
        The resulting physical dimensions and the type of the variable.

    Raises
    ------
    ValueError
        If the string cannot be evaluated to a unit.
    '''
    # Lazy import to avoid circular dependency
    from brian2.core.namespace import DEFAULT_UNITS
    global _base_units  # we only want to do this once
    global _single_base_units

    if _base_units is None:
        base_units_for_dims = {}
        _base_units = collections.OrderedDict()
        for unit_name, unit in DEFAULT_UNITS.iteritems():
            if float(unit) == 1.0:
                _base_units[unit_name] = unit
        # Go through it a second time -- we only want to display one unit per
        # dimensionality to the user and don't bother displaying powered units
        # (meter2, meter3, ...)
        for unit in _base_units.itervalues():
            if (unit.dim not in base_units_for_dims
                    and repr(unit)[-1] not in ['2', '3']):
                base_units_for_dims[unit.dim] = unit
        _single_base_units = sorted(
            [repr(unit) for unit in base_units_for_dims.itervalues()])

    unit_string = unit_string.strip()

    # Special case: dimensionless unit
    if unit_string == '1':
        return DIMENSIONLESS, FLOAT

    # Another special case: boolean variable
    if unit_string == 'boolean':
        return DIMENSIONLESS, BOOLEAN
    if unit_string == 'bool':
        raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean "
                        "variable.")

    # Yet another special case: integer variable
    if unit_string == 'integer':
        return DIMENSIONLESS, INTEGER

    # Check first whether the expression only refers to base units
    identifiers = get_identifiers(unit_string)
    for identifier in identifiers:
        if identifier not in _base_units:
            if identifier in DEFAULT_UNITS:
                # A known unit, but not a base unit
                base_unit = get_unit(DEFAULT_UNITS[identifier].dim)
                if not repr(base_unit) in _base_units:
                    # Make sure that we don't suggest a unit that is not allowed
                    # (should not happen, normally)
                    base_unit = Unit(1, dim=base_unit.dim)
                raise ValueError(
                    ('Unit specification refers to '
                     '"{identifier}", but this is not a base '
                     'unit. Use "{base_unit}" '
                     'instead.').format(identifier=identifier,
                                        base_unit=repr(base_unit)))
            else:
                # Not a known unit
                allowed = ', '.join(_single_base_units)
                raise ValueError(('Unit specification refers to '
                                  '"{identifier}", but this is not a base '
                                  'unit. The following base units are '
                                  'allowed: {allowed_units} (plus some '
                                  'variants of these, e.g. "Hz" instead of '
                                  '"hertz", or "meter" instead of '
                                  '"metre").').format(identifier=identifier,
                                                      allowed_units=allowed))
    try:
        evaluated_unit = eval(unit_string, _base_units)
    except Exception as ex:
        raise ValueError(('Could not interpret "%s" as a unit specification: '
                          '%s') % (unit_string, ex))

    # Check whether the result is a unit
    if not isinstance(evaluated_unit, Unit):
        if isinstance(evaluated_unit, Quantity):
            raise ValueError(
                ('"%s" does not evaluate to a unit but to a '
                 'quantity -- make sure to only use units, e.g. '
                 '"siemens/metre**2" and not "1 * siemens/metre**2"') %
                unit_string)
        else:
            raise ValueError(
                ('"%s" does not evaluate to a unit, the result '
                 'has type %s instead.' % (unit_string, type(evaluated_unit))))

    # No error has been raised, all good
    return evaluated_unit.dim, FLOAT
Пример #30
0
    def _resolve_external(self, identifier, run_namespace, user_identifier=True,
                          internal_variable=None):
        '''
        Resolve an external identifier in the context of a `Group`. If the `Group`
        declares an explicit namespace, this namespace is used in addition to the
        standard namespace for units and functions. Additionally, the namespace in
        the `run_namespace` argument (i.e. the namespace provided to `Network.run`)
        is used.

        Parameters
        ----------
        identifier : str
            The name to resolve.
        group : `Group`
            The group that potentially defines an explicit namespace for looking up
            external names.
        run_namespace : dict
            A namespace (mapping from strings to objects), as provided as an
            argument to the `Network.run` function or returned by
            `get_local_namespace`.
        user_identifier : bool, optional
            Whether this is an identifier that was used by the user (and not
            something automatically generated that the user might not even
            know about). Will be used to determine whether to display a
            warning in the case of namespace clashes. Defaults to ``True``.
        internal_variable : `Variable`, optional
            The internal variable object that corresponds to this name (if any).
            This is used to give warnings if it also corresponds to a variable
            from an external namespace.
        '''
        # We save tuples of (namespace description, referred object) to
        # give meaningful warnings in case of duplicate definitions
        matches = []

        namespaces = OrderedDict()
        # Default namespaces (units and functions)
        namespaces['constants'] = DEFAULT_CONSTANTS
        namespaces['units'] = DEFAULT_UNITS
        namespaces['functions'] = DEFAULT_FUNCTIONS
        if getattr(self, 'namespace', None) is not None:
            namespaces['group-specific'] = self.namespace

        # explicit or implicit run namespace
        namespaces['run'] = run_namespace

        for description, namespace in namespaces.iteritems():
            if identifier in namespace:
                matches.append((description, namespace[identifier]))

        if len(matches) == 0:
            # No match at all
            if internal_variable is not None:
                return None
            else:
                raise KeyError(('The identifier "%s" could not be resolved.') %
                               (identifier))

        elif len(matches) > 1:
            # Possibly, all matches refer to the same object
            first_obj = matches[0][1]
            found_mismatch = False
            for m in matches:
                if _same_value(m[1], first_obj):
                    continue
                if _same_function(m[1], first_obj):
                    continue
                try:
                    proxy = weakref.proxy(first_obj)
                    if m[1] is proxy:
                        continue
                except TypeError:
                    pass

                # Found a mismatch
                found_mismatch = True
                break

            if found_mismatch and user_identifier and internal_variable is None:
                _conflict_warning(('The name "%s" refers to different objects '
                                   'in different namespaces used for resolving '
                                   'names in the context of group "%s". '
                                   'Will use the object from the %s namespace '
                                   'with the value %s,') %
                                  (identifier, getattr(self, 'name',
                                                       '<unknown>'),
                                   matches[0][0],
                                   _display_value(first_obj)), matches[1:])

        if internal_variable is not None and user_identifier:
            # Filter out matches that are identical (a typical case being an
            # externally defined "N" with the the number of neurons and a later
            # use of "N" in an expression (which refers to the internal variable
            # storing the number of neurons in the group)
            if isinstance(internal_variable, Constant):
                filtered_matches = []
                for match in matches:
                    if not _same_value(match[1], internal_variable):
                        filtered_matches.append(match)
            else:
                filtered_matches = matches
            if len(filtered_matches) == 0:
                pass  # Nothing to warn about
            else:
                warning_message = ('"{name}" is an internal variable of group '
                                   '"{group}", but also exists in the ')
                if len(matches) == 1:
                    warning_message += ('{namespace} namespace with the value '
                                        '{value}. ').format(namespace=filtered_matches[0][0],
                                                           value=_display_value(filtered_matches[0][1]))
                else:
                    warning_message += ('following namespaces: '
                                        '{namespaces}. ').format(namespaces=' ,'.join(match[0]
                                                                                     for match in filtered_matches))
                warning_message += 'The internal variable will be used.'
                logger.warn(warning_message.format(name=identifier,
                                                   group=self.name),
                            'Group.resolve.resolution_conflict', once=True)

        # use the first match (according to resolution order)
        resolved = matches[0][1]

        # Replace pure Python functions by a Functions object
        if callable(resolved) and not isinstance(resolved, Function):
            resolved = Function(resolved, stateless=False)

        if not isinstance(resolved, (Function, Variable)):
            # Wrap the value in a Constant object
            unit = get_unit(resolved)
            value = np.asarray(resolved)
            if value.shape != ():
                raise KeyError('Variable %s was found in the namespace, but is'
                               ' not a scalar value' % identifier)
            resolved = Constant(identifier, unit=unit, value=value)

        return resolved
Пример #31
0
    def _resolve_external(self,
                          identifier,
                          run_namespace=None,
                          level=0,
                          do_warn=True):
        '''
        Resolve an external identifier in the context of a `Group`. If the `Group`
        declares an explicit namespace, this namespace is used in addition to the
        standard namespace for units and functions. Additionally, the namespace in
        the `run_namespace` argument (i.e. the namespace provided to `Network.run`)
        or, if this argument is unspecified, the implicit namespace of
        surrounding variables in the stack frame where the original call was made
        is used (to determine this stack frame, the `level` argument has to be set
        correctly).

        Parameters
        ----------
        identifier : str
            The name to resolve.
        group : `Group`
            The group that potentially defines an explicit namespace for looking up
            external names.
        run_namespace : dict, optional
            A namespace (mapping from strings to objects), as provided as an
            argument to the `Network.run` function.
        level : int, optional
            How far to go up in the stack to find the calling frame.
        do_warn : int, optional
            Whether to display a warning if an identifier resolves to different
            objects in different namespaces. Defaults to ``True``.
        '''
        # We save tuples of (namespace description, referred object) to
        # give meaningful warnings in case of duplicate definitions
        matches = []

        namespaces = OrderedDict()
        # Default namespaces (units and functions)
        namespaces['constants'] = DEFAULT_CONSTANTS
        namespaces['units'] = DEFAULT_UNITS
        namespaces['functions'] = DEFAULT_FUNCTIONS
        if getattr(self, 'namespace', None) is not None:
            namespaces['group-specific'] = self.namespace

        # explicit or implicit run namespace
        if run_namespace is not None:
            namespaces['run'] = run_namespace
        else:
            namespaces['implicit'] = get_local_namespace(level + 1)

        for description, namespace in namespaces.iteritems():
            if identifier in namespace:
                matches.append((description, namespace[identifier]))

        if len(matches) == 0:
            # No match at all
            raise KeyError(
                ('The identifier "%s" could not be resolved.') % (identifier))
        elif len(matches) > 1:
            # Possibly, all matches refer to the same object
            first_obj = matches[0][1]
            found_mismatch = False
            for m in matches:
                if _same_value(m[1], first_obj):
                    continue
                if _same_function(m[1], first_obj):
                    continue
                try:
                    proxy = weakref.proxy(first_obj)
                    if m[1] is proxy:
                        continue
                except TypeError:
                    pass

                # Found a mismatch
                found_mismatch = True
                break

            if found_mismatch and do_warn:
                _conflict_warning(
                    ('The name "%s" refers to different objects '
                     'in different namespaces used for resolving '
                     'names in the context of group "%s". '
                     'Will use the object from the %s namespace '
                     'with the value %r') %
                    (identifier, getattr(
                        self, 'name', '<unknown>'), matches[0][0], first_obj),
                    matches[1:])

        # use the first match (according to resolution order)
        resolved = matches[0][1]

        # Replace pure Python functions by a Functions object
        if callable(resolved) and not isinstance(resolved, Function):
            resolved = Function(resolved)

        if not isinstance(resolved, (Function, Variable)):
            # Wrap the value in a Constant object
            unit = get_unit(resolved)
            value = np.asarray(resolved)
            if value.shape != ():
                raise KeyError('Variable %s was found in the namespace, but is'
                               ' not a scalar value' % identifier)
            resolved = Constant(identifier, unit=unit, value=value)

        return resolved
Пример #32
0
def parse_expression_dimensions(expr, variables):
    '''
    Returns the unit value of an expression, and checks its validity
    
    Parameters
    ----------
    expr : str
        The expression to check.
    variables : dict
        Dictionary of all variables used in the `expr` (including `Constant`
        objects for external variables)
    
    Returns
    -------
    unit : Quantity
        The output unit of the expression
    
    Raises
    ------
    SyntaxError
        If the expression cannot be parsed, or if it uses ``a**b`` for ``b``
        anything other than a constant number.
    DimensionMismatchError
        If any part of the expression is dimensionally inconsistent.
    '''

    # If we are working on a string, convert to the top level node
    if isinstance(expr, basestring):
        mod = ast.parse(expr, mode='eval')
        expr = mod.body
    if expr.__class__ is getattr(ast, 'NameConstant', None):
        # new class for True, False, None in Python 3.4
        value = expr.value
        if value is True or value is False:
            return DIMENSIONLESS
        else:
            raise ValueError('Do not know how to handle value %s' % value)
    if expr.__class__ is ast.Name:
        name = expr.id
        # Raise an error if a function is called as if it were a variable
        # (most of the time this happens for a TimedArray)
        if name in variables and isinstance(variables[name], Function):
            raise SyntaxError(
                '%s was used like a variable/constant, but it is '
                'a function.' % name)
        if name in variables:
            return variables[name].dim
        elif name in ['True', 'False']:
            return DIMENSIONLESS
        else:
            raise KeyError('Unknown identifier %s' % name)
    elif expr.__class__ is ast.Num:
        return DIMENSIONLESS
    elif expr.__class__ is ast.BoolOp:
        # check that the units are valid in each subexpression
        for node in expr.values:
            parse_expression_dimensions(node, variables)
        # but the result is a bool, so we just return 1 as the unit
        return DIMENSIONLESS
    elif expr.__class__ is ast.Compare:
        # check that the units are consistent in each subexpression
        subexprs = [expr.left] + expr.comparators
        subunits = []
        for node in subexprs:
            subunits.append(parse_expression_dimensions(node, variables))
        for left_dim, right_dim in zip(subunits[:-1], subunits[1:]):
            if not have_same_dimensions(left_dim, right_dim):
                msg = (
                    'Comparison of expressions with different units. Expression '
                    '"{}" has unit ({}), while expression "{}" has units ({})'
                ).format(NodeRenderer().render_node(expr.left),
                         get_dimensions(left_dim),
                         NodeRenderer().render_node(expr.comparators[0]),
                         get_dimensions(right_dim))
                raise DimensionMismatchError(msg)
        # but the result is a bool, so we just return 1 as the unit
        return DIMENSIONLESS
    elif expr.__class__ is ast.Call:
        if len(expr.keywords):
            raise ValueError("Keyword arguments not supported.")
        elif getattr(expr, 'starargs', None) is not None:
            raise ValueError("Variable number of arguments not supported")
        elif getattr(expr, 'kwargs', None) is not None:
            raise ValueError("Keyword arguments not supported")

        func = variables.get(expr.func.id, None)
        if func is None:
            raise SyntaxError('Unknown function %s' % expr.func.id)
        if not hasattr(func, '_arg_units') or not hasattr(
                func, '_return_unit'):
            raise ValueError(('Function %s does not specify how it '
                              'deals with units.') % expr.func.id)

        if len(func._arg_units) != len(expr.args):
            raise SyntaxError(
                'Function %s was called with %d parameters, '
                'needs %d.' %
                (expr.func.id, len(expr.args), len(func._arg_units)))

        for idx, (arg,
                  expected_unit) in enumerate(zip(expr.args, func._arg_units)):
            # A "None" in func._arg_units means: No matter what unit
            if expected_unit is None:
                continue
            elif expected_unit == bool:
                if not is_boolean_expression(arg, variables):
                    raise TypeError(
                        ('Argument number %d for function %s was '
                         'expected to be a boolean value, but is '
                         '"%s".') % (idx + 1, expr.func.id,
                                     NodeRenderer().render_node(arg)))
            else:
                arg_unit = parse_expression_dimensions(arg, variables)
                if not have_same_dimensions(arg_unit, expected_unit):
                    msg = (
                        'Argument number {} for function {} does not have the '
                        'correct units. Expression "{}" has units ({}), but '
                        'should be ({}).').format(
                            idx + 1, expr.func.id,
                            NodeRenderer().render_node(arg),
                            get_dimensions(arg_unit),
                            get_dimensions(expected_unit))
                    raise DimensionMismatchError(msg)

        if func._return_unit == bool:
            return DIMENSIONLESS
        elif isinstance(func._return_unit, (Unit, int)):
            # Function always returns the same unit
            return getattr(func._return_unit, 'dim', DIMENSIONLESS)
        else:
            # Function returns a unit that depends on the arguments
            arg_units = [
                parse_expression_dimensions(arg, variables)
                for arg in expr.args
            ]
            return func._return_unit(*arg_units).dim

    elif expr.__class__ is ast.BinOp:
        op = expr.op.__class__.__name__
        left_dim = parse_expression_dimensions(expr.left, variables)
        right_dim = parse_expression_dimensions(expr.right, variables)
        if op in ['Add', 'Sub', 'Mod']:
            # dimensions should be the same
            if left_dim is not right_dim:
                op_symbol = {'Add': '+', 'Sub': '-', 'Mod': '%'}.get(op)
                left_str = NodeRenderer().render_node(expr.left)
                right_str = NodeRenderer().render_node(expr.right)
                left_unit = repr(get_unit(left_dim))
                right_unit = repr(get_unit(right_dim))
                error_msg = ('Expression "{left} {op} {right}" uses '
                             'inconsistent units ("{left}" has unit '
                             '{left_unit}; "{right}" '
                             'has unit {right_unit})').format(
                                 left=left_str,
                                 right=right_str,
                                 op=op_symbol,
                                 left_unit=left_unit,
                                 right_unit=right_unit)
                raise DimensionMismatchError(error_msg)
            u = left_dim
        elif op == 'Mult':
            u = left_dim * right_dim
        elif op == 'Div':
            u = left_dim / right_dim
        elif op == 'FloorDiv':
            if not (left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS):
                raise SyntaxError('Floor division can only be used on '
                                  'dimensionless values.')
            u = DIMENSIONLESS
        elif op == 'Pow':
            if left_dim is DIMENSIONLESS and right_dim is DIMENSIONLESS:
                return DIMENSIONLESS
            n = _get_value_from_expression(expr.right, variables)
            u = left_dim**n
        else:
            raise SyntaxError("Unsupported operation " + op)
        return u
    elif expr.__class__ is ast.UnaryOp:
        op = expr.op.__class__.__name__
        # check validity of operand and get its unit
        u = parse_expression_dimensions(expr.operand, variables)
        if op == 'Not':
            return DIMENSIONLESS
        else:
            return u
    else:
        raise SyntaxError('Unsupported operation ' + str(expr.__class__))
Пример #33
0
def check_units_statements(code, variables):
    '''
    Check the units for a series of statements. Setting a model variable has to
    use the correct unit. For newly introduced temporary variables, the unit
    is determined and used to check the following statements to ensure
    consistency.
    
    Parameters
    ----------
    code : str
        The statements as a (multi-line) string
    variables : dict of `Variable` objects
        The information about all variables used in `code` (including
        `Constant` objects for external variables)
    
    Raises
    ------
    KeyError
        In case on of the identifiers cannot be resolved.
    DimensionMismatchError
        If an unit mismatch occurs during the evaluation.
    '''
    variables = dict(variables)
    # Avoid a circular import
    from brian2.codegen.translation import analyse_identifiers
    known = set(variables.keys())
    newly_defined, _, unknown = analyse_identifiers(code, known)

    if len(unknown):
        raise AssertionError(
            ('Encountered unknown identifiers, this should '
             'not happen at this stage. Unkown identifiers: %s' % unknown))

    code = re.split(r'[;\n]', code)
    for line in code:
        line = line.strip()
        if not len(line):
            continue  # skip empty lines

        varname, op, expr, comment = parse_statement(line)
        if op in ('+=', '-=', '*=', '/=', '%='):
            # Replace statements such as "w *=2" by "w = w * 2"
            expr = '{var} {op_first} {expr}'.format(var=varname,
                                                    op_first=op[0],
                                                    expr=expr)
            op = '='
        elif op == '=':
            pass
        else:
            raise AssertionError('Unknown operator "%s"' % op)

        expr_unit = parse_expression_unit(expr, variables)

        if varname in variables:
            expected_unit = variables[varname].unit
            fail_for_dimension_mismatch(expr_unit, expected_unit,
                                        ('The right-hand-side of code '
                                         'statement ""%s" does not have the '
                                         'expected unit %r') %
                                        (line, expected_unit))
        elif varname in newly_defined:
            # note the unit for later
            variables[varname] = Variable(name=varname,
                                          unit=get_unit(expr_unit),
                                          scalar=False)
        else:
            raise AssertionError(('Variable "%s" is neither in the variables '
                                  'dictionary nor in the list of undefined '
                                  'variables.' % varname))
Пример #34
0
    def _init_1d(self):
        dimensions = self.dim
        unit = get_unit(dimensions)
        values = self.values
        dt = self.dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(t=second, result=unit)
        def timed_array_func(t):
            # We round according to the current defaultclock.dt
            K = _find_K(float(defaultclock.dt), dt)
            epsilon = dt / K
            i = np.clip(np.int_(np.round(np.asarray(t/epsilon)) / K),
                        0, len(values)-1)
            return Quantity(values[i], dim=dimensions)

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_

            K = _find_K(group_dt, dt)
            n_values = len(values)
            epsilon = dt / K
            def unitless_timed_array_func(t):
                timestep = np.clip(np.int_(np.round(t/epsilon) / K),
                                   0, n_values-1)
                return values[timestep]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation('numpy',
                                                        create_numpy_implementation)

        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            support_code = '''
            static inline double %NAME%(const double t)
            {
                const double epsilon = %DT% / %K%;
                int i = (int)((t/epsilon + 0.5)/%K%);
                if(i < 0)
                   i = 0;
                if(i >= %NUM_VALUES%)
                    i = %NUM_VALUES%-1;
                return _namespace%NAME%_values[i];
            }
            '''.replace('%NAME%', self.name).replace('%DT%', '%.18f' % dt).replace('%K%', str(K)).replace('%NUM_VALUES%', str(len(self.values)))
            cpp_code = {'support_code': support_code}

            return cpp_code

        def create_cpp_namespace(owner):
            return {'%s_values' % self.name: self.values}

        self.implementations.add_dynamic_implementation('cpp',
                                                        code=create_cpp_implementation,
                                                        namespace=create_cpp_namespace,
                                                        name=self.name)
        def create_cython_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            code = '''
            cdef double %NAME%(const double t):
                global _namespace%NAME%_values
                cdef double epsilon = %DT% / %K%
                cdef int i = (int)((t/epsilon + 0.5)/%K%)
                if i < 0:
                   i = 0
                if i >= %NUM_VALUES%:
                    i = %NUM_VALUES% - 1
                return _namespace%NAME%_values[i]
            '''.replace('%NAME%', self.name).replace('%DT%', '%.18f' % dt).replace('%K%', str(K)).replace('%NUM_VALUES%', str(len(self.values)))

            return code

        def create_cython_namespace(owner):
            return {'%s_values' % self.name: self.values}

        self.implementations.add_dynamic_implementation('cython',
                                                        code=create_cython_implementation,
                                                        namespace=create_cython_namespace,
                                                        name=self.name)
Пример #35
0
 def __setattr__(self, name, val, level=0):
     # attribute access is switched off until this attribute is created by
     # _enable_group_attributes
     if not hasattr(
             self,
             '_group_attribute_access_active') or name in self.__dict__:
         object.__setattr__(self, name, val)
     elif (name in self.__getattribute__('__dict__')
           or name in self.__getattribute__('__class__').__dict__):
         # Makes sure that classes can override the "variables" mechanism
         # with instance/class attributes and properties
         return object.__setattr__(self, name, val)
     elif name in self.variables:
         var = self.variables[name]
         if not isinstance(val, basestring):
             if var.dim is DIMENSIONLESS:
                 fail_for_dimension_mismatch(
                     val,
                     var.dim,
                     ('%s should be set with a '
                      'dimensionless value, but got '
                      '{value}') % name,
                     value=val)
             else:
                 fail_for_dimension_mismatch(
                     val,
                     var.dim,
                     ('%s should be set with a '
                      'value with units %r, but got '
                      '{value}') % (name, get_unit(var.dim)),
                     value=val)
         if var.read_only:
             raise TypeError('Variable %s is read-only.' % name)
         # Make the call X.var = ... equivalent to X.var[:] = ...
         var.get_addressable_value_with_unit(name,
                                             self).set_item(slice(None),
                                                            val,
                                                            level=level + 1)
     elif len(name) and name[-1] == '_' and name[:-1] in self.variables:
         # no unit checking
         var = self.variables[name[:-1]]
         if var.read_only:
             raise TypeError('Variable %s is read-only.' % name[:-1])
         # Make the call X.var = ... equivalent to X.var[:] = ...
         var.get_addressable_value(name[:-1],
                                   self).set_item(slice(None),
                                                  val,
                                                  level=level + 1)
     elif hasattr(self, name) or name.startswith('_'):
         object.__setattr__(self, name, val)
     else:
         # Try to suggest the correct name in case of a typo
         checker = SpellChecker([
             varname for varname, var in self.variables.items()
             if not (varname.startswith('_') or var.read_only)
         ])
         if name.endswith('_'):
             suffix = '_'
             name = name[:-1]
         else:
             suffix = ''
         error_msg = 'Could not find a state variable with name "%s".' % name
         suggestions = checker.suggest(name)
         if len(suggestions) == 1:
             suggestion, = suggestions
             error_msg += ' Did you mean to write "%s%s"?' % (suggestion,
                                                              suffix)
         elif len(suggestions) > 1:
             error_msg += (
                 ' Did you mean to write any of the following: %s ?' %
                 (', '.join([
                     '"%s%s"' % (suggestion, suffix)
                     for suggestion in suggestions
                 ])))
         error_msg += (' Use the add_attribute method if you intend to add '
                       'a new attribute to the object.')
         raise AttributeError(error_msg)
Пример #36
0
    def __init__(self, values, dt, name=None):
        if name is None:
            name = '_timedarray*'
        Nameable.__init__(self, name)
        unit = get_unit(values)
        values = np.asarray(values)
        self.values = values
        dt = float(dt)
        self.dt = dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(t=second, result=unit)
        def timed_array_func(t):
            i = np.clip(np.int_(np.float_(t) / dt + 0.5), 0, len(values) - 1)
            return values[i] * unit

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            epsilon = dt / K
            n_values = len(values)

            def unitless_timed_array_func(t):
                timestep = np.clip(
                    np.int_(np.round(t / epsilon)) / K, 0, n_values - 1)
                return values[timestep]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation(
            'numpy', create_numpy_implementation)

        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            cpp_code = {
                'support_code':
                '''
            inline double _timedarray_%NAME%(const double t, const int _num_values, const double* _values)
            {
                const double epsilon = %DT% / %K%;
                int i = (int)((t/epsilon + 0.5)/%K%); // rounds to nearest int for positive values
                if(i<0)
                    i = 0;
                if(i>=_num_values)
                    i = _num_values-1;
                return _values[i];
            }
            '''.replace('%NAME%', self.name).replace('%DT%',
                                                     '%.18f' % dt).replace(
                                                         '%K%', str(K)),
                'hashdefine_code':
                '''
            #define %NAME%(t) _timedarray_%NAME%(t, _%NAME%_num_values, _%NAME%_values)
            '''.replace('%NAME%', self.name)
            }

            return cpp_code

        def create_cpp_namespace(owner):
            return {
                '_%s_num_values' % self.name: len(self.values),
                '_%s_values' % self.name: self.values
            }

        self.implementations.add_dynamic_implementation(
            'cpp',
            create_cpp_implementation,
            create_cpp_namespace,
            name=self.name)
Пример #37
0
def check_units_statements(code, variables):
    '''
    Check the units for a series of statements. Setting a model variable has to
    use the correct unit. For newly introduced temporary variables, the unit
    is determined and used to check the following statements to ensure
    consistency.
    
    Parameters
    ----------
    code : str
        The statements as a (multi-line) string
    variables : dict of `Variable` objects
        The information about all variables used in `code` (including
        `Constant` objects for external variables)
    
    Raises
    ------
    KeyError
        In case on of the identifiers cannot be resolved.
    DimensionMismatchError
        If an unit mismatch occurs during the evaluation.
    '''
    variables = dict(variables)
    # Avoid a circular import
    from brian2.codegen.translation import analyse_identifiers
    known = set(variables.keys())
    newly_defined, _, unknown = analyse_identifiers(code, known)
    
    if len(unknown):
        raise AssertionError(('Encountered unknown identifiers, this should '
                             'not happen at this stage. Unkown identifiers: %s'
                             % unknown))

    
    code = re.split(r'[;\n]', code)
    for line in code:
        line = line.strip()
        if not len(line):
            continue  # skip empty lines
        
        varname, op, expr, comment = parse_statement(line)
        if op in ('+=', '-=', '*=', '/=', '%='):
            # Replace statements such as "w *=2" by "w = w * 2"
            expr = '{var} {op_first} {expr}'.format(var=varname,
                                                    op_first=op[0],
                                                    expr=expr)
            op = '='
        elif op == '=':
            pass
        else:
            raise AssertionError('Unknown operator "%s"' % op) 

        expr_unit = parse_expression_unit(expr, variables)

        if varname in variables:
            expected_unit = variables[varname].unit
            fail_for_dimension_mismatch(expr_unit, expected_unit,
                                        ('The right-hand-side of code '
                                         'statement ""%s" does not have the '
                                         'expected unit %r') % (line,
                                                               expected_unit))
        elif varname in newly_defined:
            # note the unit for later
            variables[varname] = Variable(name=varname,
                                          unit=get_unit(expr_unit),
                                          scalar=False)
        else:
            raise AssertionError(('Variable "%s" is neither in the variables '
                                  'dictionary nor in the list of undefined '
                                  'variables.' % varname))
Пример #38
0
def dimensions_and_type_from_string(unit_string):
    '''
    Returns the physical dimensions that results from evaluating a string like
    "siemens / metre ** 2", allowing for the special string "1" to signify
    dimensionless units, the string "boolean" for a boolean and "integer" for
    an integer variable.

    Parameters
    ----------
    unit_string : str
        The string that should evaluate to a unit

    Returns
    -------
    d, type : (`Dimension`, {FLOAT, INTEGER or BOOL})
        The resulting physical dimensions and the type of the variable.

    Raises
    ------
    ValueError
        If the string cannot be evaluated to a unit.
    '''
    # Lazy import to avoid circular dependency
    from brian2.core.namespace import DEFAULT_UNITS
    global _base_units_with_alternatives
    global _base_units
    if _base_units_with_alternatives is None:
        base_units_for_dims = {}
        for unit_name, unit in reversed(DEFAULT_UNITS.items()):
            if float(unit) == 1.0 and repr(unit)[-1] not in ['2', '3']:
                if unit.dim in base_units_for_dims:
                    if unit_name not in base_units_for_dims[unit.dim]:
                        base_units_for_dims[unit.dim].append(unit_name)
                else:
                    base_units_for_dims[unit.dim] = [repr(unit)]
                    if unit_name != repr(unit):
                        base_units_for_dims[unit.dim].append(unit_name)
        alternatives = sorted(
            [tuple(values) for values in base_units_for_dims.itervalues()])
        _base_units = dict([(v, DEFAULT_UNITS[v]) for values in alternatives
                            for v in values])
        # Create a string that lists all allowed base units
        alternative_strings = []
        for units in alternatives:
            string = units[0]
            if len(units) > 1:
                string += ' ({other_units})'.format(
                    other_units=', '.join(units[1:]))
            alternative_strings.append(string)
        _base_units_with_alternatives = ', '.join(alternative_strings)

    unit_string = unit_string.strip()

    # Special case: dimensionless unit
    if unit_string == '1':
        return DIMENSIONLESS, FLOAT

    # Another special case: boolean variable
    if unit_string == 'boolean':
        return DIMENSIONLESS, BOOLEAN
    if unit_string == 'bool':
        raise TypeError("Use 'boolean' not 'bool' as the unit for a boolean "
                        "variable.")

    # Yet another special case: integer variable
    if unit_string == 'integer':
        return DIMENSIONLESS, INTEGER

    # Check first whether the expression only refers to base units
    identifiers = get_identifiers(unit_string)
    for identifier in identifiers:
        if identifier not in _base_units:
            if identifier in DEFAULT_UNITS:
                # A known unit, but not a base unit
                base_unit = get_unit(DEFAULT_UNITS[identifier].dim)
                if not repr(base_unit) in _base_units:
                    # Make sure that we don't suggest a unit that is not allowed
                    # (should not happen, normally)
                    base_unit = Unit(1, dim=base_unit.dim)
                raise ValueError(
                    ('Unit specification refers to '
                     '"{identifier}", but this is not a base '
                     'unit. Use "{base_unit}" '
                     'instead.').format(identifier=identifier,
                                        base_unit=repr(base_unit)))
            else:
                # Not a known unit
                raise ValueError(
                    ('Unit specification refers to '
                     '"{identifier}", but this is not a base '
                     'unit. The following base units are '
                     'allowed: '
                     '{allowed_units}.').format(
                         identifier=identifier,
                         allowed_units=_base_units_with_alternatives))
    try:
        evaluated_unit = eval(unit_string, _base_units)
    except Exception as ex:
        raise ValueError(('Could not interpret "%s" as a unit specification: '
                          '%s') % (unit_string, ex))

    # Check whether the result is a unit
    if not isinstance(evaluated_unit, Unit):
        if isinstance(evaluated_unit, Quantity):
            raise ValueError(
                ('"%s" does not evaluate to a unit but to a '
                 'quantity -- make sure to only use units, e.g. '
                 '"siemens/metre**2" and not "1 * siemens/metre**2"') %
                unit_string)
        else:
            raise ValueError(
                ('"%s" does not evaluate to a unit, the result '
                 'has type %s instead.' % (unit_string, type(evaluated_unit))))

    # No error has been raised, all good
    return evaluated_unit.dim, FLOAT
Пример #39
0
    def _init_1d(self):
        dimensions = self.dim
        unit = get_unit(dimensions)
        values = self.values
        dt = self.dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(t=second, result=unit)
        def timed_array_func(t):
            # We round according to the current defaultclock.dt
            K = _find_K(float(defaultclock.dt), dt)
            epsilon = dt / K
            i = np.clip(np.int_(np.round(np.asarray(t / epsilon)) / K), 0,
                        len(values) - 1)
            return Quantity(values[i], dim=dimensions)

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_

            K = _find_K(group_dt, dt)
            n_values = len(values)
            epsilon = dt / K

            def unitless_timed_array_func(t):
                timestep = np.clip(np.int_(np.round(t / epsilon) / K), 0,
                                   n_values - 1)
                return values[timestep]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation(
            'numpy', create_numpy_implementation)

        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            support_code = '''
            static inline double %NAME%(const double t)
            {
                const double epsilon = %DT% / %K%;
                int i = (int)((t/epsilon + 0.5)/%K%);
                if(i < 0)
                   i = 0;
                if(i >= %NUM_VALUES%)
                    i = %NUM_VALUES%-1;
                return _namespace%NAME%_values[i];
            }
            '''.replace('%NAME%',
                        self.name).replace('%DT%', '%.18f' % dt).replace(
                            '%K%', str(K)).replace('%NUM_VALUES%',
                                                   str(len(self.values)))
            cpp_code = {'support_code': support_code}

            return cpp_code

        def create_cpp_namespace(owner):
            return {'%s_values' % self.name: self.values}

        self.implementations.add_dynamic_implementation(
            'cpp',
            code=create_cpp_implementation,
            namespace=create_cpp_namespace,
            name=self.name)

        def create_cython_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            code = '''
            cdef double %NAME%(const double t):
                global _namespace%NAME%_values
                cdef double epsilon = %DT% / %K%
                cdef int i = (int)((t/epsilon + 0.5)/%K%)
                if i < 0:
                   i = 0
                if i >= %NUM_VALUES%:
                    i = %NUM_VALUES% - 1
                return _namespace%NAME%_values[i]
            '''.replace('%NAME%',
                        self.name).replace('%DT%', '%.18f' % dt).replace(
                            '%K%', str(K)).replace('%NUM_VALUES%',
                                                   str(len(self.values)))

            return code

        def create_cython_namespace(owner):
            return {'%s_values' % self.name: self.values}

        self.implementations.add_dynamic_implementation(
            'cython',
            code=create_cython_implementation,
            namespace=create_cython_namespace,
            name=self.name)
Пример #40
0
    def _resolve_external(self, identifier, user_identifier=True, run_namespace=None, level=0):
        """
        Resolve an external identifier in the context of a `Group`. If the `Group`
        declares an explicit namespace, this namespace is used in addition to the
        standard namespace for units and functions. Additionally, the namespace in
        the `run_namespace` argument (i.e. the namespace provided to `Network.run`)
        or, if this argument is unspecified, the implicit namespace of
        surrounding variables in the stack frame where the original call was made
        is used (to determine this stack frame, the `level` argument has to be set
        correctly).

        Parameters
        ----------
        identifier : str
            The name to resolve.
        user_identifier : bool, optional
            Whether this is an identifier that was used by the user (and not
            something automatically generated that the user might not even
            know about). Will be used to determine whether to display a
            warning in the case of namespace clashes. Defaults to ``True``.
        group : `Group`
            The group that potentially defines an explicit namespace for looking up
            external names.
        run_namespace : dict, optional
            A namespace (mapping from strings to objects), as provided as an
            argument to the `Network.run` function.
        level : int, optional
            How far to go up in the stack to find the calling frame.
        """
        # We save tuples of (namespace description, referred object) to
        # give meaningful warnings in case of duplicate definitions
        matches = []

        namespaces = OrderedDict()
        # Default namespaces (units and functions)
        namespaces["constants"] = DEFAULT_CONSTANTS
        namespaces["units"] = DEFAULT_UNITS
        namespaces["functions"] = DEFAULT_FUNCTIONS
        if getattr(self, "namespace", None) is not None:
            namespaces["group-specific"] = self.namespace

        # explicit or implicit run namespace
        if run_namespace is not None:
            namespaces["run"] = run_namespace
        else:
            namespaces["implicit"] = get_local_namespace(level + 1)

        for description, namespace in namespaces.iteritems():
            if identifier in namespace:
                matches.append((description, namespace[identifier]))

        if len(matches) == 0:
            # No match at all
            raise KeyError(('The identifier "%s" could not be resolved.') % (identifier))
        elif len(matches) > 1:
            # Possibly, all matches refer to the same object
            first_obj = matches[0][1]
            found_mismatch = False
            for m in matches:
                if _same_value(m[1], first_obj):
                    continue
                if _same_function(m[1], first_obj):
                    continue
                try:
                    proxy = weakref.proxy(first_obj)
                    if m[1] is proxy:
                        continue
                except TypeError:
                    pass

                # Found a mismatch
                found_mismatch = True
                break

            if found_mismatch and user_identifier:
                _conflict_warning(
                    (
                        'The name "%s" refers to different objects '
                        "in different namespaces used for resolving "
                        'names in the context of group "%s". '
                        "Will use the object from the %s namespace "
                        "with the value %r"
                    )
                    % (identifier, getattr(self, "name", "<unknown>"), matches[0][0], first_obj),
                    matches[1:],
                )

        # use the first match (according to resolution order)
        resolved = matches[0][1]

        # Replace pure Python functions by a Functions object
        if callable(resolved) and not isinstance(resolved, Function):
            resolved = Function(resolved, stateless=False)

        if not isinstance(resolved, (Function, Variable)):
            # Wrap the value in a Constant object
            unit = get_unit(resolved)
            value = np.asarray(resolved)
            if value.shape != ():
                raise KeyError("Variable %s was found in the namespace, but is" " not a scalar value" % identifier)
            resolved = Constant(identifier, unit=unit, value=value)

        return resolved
Пример #41
0
    def _init_2d(self):
        dimensions = self.dim
        unit = get_unit(dimensions)
        values = self.values
        dt = self.dt

        # Python implementation (with units), used when calling the TimedArray
        # directly, outside of a simulation
        @check_units(i=1, t=second, result=unit)
        def timed_array_func(t, i):
            # We round according to the current defaultclock.dt
            K = _find_K(float(defaultclock.dt), dt)
            epsilon = dt / K
            time_step = np.clip(np.int_(np.round(np.asarray(t / epsilon)) / K),
                                0,
                                len(values) - 1)
            return Quantity(values[time_step, i], dim=dimensions)

        Function.__init__(self, pyfunc=timed_array_func)

        # we use dynamic implementations because we want to do upsampling
        # in a way that avoids rounding problems with the group's dt
        def create_numpy_implementation(owner):
            group_dt = owner.clock.dt_

            K = _find_K(group_dt, dt)
            n_values = len(values)
            epsilon = dt / K

            def unitless_timed_array_func(t, i):
                timestep = np.clip(np.int_(np.round(t / epsilon) / K), 0,
                                   n_values - 1)
                return values[timestep, i]

            unitless_timed_array_func._arg_units = [second]
            unitless_timed_array_func._return_unit = unit

            return unitless_timed_array_func

        self.implementations.add_dynamic_implementation(
            'numpy', create_numpy_implementation)

        def create_cpp_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            support_code = '''
            static inline double %NAME%(const double t, const int i)
            {
                const double epsilon = %DT% / %K%;
                if (i < 0 || i >= %COLS%)
                    return NAN;
                int timestep = (int)((t/epsilon + 0.5)/%K%);
                if(timestep < 0)
                   timestep = 0;
                else if(timestep >= %ROWS%)
                    timestep = %ROWS%-1;
                return _namespace%NAME%_values[timestep*%COLS% + i];
            }
            '''
            support_code = replace(
                support_code, {
                    '%NAME%': self.name,
                    '%DT%': '%.18f' % dt,
                    '%K%': str(K),
                    '%COLS%': str(self.values.shape[1]),
                    '%ROWS%': str(self.values.shape[0])
                })
            cpp_code = {'support_code': support_code}

            return cpp_code

        def create_cpp_namespace(owner):
            return {
                '%s_values' % self.name:
                self.values.astype(np.double, order='C', copy=False).ravel()
            }

        self.implementations.add_dynamic_implementation(
            'cpp',
            code=create_cpp_implementation,
            namespace=create_cpp_namespace,
            name=self.name)

        def create_cython_implementation(owner):
            group_dt = owner.clock.dt_
            K = _find_K(group_dt, dt)
            code = '''
            cdef double %NAME%(const double t, const int i):
                global _namespace%NAME%_values
                cdef double epsilon = %DT% / %K%
                if i < 0 or i >= %COLS%:
                    return _numpy.nan
                cdef int timestep = (int)((t/epsilon + 0.5)/%K%)
                if timestep < 0:
                   timestep = 0
                elif timestep >= %ROWS%:
                    timestep = %ROWS%-1
                return _namespace%NAME%_values[timestep*%COLS% + i]
            '''
            code = replace(
                code, {
                    '%NAME%': self.name,
                    '%DT%': '%.18f' % dt,
                    '%K%': str(K),
                    '%COLS%': str(self.values.shape[1]),
                    '%ROWS%': str(self.values.shape[0])
                })

            return code

        def create_cython_namespace(owner):
            return {
                '%s_values' % self.name:
                self.values.astype(np.double, order='C', copy=False).ravel()
            }

        self.implementations.add_dynamic_implementation(
            'cython',
            code=create_cython_implementation,
            namespace=create_cython_namespace,
            name=self.name)