Пример #1
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)
            def wrapper_function(*args):
                arg_units = list(self._function._arg_units)

                if self._function.auto_vectorise:
                    arg_units += [DIMENSIONLESS]
                if not len(args) == len(arg_units):
                    raise ValueError(
                        ('Function %s got %d arguments, '
                         'expected %d') % (self._function.pyfunc.__name__,
                                           len(args), len(arg_units)))
                new_args = []
                for arg, arg_unit in zip(args, arg_units):
                    if arg_unit == bool or arg_unit is None or isinstance(
                            arg_unit, str):
                        new_args.append(arg)
                    else:
                        new_args.append(
                            Quantity.with_dimensions(arg,
                                                     get_dimensions(arg_unit)))
                result = orig_func(*new_args)
                if isinstance(self._function._return_unit, Callable):
                    return_unit = self._function._return_unit(
                        *[get_dimensions(a) for a in args])
                else:
                    return_unit = self._function._return_unit
                if return_unit == bool:
                    if not (isinstance(result, bool)
                            or np.asarray(result).dtype == bool):
                        raise TypeError('The function %s returned '
                                        '%s, but it was expected '
                                        'to return a boolean '
                                        'value ' %
                                        (orig_func.__name__, result))
                elif (isinstance(return_unit, int) and return_unit
                      == 1) or return_unit.dim is DIMENSIONLESS:
                    fail_for_dimension_mismatch(result,
                                                return_unit,
                                                'The function %s returned '
                                                '{value}, but it was expected '
                                                'to return a dimensionless '
                                                'quantity' %
                                                orig_func.__name__,
                                                value=result)
                else:
                    fail_for_dimension_mismatch(
                        result,
                        return_unit,
                        ('The function %s returned '
                         '{value}, but it was expected '
                         'to return a quantity with '
                         'units %r') % (orig_func.__name__, return_unit),
                        value=result)
                return np.asarray(result)
 def before_run(self, run_namespace=None):
     rates_var = self.variables['rates']
     if isinstance(rates_var, Subexpression):
         # Check that the units of the expression make sense
         expr = rates_var.expr
         identifiers = get_identifiers(expr)
         variables = self.resolve_all(identifiers,
                                      run_namespace,
                                      user_identifiers=identifiers)
         unit = parse_expression_dimensions(rates_var.expr, variables)
         fail_for_dimension_mismatch(
             unit, Hz, "The expression provided for "
             "PoissonGroup's 'rates' "
             "argument, has to have units "
             "of Hz")
     super(PoissonGroup, self).before_run(run_namespace)
Пример #4
0
 def _get_refractory_code(self, run_namespace):
     ref = self.group._refractory
     if ref is False:
         # No refractoriness
         abstract_code = ''
     elif isinstance(ref, Quantity):
         fail_for_dimension_mismatch(ref,
                                     second, ('Refractory period has to '
                                              'be specified in units '
                                              'of seconds but got '
                                              '{value}'),
                                     value=ref)
         if prefs.legacy.refractory_timing:
             abstract_code = 'not_refractory = (t - lastspike) > %f\n' % ref
         else:
             abstract_code = 'not_refractory = timestep(t - lastspike, dt) >= timestep(%f, dt)\n' % ref
     else:
         identifiers = get_identifiers(ref)
         variables = self.group.resolve_all(identifiers,
                                            run_namespace,
                                            user_identifiers=identifiers)
         dims = parse_expression_dimensions(str(ref), variables)
         if dims is second.dim:
             if prefs.legacy.refractory_timing:
                 abstract_code = '(t - lastspike) > %s\n' % ref
             else:
                 abstract_code = 'not_refractory = timestep(t - lastspike, dt) >= timestep(%s, dt)\n' % ref
         elif dims is DIMENSIONLESS:
             if not is_boolean_expression(str(ref), variables):
                 raise TypeError(('Refractory expression is dimensionless '
                                  'but not a boolean value. It needs to '
                                  'either evaluate to a timespan or to a '
                                  'boolean value.'))
             # boolean condition
             # we have to be a bit careful here, we can't just use the given
             # condition as it is, because we only want to *leave*
             # refractoriness, based on the condition
             abstract_code = 'not_refractory = not_refractory or not (%s)\n' % ref
         else:
             raise TypeError(('Refractory expression has to evaluate to a '
                              'timespan or a boolean value, expression'
                              '"%s" has units %s instead') % (ref, dims))
     return abstract_code
