Chronaxis用户手册English
本页目录

chronaxis.core.expression_numpy

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""在同长度信号列上批量求值表达式,并逐位置保留质量诊断。23evaluate_numpy接收当前编译绑定和context中的NumericArray,输出4ArrayEvaluation:数值结果列与独立的uint16依赖诊断列均为形状(N,)。5布尔逻辑用掩码区分TRUE、FALSE和UNKNOWN(未知),不把无效False占位6当作条件不成立;已知值决定结果时,原依赖异常仍留在诊断列。78解释器按表达式树遍历节点,每个节点用NumPy逐元素函数计算整列。9isclose使用显式atol与rtol计算abs(a-b) <= atol + rtol*abs(b)。10evaluate_memory_numpy从已有内存行归一化信号,用searchsorted找到每个11查询时间的最近观测,再按有效期保持并求值,不额外读取或解码。1213依赖NumPy及核心编译、数值和对齐模块;共享检查协议,不调用参考数值14求值器。结果为独立只读存储,当前批次缓存不跨调用保留,不修改输入。15"""1617# 冻结输出及明确映射接口。18from dataclasses import dataclass19# 数组依赖显式,不污染标准库core导入。20import numpy as np21# 当前静态树是共享已校验协议,不共享样本数值内核。22from .enum_codes import memory_histories, aligned_column23from .expression import CompiledExpression24# 真实绑定参与执行身份检查。25from .binding import SignalBindings26# 解码运行证据与实际memory绑定共用检查协议,不共享参考数值计算。27from .decoded_binding import DecodedBindings28# 消息绑定只扩显式typed context,不改变普通数值核或memory时间保持入口。29from .message_binding import MessageBindings30# 只共享dtype常量,精确整数比较literal不转float。31from .numeric import INTEGER_BOUNDS32# 数组真实数值入口与布局模型。33from .numeric_numpy import (COMPARE_UFUNCS, DTYPES, NumericArray, _output, _readonly,34                            binary_numpy, to_float_numpy, unary_numpy)35# 批量单位转换独立于标量数值核。36from .affine_numpy import affine_numpy37from .expression_math_v4 import MATH_KERNELS38from .expression_math_numpy import math_numpy39from .expression_integer_v4 import INTEGER_KERNELS40from .expression_integer_numpy import integer_numpy41from .lookup_numpy import lookup_numpy42from .enum_codes import enum_code_numpy43# 此函数只校验身份与列映射,没有参考数值运算。44from .expression_reference import require_context45# 原行批量适配独立于参考转换。46from .normalize_numpy import memory_series_numpy47# 独立NumPy保持,不调用参考对齐。48from .state_numpy import align_numpy, build_signal_array, _require_axis49# 基础质量及bool有效mask实施三值,不对数组bool求值。50from .types import QualityFlag as Q, SignalDType as D515253@dataclass(frozen=True)54class ArrayEvaluation:55    """保存同长度的计算结果列与依赖质量诊断列。5657    value为NumericArray,dependency_quality是形状(N,)的uint16数组。58    布尔结果可能因已知TRUE或FALSE而有效,诊断仍可包含非零的原依赖原因;59    不能因此把UNKNOWN填为FALSE。构造时检查诊断布局并复制为只读存储。60    实例不可重新赋值,不持有跨批次的求值状态。61    """6263    value: NumericArray  # 真实逻辑结果列。64    dependency_quality: np.ndarray  # 同长uint16原因并集。6566    def __post_init__(self):67        """验证诊断布局和已知位后复制只读列。6869        参数: 无,读取value与dependency_quality。70        返回: None,不修改调用侧数组。71        异常: TypeError/ValueError为结构/长度/未知位。72        """73        # 真实typed结果需与诊断同一布局。74        if not isinstance(self.value, NumericArray) or type(self.dependency_quality) is not np.ndarray or self.dependency_quality.ndim != 1 or self.dependency_quality.dtype != np.dtype('uint16'):75            # 不用广播/隐式cast修正错误接口。76            raise TypeError('ArrayEvaluation诊断需1维uint16列')77        # 未知位不能以诊断为名通过。78        if len(self.dependency_quality) != len(self.value.values) or np.any(self.dependency_quality > 511):79            # 正常BOOL可带诊断,但结构仍严格。80            raise ValueError('ArrayEvaluation诊断长度/原因不合法')81        # 结果与诊断都具独立所有权。82        object.__setattr__(self, 'dependency_quality', _readonly(self.dependency_quality))838485def _logic(operator, values):86    """独立以整列掩码实现and/or/not三值真值表。8788    参数: operator固定逻辑名;values为同长BOOL列tuple。89    返回: NumericArray,known决定时质量NONE,否则UNKNOWN原因并集。90    异常: 调用者保证static BOOL及shape,分配错误传播。91    """92    # not不将invalid False占位当作真False。93    if operator == 'not':94        # 正常位置取反,valid/原因不改变。95        result = np.zeros(len(values[0].values), dtype=bool)96        # where避免对invalid占位作正常逻辑。97        np.logical_not(values[0].values, out=result, where=values[0].valid)98        # 原原因保持,不清UNKNOWN。99        return _output(D.BOOL, result, values[0].quality.copy())100    # 行数明确,不对数组做Python truthiness。101    size = len(values[0].values)102    # 默认没有known决断/没有原因。103    decided = np.zeros(size, dtype=bool)104    # uint16保留高位诊断。105    quality = np.zeros(size, dtype=np.uint16)106    # 逻辑节点的静态操作数循环,每次都真实整列mask。107    for value in values:108        # unknown原因先并集,decision后只清结果质量。109        quality |= value.quality110        # and由knownFalse决定,or由knownTrue决定。111        decided |= value.valid & (~value.values if operator == 'and' else value.values)112    # 已知决断给正常结果,原诊断由ArrayEvaluation另保存。113    quality[decided] = 0114    # and没有knownFalse且没有unknown时TRUE;or仅decided位置TRUE。115    result = ~decided if operator == 'and' else decided116    # unknown存储值不解释为正常bool,valid由quality决定。117    return _output(D.BOOL, result, quality)118119120def _constant(node, size):121    """将已校验literal装箱同长typed列,不猜dtype或enum域。122123    参数: node为const,size为非负明确行数。124    返回: 正常NumericArray;integer边界此前静态检查。125    异常: 模型/分配错误传播,不自动promotion。126    """127    # enum固定标签域,正常code由当前域唯一确定。128    value = node.labels.index(node.value) if node.labels else node.value129    # 常量复制为整列,不靠广播误配输入。130    values = np.full(size, value, dtype=DTYPES[node.dtype])131    # literal没有上游质量异常。132    return _output(node.dtype, values, np.zeros(size, dtype=np.uint16), node.labels)133134135def _literal_compare(node, value):136    """在整数域比较原列与任意精确Python整数常量边界。137138    参数: node含固定比较名/原整数边界;value为定宽整数列。139    返回: BOOL列,invalid保持原原因,不转float或cast越界常量。140    异常: 编译器保证类型与注册比较,分配错误传播。141    """142    # 边界上下限是精确Python静态配置,不是逐样本计算。143    lower, upper = INTEGER_BOUNDS[value.dtype]144    # 运算名来自白名单编译节点。145    operator = node.operator[12:]146    # 输出占位不正常使用。147    result = np.zeros(len(value.values), dtype=bool)148    # 太小边界:所有正常原值都大于它。149    if node.value < lower:150        # 不把超界常量cast成机器整数。151        result[value.valid] = operator in ('ne', 'gt', 'ge')152    # 太大边界:所有正常原值都小于它。153    elif node.value > upper:154        # 即使2**100也精确判定,不经过float。155        result[value.valid] = operator in ('ne', 'lt', 'le')156    else:157        # 已证明可表示,才按相同dtype精确ufunc比较。158        COMPARE_UFUNCS[operator](value.values, DTYPES[value.dtype].type(node.value), out=result, where=value.valid)159    # known比较正常,unknown原因原样保持。160    return _output(D.BOOL, result, value.quality.copy())161162163def _where(node, context, size, cache):164    """按有效条件拆成真、假两个活动子批,再按原行序还原输出。165166    参数: node为已统一分支类型/单位的where,context各列均为(size,);167        cache仅对应当前批次的坐标系。168    返回: ArrayEvaluation;条件未知的行保留条件原因,其他行仅并入所选分支诊断。169    异常: 模型、数组布局或分配异常原样传播;不以逐行解释器代替掩码计算。170    """171    # 未知条件不进入任一子批;初值保留其实际质量,数值占位不作为观测。172    condition = _evaluate(node.args[0], context, size, cache)173    values = np.zeros(size, dtype=DTYPES[node.dtype])174    quality = condition.value.quality.copy()175    diagnostic = condition.dependency_quality.copy()176    for truth, branch in ((True, node.args[1]), (False, node.args[2])):177        # 即使分支包含除零或定宽溢出,也只运算确实选到该分支的行。178        keep = condition.value.valid & (condition.value.values if truth else ~condition.value.values)179        count = int(np.count_nonzero(keep))180        if count == 0:181            continue182        if count == size:183            # 全批选择一致时保持同一坐标系,公共子树仍使用原缓存。184            selected = _evaluate(branch, context, size, cache)185        else:186            # 子批只复制此分支静态所需的列;嵌套where会在子批内再次按条件选行。187            # 新缓存防止原坐标系的整列结果误用在不同长度/行序的子批上。188            subset = {}189            for name in branch.aliases:190                column = context[name]191                subset[name] = column.take(keep)192            selected = _evaluate(branch, subset, count, {})193        # 按分支位置写回结果及该路径诊断;未选独有依赖不产生原因。194        values[keep] = selected.value.values195        quality[keep] = selected.value.quality196        diagnostic[keep] |= selected.dependency_quality197    return ArrayEvaluation(_output(node.dtype, values, quality), diagnostic)198199200def _evaluate(node, context, size, cache):201    """按静态节点结构调用真实批量数值/逻辑内核。202203    参数: node冻结树;context当前同长列;size行数;cache本次batch局部缓存。204    返回: ArrayEvaluation,完整诊断不因known逻辑结果丢失。205    异常: 静态/输入模型问题传播,不调用reference/eval/逐样本Python。206    """207    # 当前batch/真实绑定局部公共子表达式复用。208    if node in cache:209        # 不能跨batch/会话继承数值。210        return cache[node]211    # 裸literal仅类型装箱,没有逐样本计算。212    if node.operator == 'const':213        # shape与所有信号相同。214        value = _constant(node, size)215        # 常量没有原依赖异常。216        result = ArrayEvaluation(value, value.quality)217    # where按活动行递归,不能使用普通节点先计算全部操作数的策略。218    elif node.operator == 'where':219        result = _where(node, context, size, cache)220    elif node.operator == 'enum_code':221        # 读取与标签独立的原码质量;未映射标签不能屏蔽有效的整数代码。222        operand = _evaluate(node.args[0], context, size, cache).value223        value = enum_code_numpy(node, operand)224        result = ArrayEvaluation(value, value.quality)225    # Name只取当前显式context已解码列。226    elif node.operator == 'name':227        # 外层已校验dtype/labels/shape及真实绑定身份。228        value = context[node.name]229        # 原质量同步保存在诊断列。230        result = ArrayEvaluation(value, value.quality)231    else:232        # 对静态子树递归,每个节点执行整列,不逐样本解释。233        children = tuple(_evaluate(arg, context, size, cache) for arg in node.args)234        # 真正数值输入不是diagnostic数组。235        values = tuple(child.value for child in children)236        # 诊断逐节点整列并集,不借normal bool清原因。237        diagnostic = np.zeros(size, dtype=np.uint16)238        # 此循环维度是表达式静态操作数个数。239        for child in children:240            # 所有求值子树的原/计算质量纳入诊断。241            diagnostic |= child.dependency_quality242        # 三值布尔核独立于标量参考真值表代码。243        if node.operator in ('and', 'or', 'not'):244            # 已知决断可normal;diagnostic仍保留UNKNOWN。245            value = _logic(node.operator, values)246        # 数值单目先检查定宽再ufunc。247        elif node.operator in ('abs', 'neg'):248            # 调用真实数组数值核,而非np.vectorize。249            value = unary_numpy(node.operator, values[0])250        # 显式常量float上下文仍用独立尾位精度核。251        elif node.operator == 'to_float':252            # 大整数不默默变近似float。253            value = to_float_numpy(values[0])254        elif node.operator == 'unit_convert':255            # 整批应用已编译系数,数组核检查定宽和质量,不逐行构造单位对象。256            value = affine_numpy(values[0], *node.value, node.dtype)257        elif node.operator == 'lookup':258            value = lookup_numpy(node.value, values[0])259        elif node.operator == 'host_function':260            from ..extension_runtime import point_call261            value = point_call(node.host_function,'numpy',values)262        elif node.operator in MATH_KERNELS:263            # 同一时间行跨列求值,逐行无效原因与dtype保留。264            value = math_numpy(node.operator, values)265        elif node.operator in INTEGER_KERNELS:266            # 位、定宽取余和精确类型转换都按整列有效位置计算。267            value = integer_numpy(node, values)268        # 常量大边界不能塞进定宽列后wrap。269        elif node.operator.startswith('int_literal_'):270            # 独立静态端点判定及正常整数ufunc。271            value = _literal_compare(node, values[0])272        # 显式isclose判据由安全批量数值原语实施。273        elif node.operator == 'isclose':274            # 四个实参各自先检查整数精度,不调用np.isclose默认策略。275            a, b, atol, rtol = (to_float_numpy(item) for item in values)276            # 左边abs(a-b),非有限中间结果UNKNOWN。277            distance = unary_numpy('abs', binary_numpy('sub', a, b))278            # 右边rtol*abs(b),保持判据非对称。279            relative = binary_numpy('mul', rtol, unary_numpy('abs', b))280            # atol+relative真实float64列。281            tolerance = binary_numpy('add', atol, relative)282            # 不把非有限容差当作可正常无限阈值。283            value = binary_numpy('le', distance, tolerance)284            # 计算中间诊断同步纳入整列统计。285            diagnostic |= a.quality | b.quality | atol.quality | rtol.quality | distance.quality | tolerance.quality286        else:287            # 只对静态白名单operator执行真实批量数值。288            value = binary_numpy(node.operator, values[0], values[1], allow_float_literal=node.allow_float_literal)289        # known逻辑输出可以有效,诊断是独立原因列。290        result = ArrayEvaluation(value, diagnostic | value.quality)291    # 局部cache只存当前批列结果。292    cache[node] = result293    # 不持有跨调用输入绑定或信号状态。294    return result295296297def evaluate_numpy(compiled: CompiledExpression, context, bindings: SignalBindings | DecodedBindings | MessageBindings, *, size: int | None = None) -> ArrayEvaluation:298    """检查绑定、依赖列与行数后,批量计算一个表达式。299300    参数:301        compiled: 当前CompiledExpression;bindings为对应普通或MessageBindings运行绑定。302        context: 以精确依赖别名为键的NumericArray映射;所有列须同长同布局。303        size: 可选的非负行数N。可从第一列推断,纯常量无依赖时必须显式提供。304    返回:305        ArrayEvaluation,结果和诊断均为形状(N,)的独立只读数组,允许N为零。306    异常:307        TypeError、ValueError: 模型、绑定身份、依赖键、类型、枚举域或形状不合法。308        数值失败追加质量原因;内存分配异常直接传播。309    边界:310        不广播或补齐错位列。每次调用创建新缓存,只复用当前批次的公共子树。311    """312    # 共享协议校验不包含参考数据计算。313    require_context(compiled, context, bindings)314    # 显式行数不得用bool或任意隐式整数对象替代。315    if size is not None and (type(size) is not int or size < 0):316        # 不按int truthiness解读shape。317        raise TypeError('size需要非负原生int')318    # 每个实际依赖列都需相同当前dtype和enum域。319    for name, value in context.items():320        # 对照当前真实定义,不从全局查找。321        definition = bindings.resolve(name)322        # 不接裸数组或错误类型并偷偷astype。323        if not isinstance(value, NumericArray) or value.dtype is not definition.dtype:324            # 类型错误不能补成UNKNOWN。325            raise TypeError('context列dtype与当前绑定不同')326        # 当前声明固定标签域。327        labels = tuple(dict.fromkeys(label for _, label in definition.choices)) if definition.dtype is D.ENUM else ()328        # 相同代码但不同域不允许。329        if value.labels != labels:330            # 来源语义必须明确隔离。331            raise ValueError('context enum域不同')332        # 没有显式行数时取第一合法列,不broadcast。333        if size is None:334            # 只有真正数据列决定N。335            size = len(value.values)336        # 错行数必须执行前拒绝。337        if len(value.values) != size:338            # 不补齐或截断以凑shape。339            raise ValueError('context各列shape不同')340    # 常量无依赖也不能猜输出行数。341    if size is None:342        # 此时context为空,没有合法可推断shape。343        raise ValueError('纯常量批量执行须明确size')344    # 每次批量求值创建新缓存;编译树可复用,上一批的数值结果不继承。345    return _evaluate(compiled.root, context, size, {})346347348def evaluate_memory_numpy(compiled: CompiledExpression, times: np.ndarray, *, point: bool = False) -> ArrayEvaluation:349    """从已有内存信号批量对齐到查询时间,再计算表达式。350351    参数:352        compiled: 关联真实输入的CompiledExpression。353        times: 非递减的一维int64纳秒轴,形状(N,);point控制真实末点查询视图。354    返回:355        同形状ArrayEvaluation,保留有效掩码、质量原因和绑定身份。356    异常:357        TypeError: 非memory绑定,包括DecodedBindings;其他时间/归属/形状异常原样传播。358    流程:359        只建立实际依赖的历史列,独立归一化并按有效期对齐,再整列求值。360        纯常量也检查时间轴;保留完整历史,结果复制存储,不额外读取或解码。361    """362    # 纯常量也不能绕过memory来源门槛,显式错误避免解码绑定缺session属性的偶发异常。363    if not isinstance(compiled, CompiledExpression) or not isinstance(compiled.bindings, SignalBindings):364        raise TypeError('memory快捷求值需要真实SignalBindings的CompiledExpression')365    # 常量表达式也校验真正时间轴,拒绝MaskArray/回退/浮点时间。366    _require_axis(times)367    # 末点控制必须明确bool,不猜truthiness。368    if type(point) is not bool:369        # 保持协议与reference一致。370        raise TypeError('point需要bool')371    # 局部context只材料化实际依赖列。372    context = {}373    # 循环维度是依赖列,列内对齐/计算实际数组化。374    for name, history in memory_histories(compiled, compiled.dependencies, memory_series_numpy).items():375        # 用searchsorted定位最近观测,并结合有效期与质量掩码决定哪些保持值可用。376        aligned = align_numpy(build_signal_array(history), times, point=point)377        # 原valid/质量保留,包括最新invalid覆盖,enum代码域来自历史。378        context[name] = aligned_column(aligned)379    # 精确行数还覆盖无依赖/空查询情况。380    return evaluate_numpy(compiled, context, compiled.bindings, size=len(times))381
Chronaxis 用户手册 · 离线阅读