Chronaxis用户手册English
本页目录

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
Chronaxis 用户手册 · 离线阅读