Chronaxis用户手册English
本页目录

chronaxis.core.expression_v4

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""为版本4表达式编译一次性单位转换,统一处理当前单位与函数规则。23输入为已校验绑定和AST,输出包含unit_convert节点的不可变表达式树。4先解释裸常量的单位,再统一兼容量的尺度,最后推导结果单位及数值类型。5编译只使用声明与常量。clip的两个边界均无信号依赖时,标量核在此判定6其顺序;编译器不读取日志或求样本值。7温度偏移、量种和整数精度规则不能通过只改单位标签绕过。8"""910# 复用白名单语法和类型规则;所有量种关系来自同一单位注册表。11import ast12from dataclasses import replace13from .expression import _SyntaxCompiler, ExprNode, _node14from .expression_math_v4 import MATH_FUNCTIONS, check_integer_leaves, compile_math15from .lookup_compiler import compile_lookup16from .expression_integer_v4 import INTEGER_FUNCTIONS, compile_integer17from .enum_codes import compile_enum_code18from .numeric import INTEGER_BOUNDS19from .types import SignalDType as D20from . import unit_registry as units212223def numeric_coefficient(value):24    """将精确有理系数压为整数或float,供已选后端执行。2526    参数: value为Fraction。27    返回: 分母为1时返回int,否则返回float。28    异常: 不能表示的浮点范围错误由调用者/倍率模型检查。29    """30    # 保留整数路径,不能把无损倍率1000提前变成浮点。31    return value.numerator if value.denominator == 1 else float(value)323334class UnitCompiler(_SyntaxCompiler):35    """在原受限语法上接入v4单位、常量和转换规则。3637    每次编译独享状态。声明单位在裸常量适配之前保留,转换只写入树一次;38    数值执行根据节点系数转换,编译器不逐条处理数据。39    """4041    def __init__(self, bindings, derived, extensions=None):42        """建立绑定并检查本版本已经开放的函数名冲突。4344        参数: bindings为v4绑定,derived为完整派生声明tuple。45        返回: None。46        异常: ValueError为别名与本版本函数重名;其余声明错误沿原校验传播。47        """48        # 内置函数名称不能再作为信号、派生或参数别名。49        super().__init__(bindings, derived)50        self.extensions = extensions51        reserved = {'quantity', 'to_unit', 'where', 'enum_code', 'lookup'} | MATH_FUNCTIONS | INTEGER_FUNCTIONS52        if extensions is not None:53            reserved.update(extensions.functions)54        conflicts = reserved.intersection((*self.signals, *self.derived, *self.parameters))55        if conflicts:56            raise ValueError('v4名称与函数冲突:' + ', '.join(sorted(conflicts)))5758    def _known_node(self, result):59        """按v4目录判定组合结果的单位状态,不提升未知输入的可信度。6061        参数: result为已编译ExprNode。62        返回: 单位状态准确的新节点或原节点。63        异常: 非法单位文本按编译错误传播。64        """65        # name状态来自信号完整转换验证,不能用已知目标标签覆盖未验证输入。66        if result.operator == 'name':67            return result68        verified = units.resolve_unit(result.unit) is not None and all(arg.unit_status == 'verified' for arg in result.args)69        return replace(result, unit_status='verified' if verified else 'unverified')7071    def _name(self, node):72        """取得信号/派生原声明尺度,附带已有输入转换的类型提升依据。7374        参数: node为Name AST。75        返回: 已校验ExprNode。76        异常: 名称/循环/声明错误沿原编译器传播。77        """78        # 保留mV等声明尺度,使x>500里的500仍是mV。79        result = super()._name(node)80        definition = self.signals.get(node.id)81        if definition is not None and getattr(definition, 'unit_conversion_promoted', False):82            result = replace(result, conversion_promoted=True)83        return result8485    def _convert(self, child, target, node, *, explicit=False):86        """为兼容单位生成一次仿射转换,不重复应用源层的DBC或标定系数。8788        参数: child为当前数值表示;target为单位文本;node定位;explicit保留输出尺度。89        返回: 原节点或unit_convert节点,dtype在编译期确定。90        异常: ValueError为非数值转换、未知关系、量种不兼容或不可表示倍率。91        """92        # 同标签的未知单位可以继续使用,但不伪造已验证转换。93        target_unit = units.resolve_unit(target)94        label = units.output_label(target_unit, target) if target_unit is not None else target95        if child.unit == target or child.unit == label:96            return replace(child, unit=label, unit_explicit=explicit or child.unit_explicit)97        if child.dtype not in (*INTEGER_BOUNDS, D.FLOAT64):98            self._error(node, '单位转换只支持数值')99        source_unit = units.resolve_unit(child.unit)100        if source_unit is None or target_unit is None:101            self._error(node, f'未登记单位转换:{child.unit}→{target}')102        # 原子别名使用规范名;倍率为1的复合目标使用稳定名称,其他保留可解析输入。103        target = label104        try:105            scale, offset = units.conversion(source_unit, target_unit)106            factor = scale.to_float() if scale.pi_power else numeric_coefficient(scale.factor)107            # to_float还检查下溢到零,不能直接float极小Fraction。108            if type(factor) is float:109                factor = scale.to_float()110            shift = numeric_coefficient(offset)111        except (TypeError, ValueError, OverflowError) as error:112            self._error(node, str(error))113        # 整数倍且整数偏移保留原整数类型,其余必须有float容纳小数。114        promoted = child.dtype in INTEGER_BOUNDS and (type(factor) is not int or type(shift) is not int)115        dtype = D.FLOAT64 if promoted else child.dtype116        if factor == 1 and shift == 0:117            return replace(child, unit=target, unit_explicit=explicit or child.unit_explicit)118        return ExprNode('unit_convert', dtype, target, child.unit_status, (child,),119                        value=(factor, shift), aliases=child.aliases, constant=child.constant,120                        conversion_promoted=promoted or child.conversion_promoted,121                        unit_explicit=explicit, typed_constant=child.typed_constant)122123    def _canonical(self, child, node):124        """把已知数值换到该量种的固定基准,未知标签保持。125126        参数: child为节点,node为错误位置。127        返回: 已转换节点;逻辑/枚举不作数值换算。128        异常: 单位或转换边界错误传播。129        """130        # 不是每个结果都是数值,BOOL和枚举仍沿原协议。131        if child.dtype not in (*INTEGER_BOUNDS, D.FLOAT64):132            return child133        unit = units.resolve_unit(child.unit)134        return child if unit is None else self._convert(child, units.canonical_unit(unit).symbol, node)135136    def _final_unit(self, root, unit, node):137        """在表达式输出边界同时转换目标数值和标签。138139        参数: root为表达式树;unit为显式输出单位或None;node定位。140        返回: 显式目标优先,否则用规范基准。141        异常: 不兼容目标或未知转换明确报错。142        """143        # to_unit和quantity直接作为根时保留其明确尺度,复合运算再按量种规范化。144        if unit is not None:145            return self._convert(root, unit, node, explicit=True)146        return root if root.unit_explicit else self._canonical(root, node)147148    def _context(self, literal, other, node, *, arithmetic):149        """复用定宽字面量检查,同时保持新单位的验证状态。150151        参数: literal/other为数值节点;node定位;arithmetic区分算术和比较。152        返回: 按上下文检查过的节点。153        异常: 类型、范围或枚举域错误沿原检查传播。154        """155        # 纯常量仍需保持旧精度检查,不给BOOL或ENUM增加隐式数值转换。156        if literal.typed_constant:157            # quantity已经明确单位,只借用数值字面量的定宽/精度适配,不再重标单位。158            result = super()._context(replace(literal, constant=True), other, node, arithmetic=arithmetic)159            result = replace(result, constant=False, typed_constant=True)160        else:161            result = super()._context(literal, other, node, arithmetic=arithmetic)162        return self._known_node(result) if result.args else result163164    def _units(self, operator, left, right, node, declared_unit=None):165        """先解释裸常量,再按兼容量换算,独立处理温度加减。166167        参数: operator为add/sub/compare/mul/div;左右节点及错误位置;declared_unit为未知组合标签。168        返回: 已转换左右节点与结果单位。169        异常: ValueError为不兼容量种、温度组合或未登记关系。170        """171        # 单一对方的原声明单位是裸常量锚点,quantity不是裸常量。172        if operator in ('add', 'sub', 'compare'):173            if left.constant and not right.constant and left.dtype in (*INTEGER_BOUNDS, D.FLOAT64):174                left = replace(left, unit=right.unit, unit_status=right.unit_status)175            if right.constant and not left.constant and right.dtype in (*INTEGER_BOUNDS, D.FLOAT64):176                right = replace(right, unit=left.unit, unit_status=left.unit_status)177        a, b = units.resolve_unit(left.unit), units.resolve_unit(right.unit)178        if a is None or b is None:179            # 未知组合继续要求明确标签,不能借已知输出单位伪造关系证明。180            return super()._units(operator, left, right, node, declared_unit)181        try:182            if operator in ('add', 'sub', 'compare'):183                roles = (a.temperature_role, b.temperature_role)184                if operator != 'compare' and 'absolute' in roles:185                    if roles == ('absolute', 'absolute') and operator == 'sub':186                        result_unit = 'delta_K'187                    elif roles == ('absolute', 'delta') or (roles == ('delta', 'absolute') and operator == 'add'):188                        result_unit = 'K'189                    else:190                        raise ValueError('绝对温度仅允许相减得到温差,或加减温差;裸常量按当前温度单位解释')191                else:192                    units.conversion(a, b)193                    result_unit = '1' if operator == 'compare' else units.canonical_unit(a).symbol194                return self._canonical(left, node), self._canonical(right, node), result_unit195            # 必须先检查偏移单位,不能用自动转K绕过摄氏乘除限制。196            combined = units.multiply(a, b) if operator == 'mul' else units.divide(a, b)197            return self._canonical(left, node), self._canonical(right, node), units.canonical_unit(combined).symbol198        except (TypeError, ValueError) as error:199            self._error(node, str(error))200201    def _binary(self, operator, left, right, node, declared_unit=None):202        """复用原定宽编译规则,并按新目录确定结果单位状态。203204        参数: operator、左右节点、node及可选声明标签与基类相同。205        返回: 不可变数值/比较节点。206        异常: 原类型校验和新单位校验错误直接传播。207        """208        # 单位转换产生的float提升由基类显式标志接线,不使用NumPy隐式promotion。209        return self._known_node(super()._binary(operator, left, right, node, declared_unit))210211    def _where(self, node):212        """编译只在条件已知时选取一条数值分支的节点。213214        参数: node为where调用AST;三个位置参数都必须能通过静态检查。215        返回: 包含完整依赖的节点,两条分支已统一单位和dtype。216        异常: ValueError为签名、非布尔条件、类型/量种不兼容或字面量越界。217        """218        # 即使条件为常量,未选路径同样要做名称、安全、类型和单位检查。219        if len(node.args) != 3 or node.keywords:220            self._error(node, 'where需要三个位置参数:条件、真分支、假分支')221        condition, left, right = (self.visit(arg) for arg in node.args)222        if condition.dtype is not D.BOOL:223            self._error(node.args[0], 'where条件必须是BOOL')224        if any(branch.dtype not in (*INTEGER_BOUNDS, D.FLOAT64) for branch in (left, right)):225            self._error(node, 'where的两个分支都必须是数值')226        # abs(超宽整数)等子树即使永远不被选中也不能隐藏非法输入。整数比较227        # 允许的无界阈值已编译成专用节点字段,不是需要按定宽装箱的const叶。228        check_integer_leaves(self, (condition, left, right), node)229230        # 使用比较所需的同量种关系:绝对温度可以互选,但不能与温差互选。231        # 它还会先把裸常量解释为另一侧原声明单位,之后才换到共同基准。232        left, right, _ = self._units('compare', left, right, node)233        if (all(b.constant or b.typed_constant for b in (left, right))234                and left.dtype in INTEGER_BOUNDS and right.dtype in INTEGER_BOUNDS):235            # 两侧纯整数字面量对称选择能容纳正大数的共同定宽。236            target = D.UINT64 if D.UINT64 in (left.dtype, right.dtype) else D.INT64237            anchor = ExprNode('const', target, '1', 'verified', value=0, constant=True)238            left, right = (self._context(b, anchor, node, arithmetic=True) for b in (left, right))239        else:240            # 常量适配不放宽一般整数/浮点信号或不同有符号性的类型约束。241            left = self._context(left, right, node, arithmetic=True)242            right = self._context(right, left, node, arithmetic=True)243        if left.dtype is not right.dtype:244            # 浮点字面量、消息参数或单位换算可要求提升另一侧;精度在活动行检查。245            allow_float = any(b.dtype is D.FLOAT64 and (b.constant or b.typed_constant or b.conversion_promoted246                              or self.message_domain and not b.aliases) for b in (left, right))247            if not allow_float:248                self._error(node, 'where数值分支dtype不兼容')249            left, right = (b if b.dtype is D.FLOAT64 else self._known_node(_node('to_float', D.FLOAT64, b.unit, (b,)))250                           for b in (left, right))251        return self._known_node(_node('where', left.dtype, left.unit, (condition, left, right)))252253    def visit(self, node, declared_unit=None):254        """解析已登记的v4单位、选择与数学函数,其余受限语法沿原访问器。255256        参数: node为AST节点;declared_unit为根目标标签。257        返回: 已检查类型、单位和依赖的表达式节点。258        异常: ValueError为签名、非字面量单位、非法数值或未知函数。259        """260        # 数学函数在编译期确定所有参数的单位和类型;属性调用仍由原白名单拒绝。261        if (self.extensions is not None and isinstance(node,ast.Call) and isinstance(node.func,ast.Name)262                and node.func.id in self.extensions.functions):263            from .extension_points import compile_host_function264            return compile_host_function(self,node,self.extensions.functions[node.func.id])265        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in MATH_FUNCTIONS:266            return compile_math(self, node)267        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'lookup':268            return compile_lookup(self, node)269        # 原码节点只接受带来源的枚举声明;普通整数函数独立检查类型及位宽。270        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'enum_code':271            return compile_enum_code(self, node)272        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in INTEGER_FUNCTIONS:273            return compile_integer(self, node)274        # where是控制求值的专门节点。275        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id == 'where':276            return self._where(node)277        if isinstance(node, ast.Call) and isinstance(node.func, ast.Name) and node.func.id in ('quantity', 'to_unit'):278            if len(node.args) != 2 or node.keywords:279                self._error(node, f'{node.func.id}需要两个位置参数')280            unit_ast = node.args[1]281            if not isinstance(unit_ast, ast.Constant) or type(unit_ast.value) is not str:282                self._error(unit_ast, '单位必须为字符串字面量')283            target = units.resolve_unit(unit_ast.value)284            if target is None:285                self._error(unit_ast, 'quantity/to_unit需要已登记单位')286            child = self.visit(node.args[0])287            if child.dtype not in (*INTEGER_BOUNDS, D.FLOAT64):288                self._error(node, 'quantity/to_unit只支持数值')289            if node.func.id == 'quantity':290                # 带单位常量不再参与裸常量单位继承;保留原整数字面量精度。291                if child.operator != 'const' or not child.constant:292                    self._error(node, 'quantity的数值必须为有限字面量')293                if child.dtype in INTEGER_BOUNDS and not INTEGER_BOUNDS[child.dtype][0] <= child.value <= INTEGER_BOUNDS[child.dtype][1]:294                    self._error(node, 'quantity字面量超出64位范围')295                label = units.output_label(target, unit_ast.value)296                return replace(child, unit=label, constant=False, unit_explicit=True, typed_constant=True)297            converted = self._convert(child, unit_ast.value, node, explicit=True)298            # 显式选为无量纲的常量不能再被相邻信号解释为电压或电流。299            return replace(converted, constant=False, typed_constant=True) if child.constant else converted300        # 原访问器负责语法安全、逻辑、abs及isclose;新单位状态由同一目录确定。301        result = super().visit(node, declared_unit)302        return self._known_node(result) if result.args else result303
Chronaxis 用户手册 · 离线阅读