Пример #5
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 angela2.codegen.translation import analyse_identifiers
    newly_defined, _, unknown = analyse_identifiers(code, variables)

    if len(unknown):
        raise AssertionError(
            ('Encountered unknown identifiers, this should '
             'not happen at this stage. Unknown 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_dimensions(expr, variables)

        if varname in variables:
            expected_unit = variables[varname].dim
            fail_for_dimension_mismatch(expr_unit,
                                        expected_unit,
                                        ('The right-hand-side of code '
                                         'statement "%s" does not have the '
                                         'expected unit {expected}') % line,
                                        expected=expected_unit)
        elif varname in newly_defined:
            # note the unit for later
            variables[varname] = Variable(name=varname,
                                          dimensions=get_dimensions(expr_unit),
                                          scalar=False)
        else:
            raise AssertionError(('Variable "%s" is neither in the variables '
                                  'dictionary nor in the list of undefined '
                                  'variables.' % varname))
    def __init__(self, morphology=None, model=None, threshold=None,
                 refractory=False, reset=None, events=None,
                 threshold_location=None,
                 dt=None, clock=None, order=0, Cm=0.9 * uF / cm ** 2, Ri=150 * ohm * cm,
                 name='spatialneuron*', dtype=None, namespace=None,
                 method=('exact', 'exponential_euler', 'rk2', 'heun'),
                 method_options=None):

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

        # Insert the threshold mechanism at the specified location
        if threshold_location is not None:
            if hasattr(threshold_location,
                       '_indices'):  # assuming this is a method
                threshold_location = threshold_location._indices()
                # for now, only a single compartment allowed
                if len(threshold_location) == 1:
                    threshold_location = threshold_location[0]
                else:
                    raise AttributeError(('Threshold can only be applied on a '
                                          'single location'))
            threshold = '(' + threshold + ') and (i == ' + str(threshold_location) + ')'

        # Check flags (we have point currents)
        model.check_flags({DIFFERENTIAL_EQUATION: ('point current',),
                           PARAMETER: ('constant', 'shared', 'linked', 'point current'),
                           SUBEXPRESSION: ('shared', 'point current',
                                           'constant over dt')})
        #: The original equations as specified by the user (i.e. before
        #: inserting point-currents into the membrane equation, before adding
        #: all the internally used variables and constants, etc.).
        self.user_equations = model

        # Separate subexpressions depending whether they are considered to be
        # constant over a time step or not (this would also be done by the
        # NeuronGroup initializer later, but this would give incorrect results
        # for the linearity check)
        model, constant_over_dt = extract_constant_subexpressions(model)

        # Extract membrane equation
        if 'Im' in model:
            if len(model['Im'].flags):
                raise TypeError('Cannot specify any flags for the transmembrane '
                                'current Im.')
            membrane_expr = model['Im'].expr  # the membrane equation
        else:
            raise TypeError('The transmembrane current Im must be defined')

        model_equations = []
        # Insert point currents in the membrane equation
        for eq in model.values():
            if eq.varname == 'Im':
                continue  # ignore -- handled separately
            if 'point current' in eq.flags:
                fail_for_dimension_mismatch(eq.dim, amp,
                                            "Point current " + eq.varname + " should be in amp")
                membrane_expr = Expression(
                    str(membrane_expr.code) + '+' + eq.varname + '/area')
                eq = SingleEquation(eq.type, eq.varname, eq.dim, expr=eq.expr,
                                    flags=list(set(eq.flags)-{'point current'}))
            model_equations.append(eq)

        model_equations.append(SingleEquation(SUBEXPRESSION, 'Im',
                                              dimensions=(amp/meter**2).dim,
                                              expr=membrane_expr))
        model_equations.append(SingleEquation(PARAMETER, 'v', volt.dim))
        model = Equations(model_equations)

        ###### Process model equations (Im) to extract total conductance and the remaining current
        # Expand expressions in the membrane equation
        for var, expr in model.get_substituted_expressions(include_subexpressions=True):
            if var == 'Im':
                Im_expr = expr
                break
        else:
            raise AssertionError('Model equations did not contain Im!')

        # Differentiate Im with respect to v
        Im_sympy_exp = str_to_sympy(Im_expr.code)
        v_sympy = sp.Symbol('v', real=True)
        diffed = sp.diff(Im_sympy_exp, v_sympy)

        unevaled_derivatives = diffed.atoms(sp.Derivative)
        if len(unevaled_derivatives):
            raise TypeError('Cannot take the derivative of "{Im}" with respect '
                            'to v.'.format(Im=Im_expr.code))

        gtot_str = sympy_to_str(sp.simplify(-diffed))
        I0_str = sympy_to_str(sp.simplify(Im_sympy_exp - diffed*v_sympy))

        if gtot_str == '0':
            gtot_str += '*siemens/meter**2'
        if I0_str == '0':
            I0_str += '*amp/meter**2'
        gtot_str = "gtot__private=" + gtot_str + ": siemens/meter**2"
        I0_str = "I0__private=" + I0_str + ": amp/meter**2"

        model += Equations(gtot_str + "\n" + I0_str)

        # Insert morphology (store a copy)
        self.morphology = copy.deepcopy(morphology)

        # Flatten the morphology
        self.flat_morphology = FlatMorphology(morphology)

        # Equations for morphology
        # TODO: check whether Cm and Ri are already in the equations
        #       no: should be shared instead of constant
        #       yes: should be constant (check)
        eqs_constants = Equations("""
        length : meter (constant)
        distance : meter (constant)
        area : meter**2 (constant)
        volume : meter**3
        Ic : amp/meter**2
        diameter : meter (constant)
        Cm : farad/meter**2 (constant)
        Ri : ohm*meter (constant, shared)
        r_length_1 : meter (constant)
        r_length_2 : meter (constant)
        time_constant = Cm/gtot__private : second
        space_constant = (2/pi)**(1.0/3.0) * (area/(1/r_length_1 + 1/r_length_2))**(1.0/6.0) /
                         (2*(Ri*gtot__private)**(1.0/2.0)) : meter
        """)
        if self.flat_morphology.has_coordinates:
            eqs_constants += Equations('''
            x : meter (constant)
            y : meter (constant)
            z : meter (constant)
            ''')

        NeuronGroup.__init__(self, morphology.total_compartments,
                             model=model + eqs_constants,
                             method_options=method_options,
                             threshold=threshold, refractory=refractory,
                             reset=reset, events=events,
                             method=method, dt=dt, clock=clock, order=order,
                             namespace=namespace, dtype=dtype, name=name)
        # Parameters and intermediate variables for solving the cable equations
        # Note that some of these variables could have meaningful physical
        # units (e.g. _v_star is in volt, _I0_all is in amp/meter**2 etc.) but
        # since these variables should never be used in user code, we don't
        # assign them any units
        self.variables.add_arrays(['_ab_star0', '_ab_star1', '_ab_star2',
                                   '_b_plus', '_b_minus',
                                   '_v_star', '_u_plus', '_u_minus',
                                   '_v_previous', '_c',
                                   # The following two are only necessary for
                                   # C code where we cannot deal with scalars
                                   # and arrays interchangeably:
                                   '_I0_all', '_gtot_all'],
                                  size=self.N, read_only=True)

        self.Cm = Cm
        self.Ri = Ri
        # These explict assignments will load the morphology values from disk
        # in standalone mode
        self.distance_ = self.flat_morphology.distance
        self.length_ = self.flat_morphology.length
        self.area_ = self.flat_morphology.area
        self.diameter_ = self.flat_morphology.diameter
        self.r_length_1_ = self.flat_morphology.r_length_1
        self.r_length_2_ = self.flat_morphology.r_length_2
        if self.flat_morphology.has_coordinates:
            self.x_ = self.flat_morphology.x
            self.y_ = self.flat_morphology.y
            self.z_ = self.flat_morphology.z

        # Performs numerical integration step
        self.add_attribute('diffusion_state_updater')
        self.diffusion_state_updater = SpatialStateUpdater(self, method,
                                                           clock=self.clock,
                                                           order=order)

        # Update v after the gating variables to obtain consistent Ic and Im
        self.diffusion_state_updater.order = 1

        # Creation of contained_objects that do the work
        self.contained_objects.extend([self.diffusion_state_updater])

        if len(constant_over_dt):
            self.subexpression_updater = SubexpressionUpdater(self,
                                                              constant_over_dt)
            self.contained_objects.append(self.subexpression_updater)
Пример #7
0
    def scale_array_code(self, diff_vars, method_options):
        '''
        Return code for definition of ``_GSL_scale_array`` in generated code.

        Parameters
        ----------
        diff_vars : dict
            dictionary with variable name (str) as key and differential variable
            index (int) as value
        method_options : dict
            dictionary containing integrator settings

        Returns
        -------
        code : str
            full code describing a function returning a array containing doubles
            with the absolute errors for each differential variable (according
            to their assigned index in the GSL StateUpdater)
        '''
        # get scale values per variable from method_options
        abs_per_var = method_options['absolute_error_per_variable']
        abs_default = method_options['absolute_error']

        if not isinstance(abs_default, float):
            raise TypeError(
                ("The absolute_error key in method_options should be "
                 "a float. Was type %s" % (str(type(abs_default)))))

        if abs_per_var is None:
            diff_scale = {
                var: float(abs_default)
                for var in list(diff_vars.keys())
            }
        elif isinstance(abs_per_var, dict):
            diff_scale = {}
            for var, error in list(abs_per_var.items()):
                # first do some checks on input
                if not var in diff_vars:
                    if not var in self.variables:
                        raise KeyError("absolute_error specified for variable "
                                       "that does not exist: %s" % var)
                    else:
                        raise KeyError("absolute_error specified for variable "
                                       "that is not being integrated: %s" %
                                       var)
                fail_for_dimension_mismatch(
                    error, self.variables[var],
                    ("Unit of absolute_error_per_variable "
                     "for variable %s does not match "
                     "unit of varialbe itself" % var))
                # if all these are passed we can add the value for error in base units
                diff_scale[var] = float(error)
            # set the variables that are not mentioned to default value
            for var in list(diff_vars.keys()):
                if var not in abs_per_var:
                    diff_scale[var] = float(abs_default)
        else:
            raise TypeError(
                ("The absolute_error_per_variable key in method_options "
                 "should either be None or a dictionary "
                 "containing the error for each individual state variable. "
                 "Was type %s" % (str(type(abs_per_var)))))
        # write code
        return self.initialize_array(
            '_GSL_scale_array', [diff_scale[var] for var in sorted(diff_vars)])