Chronaxis用户手册English
本页目录

chronaxis.core.lookup

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""校验 v4 一维标定表声明,并按已请求的插值方式生成只读计算表。23声明只保存配置点对和内容身份。被表达式引用时才将两列写为固定小端字节,4支持标量随机读取或 NumPy 零复制视图。无文件、全局缓存或第三方依赖。5"""67from dataclasses import dataclass, field, replace8from hashlib import sha2569from math import isfinite10from struct import Struct1112from .types import SignalDType as D13from .unit_registry import resolve_unit14from .validation import require_identifier151617MAX_TABLES = 6418MAX_POINTS = 409619_FORMATS = {D.INT64: Struct('<q'), D.UINT64: Struct('<Q'), D.FLOAT64: Struct('<d')}202122def _exact_f64(value):23    """校验 float64 转换。参数: 表内原生数。返回: float。异常: 非有限、整数超界或精度损失。"""24    if type(value) not in (int, float):25        raise TypeError('查表点必须为原生数值,不能是BOOL或文本')26    if type(value) is int and not -(1 << 63) <= value < (1 << 64):27        raise ValueError('查表整数超出64位范围')28    converted = float(value)29    if not isfinite(converted) or converted != value:30        raise ValueError('查表点必须可由有限float64精确表示')31    return converted323334@dataclass(frozen=True)35class LookupTablePlan:36    """保存经校验的有限标定点。身份保留原生数字类型、单位和每一点的次序。"""3738    name: str39    points: tuple[tuple[int | float, int | float], ...]40    input_unit: str41    unit: str42    dtype: D = field(init=False)43    identity: str = field(init=False)4445    def __post_init__(self):46        """校验轴和阶梯输出的共同类型。参数: 无。返回: 无。异常: 类型、单位、次序、大小或精度错误。"""47        require_identifier(self.name, 'lookup.name')48        if resolve_unit(self.input_unit) is None or resolve_unit(self.unit) is None:49            raise ValueError('查表输入/输出须声明已登记的数值单位')50        if type(self.points) is not tuple or not 2 <= len(self.points) <= MAX_POINTS:51            raise ValueError(f'查表points需要2至{MAX_POINTS}个冻结点对')52        previous, low, high, floating = None, 0, 0, False53        for row in self.points:54            if type(row) is not tuple or len(row) != 2:55                raise ValueError('查表points须为输入/输出的二元点对')56            x, y = row57            axis = _exact_f64(x)58            if previous is not None and axis <= previous:59                raise ValueError('查表轴须严格递增且不允许重复')60            previous = axis61            if type(y) is float:62                _exact_f64(y)63                floating = True64            elif type(y) is int and -(1 << 63) <= y < (1 << 64):65                low, high = min(low, y), max(high, y)66            else:67                raise ValueError('查表输出须为有限浮点数或64位整数')68        # 整数阶梯不经过 float64,可以无损保存 uint64 最大值。69        if floating:70            for _, y in self.points:71                _exact_f64(y)72            dtype = D.FLOAT6473        elif high < (1 << 63):74            dtype = D.INT6475        elif low >= 0:76            dtype = D.UINT6477        else:78            raise ValueError('查表输出负数与大uint64无法共用定宽整数类型')79        object.__setattr__(self, 'dtype', dtype)80        # 延迟导入避免基础绑定依赖查表声明时循环加载。81        from .binding import _identity82        payload = ('lookup-table-v1', self.name, self.input_unit, self.unit, self.points)83        object.__setattr__(self, 'identity', sha256(_identity(payload).encode('utf-8')).hexdigest())8485    def prepare(self, mode, bounds):86        """校验查表调用,不生成数值列。参数: 插值/越界模式。返回: PreparedLookup。异常: 模式或精度错误。"""87        if mode not in ('linear', 'previous', 'next') or bounds not in ('invalid', 'clamp'):88            raise ValueError('lookup仅支持linear/previous/next以及invalid/clamp')89        dtype = D.FLOAT64 if mode == 'linear' else self.dtype90        if dtype is D.FLOAT64 and self.dtype is not D.FLOAT64:91            for _, y in self.points:92                _exact_f64(y)93        return PreparedLookup(self.identity, mode, bounds, dtype, self.points)949596@dataclass(frozen=True)97class PreparedLookup:98    """保存已校验的模式和冻结点引用;实际数组执行者可单独持有只读打包列。"""99100    identity: str101    mode: str102    bounds: str103    dtype: D104    points: tuple = field(repr=False, compare=False)105    axis: bytes | None = field(default=None, repr=False, compare=False)106    values: bytes | None = field(default=None, repr=False, compare=False)107108    def packed(self):109        """产生只读数组视图的底层列。参数: 无。返回: 定宽两列的执行副本。异常: 分配错误。"""110        if self.axis is not None:111            return self112        axis, values = bytearray(8 * len(self.points)), bytearray(8 * len(self.points))113        for index, (x, y) in enumerate(self.points):114            _FORMATS[D.FLOAT64].pack_into(axis, 8 * index, x)115            _FORMATS[self.dtype].pack_into(values, 8 * index, y)116        return replace(self, axis=bytes(axis), values=bytes(values))117118    def axis_at(self, index):119        """按序号读取物理坐标。参数: 合法索引。返回: float。异常: 非法索引。"""120        return float(self.points[index][0])121122    def value_at(self, index):123        """按序号读取定宽值。参数: 合法索引。返回: 原生int或float。异常: 非法索引。"""124        y = self.points[index][1]125        return float(y) if self.dtype is D.FLOAT64 else y126127128def pack_requested_lookups(expression):129    """只为实际执行的根建立数组查表列。参数: 执行者的表达式。返回: 局部执行副本。异常: 分配错误。130131    共用同一配置及模式的调用只打包一次。未消费的派生定义不会进入此根,原声明保持不可变。132    """133    if not expression.bindings.lookups:134        return expression135    nodes, tables = {}, {}136137    def visit(node):138        """改写本根的查表节点。参数: 原节点。返回: 原节点或带执行列的副本。异常: 分配错误。"""139        if id(node) not in nodes:140            args = tuple(visit(child) for child in node.args)141            if node.operator == 'lookup':142                if node.value not in tables:143                    tables[node.value] = node.value.packed()144                changed = replace(node, args=args, value=tables[node.value])145            elif any(a is not b for a, b in zip(args, node.args)):146                changed = replace(node, args=args)147            else:148                changed = node149            nodes[id(node)] = changed150        return nodes[id(node)]151152    root = visit(expression.root)153    return expression if root is expression.root else replace(expression, root=root)154155156def validate_lookups(tables, version):157    """检查有限表集合。参数: 冻结表元组和配置版本。返回: 无。异常: 非v4、重复名或表过多。"""158    if type(tables) is not tuple or any(type(table) is not LookupTablePlan for table in tables):159        raise TypeError('lookups需为LookupTablePlan tuple')160    if tables and version != 4:161        raise ValueError('lookups需要config_version=4')162    if len(tables) > MAX_TABLES or len({t.name for t in tables}) != len(tables):163        raise ValueError(f'lookups表名须唯一且最多{MAX_TABLES}张')164
Chronaxis 用户手册 · 离线阅读