Chronaxis用户手册English
本页目录

chronaxis.core.binding

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""将内存来源的原字段与当前计划的信号别名明确关联,并保存可追溯身份。23输入为SessionSpec、SignalDefinition、派生名称和可选同步证据;输出不可变的4SignalBindings及SHA-256身份。先检查信号转换声明,再检查名称冲突、实际字段5存在性和来源归属;联合计算另按实际依赖检查公共时间基准。身份包含实际内存6内容、时间映射、转换、有效期和策略,避免同名不同输入混用。7本模块只依赖标准库及核心模型,不读取文件,不加载DBC,不持有全局缓存;8样本转换、保持和表达式计算由相应执行模块完成。9"""1011# 冻结数据类存元数据,哈希只处理明确的不可变原生输入。12from dataclasses import dataclass, field as dataclass_field13from hashlib import sha2561415# 共享已校验模型和精确时间协议,不依赖被保护的旧链。16from .models import SessionSpec, SourceRef, SourceSpec, TimebaseSpec17from .types import BASE_FORBIDDEN_QUALITY, SignalDType, TimeType18from .units import require_number19from .unit_conversion import binding_version, converted_dtype, require_config_version, validate_conversion_v420from .unit_registry import REGISTRY_VERSION21from .lookup import LookupTablePlan, validate_lookups22from .validation import require_identifier, require_scalar, require_text23from ..timebase.conversion import require_int64_ns242526def _identity(value) -> str:27    """把已知不可变元数据编码为带类型的确定身份文本。2829    参数: value为本模块内部的原生标量或tuple,可含无效原NaN/Inf。30    返回: str;保留bool/int区别及浮点位值,tuple用长度前缀防歧义。31    异常: TypeError为内部未声明结构;不读取文件或执行任意对象repr。32    """33    # tuple逐项编码并保留结构,避免分隔符碰撞。34    if isinstance(value, tuple):35        # 局部输出只用于计划身份,不是产品JSON序列化。36        parts = tuple(_identity(item) for item in value)37        # 每段字符长度明确,字段中的点号/冒号不会改变层级。38        return "t" + "".join(f"{len(item)}:{item}" for item in parts)39    # None与缺失观测保留明确类型。40    if value is None:41        # 不将None等价为0或空文本。42        return "n"43    # 原生类型分别编码,不让True与1共享身份。44    if type(value) is bool:45        # 布尔身份只存在两个值。46        return "b1" if value else "b0"47    # Python整数保留全部位值,不经float。48    if type(value) is int:49        # 负号和十进制值构成精确身份。50        return "i" + str(value)51    # float.hex保留-0及有限浮点身份,诊断NaN/Inf可稳定表示。52    if type(value) is float:53        # NaN不参与普通有效计算,但可存在原始行的输入身份。54        return "f" + value.hex()55    # 已校验文本原样保留,大小写及semantic点号不归一化。56    if type(value) is str:57        # 字符串类型标记避免与整数文本同一身份。58        return "s" + value59    # 只允许本模块明确构造的身份值。60    raise TypeError("绑定身份含未声明结构")616263def _source_identity(source: SourceSpec) -> str:64    """保存内存来源定位、时间映射、观测域和实际行内容的身份。6566    参数: source为已校验SourceSpec;原时间单位ns,原sequence不变。67    返回: SHA256文本;不同原内容不会只因同名来源而复用绑定。68    异常: TypeError为内部结构错误;无文件或旧reader副作用。69    """70    # 字典字段按名称规范排列,仅用于身份,不改变更新顺序。71    rows = tuple((row.time_ns, row.sequence, tuple(sorted(row.values.items()))) for row in source.rows)72    # 保留原始坐标/offset/范围,不把局部从0开始视为公共起点。73    payload = (source.name, source.source_type.value, source.timebase.timebase_id, source.timebase.time_type.value,74               source.timebase.origin, source.offset_ns, source.observation_start_ns,75               source.observation_end_ns, rows)76    # SHA输入完全来源于冻结模型,不调用任意外部序列化器。77    return sha256(_identity(payload).encode("utf-8")).hexdigest()787980@dataclass(frozen=True)81class SignalDefinition:82    """声明原字段如何映射为分析信号,以及转换后值的类型、单位和有效期。8384    name是表达式使用的简单别名,field保留原字段文本;valid_for_ns为正的有限85    有效期,None仅表示声明阶段等待DBC周期,不能交给实际状态计算。86    choices保存不可变的原编码与目标标签或bool配对。基础质量策略87    拒绝全部非零质量位,不接受任意质量策略。方向、位置及可选语义信息只保存88    调用者声明,不推断业务含义;本对象不保存随时间变化的值。89    input_sign_convention等方向证据属于核心模型元数据,不能据此认为公开JSON90    配置已经支持同名扩展字段。91    """9293    name: str  # 当前计划简单别名。94    source: str  # 会话内显式来源名称。95    field: str  # memory原字段,不替换点号。96    dtype: SignalDType  # 转换后逻辑类型。97    unit: str  # 转换后单位,未知标签保留未校验状态。98    valid_for_ns: int | None  # 有限有效期,单位纳秒;None为尚待DBC周期的声明。99    input_unit: str | None = None  # 缺省使用unit,不推测原DBC单位。100    scale: int | float = 1  # 已解码值之后额外转换,包含方向因子。101    offset: int | float = 0  # 已解码值之后额外偏移。102    choices: tuple[tuple[int | bool | str, str | bool], ...] = ()  # 明示编码映射。103    valid_range: tuple[int | float, int | float] | None = None  # 转换后业务范围。104    invalid_values: tuple[int | float | bool | str | None, ...] = ()  # 明示原输入无效值。105    sample_policy: str = "backward_hold"  # 首版只实施有限向后保持。106    same_time_policy: str = "error"  # 可显式选择last_by_sequence,由状态模块按来源序号取最后值。107    semantic_id: str | None = None  # 可选领域字符串,不是DSL路径。108    semantic_contract_version: str | None = None  # 声明身份,不证明契约通过。109    mapping_version: str | None = None  # 当前映射版本身份。110    sign_convention: str | None = None  # 转换后方向,由调用者声明。111    measurement_location: str | None = None  # 可选测量位置,不从名称猜测。112    input_sign_convention: str | None = None  # 转换前方向证据,core typed元数据。113    direction_factor: int = 1  # 方向不变1,显式反转-1。114    config_version: int = dataclass_field(default=4, kw_only=True)  # 规则版本;低层系数已确定,不按省略推导。115    input_dtype: SignalDType | None = dataclass_field(default=None, kw_only=True)  # 配置推导前类型;不新增用户字段。116    code_dtype: SignalDType | None = dataclass_field(default=None, kw_only=True)  # 仅 v4 枚举的原始整数类型。117    capture_code: bool = dataclass_field(default=False, kw_only=True, compare=False, repr=False)  # 当前执行投影要求。118    unit_status: str = dataclass_field(init=False)  # 构造校验所得单位状态。119120    def __post_init__(self) -> None:121        """校验完整声明并冻结默认输入单位及精确有效期。122123        参数: 无额外参数;valid_for_ns为正int64纳秒,或尚待DBC周期的None。124        返回: None;unit_status为verified/unverified,未修改输入值。125        异常: TypeError为字段结构;ValueError为缺项/策略/映射/转换冲突;126            TimeOverflowError为有效期超界。数值样本失败由数值层生成质量原因。127        """128        # 配置版本决定单位规则,不能因新增目录改变旧声明。129        require_config_version(self.config_version)130        if self.code_dtype is not None:131            from .enum_codes import validate_code_dtype132            if self.config_version != 4 or self.dtype is not SignalDType.ENUM:133                raise ValueError('code_dtype 仅适用于 v4 枚举信号')134            validate_code_dtype(self.code_dtype, self.choices)135        if type(self.capture_code) is not bool or self.capture_code and self.code_dtype is None:136            raise ValueError('保留原始枚举码需声明 code_dtype')137        # 别名和来源都服从简单名称,不接受属性路径。138        require_identifier(self.name, "signal.name")139        # 当前会话显式定位来源,不能继承上次任务。140        require_identifier(self.source, "signal.source")141        # 原字段独立保留文本,合法同名绑定由名称空间校验。142        require_text(self.field, "signal.field")143        # dtype不是字符串占位,也不从值推断。144        if not isinstance(self.dtype, SignalDType):145            # 未解析的配置不能冒充typed定义。146            raise TypeError("signal.dtype必须为SignalDType")147        # 原始声明类型仅用于证明单位换算引起的提升,低层调用不猜缺省来源类型。148        if self.input_dtype is not None:149            if not isinstance(self.input_dtype, SignalDType):150                raise TypeError("input_dtype必须为SignalDType或None")151            if self.config_version != 4 or converted_dtype(self.input_dtype, self.scale, self.offset) is not self.dtype:152                raise ValueError("input_dtype与已确定的单位转换类型不一致")153        # 目标单位必填,未知标签可以显式未校验。154        require_text(self.unit, "signal.unit")155        # 默认表示没有额外单位变化,不补DBC信息。156        input_unit = self.unit if self.input_unit is None else require_text(self.input_unit, "signal.input_unit")157        # 声明可以等待DBC;已有数值仍须精确且有限,0不代表无限保持。158        valid_for_ns = (None if self.valid_for_ns is None else159                        require_int64_ns(self.valid_for_ns, "valid_for_ns"))160        if valid_for_ns is not None and valid_for_ns <= 0:161            # 错误工程声明执行前失败。162            raise ValueError("valid_for_ns必须大于0")163        # 转换系数拒绝bool、非有限和任意对象。164        require_number(self.scale, "signal.scale")165        # 偏移同样在配置入口校验。166        require_number(self.offset, "signal.offset")167        # 方向因子只支持明确不变或反转。168        if type(self.direction_factor) is not int or self.direction_factor not in (-1, 1):169            # 不接受truthiness或任意工程倍率作为方向。170            raise ValueError("direction_factor必须为1或-1")171        # 可选领域/版本/位置只保存非空原字符串,不建立全局目录。172        for key in ("semantic_id", "semantic_contract_version", "mapping_version", "sign_convention",173                    "measurement_location", "input_sign_convention"):174            # 只读取已声明dataclass字段,不用于表达式对象访问。175            value = getattr(self, key)176            # None表示未声明,不伪造缺省契约。177            if value is not None:178                # 空声明不等于有效可选元数据。179                require_text(value, f"signal.{key}")180        # 契约版本没有语义声明时不能独立伪装为绑定证明。181        if self.semantic_contract_version is not None and self.semantic_id is None:182            # 版本须关联可选语义身份。183            raise ValueError("semantic_contract_version需要semantic_id")184        # 明示反转同时保留原/目标方向,不从负scale猜测业务。185        if self.direction_factor == -1 and (self.input_sign_convention is None or self.sign_convention is None):186            # 缺少方向证据的反转不能用于契约校验。187            raise ValueError("方向反转必须声明转换前和转换后方向")188        # 只接受向后保持及明确的同时间策略,行为由状态模块实现。189        if self.sample_policy != "backward_hold" or self.same_time_policy not in ("error", "last_by_sequence"):190            # 不接受插值/未实现策略作为已支持声明。191            raise ValueError("首版仅支持backward_hold及error/last_by_sequence")192        # 固定不可变容器,避免后续外部修改映射或无效值。193        if not isinstance(self.choices, tuple) or not isinstance(self.invalid_values, tuple):194            # 不隐式接受可变dict/list。195            raise TypeError("choices/invalid_values必须为tuple")196        # 输入无效值保留类型,不让bool自动等同整数编码。197        for value in self.invalid_values:198            # 无效原标量可含NaN,数值层另行标记NONFINITE。199            require_scalar(value)200        # 编码身份类型化,避免Python的True==1掩盖冲突。201        keys = set()202        # 每条映射都需明确原编码和目标标签。203        for choice in self.choices:204            # tuple配对之外的结构不可定位。205            if not isinstance(choice, tuple) or len(choice) != 2:206                # 配置层不能传入半个声明。207                raise TypeError("choices各项必须为二元tuple")208            # 明示映射两端,不按顺序自动编号。209            code, label = choice210            # 原编码只接可精确比较的固定类型。211            if type(code) not in (int, bool, str):212                # 浮点/容器不能作为首版枚举编码。213                raise TypeError("choices原编码仅支持int/bool/str")214            # 类型加值构成明确原编码身份。215            key = (type(code), code)216            # 重复编码无法唯一定位业务标签。217            if key in keys:218                # 不取第一条或最后一条隐式覆盖。219                raise ValueError("choices原编码重复")220            # 当前声明中的唯一编码在局部集合登记。221            keys.add(key)222            # enum目标是公开逻辑标签,不允许数值别名。223            if self.dtype is SignalDType.ENUM:224                # 标签保留大小写和领域文本。225                require_text(label, "choices.label")226            elif self.dtype is SignalDType.BOOL:227                # 任意数值truthiness不属于声明布尔映射。228                if type(label) is not bool:229                    # 1不是合法目标True。230                    raise TypeError("bool choices目标必须为bool")231            else:232                # 数值信号的编码可作为数值处理,不套业务标签映射。233                raise ValueError("choices仅用于enum或bool")234        # enum必须有明确编码/标签域,不接任意字符串。235        if self.dtype is SignalDType.ENUM and not self.choices:236            # 空域不是可执行枚举声明。237            raise ValueError("enum需要非空choices")238        # 标签可由多个编码指向同一含义,域按第一次出现顺序稳定保留。239        if self.dtype in (SignalDType.BOOL, SignalDType.ENUM) and (self.scale != 1 or self.offset != 0):240            # 不对逻辑值施加数值缩放。241            raise ValueError("bool/enum不能声明数值转换")242        # 范围只支持转换后数值,两个端点都要明确。243        if self.valid_range is not None:244            # 不接列表或不完整端点。245            if not isinstance(self.valid_range, tuple) or len(self.valid_range) != 2:246                # 配置结构必须完整。247                raise TypeError("valid_range必须为二元tuple")248            # 逻辑bool/enum不得以数值区间验证业务含义。249            if self.dtype in (SignalDType.BOOL, SignalDType.ENUM):250                # 使用choices声明逻辑域。251                raise ValueError("bool/enum不能使用数值valid_range")252            # 范围端点原生有限,保留整数精度。253            lower = require_number(self.valid_range[0], "valid_range.lower")254            # 上限与下限采用相同入口。255            upper = require_number(self.valid_range[1], "valid_range.upper")256            # 不自动交换反向范围。257            if upper < lower:258                # 声明冲突须执行前拒绝。259                raise ValueError("valid_range上限必须不小于下限")260        # 已知单位校验关系,未知单位明确保留状态。261        status = validate_conversion_v4(input_unit, self.unit, self.scale, self.offset, self.direction_factor)262        # v4整数转换中的1.0等整数系数规范为int,保留安全定宽整数计算路径。263        if self.dtype in (SignalDType.INT64, SignalDType.UINT64):264            for coefficient in ('scale', 'offset'):265                value = getattr(self, coefficient)266                if type(value) is float and value.is_integer():267                    object.__setattr__(self, coefficient, int(value))268        # 冻结缺省输入单位,后续身份与转换都读取同一事实。269        object.__setattr__(self, "input_unit", input_unit)270        # 规范有效期为Python整数纳秒,不改变数值。271        object.__setattr__(self, "valid_for_ns", valid_for_ns)272        # 状态来自真实校验,不允许调用者填verified。273        object.__setattr__(self, "unit_status", status)274275    def identity(self) -> tuple:276        """返回声明字段完整身份,供执行计划缓存和追溯使用。277278        参数: 无;读取当前冻结声明。279        返回: tuple;含实际定位、转换、有效期、质量及语义/版本元数据。280        异常: 无主动异常;不使用全局缓存或当前样本值。281        """282        # 全部影响绑定/质量/单位的字段进入身份,基础质量固定九位全禁。283        payload = (self.name, self.source, self.field, self.dtype.value, self.unit, self.valid_for_ns,284                self.input_unit, self.scale, self.offset, self.choices, self.valid_range,285                self.invalid_values, self.sample_policy, self.same_time_policy, self.semantic_id,286                self.semantic_contract_version, self.mapping_version, self.sign_convention,287                self.measurement_location, self.input_sign_convention, self.direction_factor,288                self.unit_status)289        # 身份包含固定单位规则,避免不同规则的结果复用。290        if self.code_dtype is not None:291            payload += ('code_dtype', self.code_dtype.value)292        # 预设核对真实身份的末项质量策略;版本和原码类型不能占据该位置。293        return (*payload, self.config_version, REGISTRY_VERSION,294                None if self.input_dtype is None else self.input_dtype.value, BASE_FORBIDDEN_QUALITY)295296    @property297    def unit_conversion_promoted(self) -> bool:298        """说明浮点类型是否由已知整数输入的单位换算引起。299300        参数: 无。301        返回: 仅有整数输入证据且实际提升时为True。302        异常: 无;不依据样本数值猜类型。303        """304        # 显式FLOAT64但没有原输入声明时不能冒称整数提升。305        return (self.dtype is SignalDType.FLOAT64306                and self.input_dtype in (SignalDType.INT64, SignalDType.UINT64)307                and converted_dtype(self.input_dtype, self.scale, self.offset) is SignalDType.FLOAT64)308309310@dataclass(frozen=True)311class SynchronizationEvidence:312    """保存来源使用固定时间偏移进入公共分析时基的核实说明。313314    source指定会话内来源,analysis_timebase指定目标时基,evidence保留调用者315    提供的依据。本模型检查声明结构,不检查物理设备、不估计漂移或改动偏移。316    来源缺少同步依据时仍可独立分析;参与联合计算时须满足同步检查。317    """318319    source: str  # 明确会话内来源。320    analysis_timebase: TimebaseSpec  # offset映射后的公共分析基准。321    evidence: str  # 调用者声明的核实依据,不自动生成业务事实。322323    def __post_init__(self) -> None:324        """校验显式来源和时基证据结构。325326        参数: 无;读取source、analysis_timebase及evidence。327        返回: None;不更改来源或推断时间映射。328        异常: TypeError/ValueError为无效声明。329        """330        # 来源必须能在当前会话唯一定位。331        require_identifier(self.source, "synchronization.source")332        # 空依据不能当作已核实同步。333        require_text(self.evidence, "synchronization.evidence")334        # 目标分析时基也必须通过模型构造门槛。335        if not isinstance(self.analysis_timebase, TimebaseSpec):336            # 字典或字符串不提供完整公共起点身份。337            raise TypeError("analysis_timebase必须为TimebaseSpec")338339340@dataclass(frozen=True)341class SignalBindings:342    """检查并冻结一个会话的信号定义、实际来源和同步证据。343344    signals与derived_names共用简单名称空间,每个原字段必须出现在本会话内存行中。345    identity包含实际内容、时基、转换和策略,来源同名也不能替代完整身份。346    本对象没有全局注册表,不继承其他任务绑定;值更新由状态执行器读取冻结输入,347    不会修改本对象。348    """349350    session: SessionSpec  # 当前计划单会话来源上下文。351    signals: tuple[SignalDefinition, ...]  # 已声明原字段到别名的关联。352    derived_names: tuple[str, ...] = ()  # 实际派生定义由表达式层提供,此处只检查名称冲突。353    synchronization: tuple[SynchronizationEvidence, ...] = ()  # 明示固定offset依据。354    config_version: int | None = dataclass_field(default=None, kw_only=True)  # 缺省继承定义;空绑定默认4。355    extensions: object = dataclass_field(default=None, kw_only=True, repr=False)  # 仅接收宿主传入的不可变注册。356    lookups: tuple[LookupTablePlan, ...] = dataclass_field(default=(), kw_only=True)  # 数值标定表声明。357    identity: str = dataclass_field(init=False)  # 实际校验所得完整绑定SHA。358359    def __post_init__(self) -> None:360        """检查统一名称空间、来源定位和同步证据,再构建身份。361362        参数: 无;session/signals及可选derived_names/synchronization均为已校验且不可变的模型输入。363        返回: None;通过后所有字段保持不可变。364        异常: TypeError为结构;ValueError为名称、来源字段或证据冲突。365        """366        # 顶层结构不能以可变列表或未校验对象进入计划。367        if not isinstance(self.session, SessionSpec) or not all(isinstance(item, tuple) for item in368                (self.signals, self.derived_names, self.synchronization)):369            # 不隐式解析自由配置或无限迭代器。370            raise TypeError("绑定需要SessionSpec及tuple声明")371        # 来源名唯一性已由SessionSpec构造证明。372        sources = {source.name: source for source in self.session.sources}373        # 名称集合局部于当前计划,signals/derived不能各自重复。374        names = set()375        # 保留信号声明顺序,不取某个冲突项覆盖。376        for definition in self.signals:377            # 每项必须通过完整SignalDefinition校验。378            if not isinstance(definition, SignalDefinition):379                # 类名或dict占位不属于真实定义。380                raise TypeError("signals必须包含SignalDefinition")381            # 实际内存绑定没有待查询DBC,不允许未确定的有效期进入对齐内核。382            if definition.valid_for_ns is None:383                raise ValueError(f"{definition.name}有效期尚未确定;请提供period或valid_for")384            # 重复别名不能解析为唯一原字段。385            if definition.name in names:386                # 不自动加后缀或取第一项。387                raise ValueError(f"signal别名重复:{definition.name}")388            # 登记唯一别名供派生名称冲突检查。389            names.add(definition.name)390            # 来源必须在本会话存在,不能靠语义名称补齐。391            if definition.source not in sources:392                # 错误定位在编译前拒绝。393                raise ValueError(f"{definition.name}来源不存在:{definition.source}")394            # memory字段必须已在实际行中暴露,不猜列或读旧DBC。395            if not any(definition.field in row.values for row in sources[definition.source].rows):396                # 空来源没有已声明字段schema,不能声称字段可解析。397                raise ValueError(f"{definition.name}原字段不存在:{definition.field}")398        # 所有定义属于同一不可变版本;空绑定也可显式选择4。399        object.__setattr__(self, "config_version", binding_version(self.signals, self.config_version))400        validate_lookups(self.lookups, self.config_version)401        if self.extensions is not None:402            from ..extensions import ExtensionRegistry403            if type(self.extensions) is not ExtensionRegistry or self.config_version != 4:404                raise ValueError('extensions需要版本4及宿主ExtensionRegistry')405        # 派生声明也遵守同一简单别名规则。406        for name in self.derived_names:407            # 包括关键字及首版函数名冲突。408            require_identifier(name, "derived.name")409            # signals和derived统一校验,未生成表达式也不能隐藏同名。410            if name in names:411                # 不修改用户声明名称。412                raise ValueError(f"signals/derived名称冲突:{name}")413            # 保留派生名称登记事实,供后续表达式层接线。414            names.add(name)415        # 同步证据也只能一个来源一个明确目标。416        evidence_names = set()417        # 所有显式证据须关联实际来源和当前分析时基。418        for evidence in self.synchronization:419            # 保留typed证据结构,不接自由字典。420            if not isinstance(evidence, SynchronizationEvidence):421                # 不能以名称声明冒充完整同步依据。422                raise TypeError("synchronization需要SynchronizationEvidence")423            # 不存在/重复来源证据都是配置错误。424            if evidence.source not in sources or evidence.source in evidence_names:425                # 不默认选第一条同步说明。426                raise ValueError("同步证据来源不存在或重复")427            # 所有校准声明必须进入当前会话分析基准。428            if evidence.analysis_timebase != self.session.timebase:429                # 不接受指向其他会话时基的旧映射。430                raise ValueError("同步证据目标与会话分析时基不一致")431            # 记录该来源已有明确证据。432            evidence_names.add(evidence.source)433        # 完整实际输入身份避免跨任务同名不同数据误复用执行缓存。434        payload = (self.session.session_id, self.session.timebase.timebase_id, self.session.timebase.time_type.value,435                   self.session.timebase.origin, self.session.domain_start_ns, self.session.domain_end_ns,436                   tuple(_source_identity(source) for source in self.session.sources),437                   tuple(definition.identity() for definition in self.signals), self.derived_names,438                   tuple((item.source, item.analysis_timebase.timebase_id, item.analysis_timebase.time_type.value,439                          item.analysis_timebase.origin, item.evidence) for item in self.synchronization))440        # 即使没有信号,v4绑定也不能与旧空绑定共享身份。441        payload += (self.config_version, REGISTRY_VERSION)442        if self.extensions is not None:443            payload += ('host-extensions-v1', self.extensions.identity)444        if self.lookups:445            payload += ('lookup-table-v1', tuple(t.identity for t in self.lookups))446        # 身份由全部校验事实生成,不允许调用者提供假SHA。447        object.__setattr__(self, "identity", sha256(_identity(payload).encode("utf-8")).hexdigest())448449    def resolve(self, name: str) -> SignalDefinition:450        """解析当前计划真实信号别名,不自动寻找semantic候选。451452        参数: name为简单别名;派生名称由表达式层另行处理。453        返回: 对应冻结SignalDefinition。454        异常: ValueError为非法或未知别名;不访问Python对象属性。455        """456        # 与声明使用相同名称规则,不转换点号。457        require_identifier(name, "signal alias")458        # 当前会话逐项查找明确唯一引用。459        for definition in self.signals:460            # 比较原大小写完整别名。461            if definition.name == name:462                # 不返回其他任务中同名定义。463                return definition464        # 未注册名字不能推断为字段或领域语义。465        raise ValueError(f"未知信号别名:{name};可用:{', '.join(item.name for item in self.signals)}")466467    def source_ref(self, name: str) -> SourceRef:468        """给出当前会话下解析后的完整memory字段引用。469470        参数: name为已定义信号别名。471        返回: SourceRef;含当前session_id、source_name和原field。472        异常: ValueError为未定义或非法别名。473        """474        # 先证明引用属于当前定义,不从全局映射继承。475        definition = self.resolve(name)476        # 完整会话身份使跨会话同名输入可以明确区别。477        return SourceRef(self.session.session_id, definition.source, definition.field)478479    def require_synchronized(self, names: tuple[str, ...]) -> None:480        """在联合依赖执行前检查实际来源的公共分析基准。481482        参数: names为参与同一表达式/指标的原信号别名tuple;单来源允许独立分析。483        返回: None;公共起点或显式固定offset依据齐全时通过。484        异常: TypeError为容器;ValueError为未知别名或同步缺项。485        """486        # 不接会被调用者更改的依赖容器。487        if not isinstance(names, tuple):488            # 编译器需显式给出当前节点依赖。489            raise TypeError("联合依赖names必须为tuple")490        # 同一来源多个信号不会制造额外同步要求。491        used = {self.resolve(name).source for name in names}492        # 单来源可在自己坐标上独立分析。493        if len(used) <= 1:494            # 未使用来源不参与当前绑定的证据检查。495            return496        # 只检查实际参与联合计算的来源。497        sources = {source.name: source for source in self.session.sources if source.name in used}498        # 显式证据已在构造时证明指向当前分析基准。499        evidenced = {item.source for item in self.synchronization}500        # 没有校准声明的来源需直接拥有相同公共起点。501        for source in sources.values():502            # 明示证据提供固定offset映射依据,不估计漂移。503            if source.name in evidenced:504                # offset在SourceSpec已检查范围,执行器按原映射应用。505                continue506            # absolute统一UTC;relative需timebase_id/origin共同一致。507            same = source.timebase.time_type is self.session.timebase.time_type and (508                source.timebase.time_type is TimeType.ABSOLUTE or source.timebase == self.session.timebase)509            # 仅offset数值或各自首点0不证明公共起点。510            if not same:511                # 错误列出缺项来源,调用者可补核实声明或独立分析。512                raise ValueError(f"联合依赖缺少同步依据:{source.name};需要公共起点或固定offset核实证据")513
Chronaxis 用户手册 · 离线阅读