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