def update_abstract_code(self): # Update the not_refractory variable for the refractory period mechanism ref = self.group._refractory if ref is None: # No refractoriness self.abstract_code = '' elif isinstance(ref, Quantity): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %f)\n' % ref else: namespace = self.group.namespace unit = parse_expression_unit(str(ref), namespace, self.group.variables) if have_same_dimensions(unit, second): self.abstract_code = 'not_refractory = 1*((t - lastspike) > %s)\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), namespace, self.group.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 self.abstract_code = 'not_refractory = 1*(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, unit)) self.abstract_code += self.method(self.group.equations, self.group.variables)
def check_unit(expression, unit, namespace, variables): ''' Evaluates the unit for an expression in a given namespace. Parameters ---------- expression : str The expression to evaluate. namespace : dict-like The namespace of external variables. variables : dict of `Variable` objects The information about the internal variables Raises ------ KeyError In case on of the identifiers cannot be resolved. DimensionMismatchError If an unit mismatch occurs during the evaluation. See Also -------- unit_from_expression ''' expr_unit = parse_expression_unit(expression, namespace, variables) fail_for_dimension_mismatch(expr_unit, unit, ('Expression %s does not ' 'have the expected units' % expression))
def test_parse_expression_unit(): Var = namedtuple('Var', ['unit', 'dtype']) variables = {'a': Var(unit=volt*amp, dtype=np.float64), 'b': Var(unit=volt, dtype=np.float64), 'c': Var(unit=amp, 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_unit, expr, all_variables) else: u = parse_expression_unit(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_unit, expr, all_variables)
def get_refractory_code(group): ref = group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = 1*((t - lastspike) > %f)\n' % ref else: namespace = group.namespace unit = parse_expression_unit(str(ref), namespace, group.variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = 1*((t - lastspike) > %s)\n' % ref elif have_same_dimensions(unit, Unit(1)): if not is_boolean_expression(str(ref), namespace, group.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 = 1*(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, unit)) return abstract_code
def check_unit(expression, unit, variables): ''' Compares the unit for an expression to an expected unit in a given namespace. Parameters ---------- expression : str The expression to evaluate. unit : `Unit` The expected unit 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_unit = parse_expression_unit(expression, variables) fail_for_dimension_mismatch(expr_unit, unit, ('Expression %s does not ' 'have the expected units' % expression))
def check_unit(expression, unit, variables): ''' Compares the unit for an expression to an expected unit in a given namespace. Parameters ---------- expression : str The expression to evaluate. unit : `Unit` The expected unit 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_unit = parse_expression_unit(expression, variables) fail_for_dimension_mismatch(expr_unit, unit, ('Expression %s does not ' 'have the expected unit %r') % (expression.strip(), unit))
def test_parse_expression_unit(): Var = namedtuple('Var', ['unit', 'dtype']) variables = { 'a': Var(unit=volt * amp, dtype=np.float64), 'b': Var(unit=volt, dtype=np.float64), 'c': Var(unit=amp, 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') ] 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 expect is DimensionMismatchError: assert_raises(DimensionMismatchError, parse_expression_unit, expr, all_variables) else: u = parse_expression_unit(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_unit, expr, all_variables)
def _get_refractory_code(self, run_namespace, level=0): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = (t - lastspike) > %f\n' % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace, level=level+1) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = (t - lastspike) > %s\n' % ref elif have_same_dimensions(unit, Unit(1)): 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, unit)) return abstract_code
def _get_refractory_code(self, run_namespace, level=0): ref = self.group._refractory if ref is False: # No refractoriness abstract_code = '' elif isinstance(ref, Quantity): abstract_code = 'not_refractory = (t - lastspike) > %f\n' % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace, level=level + 1) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = 'not_refractory = (t - lastspike) > %s\n' % ref elif have_same_dimensions(unit, Unit(1)): 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, unit)) return abstract_code
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_unit(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, ) abstract_code = "not_refractory = (t - lastspike) > %f\n" % ref else: identifiers = get_identifiers(ref) variables = self.group.resolve_all(identifiers, identifiers, run_namespace=run_namespace) unit = parse_expression_unit(str(ref), variables) if have_same_dimensions(unit, second): abstract_code = "not_refractory = (t - lastspike) > %s\n" % ref elif have_same_dimensions(unit, Unit(1)): 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, unit) ) return abstract_code
def test_parse_expression_unit(): default_namespace = create_namespace({}) varunits = dict(default_namespace) varunits.update({'a': volt * amp, 'b': volt, 'c': amp}) 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') ] for expect, expr in EE: if expect is DimensionMismatchError: assert_raises(DimensionMismatchError, parse_expression_unit, expr, varunits, {}) else: u = parse_expression_unit(expr, varunits, {}) assert have_same_dimensions(u, expect) wrong_expressions = [ 'a**b', 'a << b', 'ot True' # typo ] for expr in wrong_expressions: assert_raises(SyntaxError, parse_expression_unit, expr, varunits, {})
def test_parse_expression_unit(): default_namespace = create_namespace({}) varunits = dict(default_namespace) varunits.update({'a': volt*amp, 'b': volt, 'c': amp}) 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') ] for expect, expr in EE: if expect is DimensionMismatchError: assert_raises(DimensionMismatchError, parse_expression_unit, expr, varunits, {}) else: u = parse_expression_unit(expr, varunits, {}) assert have_same_dimensions(u, expect) wrong_expressions = ['a**b', 'a << b', 'ot True' # typo ] for expr in wrong_expressions: assert_raises(SyntaxError, parse_expression_unit, expr, varunits, {})
def check_units_statements(code, namespace, 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 ---------- expression : str The expression to evaluate. namespace : dict-like The namespace of external variables. variables : dict of `Variable` objects The information about the internal variables Raises ------ KeyError In case on of the identifiers cannot be resolved. DimensionMismatchError If an unit mismatch occurs during the evaluation. ''' known = set(variables.keys()) | set(namespace.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)) # We want to add newly defined variables to the variables dictionary so we # make a copy now variables = dict(variables) code = re.split(r'[;\n]', code) for line in code: line = line.strip() if not len(line): continue # skip empty lines varname, op, expr = 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, namespace, variables) if varname in variables: fail_for_dimension_mismatch(variables[varname].unit, expr_unit, ('Code statement "%s" does not use ' 'correct units' % line)) elif varname in newly_defined: # note the unit for later variables[varname] = Variable(expr_unit, is_bool=False, 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. ''' # 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: fail_for_dimension_mismatch(variables[varname].unit, expr_unit, ('Code statement "%s" does not use ' 'correct units' % line)) elif varname in newly_defined: # note the unit for later variables[varname] = Variable(name=varname, unit=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. ''' # 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=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, namespace, 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 ---------- expression : str The expression to evaluate. namespace : dict-like The namespace of external variables. variables : dict of `Variable` objects The information about the internal variables Raises ------ KeyError In case on of the identifiers cannot be resolved. DimensionMismatchError If an unit mismatch occurs during the evaluation. ''' known = set(variables.keys()) | set(namespace.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)) # We want to add newly defined variables to the variables dictionary so we # make a copy now variables = dict(variables) code = re.split(r'[;\n]', code) for line in code: line = line.strip() if not len(line): continue # skip empty lines varname, op, expr = 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, namespace, variables) if varname in variables: fail_for_dimension_mismatch(variables[varname].unit, expr_unit, ('Code statement "%s" does not use ' 'correct units' % line)) elif varname in newly_defined: # note the unit for later variables[varname] = Variable(expr_unit, is_bool=False, scalar=False) else: raise AssertionError(('Variable "%s" is neither in the variables ' 'dictionary nor in the list of undefined ' 'variables.' % varname))