File: unitcheck.py

package info (click to toggle)
brian 2.9.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,872 kB
  • sloc: python: 51,820; cpp: 2,033; makefile: 108; sh: 72
file content (120 lines) | stat: -rw-r--r-- 3,866 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
"""
Utility functions for handling the units in `Equations`.
"""

import re

from brian2.core.variables import Variable
from brian2.parsing.expressions import parse_expression_dimensions
from brian2.parsing.statements import parse_statement
from brian2.units.fundamentalunits import (
    fail_for_dimension_mismatch,
    get_dimensions,
    get_unit,
)

__all__ = ["check_dimensions", "check_units_statements"]


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)
    expected = repr(get_unit(dimensions))
    err_msg = (
        f"Expression '{expression.strip()}' does not have the expected unit {expected}"
    )
    fail_for_dimension_mismatch(expr_dims, dimensions, err_msg)


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 "
            f"happen at this stage. Unknown identifiers: {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 = f"{varname} {op[0]} {expr}"
        elif op == "=":
            pass
        else:
            raise AssertionError(f'Unknown operator "{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(
                f"Variable '{varname}' is neither in the variables "
                "dictionary nor in the list of undefined "
                "variables."
            )