Chronaxis用户手册English
本页目录

chronaxis.planning.intervals

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""声明计算所用的间隔及可选诊断,不读取报文或建立额外时间轴。23输入是配置中的intervals/diagnostics和已编译计划。输出为冻结选择,4记录每个消费者采用actual、fixed或dbc,以及明确的驱动信号。5DBC周期只在运行初始化读取;这里检查来源种类和完整报文定位。6当前配置3支持这些声明;执行是否就绪由公共能力报告判断。7"""89from ..core.moments import TIME_MOMENTS10from dataclasses import dataclass, replace1112from ..core.validation import require_identifier13from ..sources.can_spec import can_field_from_key14from .checks import PlanValidationError, duration_at, model_at, object_at15from .sequences import sequence_dependencies161718@dataclass(frozen=True)19class IntervalChoice:20    """选择一次计时依据;step_ns仅用于fixed,driver为原信号别名。2122    actual沿既有真实时间推进,不额外指定驱动。fixed/dbc的驱动可以在23    配置解析时按唯一候选补齐;不在对象中保存逐帧时间或DBC实例。24    """2526    mode: str = 'actual'  # 实际差、固定值或运行时读取的DBC周期。27    step_ns: int | None = None  # 仅fixed使用的正纳秒数;DBC在这里不提前填值。28    driver: str | None = None  # 推进累计的原信号别名;不存另一列时间。2930    def __post_init__(self):31        """拒绝矛盾字段、布尔间隔及溢出;无I/O或隐式数值转换。3233        参数: 无,读取mode、step_ns和driver。返回: None。异常: TypeError/ValueError为模式、纳秒范围或信号名错误。副作用: 无。34        """35        if type(self.mode) is not str or self.mode not in ('actual', 'fixed', 'dbc'):36            raise ValueError('interval.mode需要actual、fixed或dbc')37        if self.mode == 'fixed':38            if type(self.step_ns) is not int or not 0 < self.step_ns <= (1 << 63) - 1:39                raise ValueError('fixed间隔需要int64范围内正整数纳秒')40        elif self.step_ns is not None:41            raise ValueError('只有fixed接受间隔数值')42        if self.driver is not None:43            require_identifier(self.driver, 'interval.driver')44            if self.mode == 'actual':45                raise ValueError('actual沿既有更新时间推进,不接受driver')464748@dataclass(frozen=True)49class IntervalPlan:50    """保存任务默认、显式覆盖及已解析的消费者选择。5152    三组数据均为有限配置,不是样本历史。resolved按声明顺序列出实际53    消费者;legacy_sum自带period,不进入普通间隔解析。54    """5556    default: IntervalChoice = IntervalChoice()  # 任务级缺省选择。57    overrides: tuple[tuple[str, IntervalChoice], ...] = ()  # 按稳定节点ID的显式选择。58    resolved: tuple[tuple[str, IntervalChoice], ...] = ()  # 当前消费者的唯一有效选择。5960    def __post_init__(self):61        """检查冻结容器、唯一节点与选择类型,防止typed替换引入可变对象。6263        参数: 无,读取default、overrides及resolved。返回: None。异常: TypeError/ValueError为非冻结选择、节点重复或非法名称。副作用: 无。64        """65        if type(self.default) is not IntervalChoice:66            raise TypeError('intervals.default需要IntervalChoice')67        for label, entries in (('overrides', self.overrides), ('resolved', self.resolved)):68            if type(entries) is not tuple:69                raise TypeError('intervals.' + label + '需要tuple')70            names = set()71            for entry in entries:72                if (type(entry) is not tuple or len(entry) != 273                        or type(entry[1]) is not IntervalChoice):74                    raise TypeError('interval选择需要(node_id, IntervalChoice)')75                require_identifier(entry[0], 'interval.node_id')76                if entry[0] in names:77                    raise ValueError('interval节点重复:' + entry[0])78                names.add(entry[0])798081@dataclass(frozen=True)82class DiagnosticPlan:83    """分别声明详细追溯、完整性诊断及现场留存,全部默认关闭。8485    这些开关不取消必要输入检查,不代替outputs.contributions,彼此不捆绑。86    此对象不分配暂存或执行核验;实际资源能力接线前必须拒绝执行。87    """8889    trace: bool = False  # 请求详细追溯,独立于贡献明细。90    integrity: bool = False  # 请求额外完整性诊断,不代替必要输入检查。91    retain_work: bool = False  # 请求保留本次自有现场,不授权保留无关用户数据。9293    def __post_init__(self):94        """仅接受原生bool;不把0/1或文本解释成开关。9596        参数: 无,读取三个独立开关。返回: None。异常: TypeError为任一值不是原生bool。副作用: 无。97        """98        if any(type(value) is not bool for value in (self.trace, self.integrity, self.retain_work)):99            raise TypeError('diagnostics各项需要bool')100101102def parse_diagnostics(value) -> DiagnosticPlan:103    """解析可选诊断对象;未知键或非bool报配置错误,不打开文件。104105    参数: value(dict)为可省略的三个bool开关。返回: DiagnosticPlan,省略项为False。异常: PlanValidationError为未知键或非bool。副作用: 无。106    """107    raw = object_at(value, '$.diagnostics', (), ('trace', 'integrity', 'retain_work'))108    return model_at(DiagnosticPlan, '$.diagnostics', **raw)109110111def _choice(value, path) -> IntervalChoice:112    """将显式单位数值解析为纳秒;不查询DBC或猜测缺失的fixed数值。113114    参数: value(dict)为mode及可选带单位value/driver;path(str)定位错误。返回: IntervalChoice,固定间隔以整数ns保存。异常: PlanValidationError为缺项、矛盾、单位或类型错误。副作用: 无。115    """116    raw = object_at(value, path, ('mode',), ('value', 'driver'))117    if 'driver' in raw and type(raw['driver']) is not str:118        raise PlanValidationError(path + '.driver', '显式driver需要原信号名称,不能为null或其他类型')119    if (raw['mode'] == 'fixed') != ('value' in raw):120        raise PlanValidationError(path + '.value', 'fixed必须给value,其他模式不接受value')121    step = duration_at(raw['value'], path + '.value', positive=True) if 'value' in raw else None122    return model_at(IntervalChoice, path, raw['mode'], step, raw.get('driver'))123124125def _consumers(plan):126    """列出规则、配对、窗口和需时长指标的原信号依赖,保持计划顺序。127128    返回(node_id, aliases)元组。样本min/max/select与旧逐消息求和没有129    普通dt消费者;对它们单独配置覆盖属于无效需求,不能静默忽略。130131    参数: plan(AnalysisPlan)为已校验引用的计划。返回: tuple,每项为节点ID及原信号别名tuple,无消费者为空。异常: 错误typed引用的KeyError/StopIteration由外层验证报告捕获。副作用: 无。132    """133    rules = {rule.rule_id: rule for rule in plan.rules}134    # 纯上升/下降沿不使用帧间时长,不能因任务默认fixed而强迫它选择驱动。135    entries = [(rule.rule_id, rule.dependencies) for rule in plan.rules136               if rule.rule_type not in ('rising_edge', 'falling_edge')]137    entries.extend((item.sequence_id, sequence_dependencies(item, plan.rules, plan.bindings))138                   for item in plan.sequences)139    entries.extend((item.window_id, rules[item.event].dependencies) for item in plan.windows)140    entries.extend((t.name, t.physical) for t in plan.transforms)141    for item in plan.calculations:142        if item.operation not in ('integral', 'duration', 'time_weighted_mean', 'min_time', 'max_time', *TIME_MOMENTS):143            continue144        aliases = {alias for expression in (item.signal, item.condition)145                   if expression is not None for alias in expression.dependencies}146        # event/segment的区间也由父规则决定,不能漏掉其唯一驱动或多源歧义。147        if item.scope != 'session':148            scope, target = item.scope.split(':', 1)149            event = target if scope == 'event' else next(window.event for window in plan.windows if window.window_id == target)150            aliases.update(rules[event].dependencies)151        entries.append((item.calculation_id, tuple(sorted(aliases))))152    if plan.transforms:153        from .transforms import physical_dependencies154        return tuple((name,physical_dependencies(plan,aliases)) for name,aliases in entries)155    return tuple(entries)156157158def _driver_key(plan, alias):159    """按真实报文定位合并同帧信号候选;已解析信号各自独立。160161    返回来源与报文身份或来源与字段;只读已校验声明,不加载DBC。162163    参数: plan为当前计划,alias为原信号别名。返回: tuple物理更新身份。异常: 无效别名/报文定位的ValueError或缺来源StopIteration。副作用: 无。164    """165    definition = plan.bindings.resolve(alias)166    source = next(item for item in plan.sources if item.name == definition.source)167    if source.source_type in ('can_blf', 'can_asc', 'asc_or_blf', 'raw_can_csv'):168        field = can_field_from_key(definition.field)169        return (source.name, field.channel, field.frame_id, field.is_extended, field.is_fd)170    return (source.name, definition.field)171172173def _resolve(plan, choice, aliases, path):174    """选定唯一驱动,检查DBC适用性;多候选/常量无驱动时要求明确填写。175176    参数: plan为当前计划;choice为IntervalChoice;aliases为原依赖tuple;path为错误位置。返回: 补齐driver的IntervalChoice,actual原样返回。异常: PlanValidationError为歧义、未知驱动、时基不符或DBC不适用。副作用: 无。177    """178    if choice.mode == 'actual':179        return choice180    driver = choice.driver181    if driver is None:182        candidates = {}183        for alias in aliases:184            candidates.setdefault(_driver_key(plan, alias), alias)185        if len(candidates) != 1:186            raise PlanValidationError(path + '.driver', '需要显式指定驱动信号;候选数量=' + str(len(candidates)))187        driver = next(iter(candidates.values()))188    # 显式驱动允许作为独立时钟信号,但必须是当前原信号,而非派生名称。189    declared = {item.definition.name: item.definition for item in plan.signals}190    if driver not in declared:191        raise PlanValidationError(path + '.driver', 'driver必须是当前已声明的原信号')192    source = next(item for item in plan.sources if item.name == declared[driver].source)193    # 独立时钟驱动也必须与表达式来源可比较,不能把不同相对起点直接计成同一段。194    model_at(plan.bindings.require_synchronized, path + '.driver',195             tuple(dict.fromkeys((*aliases, driver))))196    if choice.mode == 'dbc' and source.source_type not in ('can_blf', 'can_asc', 'asc_or_blf', 'raw_can_csv'):197        raise PlanValidationError(path + '.mode', 'DBC周期仅适用于原始报文,已解析数据不可选择')198    return replace(choice, driver=driver)199200201def resolve_intervals(plan, default, overrides) -> IntervalPlan:202    """把有限声明解析为消费者选择;typed校验与JSON解析共用本函数。203204    输入是已构造的计划和冻结选择;返回新的IntervalPlan。未知节点、205    无用覆盖或不适用DBC报ValueError/PlanValidationError;无数据读取。206207    参数: plan为当前计划;default为IntervalChoice;overrides为(node_id, choice)冻结tuple。返回: IntervalPlan,resolved保持消费者声明顺序。异常: TypeError/ValueError及PlanValidationError为非法声明或不明确驱动。副作用: 无。208    """209    declared = IntervalPlan(default, overrides)210    consumers = _consumers(plan)211    names = {name for name, _ in consumers}212    unknown = set(dict(overrides)) - names213    if unknown:214        raise PlanValidationError('$.intervals.overrides', '不是间隔消费者:' + ', '.join(sorted(unknown)))215    if not consumers and default != IntervalChoice():216        raise PlanValidationError('$.intervals.default', '本任务没有普通间隔消费者;legacy_sum保留表达式period')217    lookup = dict(declared.overrides)218    resolved = tuple((name, _resolve(plan, lookup.get(name, default), aliases,219                                    '$.intervals.overrides.' + name)) for name, aliases in consumers)220    # 每个宿主实现声明自己的周期能力,固定及DBC与内置变换使用同一驱动和折算权重。221    modes = dict(resolved)222    for transform in plan.transforms:223        if transform.host_spec is not None and modes[transform.name].mode not in transform.host_spec.interval_modes:224            raise PlanValidationError('$.intervals.overrides.'+transform.name,225                                      f'宿主时序扩展 {transform.operation} 不支持所选间隔,支持 {transform.host_spec.interval_modes}')226    return IntervalPlan(default, overrides, resolved)227228229def parse_intervals(value, plan) -> IntervalPlan:230    """解析任务默认和节点覆盖;显式null与未知字段均拒绝,缺省使用actual。231232    参数: value(dict)为default及overrides;plan为已解析消费者的计划。返回: IntervalPlan。异常: PlanValidationError为结构、取值或引用错误。副作用: 无。233    """234    raw = object_at(value, '$.intervals', (), ('default', 'overrides'))235    default = _choice(raw.get('default', {'mode': 'actual'}), '$.intervals.default')236    overrides = raw.get('overrides', {})237    if type(overrides) is not dict:238        raise PlanValidationError('$.intervals.overrides', '需要以节点ID为键的对象')239    entries = tuple((name, _choice(choice, '$.intervals.overrides.' + name))240                    for name, choice in overrides.items())241    return model_at(resolve_intervals, '$.intervals', plan, default, entries)242243244def require_interval_declarations(plan):245    """核对类型化计划中的间隔和诊断声明未被替换为不一致的解析结果。246247    参数: plan为待验证的AnalysisPlan。返回: None。异常: TypeError/ValueError为版本、新类型或解析结果不一致;引用错误由公共报告收集。副作用: 无,不重编译表达式。248    """249    if type(plan.intervals) is not IntervalPlan or type(plan.diagnostics) is not DiagnosticPlan:250        raise TypeError('当前计划需要IntervalPlan与DiagnosticPlan')251    expected = resolve_intervals(plan, plan.intervals.default, plan.intervals.overrides)252    if expected != plan.intervals:253        raise ValueError('intervals.resolved与当前消费者或驱动不一致')254
Chronaxis 用户手册 · 离线阅读