Chronaxis用户手册English
本页目录

chronaxis.core.expression_point

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""为流式状态上的一个时刻准备独立生产求值,不装箱成单元素数组。23PointProgram只持有当前表达式的只读节点、常量和查表视图。每次求值的缓存4由调用者创建和释放,不能跨点复用数值。内建操作由numeric_point独立计算;5reference和真正批量NumPy入口不变。宿主扩展保留所选NumPy回调和列接口,6只在该边界产生必要的单元素列,未选分支不调用宿主。7"""89from .expression import ExprNode10from .expression_reference import Evaluation11from .expression_integer_v4 import INTEGER_KERNELS12from .expression_math_v4 import MATH_KERNELS13from .numeric import result_dtype14from .numeric_point import (15    _COMPARE, affine_point, binary_point, failed, integer_point, logic_point,16    lookup_point, math_point, to_float_point, unary_point)17from .scalar import ScalarValue18from .types import QualityFlag as Q, SignalDType as D, _QUALITY_BY_MASK192021def _column(value):22    """仅为NumPy宿主边界装一条列。参数: typed值。返回: NumericArray。异常: 原列模型错误。"""23    import numpy as np24    from .numeric_numpy import DTYPES, NumericArray2526    physical = value.labels.index(value.value) if value.valid and value.labels else value.value if value.valid else 027    code = None if value.code is None else _column(value.code)28    return NumericArray(value.dtype, np.array([physical], dtype=DTYPES[value.dtype]),29                        np.array([value.valid], dtype=bool),30                        np.array([int(value.quality)], dtype=np.uint16), value.labels, code)313233def _scalar(column):34    """从宿主返回列恢复第一个值。参数: 已校验单行列。返回: ScalarValue。异常: 原模型错误。"""35    valid, quality = bool(column.valid[0]), Q(int(column.quality[0]))36    value = (column.labels[int(column.values[0])] if column.labels else column.values[0].item()) if valid else None37    code = None if column.code is None else _scalar(column.code)38    return ScalarValue(value, column.dtype, valid, quality, column.labels, code)394041class PointProgram:42    """持有单个已核表达式的准备数据,不拥有输入状态或跨点值缓存。4344    构造费用由DriverExpression的固定预算预付;查表只持有打包数据的只读视图。45    实例使用期间表达式和注册保持冻结,调用者在关闭时释放整个实例。46    """4748    def __init__(self, root):49        """一次准备常量、节点索引和所需查表。5051        参数: 已通过声明/绑定核对的ExprNode。返回: 新准备对象。52        异常: 内部未支持节点、结构或分配错误;不执行任何宿主回调。53        """54        if type(root) is not ExprNode:55            raise TypeError('逐点准备需要已编译表达式节点')56        self.root = root57        self._constants, self._tables, self._names, self._nodes = {}, {}, {}, {}58        pending = [root]59        known = {'const', 'name', 'where', 'enum_code', 'and', 'or', 'not',60                 'abs', 'neg', 'to_float', 'unit_convert', 'lookup', 'host_function',61                 'isclose', 'add', 'sub', 'mul', 'div', *_COMPARE,62                 *MATH_KERNELS, *INTEGER_KERNELS}63        while pending:64            node = pending.pop()65            key = id(node)66            if key in self._nodes:67                continue68            if node.operator not in known and not (69                    node.operator.startswith('int_literal_') and node.operator[12:] in _COMPARE):70                raise ValueError('未登记的生产逐点操作:' + node.operator)71            # 混合浮点字面量的许可只影响静态类型关系;在准备时核实一次,72            # 每个有效值仍须通过独立数值核的无损float转换,不能据许可放宽精度。73            if node.operator in {'add', 'sub', 'mul', 'div', *_COMPARE}:74                expected = result_dtype(node.operator, node.args[0].dtype, node.args[1].dtype,75                                        node.allow_float_literal)76                if expected is not node.dtype:77                    raise ValueError('逐点运算结果类型与已编译节点不同')78            self._nodes[key] = node79            if node.operator == 'const':80                self._constants[key] = ScalarValue(node.value, node.dtype, labels=node.labels)81            elif node.operator == 'name':82                self._names[key] = node.name83            elif node.operator == 'lookup':84                import numpy as np85                from .numeric_numpy import DTYPES86                table = node.value.packed()87                self._tables[key] = (88                    table, np.frombuffer(table.axis, dtype='<f8'),89                    np.frombuffer(table.values, dtype=DTYPES[table.dtype].newbyteorder('<')))90            pending.extend(node.args)9192    def used_names(self, cache):93        """遍历本点实际访问的别名。参数: 本实例本次缓存。返回: 名称迭代器。异常: 非法缓存键。"""94        for key in cache:95            if key in self._names:96                yield self._names[key]9798    def evaluate(self, context, cache):99        """求根节点。参数: 当前规范标量映射、本点空缓存。返回: Evaluation。异常: 计算/宿主错误。"""100        return self._evaluate(self.root, context, cache)101102    def _evaluate(self, node, context, cache):103        """解释一个活动子树,身份缓存只属于本次点。104105        参数: 本程序节点、当前typed值、局部缓存。返回: Evaluation。106        异常: 内部节点/输入合同错误及宿主异常原样传播。107        """108        key = id(node)109        if key in cache:110            return cache[key]111        if self._nodes.get(key) is not node:112            raise ValueError('逐点节点不属于当前准备对象')113        op = node.operator114        if op == 'const':115            result = Evaluation(self._constants[key], Q.NONE)116        elif op == 'name':117            value = context[node.name]118            result = Evaluation(value, value.quality)119        elif op == 'where':120            condition = self._evaluate(node.args[0], context, cache)121            if not condition.value.valid:122                result = Evaluation(failed(node.dtype, condition.value.quality, node.labels),123                                    condition.dependency_quality)124            else:125                branch = self._evaluate(node.args[1 if condition.value.value else 2], context, cache)126                result = Evaluation(branch.value, _QUALITY_BY_MASK[127                    int(condition.dependency_quality) | int(branch.dependency_quality)])128        elif op == 'enum_code':129            value = self._evaluate(node.args[0], context, cache).value130            if value.code is not None and value.code.dtype is node.dtype:131                code = value.code132            elif value.code is None and value.quality & (Q.MISSING | Q.STALE):133                code = failed(node.dtype, value.quality)134            else:135                raise ValueError('枚举缺少与声明一致的原码投影,无法从标签还原代码')136            result = Evaluation(code, code.quality)137        else:138            children = tuple(self._evaluate(arg, context, cache) for arg in node.args)139            values = tuple(child.value for child in children)140            # 子值已由数值核/宿主边界验证;先合并整数位,返回时恢复同一公开枚举。141            diagnostic = 0142            for child in children:143                diagnostic |= int(child.dependency_quality)144            if op in ('and', 'or', 'not'):145                value = logic_point(op, values)146            elif op in ('abs', 'neg'):147                value = unary_point(op, values[0])148            elif op == 'to_float':149                value = to_float_point(values[0])150            elif op == 'unit_convert':151                value = affine_point(values[0], *node.value, node.dtype)152            elif op == 'lookup':153                value = lookup_point(*self._tables[key], values[0])154            elif op == 'host_function':155                from ..extension_runtime import point_call156                value = _scalar(point_call(node.host_function, 'numpy', tuple(_column(v) for v in values)))157            elif op in MATH_KERNELS:158                value = math_point(op, values)159            elif op in INTEGER_KERNELS:160                value = integer_point(node, values)161            elif op.startswith('int_literal_'):162                first = values[0]163                value = (ScalarValue(_COMPARE[op[12:]](first.value, node.value), D.BOOL)164                         if first.valid else failed(D.BOOL, first.quality))165            elif op == 'isclose':166                a, b, atol, rtol = tuple(to_float_point(item) for item in values)167                distance = unary_point('abs', binary_point('sub', a, b, D.FLOAT64))168                relative = binary_point('mul', rtol, unary_point('abs', b), D.FLOAT64)169                tolerance = binary_point('add', atol, relative, D.FLOAT64)170                value = binary_point('le', distance, tolerance, D.BOOL)171                for item in (a, b, atol, rtol, distance, tolerance):172                    diagnostic |= int(item.quality)173            else:174                value = binary_point(op, values[0], values[1], node.dtype)175            result = Evaluation(value, _QUALITY_BY_MASK[diagnostic | int(value.quality)])176        cache[key] = result177        return result178179    def updates(self, root, current, before, updates):180        """按活动分支判断原子组是否产生更新,不用数值相等替代更新证据。181182        参数: 本程序子树、当前/组前标量、真实更新标志;before=None仅判断活动性。183        返回: bool。异常: 条件求值或内部节点错误。184        """185        now_cache, old_cache, memo = {}, {}, {}186187        def changed(node):188            """检查子树的活动更新。参数: 子树。返回: bool。异常: 原条件求值错误。"""189            key = id(node)190            if key in memo:191                return memo[key]192            if node.operator == 'name':193                result = updates[node.name]194            elif node.operator == 'where':195                condition, positive, negative = node.args196                now = self._evaluate(condition, current, now_cache).value197                condition_updated = changed(condition)198                if before is None:199                    result = condition_updated or (now.valid and changed(positive if now.value else negative))200                elif not now.valid:201                    result = condition_updated202                else:203                    result = changed(positive if now.value else negative)204                    if condition_updated and not result:205                        old = self._evaluate(condition, before, old_cache).value206                        result = not old.valid or now.value != old.value207            else:208                result = any(changed(child) for child in node.args)209            memo[key] = result210            return result211212        return bool(changed(root))213214    def active(self, root, context, flags):215        """判断被标记的依赖是否活动。参数: 根、当前值和别名标志。返回: bool。异常: 条件错误。"""216        return self.updates(root, context, None, flags)217
Chronaxis 用户手册 · 离线阅读