chronaxis.core.record_intervals
源码基线:8904c2f744951857659abd40aaa3535d57faf53c
1"""从相邻两次相关更新取得计算间隔,不创建另一套时间坐标。23调用者先按已解析的驱动选择相关更新,再逐条调用advance,包括值无效的更新。4首条只建立前序;actual取实际时间差,固定周期每次后续更新返回同一个间隔。5本模块不依赖CAN、DBC、文件边界或信号值,不保存消息历史、累计总数或输出证明。6DBC周期由接入层初始化核实后作为step_ns传入,不能在这里猜默认周期。7"""89from ..timebase.conversion import require_int64_ns10from .models import Interval111213def require_record_step(step_ns, actual_updates):14 """核实记录对周期入口,禁止把合成边界当成驱动更新。1516 参数: step_ns为None或正整数ns;actual_updates为已经校验的真实更新标志。17 返回: None。异常: 非法周期或非真实更新报错;不读取或保存记录。18 """19 if step_ns is not None:20 require_int64_ns(step_ns, 'interval.step_ns')21 if step_ns <= 0:22 raise ValueError('计算周期必须为正整数纳秒')23 if not all(actual_updates):24 raise ValueError('固定/DBC计算块只能包含所选驱动的真实更新,不能包含合成边界')252627def clipped_record_seconds(start_ns: int, end_ns: int, window: Interval, *,28 step_ns: int | None = None) -> float:29 """按实际交集折算一对记录用于积分或加权的秒数,不生成时间坐标。3031 参数: start_ns/end_ns为相关记录对的实际整数纳秒;window为真实请求Interval;32 step_ns=None取实际差,正整数取固定或已核实DBC周期。33 返回: float秒数。固定模式使用step_ns×实际交集/实际跨度,最后才换为秒;34 不取整到纳秒,无交集或零跨度返回0。普通连续统计的同刻更新由上层按35 same_time_policy归组,零跨度不形成连续面积;旧逐消息N次累计不调用本函数。36 异常: 类型、int64边界、倒退或非法周期错误;无状态、I/O或持久化副作用。37 """38 start = require_int64_ns(start_ns, 'interval.start_ns')39 end = require_int64_ns(end_ns, 'interval.end_ns')40 if end < start:41 raise ValueError('记录对时间回退')42 if not isinstance(window, Interval):43 raise TypeError('记录对裁剪需要实际时间Interval')44 if step_ns is not None:45 step_ns = require_int64_ns(step_ns, 'interval.step_ns')46 if step_ns <= 0:47 raise ValueError('计算间隔必须为正整数纳秒')48 overlap = min(end, window.end_ns) - max(start, window.start_ns)49 if overlap <= 0:50 return 0.051 if step_ns is None:52 # 不把绝对坐标转float,避免大时间戳吃掉细小间隔。53 return require_int64_ns(overlap, 'interval.overlap_ns') / 1_000_000_00054 # Python整数乘法和除法避免int64乘积溢出;只在最终秒数处舍入一次。55 return (step_ns * overlap) / ((end - start) * 1_000_000_000)565758class RecordIntervals:59 """一个消费者的一份前序状态,跨文件/批次复用,独立任务使用独立实例。6061 只保存必要的上一条实际时间及可选固定周期。即使选择固定周期,也用上一条62 实际时间拒绝倒退;不将累计周期伪装为实际时间,不保留第二列时间。63 """6465 __slots__ = ('_step_ns', '_previous_ns')6667 def __init__(self, step_ns: int | None = None):68 """创建相邻更新状态,不读取输入或分配历史容器。6970 参数: step_ns=None表示实际间隔;正整数表示已选固定或已核实的DBC周期。71 返回: 新状态。异常: TypeError、ValueError或TimeOverflowError为非法周期。72 """73 if step_ns is not None:74 step_ns = require_int64_ns(step_ns, 'interval.step_ns')75 if step_ns <= 0:76 raise ValueError('计算间隔必须为正整数纳秒')77 self._step_ns = step_ns78 self._previous_ns = None7980 def advance(self, time_ns: int) -> int | None:81 """接收一条相关更新,返回此前一对记录的间隔。8283 参数: time_ns为当前记录实际整数纳秒,已按所需驱动筛选;无效值不能跳过调用。84 返回: 首条None,随后actual为非负实际差,固定周期为正step_ns。同刻记录不去重。85 异常: 类型、int64溢出或时间倒退;失败前不修改前序状态。86 边界: 不接收文件/EOF通知,不补尾段;既有actual期限、裁剪和TTL由消费者处理。87 """88 current = require_int64_ns(time_ns, 'interval.time_ns')89 previous = self._previous_ns90 if previous is None:91 self._previous_ns = current92 return None93 if current < previous:94 raise ValueError('相关记录时间回退,不能重排或跨过该记录')95 # 固定周期无需计算实际差;跨度溢出只对确实使用actual差值的消费者有意义。96 step = (require_int64_ns(current - previous, 'interval.actual_ns')97 if self._step_ns is None else self._step_ns)98 self._previous_ns = current99 return step100101 def advance_seconds(self, time_ns: int, window: Interval) -> float | None:102 """接收相关更新并返回裁剪后的计算秒数,只保同一份前序状态。103104 参数: time_ns为当前实际时间;window为当前消费者实际请求范围。105 返回: 首条None,之后为交集折算秒数;窗口外更新仍推进前序,不能跳过。106 异常: 时间、窗口或裁剪错误;失败不修改前序。无文件边界重置或EOF补周期。107 """108 current = require_int64_ns(time_ns, 'interval.time_ns')109 if not isinstance(window, Interval):110 raise TypeError('记录对裁剪需要实际时间Interval')111 previous = self._previous_ns112 seconds = (None if previous is None else113 clipped_record_seconds(previous, current, window, step_ns=self._step_ns))114 self._previous_ns = current115 return seconds116