Chronaxis用户手册English
本页目录

chronaxis.core.numeric_numpy

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""对同长度数值列执行算术和比较,并逐位置记录有效性与质量。23输入NumericArray保存一维values、bool类型的valid和uint16类型的quality。4算术先合并质量原因,再用布尔掩码选择能计算的位置,输出独立的只读列。5整数先检查符号、幅度和目标定宽范围,再执行NumPy逐元素函数;6需要float64时先检查整数的精确表示,失败位置不执行正常运算。78所有列的形状都是(N,),N为行数;枚举存为int64编码并携带固定labels。9无效位置的零或False仅是存储占位,不能脱离valid当作业务值使用。10本模块依赖NumPy及核心类型规则,不读写文件或加载旧解码路径。11仅共享静态类型与模型检查,不调用标量参考数值函数,也不以object数组12或逐样本Python回调代替列运算;输出复制存储,不修改调用者数组。13"""1415# 冻结模型及静态白名单;数组内核使用实际ufunc和掩码。16from dataclasses import dataclass17# 只读静态映射或显式质量/type枚举用于结构协议。18from types import MappingProxyType19# NumPy显式加载,承担真实typed列/ufunc与掩码计算。20import numpy as np2122# 仅共享类型契约,不导入参考运算。23from .numeric import COMPARISONS, result_dtype24# 只读静态映射或显式质量/type枚举用于结构协议。25from .types import QualityFlag, SignalDType26# 共享原生/文本结构校验,不共享数值参考。27from .validation import require_text2829# 每个输出声明有固定物理布局;enum是固定域的int64编码。30DTYPES = MappingProxyType({SignalDType.INT64: np.dtype('int64'), SignalDType.UINT64: np.dtype('uint64'),31                          SignalDType.FLOAT64: np.dtype('float64'), SignalDType.BOOL: np.dtype('bool'),32                          SignalDType.ENUM: np.dtype('int64')})33# ufunc只来自静态注册,用户不能传自由函数。34COMPARE_UFUNCS = MappingProxyType({'eq': np.equal, 'ne': np.not_equal, 'lt': np.less,35                                  'le': np.less_equal, 'gt': np.greater, 'ge': np.greater_equal})363738def _readonly(array: np.ndarray) -> np.ndarray:39    """复制列并设置普通只读所有权约定。4041    参数: array为已校验ndarray。42    返回: 拥有独立存储的只读列。43    异常: 内存分配错误传播;不声称防恶意篡改。44    """45    # 不借用调用侧可变缓冲。46    result = array.copy()47    # 输出不能普通原位修改。48    result.flags.writeable = False49    # 独立所有权允许调用侧继续使用其原数组。50    return result515253@dataclass(frozen=True)54class NumericArray:55    """保存没有时间轴的一组数值,以及每个位置的有效性和质量原因。5657    values、valid和quality均为形状(N,)的普通ndarray,长度必须一致。58    values的物理类型由dtype指定,valid为bool,quality为uint16;59    valid严格等于quality == 0,无效位置只能作为存储占位使用。60    枚举以int64编码表示,labels给出固定标签顺序,只检查有效位置的编码。61    构造时复制三个数组并设置只读标志,保留独立存储;这是一项普通使用62    约定,不声称能抵御主动解除写保护。实例不保存时间、单位或跨批次状态。63    """6465    dtype: SignalDType  # 逻辑类型不靠NumPy推断。66    values: np.ndarray  # int64/uint64/float64/bool列;enum为int64 code。67    valid: np.ndarray  # bool有效位置,禁止maskless运算。68    quality: np.ndarray  # uint16足够容纳九种质量位。69    labels: tuple[str, ...] = ()  # enum顺序域,其他dtype为空。70    code: 'NumericArray | None' = None  # 仅请求 enum_code 时附带独立整数列。7172    def take(self, indices):73        """按活动行切出标签及所需原码,保持同一坐标系。7475        参数: indices 为行号或布尔掩码。返回: 对应行的独立 NumericArray。76        异常: 索引或布局不合法时传播数组错误。77        """78        return NumericArray(self.dtype, self.values[indices], self.valid[indices],79                            self.quality[indices], self.labels,80                            None if self.code is None else self.code.take(indices))8182    def __post_init__(self) -> None:83        """验证布局、基础质量、有限性及enum域后冻结独立副本。8485        参数: 无;读取数据类字段。86        返回: None;不隐式cast或清除质量。87        异常: TypeError为布局;ValueError为shape/质量/非法正常值。88        """89        # dtype必须是当前协议声明。90        if not isinstance(self.dtype, SignalDType) or not isinstance(self.labels, tuple):91            # 无类型字段不进入数组执行。92            raise TypeError('NumericArray需要typed dtype与tuple labels')93        # 严格ndarray不接隐藏掩码或自定义子类行为。94        for array, dtype in ((self.values, DTYPES[self.dtype]), (self.valid, np.dtype('bool')), (self.quality, np.dtype('uint16'))):95            # shape和物理类型不能靠自动astype补救。96            if type(array) is not np.ndarray or array.ndim != 1 or array.dtype != dtype:97                # 输出接口与内存布局必须明确。98                raise TypeError('NumericArray需要1维精确dtype ndarray')99        # 禁止广播掩盖行错位。100        if not (len(self.values) == len(self.valid) == len(self.quality)):101            # 不截断或重复最后一行。102            raise ValueError('数值/valid/quality长度不同')103        if self.code is not None and (self.dtype is not SignalDType.ENUM or not isinstance(self.code, NumericArray)104                or self.code.dtype not in (SignalDType.INT64, SignalDType.UINT64) or len(self.code.values) != len(self.values)):105            raise ValueError('只有枚举可附带等长整数原码列')106        # 仅非负已知原因位;valid严格等于基础无质量。107        if np.any(self.quality > 511) or not np.array_equal(self.valid, self.quality == 0):108            # invalid不能无原因,ESTIMATED也不能默认当正常。109            raise ValueError('质量与valid矛盾或未知质量位')110        # 正常float不能携带NaN/Inf。111        if self.dtype is SignalDType.FLOAT64 and np.any(~np.isfinite(self.values[self.valid])):112            # invalid诊断只存储,不通过finite单一检查判断整数质量。113            raise ValueError('正常float必须有限')114        # enum域由显式labels提供,不能隐式猜顺序。115        if self.dtype is SignalDType.ENUM:116            # 非空且唯一;映射归一化已处理多码同标签。117            if not self.labels or len(set(self.labels)) != len(self.labels):118                # 空域不能解释任何枚举编码。119                raise ValueError('enum需要非空唯一域')120            # 大小写敏感的业务文本原样保留。121            for label in self.labels:122                # labels不是表达式名称。123                require_text(label, 'enum.label')124            # 只校验正常编码,invalid占位不参与业务比较。125            if np.any((self.values[self.valid] < 0) | (self.values[self.valid] >= len(self.labels))):126                # 不采用Python负索引解释未知枚举。127                raise ValueError('正常enum code超出域')128        # 非枚举字段不能隐藏带入业务标签域。129        elif self.labels:130            # 普通数值不附带隐藏枚举语义。131            raise ValueError('非enum不能携带labels')132        # 数据类只读与数组只读是两层约定,均有独立所有权。133        for field in ('values', 'valid', 'quality'):134            # 三个输入列各自复制,不改调用侧写保护。135            object.__setattr__(self, field, _readonly(getattr(self, field)))136137138def _output(dtype, values, quality, labels=()) -> NumericArray:139    """用已有质量列建立统一有效性结果。140141    参数: dtype/values/quality为局部结果;labels为enum域。142    返回: 独立只读NumericArray。143    异常: 模型校验/分配错误传播,不改变数值政策。144    """145    # 只有质量NONE的位置可以正常使用。146    return NumericArray(dtype, values, quality == 0, quality, labels)147148149def _magnitude(values: np.ndarray) -> np.ndarray:150    """在uint64中取得整数列的精确绝对幅度。151152    参数:153        values: 形状(N,)的int64或uint64数组,由调用者保证布局。154    返回:155        新的uint64数组,形状保持(N,);int64最小值得到2**63。156    异常:157        内存分配或底层数组操作异常直接传播。158    流程:159        先复制位表示,再对有符号负数按补码取反加一,不调用会溢出的abs(min)。160    """161    # unsigned输入本身就是幅度,复制防止原位别名。162    result = values.view(np.uint64).copy()163    # signed补码只用于定义良好的位解释,不先算有符号abs(min)。164    if values.dtype == np.dtype('int64'):165        # 负数的按位取反加1最大是2**63,全部在uint64范围内。166        negative = values < 0167        # 显式补码幅度算法;~位结果不超过uint64 max-1。168        result[negative] = np.bitwise_not(result[negative]) + np.uint64(1)169    # 后续乘法/精度检查均在此精确整数幅度域。170    return result171172173def _from_magnitude(magnitude: np.ndarray, negative: np.ndarray) -> np.ndarray:174    """把已证明范围合法的整数幅度和符号恢复成int64列。175176    参数:177        magnitude: 形状(N,)的uint64幅度。正数不超过2**63-1,负数不超过2**63。178        negative: 同形状bool掩码,True表示该位置应为负数。179    返回:180        独立存储的int64数组;零和最小负数均通过位表示恢复。181    异常:182        调用者负责证明范围与布局;内存分配或数组操作异常直接传播。183    边界:184        先复制幅度再构建负数补码,最后改变位视图,不先执行危险的有符号负号。185    """186    # 正数位模式与signed相同,负数逐位置构建补码。187    bits = magnitude.copy()188    # 零幅度无需形成uint64 max+1;非零负数补码不溢出。189    selected = negative & (magnitude != 0)190    # ~幅度最大uint64 max-1,加1安全。191    bits[selected] = np.bitwise_not(magnitude[selected]) + np.uint64(1)192    # 已证明符号范围后进行位视图,不隐式算术wrap。193    return bits.view(np.int64)194195196def to_float_numpy(value: NumericArray) -> NumericArray:197    """用固定11个尾位掩码独立检查整数到float64是否精确。198199    参数: value为数值NumericArray;invalid输入不转换。200    返回: float64列及保留/新增PRECISION_LOSS质量;shape保持。201    异常: TypeError为结构/逻辑类型;分配错误传播。202    """203    # bool/enum不进入数值提升。204    if not isinstance(value, NumericArray) or value.dtype in (SignalDType.BOOL, SignalDType.ENUM):205        # 不隐式把False当0或编码当数值。206        raise TypeError('float转换需要数值列')207    # 复制原因和独立输出占位,无效0不可使用。208    quality = value.quality.copy()209    # 输出固定float64且一维N。210    result = np.zeros(len(value.values), dtype=np.float64)211    # float已通过模型有限检查,不需要整数精度判据。212    if value.dtype is SignalDType.FLOAT64:213        # 仅拷贝正常位置,invalid占位不参与转换。214        result[value.valid] = value.values[value.valid]215    else:216        # 幅度精确包括signed min与unsigned max。217        magnitude = _magnitude(value.values)218        # 丢精度掩码按指数要求尾位为0,不做危险float→int64回转。219        loss = np.zeros(len(result), dtype=bool)220        # 64位幅度只需检查11种尾位间距;每轮检查整列,循环次数不随样本数增长。221        for bits in range(1, 12):222            # 指数区间最低有效位间距为2**bits;更大指数继续检查更多尾位。223            loss |= (magnitude >= np.uint64(2 ** (52 + bits))) & ((magnitude & np.uint64(2 ** bits - 1)) != 0)224        # 仅正常输入因精度变无效,已有原因不被计算占位污染。225        failed = value.valid & loss226        # 质量掩码至少uint16,不截断256等高位。227        quality[failed] |= np.uint16(QualityFlag.PRECISION_LOSS)228        # 真正转换只发生在已经证明精确且有效的位置。229        selected = value.valid & ~loss230        # 64位整数范围内不会超float64有限范围。231        result[selected] = value.values[selected].astype(np.float64)232    # 模型共享只校验输出,不共享参考数值。233    return _output(SignalDType.FLOAT64, result, quality)234235236def _integer_compare(operator, left, right, selected) -> np.ndarray:237    """在整数域精确比较signed/unsigned两列。238239    参数: operator静态比较名,left/right整数列,selected有效bool掩码。240    返回: bool结果列,invalid占位False。241    异常: 调用者保证等长/类型,分配错误传播。242    """243    # 默认为不可使用的False,占位必须随质量掩码传递。244    result = np.zeros(len(left), dtype=bool)245    # 同dtype直接精确ufunc,不经过float。246    if left.dtype == right.dtype:247        # where不触碰invalid数值。248        COMPARE_UFUNCS[operator](left, right, out=result, where=selected)249        # 同型比较已经完成。250        return result251    # 哪一侧signed明示,不依赖NumPy混合promotion。252    signed_left = left.dtype == np.dtype('int64')253    # 负signed与任何unsigned之间的序固定。254    signed = left if signed_left else right255    # signed非负位置才能安全转uint64。256    negative = selected & (signed < 0)257    # 负signed永小于unsigned;换侧序相反。258    negative_result = operator in ('ne', 'lt', 'le') if signed_left else operator in ('ne', 'gt', 'ge')259    # 一次填满确定比较的负数子集。260    result[negative] = negative_result261    # 其余正常位置在无符号域精确比较。262    remaining = selected & ~negative263    # 子集cast证明非负,不从uint64 max转signed。264    result[remaining] = COMPARE_UFUNCS[operator](left[remaining].astype(np.uint64), right[remaining].astype(np.uint64))265    # 完整结果保持原行布局。266    return result267268269def _integer_arithmetic(operator, left, right, selected):270    """先检查整数结果是否超界,再计算安全位置的加减乘。271272    参数:273        operator: add、sub或mul;left、right为同形状(N,)且同类型的整数数组。274        selected: 同形状bool掩码,标出两侧输入均有效的位置。275    返回:276        同类型结果数组和bool溢出掩码,均为形状(N,)。277        未选中或溢出位置保持零占位,调用者必须同时传递有效性与质量。278    异常:279        调用者保证输入布局和注册运算;内存分配或数组操作异常直接传播。280    流程:281        加减按符号检查安全门槛;乘法在uint64幅度域检查limit // b。282        检查后才调用逐元素函数,避免先发生整数回绕再判断溢出。283    """284    # 局部结果占位与失败掩码均为一维N。285    result = np.zeros_like(left)286    # 只考虑有效位置,invalid任意位模式不触发附加错误。287    overflow = np.zeros(len(left), dtype=bool)288    # 同型signed/unsigned路径分开,避免隐式promotion。289    signed = left.dtype == np.dtype('int64')290    # 先用减/加安全门槛检查add/sub。291    if operator in ('add', 'sub'):292        # unsigned add max-b安全;sub直接比较a<b。293        if not signed:294            # 全列门槛只是合法定宽值,无危险算术。295            overflow = selected & ((left > np.uint64(2 ** 64 - 1) - right) if operator == 'add' else (left < right))296        else:297            # 分符号取子集,min/max与相应b之差保证在范围内。298            positive = selected & (right > 0)299            # 0右值永不产生add/sub溢出。300            negative = selected & (right < 0)301            # add的正b只检查上界,负b只检查下界。302            if operator == 'add':303                # max-正数与min-负数均安全。304                overflow[positive] = left[positive] > np.int64(2 ** 63 - 1) - right[positive]305                # 包含b=min,min-min=0合法。306                overflow[negative] = left[negative] < np.int64(-(2 ** 63)) - right[negative]307            else:308                # sub正b检查min+b,负b检查max+b,门槛安全。309                overflow[positive] = left[positive] < np.int64(-(2 ** 63)) + right[positive]310                # 包含a=max,b=min,明确失败不执行subtract。311                overflow[negative] = left[negative] > np.int64(2 ** 63 - 1) + right[negative]312        # 只对已证明结果范围的元素调用ufunc。313        ufunc = np.add if operator == 'add' else np.subtract314        # where阻止无效/失败位置参与计算。315        ufunc(left, right, out=result, where=selected & ~overflow)316    else:317        # 精确幅度计算避免abs(min),乘法不先wrap再检查。318        a, b = _magnitude(left), _magnitude(right)319        # signed负结果允许幅度2**63,正结果只允许max。320        negative = (left < 0) ^ (right < 0) if signed else np.zeros(len(left), dtype=bool)321        # 所有幅度限值都使用uint64,避免浮点近似改变溢出边界。322        limit = np.where(negative, np.uint64(2 ** 63), np.uint64(2 ** 63 - 1)) if signed else np.full(len(left), 2 ** 64 - 1, dtype=np.uint64)323        # b=0不做正常除法,乘积确定0合法。324        nonzero = selected & (b != 0)325        # 除法门槛在精确unsigned整数域。326        overflow[nonzero] = a[nonzero] > limit[nonzero] // b[nonzero]327        # 正常乘法完全不接触越界位置。328        np.multiply(left, right, out=result, where=selected & ~overflow)329    # 数值内核返回质量原因,由外层统一组合。330    return result, overflow331332333def binary_numpy(operator: str, left: NumericArray, right: NumericArray, *, allow_float_literal: bool = False) -> NumericArray:334    """对两列执行注册算术或比较,只计算质量允许的位置。335336    参数:337        operator: 已注册的二元运算名称;left、right为同长度NumericArray。338        allow_float_literal: 编译器证明浮点字面量可用于混合类型时才为True。339    返回:340        独立只读NumericArray,形状保持(N,)。质量按位合并,计算失败追加原因。341        比较返回BOOL,算术遵守静态结果类型;无效占位不表示正常零或False。342    异常:343        TypeError、ValueError: 模型、类型、长度、注册运算或枚举域不合法。344        内存分配异常直接传播;除零、溢出或丢精度作为质量原因返回。345    流程:346        先检查类型与等长布局,再合并有效掩码;整数路径保留精确比较和定宽347        检查,浮点路径先做精度转换。各路径独立于标量参考实现,不修改输入。348    """349    # 完整typed输入必须存在,空列也做静态配置检查。350    if not isinstance(left, NumericArray) or not isinstance(right, NumericArray):351        # 不接受自由字典或裸数组。352        raise TypeError('binary需要两数值列')353    # 静态dtype协议允许共享,不调用参考运算。354    dtype = result_dtype(operator, left.dtype, right.dtype, allow_float_literal)355    # 同长行布局,无隐式broadcast。356    if len(left.values) != len(right.values):357        # 调用側必须显式对齐。358        raise ValueError('binary两列长度不同')359    # enum编码等值仅在同一标签域内成立。360    if left.dtype is SignalDType.ENUM and left.labels != right.labels:361        # 不能以整数code相同当不同逻辑标签相同。362        raise ValueError('enum比较需要相同标签域')363    # 基础质量合并不受存储占位影响。364    quality = left.quality | right.quality365    # 正常运算只允许双侧有效。366    selected = left.valid & right.valid367    # float算术/比较需要独立精度转换先验。368    float_path = dtype is SignalDType.FLOAT64 or (operator in COMPARISONS and SignalDType.FLOAT64 in (left.dtype, right.dtype))369    # 同型整数和逻辑比较保持精确原布局。370    if operator in COMPARISONS and not float_path:371        # 两种整数使用专门符号门槛,不隐式浮点promotion。372        if left.dtype in (SignalDType.INT64, SignalDType.UINT64):373            # 此函数单次批量精确执行。374            values = _integer_compare(operator, left.values, right.values, selected)375        else:376            # bool与同域enum等值ufunc。377            values = np.zeros(len(quality), dtype=bool)378            # 无效编码不参与比较。379            COMPARE_UFUNCS[operator](left.values, right.values, out=values, where=selected)380        # 质量并集正确保留invalid占位。381        return _output(dtype, values, quality)382    # float路径先检查两操作数,各自独立转换。383    if float_path:384        # 仅共同有效输入触发新质量,避免无效另一侧诱发多余精度错误。385        a = to_float_numpy(_output(left.dtype, left.values, quality, left.labels))386        # 同样quality使失败位置不在第二侧执行正常转换。387        b = to_float_numpy(_output(right.dtype, right.values, quality, right.labels))388        # 共同有效位置可能因整数精度丢失变无效。389        quality = a.quality | b.quality390        # 数值执行仅在全部转换成功处。391        selected = quality == 0392        # 比较结果逻辑型,float算术结果数值型。393        values = np.zeros(len(quality), dtype=DTYPES[dtype])394        # 除零先剔除,不能先产生Inf后当正常。395        if operator == 'div':396            # 包含正负0分母,0/0亦同。397            zero = selected & (b.values == 0)398            # 该位置明示域错误,不触发正常除法。399            quality[zero] |= np.uint16(QualityFlag.DOMAIN_ERROR)400            # 更新执行掩码后调用真实ufunc。401            selected &= ~zero402        # 浮点overflow/invalid转质量,不向外伪报正常数值。403        with np.errstate(over='ignore', invalid='ignore', divide='raise'):404            # 静态分派既包括比较又包括算术。405            ufunc = COMPARE_UFUNCS[operator] if operator in COMPARISONS else {'add': np.add, 'sub': np.subtract, 'mul': np.multiply, 'div': np.divide}[operator]406            # where保证invalid及除零位置未执行。407            ufunc(a.values, b.values, out=values, where=selected)408        # 正常float结果非有限需要新增诊断,bool结果不会非有限。409        if dtype is SignalDType.FLOAT64:410            # 只标记实际执行位置,不污染invalid占位。411            quality[selected & ~np.isfinite(values)] |= np.uint16(QualityFlag.NONFINITE)412        # 构造输出前模型保证所有valid浮点有限。413        return _output(dtype, values, quality)414    # 剩余路径都是同dtype整数+/-/*。415    values, overflow = _integer_arithmetic(operator, left.values, right.values, selected)416    # 精确门槛失败原因与上游质量共存。417    quality[overflow] |= np.uint16(QualityFlag.OVERFLOW)418    # 溢出0只是无效存储占位,不能当成功值。419    return _output(dtype, values, quality)420421422def unary_numpy(operator: str, value: NumericArray) -> NumericArray:423    """独立数组abs/neg,signed min和unsigned负号先拒绝。424425    参数: operator为abs/neg;value为数值列。426    返回: 同dtype列及保留/新增质量,shape保持。427    异常: TypeError/ValueError为结构/未知运算,分配错误传播。428    """429    # 不接受逻辑数值化或任意函数。430    if not isinstance(value, NumericArray) or value.dtype in (SignalDType.BOOL, SignalDType.ENUM) or operator not in ('abs', 'neg'):431        # 与参考输入拒绝语义一致。432        raise ValueError('unary仅支持数值abs/neg')433    # 输入原因先复制,输出独立存储。434    quality = value.quality.copy()435    # invalid占位从不作为正常结果使用。436    result = np.zeros_like(value.values)437    # 只有正常输入需检查目标定宽。438    overflow = np.zeros(len(result), dtype=bool)439    # signed min abs/neg均超上界。440    if value.dtype is SignalDType.INT64:441        # 不先执行危险abs(min)或neg(min)。442        overflow = value.valid & (value.values == np.int64(-(2 ** 63)))443    # 无符号abs恒合法,只有neg需要下溢检查。444    elif value.dtype is SignalDType.UINT64 and operator == 'neg':445        # 非零unsigned负号必然下溢;0合法。446        overflow = value.valid & (value.values != 0)447    # 静态ufunc仅对已证明安全的位置执行。448    ufunc = np.absolute if operator == 'abs' else np.negative449    # unsigned neg允许位置仅0,不wrap。450    ufunc(value.values, out=result, where=value.valid & ~overflow)451    # 失败原因独立可追溯。452    quality[overflow] |= np.uint16(QualityFlag.OVERFLOW)453    # 不改变输入布局或质量。454    return _output(value.dtype, result, quality)455
Chronaxis 用户手册 · 离线阅读