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 test_parse_expression_unit(): Var = namedtuple('Var', ['dim', 'dtype']) variables = {'a': Var(dim=(volt*amp).dim, dtype=np.float64), 'b': Var(dim=volt.dim, dtype=np.float64), 'c': Var(dim=amp.dim, dtype=np.float64)} group = SimpleGroup(namespace={}, variables=variables) EE = [ (volt*amp, 'a+b*c'), (DimensionMismatchError, 'a+b'), (DimensionMismatchError, 'a<b'), (1, 'a<b*c'), (1, 'a or b'), (1, 'not (a >= b*c)'), (DimensionMismatchError, 'a or b<c'), (1, 'a/(b*c)<1'), (1, 'a/(a-a)'), (1, 'a<mV*mA'), (volt**2, 'b**2'), (volt*amp, 'a%(b*c)'), (volt, '-b'), (1, '(a/a)**(a/a)'), # Expressions involving functions (volt, 'rand()*b'), (volt**0.5, 'sqrt(b)'), (volt, 'ceil(b)'), (volt, 'sqrt(randn()*b**2)'), (1, 'sin(b/b)'), (DimensionMismatchError, 'sin(b)'), (DimensionMismatchError, 'sqrt(b) + b'), (SyntaxError, 'sqrt(b, b)'), (SyntaxError, 'sqrt()'), (SyntaxError, 'int(1, 2)'), ] for expect, expr in EE: all_variables = {} for name in get_identifiers(expr): if name in variables: all_variables[name] = variables[name] else: all_variables[name] = group._resolve(name, {}) if isinstance(expect, type) and issubclass(expect, Exception): assert_raises(expect, parse_expression_dimensions, expr, all_variables) else: u = parse_expression_dimensions(expr, all_variables) assert have_same_dimensions(u, expect) wrong_expressions = ['a**b', 'a << b', 'int(True' # typo ] for expr in wrong_expressions: all_variables = {} for name in get_identifiers(expr): if name in variables: all_variables[name] = variables[name] else: all_variables[name] = group._resolve(name, {}) assert_raises(SyntaxError, parse_expression_dimensions, expr, all_variables)
def test_parse_expression_unit(expect, expr): Var = namedtuple('Var', ['dim', 'dtype']) variables = { 'a': Var(dim=(volt * amp).dim, dtype=np.float64), 'b': Var(dim=volt.dim, dtype=np.float64), 'c': Var(dim=amp.dim, dtype=np.float64) } group = SimpleGroup(namespace={}, variables=variables) all_variables = {} for name in get_identifiers(expr): if name in variables: all_variables[name] = variables[name] else: all_variables[name] = group._resolve(name, {}) if isinstance(expect, type) and issubclass(expect, Exception): with pytest.raises(expect): parse_expression_dimensions(expr, all_variables) else: u = parse_expression_dimensions(expr, all_variables) assert have_same_dimensions(u, expect)
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)
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)
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
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
def test_parse_expression_unit(): Var = namedtuple('Var', ['dim', 'dtype']) variables = { 'a': Var(dim=(volt * amp).dim, dtype=np.float64), 'b': Var(dim=volt.dim, dtype=np.float64), 'c': Var(dim=amp.dim, dtype=np.float64) } group = SimpleGroup(namespace={}, variables=variables) EE = [ (volt * amp, 'a+b*c'), (DimensionMismatchError, 'a+b'), (DimensionMismatchError, 'a<b'), (1, 'a<b*c'), (1, 'a or b'), (1, 'not (a >= b*c)'), (DimensionMismatchError, 'a or b<c'), (1, 'a/(b*c)<1'), (1, 'a/(a-a)'), (1, 'a<mV*mA'), (volt**2, 'b**2'), (volt * amp, 'a%(b*c)'), (volt, '-b'), (1, '(a/a)**(a/a)'), # Expressions involving functions (volt, 'rand()*b'), (volt**0.5, 'sqrt(b)'), (volt, 'ceil(b)'), (volt, 'sqrt(randn()*b**2)'), (1, 'sin(b/b)'), (DimensionMismatchError, 'sin(b)'), (DimensionMismatchError, 'sqrt(b) + b'), (SyntaxError, 'sqrt(b, b)'), (SyntaxError, 'sqrt()'), (SyntaxError, 'int(1, 2)'), ] for expect, expr in EE: all_variables = {} for name in get_identifiers(expr): if name in variables: all_variables[name] = variables[name] else: all_variables[name] = group._resolve(name, {}) if isinstance(expect, type) and issubclass(expect, Exception): with pytest.raises(expect): parse_expression_dimensions(expr, all_variables) else: u = parse_expression_dimensions(expr, all_variables) assert have_same_dimensions(u, expect) wrong_expressions = [ 'a**b', 'a << b', 'int(True' # typo ] for expr in wrong_expressions: all_variables = {} for name in get_identifiers(expr): if name in variables: all_variables[name] = variables[name] else: all_variables[name] = group._resolve(name, {}) with pytest.raises(SyntaxError): parse_expression_dimensions(expr, all_variables)
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_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 %r') % (line, 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 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 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 %r') % (line, 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))