chronaxis.core.expression_reference
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""用独立标量计算求值表达式,并保留未知结果的原因。23evaluate_reference接收当前CompiledExpression、真实普通或消息绑定和context。4context按依赖别名提供ScalarValue,输出Evaluation,分别保存计算结果和5全部被求值依赖的质量原因。evaluate_memory_reference还可从已有内存行6提取信号,经参考转换和按时间保持后,在给定纳秒坐标逐点求值。78布尔表达式使用TRUE、FALSE、UNKNOWN(未知)三种状态。例如FALSE and9UNKNOWN可确定为FALSE,TRUE or UNKNOWN可确定为TRUE;被遮蔽的异常10仍保留在dependency_quality中。isclose按abs(a-b) <= atol + rtol*abs(b)11计算,两种容差均由表达式显式提供,非有限中间值不能变成正常判断。1213只依赖标准库和参考数值、对齐模块,不执行任意Python代码,也不读取14旧日志或额外解码。当前求值缓存不跨时间点保留,不调用NumPy数值内核。15"""1617# 冻结结果保证逻辑质量与诊断质量清楚分离。18from dataclasses import dataclass19# context只接受真实映射,不反射对象属性。20from collections.abc import Mapping21# 用静态编译模型和当前真实绑定校验身份。22from .enum_codes import memory_histories, aligned_scalar23from .expression import CompiledExpression24# 已校验输入绑定不能用用户字符串冒充。25from .binding import SignalBindings26# 解码绑定由实际历史核对工厂生成;本入口不创建伪造内存来源。27from .decoded_binding import DecodedBindings28# 消息仓使用独立绑定;只接受其显式context,不引入时间保持或原历史。29from .message_binding import MessageBindings30# 独立参考数值内核,内部比较映射只有标准运算。31from .numeric import (COMPARE_FUNCTIONS, ScalarValue, binary_reference, memory_series_reference,32 to_float_reference, unary_reference)33# 单位转换使用独立标量仿射核,导入不在每个样本上重复执行。34from .affine import affine_reference35from .expression_math_v4 import MATH_KERNELS36from .expression_math_reference import math_reference37from .expression_integer_v4 import INTEGER_KERNELS38from .expression_integer_reference import integer_reference39from .lookup_reference import lookup_reference40from .enum_codes import enum_code_reference41# 独立参考保持为memory查询生成输入。42from .state import align_reference43# 三值使用BOOL的valid,不将UNKNOWN默认转False。44from .types import QualityFlag as Q, SignalDType as D454647@dataclass(frozen=True)48class Evaluation:49 """分别保存可用的计算结果,以及本次求值的全部质量诊断。5051 value是ScalarValue,其quality表示结果本身不可用的原因。52 dependency_quality另存依赖和计算的原因并集;即使已知FALSE或TRUE使53 布尔结果可以确定,原来的异常也仍可追溯,不能强塞回正常结果的quality。54 实例不可重新赋值,不保存下一个时间点的信号状态。55 """5657 value: ScalarValue # 实际逻辑输出。58 dependency_quality: Q # 原依赖/本次计算的全部诊断并集。596061def require_context(compiled, context, bindings):62 """核对表达式的输入身份和调用者提供的依赖名称。6364 参数:65 compiled: 可执行CompiledExpression;context为真实映射对象。66 bindings: 当前SignalBindings、DecodedBindings或MessageBindings,须与编译身份一致。67 返回:68 None。context的键必须恰好等于dependencies,纯常量表达式需空映射。69 异常:70 TypeError: 计划、映射或绑定模型类型不合法。71 ValueError: 绑定身份不同,或依赖键缺失、多余。72 边界:73 只校验结构,不读来源、不求值;各个值的类型由后续执行入口检查。74 """75 # 执行不能仅凭表达式文本或语义名复用旧绑定。76 if (not isinstance(compiled, CompiledExpression) or not isinstance(bindings, (SignalBindings, DecodedBindings, MessageBindings))77 or not isinstance(compiled.bindings, (SignalBindings, DecodedBindings, MessageBindings)) or not isinstance(context, Mapping)):78 # 不把接口配置错吞为UNKNOWN。79 raise TypeError('执行需要CompiledExpression/context及真实普通或消息绑定')80 # 相同结构名称不证明输入数据/转换/TTL相同。81 if (compiled.bindings.identity != bindings.identity or82 getattr(compiled.bindings,'extensions',None) is not getattr(bindings,'extensions',None)):83 # 必须重新编译真实当前绑定。84 raise ValueError('可执行表达式与当前输入绑定身份不同')85 # 普通解码核实际投影,消息绑定核单来源依赖;两者均拒绝静态声明冒充运行域。86 if isinstance(bindings, (DecodedBindings, MessageBindings)):87 bindings.require_synchronized(compiled.dependencies)88 # 禁止缺列/错误列映射/隐式全局取值。89 if set(context) != set(compiled.dependencies):90 # 常量表达式context为空,不猜未提供信号。91 raise ValueError('context必须精确提供表达式dependencies')929394def _logic(operator, values):95 """独立标量三值and/or/not,不以UNKNOWN当False。9697 参数: operator固定逻辑名,values为BOOL ScalarValue tuple。98 返回: ScalarValue;正常决断quality NONE,未知保留原因并集。99 异常: 调用侧静态类型保证,不接数值truthiness。100 """101 # not对未知仍保持原原因。102 if operator == 'not':103 # known TRUE/FALSE取反,不使用Truth.__bool__。104 return ScalarValue(not values[0].value, D.BOOL) if values[0].valid else ScalarValue(None, D.BOOL, False, values[0].quality)105 # 所有原因仍在外层Evaluation诊断保存。106 quality = Q.NONE107 # 标量参考逐逻辑值判断,允许独立状态循环。108 for value in values:109 # 同一操作原始原因不丢失。110 quality |= value.quality111 # 对称规则:任一已知False决定and,已知True决定or。112 deciding = False if operator == 'and' else True113 # invalid数值占位不参与判断,不能False-like导致决断。114 if any(value.valid and value.value is deciding for value in values):115 # 结果已知,原异常仍由外层依赖诊断保存。116 return ScalarValue(deciding, D.BOOL)117 # 没有决断值且有unknown,结果仍未知。118 if quality != Q.NONE:119 # 保留真实未知原因而非一律MISSING。120 return ScalarValue(None, D.BOOL, False, quality)121 # 所有已知:and全部TRUE,or全部FALSE。122 return ScalarValue(not deciding, D.BOOL)123124125def _evaluate(node, context, cache):126 """用独立参考数值解释单个冻结节点及局部公共子树。127128 参数: node静态树;context当前typed值;cache仅本次point局部缓存。129 返回: Evaluation,完整诊断与逻辑有效性分开。130 异常: 静态模型/类型错误直接传播,不eval或吞配置异常。131 """132 # 公共表达式复用只限同一点/当前真实输入。133 if node in cache:134 # 不跨point继承旧信号值。135 return cache[node]136 # 常量字面量已由编译器完成上下文dtype/域检查。137 if node.operator == 'const':138 # dtype/labels构造不能任意隐式转值。139 value = ScalarValue(node.value, node.dtype, labels=node.labels)140 # literal没有原信号异常。141 result = Evaluation(value, Q.NONE)142 # 只使用当前显式context的简单名称。143 elif node.operator == 'name':144 # 输入包装身份与dtype由外层入口校验。145 value = context[node.name]146 # 上游全部质量同时作为诊断保留。147 result = Evaluation(value, value.quality)148 elif node.operator == 'where':149 # 先求条件;UNKNOWN没有活动数值分支,它的原因就是当前输出无效的原因。150 condition = _evaluate(node.args[0], context, cache)151 if not condition.value.valid:152 value = ScalarValue(None, node.dtype, False, condition.value.quality)153 result = Evaluation(value, condition.dependency_quality)154 else:155 # 已知条件只访问所选子树,分支已在编译期统一输出类型和单位。156 branch = _evaluate(node.args[1 if condition.value.value else 2], context, cache)157 result = Evaluation(branch.value, condition.dependency_quality | branch.dependency_quality)158 elif node.operator == 'enum_code':159 # 原码有独立质量域:代码9可以有效,即使其标签没有登记。160 operand = _evaluate(node.args[0], context, cache).value161 value = enum_code_reference(node, operand)162 result = Evaluation(value, value.quality)163 else:164 # 子树全部求值以保留原依赖异常,即使逻辑已可决断。165 children = tuple(_evaluate(arg, context, cache) for arg in node.args)166 # 当前节点可用逻辑结果,不是原诊断值。167 values = tuple(child.value for child in children)168 # 诊断汇总包括已被已知逻辑结果遮蔽的异常。169 diagnostic = Q.NONE170 # 此为独立标量参考循环,不共享数组归约内核。171 for child in children:172 # 原因并集不会被normal BOOL清除。173 diagnostic |= child.dependency_quality174 # 三值逻辑独立于数值质量门槛。175 if node.operator in ('and', 'or', 'not'):176 # FALSE and UNKNOWN与TRUE or UNKNOWN都可确定结果,diagnostic仍保存原原因。177 value = _logic(node.operator, values)178 # 数值单目用已验收的独立参考函数。179 elif node.operator in ('abs', 'neg'):180 # int64 min依然严格OVERFLOW。181 value = unary_reference(node.operator, values[0])182 # float信号上下文中的整数literal显式精度检查。183 elif node.operator == 'to_float':184 # 不能通过编译隐藏舍入整数。185 value = to_float_reference(values[0])186 elif node.operator == 'unit_convert':187 # 编译期已确定系数,运行时只转换当前值并保留精度/缺失原因。188 value = affine_reference(values[0], *node.value, node.dtype)189 elif node.operator == 'lookup':190 value = lookup_reference(node.value, values[0])191 elif node.operator == 'host_function':192 from ..extension_runtime import point_call193 value = point_call(node.host_function,'reference',values)194 elif node.operator in MATH_KERNELS:195 # 逐点极值、限幅与开方沿统一参数dtype执行,原诊断仍随结果上报。196 value = math_reference(node.operator, values)197 elif node.operator in INTEGER_KERNELS:198 # 原码或普通整数都按同一64位协议计算,不使用标签索引。199 value = integer_reference(node, values)200 # 精确整数比较literal可以超过64位边界。201 elif node.operator.startswith('int_literal_'):202 # Python整数参考绝不先转float。203 value = ScalarValue(COMPARE_FUNCTIONS[node.operator[12:]](values[0].value, node.value), D.BOOL) if values[0].valid else ScalarValue(None, D.BOOL, False, values[0].quality)204 # isclose严格按明确非对称判据,拒绝默认容差。205 elif node.operator == 'isclose':206 # 四个数值都经各自严格float精度检查。207 a, b, atol, rtol = (to_float_reference(item) for item in values)208 # 判据abs(a-b),非有限中间值仍显式UNKNOWN。209 distance = unary_reference('abs', binary_reference('sub', a, b))210 # 右边rtol*abs(b)的参考IEEE路径。211 relative = binary_reference('mul', rtol, unary_reference('abs', b))212 # atol与relative依旧用明确数值质量规则。213 tolerance = binary_reference('add', atol, relative)214 # 不把非有限容差当无穷阈值正常通过。215 value = binary_reference('le', distance, tolerance)216 # 当前中间数值原因也纳入原依赖诊断。217 diagnostic |= a.quality | b.quality | atol.quality | rtol.quality | distance.quality | tolerance.quality218 else:219 # 编译树只包含注册二元数值/比较,不执行自由函数。220 value = binary_reference(node.operator, values[0], values[1], allow_float_literal=node.allow_float_literal)221 # 结果原因是诊断子集,known决断不丢所有依赖质量。222 result = Evaluation(value, diagnostic | value.quality)223 # 仅当前point的公共子树值可以复用。224 cache[node] = result225 # 冻结结果不持有跨调用可变状态。226 return result227228229def evaluate_reference(compiled: CompiledExpression, context: Mapping, bindings: SignalBindings | DecodedBindings | MessageBindings) -> Evaluation:230 """检查当前标量输入后,对表达式进行一次独立参考求值。231232 参数:233 compiled: 当前CompiledExpression;bindings为对应的普通或MessageBindings运行绑定。234 context: 以精确依赖别名为键的ScalarValue映射,声明类型与枚举域须匹配。235 返回:236 Evaluation,区分正常或未知的计算结果,并保留所有依赖的质量诊断。237 异常:238 TypeError、ValueError: 依赖键、模型、类型、枚举域或绑定身份不合法。239 数值计算失败写入质量原因,不把配置错误当作缺失数据。240 边界:241 每次调用使用新缓存,只复用本次求值中的公共子树,不继承上一次的值。242 """243 # 静态执行身份和列映射须执行前成立。244 require_context(compiled, context, bindings)245 # 校验真实当前dtype和枚举域,不能只靠上下文名字。246 for name, value in context.items():247 # 冻结定义来自当前真实绑定。248 definition = bindings.resolve(name)249 # 只有typed标量可执行,不把裸float当信号。250 if not isinstance(value, ScalarValue) or value.dtype is not definition.dtype:251 # 输入类型冲突不是样本缺失。252 raise TypeError('context值与当前signal dtype不一致')253 # enum域需与真实映射一致。254 labels = tuple(dict.fromkeys(item for _, item in definition.choices)) if definition.dtype is D.ENUM else ()255 # 不让不同任务同名code域串入。256 if value.labels != labels:257 # 命名一致不证明语义域一致。258 raise ValueError('context enum域与绑定不同')259 # 每个时间点都创建新缓存,避免沿用上个时间点的信号值。260 return _evaluate(compiled.root, context, {})261262263def evaluate_memory_reference(compiled: CompiledExpression, times: tuple[int, ...], *, point: bool = False) -> tuple[Evaluation, ...]:264 """从已有内存行取得信号,在指定纳秒坐标逐点计算表达式。265266 参数:267 compiled: 关联真实内存输入的CompiledExpression。268 times: 非递减的整数纳秒元组;point控制真实末点查询视图。269 返回:270 与times等长的Evaluation元组,保存正常值、未知状态和依赖诊断。271 异常:272 TypeError: 非memory绑定,包括DecodedBindings;其他时间/来源/配置异常原样传播。273 边界:274 只提取实际依赖,先归一化再按有效期保持;纯常量也检查时间轴。275 为完整内存历史建立新对象,不读取文件、额外解码或修改原行。276 """277 # 纯常量也须明确拒绝解码绑定,不能借空依赖意外进入memory快捷路径。278 if not isinstance(compiled, CompiledExpression) or not isinstance(compiled.bindings, SignalBindings):279 raise TypeError('memory快捷求值需要真实SignalBindings的CompiledExpression')280 # 仅材料化当前实际依赖,不改变旧链筛选或扫描次数。281 histories = memory_histories(compiled, compiled.dependencies, memory_series_reference)282 # 参考保持与数组查询实现独立。283 aligned = {name: align_reference(series, times, point=point) for name, series in histories.items()}284 # 当前memory空依赖也需严格校验时间/视图接口。285 if not histories:286 # 常量没有来源,时间轴依旧遵守typed ns协议。287 from ..timebase.conversion import require_int64_ns288 # 不能将自由序列或int truthiness解释成合法时间轴。289 if not isinstance(times, tuple) or type(point) is not bool:290 # 同参考保持输入结构要求。291 raise TypeError('times需tuple且point需bool')292 # 每个坐标仍必须合法定宽且不回退。293 for index, time in enumerate(times):294 # 精确整数时间不经float转换。295 require_int64_ns(time, 'time_ns')296 # 不排序用户输入掩盖异常。297 if index and time < times[index-1]:298 # 空依赖也不是时间回退豁免。299 raise ValueError('时间轴回退')300 # 当前点只使用已对齐的真实结果,invalid原值保留。301 results = []302 # 标量参考允许逐时间解释,不冒充数组化。303 for index in range(len(times)):304 # 当前局部context不跨时间保存数值。305 context = {name: aligned_scalar(compiled.bindings.resolve(name), states[index])306 for name, states in aligned.items()}307 # 完整绑定身份再次门槛校验后运行真实参考。308 results.append(evaluate_reference(compiled, context, compiled.bindings))309 # 返回不可变结果,不持有全局任务状态。310 return tuple(results)311