Chronaxis用户手册English
本页目录

chronaxis.core.state

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""检查内存样本的原行身份,并按有限有效期查询最新观测或提交同刻更新。23输入为当前SignalBindings下的规范样本和纳秒查询轴;输出SignalSeries、4StateValue或完整TimeGroup。先检查身份、声明类型及同时间冲突,再取最新更新,5结合有效期和来源、会话范围判断能否使用。无效更新同样覆盖历史,不能回退6取更早有效值。同刻多列更新先收齐,再交给调用者求值一次。7本模块只依赖标准库,没有文件操作或全局状态;不执行单位转换或表达式计算。8NumPy保持实现位于state_numpy,数值对齐算法独立。9"""1011# 冻结结构承载已检查输入;heap只归并内存样本,不调整旧文件读取顺序。12from dataclasses import dataclass, field13# 合并已排序来源,不排序或重排原始输入。14from heapq import merge15# 无效NaN同时间证据需明确判断,不能普通相等比较。16from math import isnan1718# 两后端可共享模型校验/提交协议,参考查询不调用NumPy对齐。19from .binding import SignalBindings20# 来源与规范样本定位保存原行身份。21from .models import SignalSample, SourceSpec22# 有效性/类型策略不由原值truthiness决定。23from .types import QualityFlag, SignalDType24# 精确整数时间先校验再运算,不允许wrap。25from ..timebase.conversion import checked_add_ns, require_int64_ns262728def _collapse(bindings: SignalBindings, samples: tuple[SignalSample, ...]) -> tuple[SignalSample, int]:29    """解析同一别名同时间候选,保留稳定原序号及冲突数量。3031    参数: bindings为当前冻结引用;samples为非空、同名同时间typed候选。32    返回: 选定SignalSample及不同事实被显式覆盖的候选数。33    异常: ValueError为默认error下不同事实冲突;不静默按遍历顺序选值。34    """35    # 每候选序号是来源内稳定身份,最大序号与遍历顺序无关。36    chosen = max(samples, key=lambda sample: sample.sequence)37    # 基线取首条仅用于判断是否一致,不作为冲突时默认结果。38    first = samples[0]39    # 无效NaN诊断可以与相同原因的NaN一致,不使用其普通比较结果。40    same = all(item.valid == first.valid and item.quality == first.quality and item.code == first.code and41               type(item.value) is type(first.value) and42               (item.value == first.value or (type(item.value) is float and isnan(item.value) and isnan(first.value)))43               for item in samples)44    # 不同值/质量/有效性属于真实同信号冲突。45    if not same and bindings.resolve(first.name).same_time_policy == "error":46        # 未配置last_by_sequence时不能依赖输入遍历顺序。47        raise ValueError(f"同信号同时间冲突:{first.name}@{first.time_ns}ns")48    # 明确选最后稳定序号,记录被处理的不同候选数量。49    return chosen, 0 if same else len(samples) - 1505152@dataclass(frozen=True)53class SignalSeries:54    """保存一个信号别名的规范内存观测,以及来源范围和同刻冲突计数。5556    样本先按原顺序检查时间非递减,再按声明策略处理同刻候选,构造结果时间57    严格递增。有效值符合声明类型,无效原值保留作诊断。未声明的来源端点取58    实际首末行,不补采样周期。序列使用当前冻结绑定,没有全局执行缓存;59    原行转换由数值层提供。60    """6162    bindings: SignalBindings  # 当前计划与会话引用。63    name: str  # 当前原信号别名。64    samples: tuple[SignalSample, ...]  # 已规范化typed样本,包含无效更新。65    source: SourceSpec = field(init=False)  # 校验所得实际来源。66    observation_start_ns: int = field(init=False)  # 分析坐标来源首端。67    observation_end_ns: int = field(init=False)  # 分析坐标来源末端。68    conflict_count: int = field(init=False)  # last_by_sequence显式覆盖数量。6970    def __post_init__(self) -> None:71        """校验样本原身份、dtype、顺序,规范同时间候选并冻结来源域。7273        参数: 无;bindings/name为真实引用,samples为tuple规范化观测。74        返回: None;只在构造时规范同时间组,不丢失无效状态。75        异常: TypeError为结构/dtype;ValueError为归属/原序号/顺序/冲突;76            时间溢出异常由既有时间协议传播。77        """78        # 只接受当前真实绑定和冻结样本容器。79        if not isinstance(self.bindings, SignalBindings) or not isinstance(self.samples, tuple):80            # 不接可变列表或未校验字典。81            raise TypeError("SignalSeries需要SignalBindings和tuple samples")82        # 别名先在当前计划解析,不能继承其他会话。83        definition = self.bindings.resolve(self.name)84        # 实际来源已经由绑定证明唯一。85        source = next(item for item in self.bindings.session.sources if item.name == definition.source)86        # 原行序号到字段/时间,用于证明规范化观测对应实际原行。87        rows = {row.sequence: row for row in source.rows}88        # 枚举逻辑域按第一次出现稳定去重,编码映射原样保留在定义。89        labels = tuple(dict.fromkeys(label for _, label in definition.choices)) if definition.dtype is SignalDType.ENUM else ()90        # 局部稳定身份和待提交时间组,不跨会话共享。91        seen, groups, pending = set(), [], []92        # 冲突计数只记录显式政策处理的不同事实。93        conflicts = 094        # 输入顺序不自动修补,逐项检查后才规范同时间。95        for sample in self.samples:96            # typed样本已校验质量矛盾,当前入口仍拒绝占位对象。97            if not isinstance(sample, SignalSample):98                # 不把自由dict当作可计算观测。99                raise TypeError("samples必须包含SignalSample")100            # 会话/来源/字段范围必须属于当前冻结输入。101            self.bindings.session.validate_sample(sample)102            # 同别名的来源引用完整匹配,不能用另一字段冒充。103            if sample.name != self.name or sample.source_ref != self.bindings.source_ref(self.name):104                # 原引用冲突不是普通缺失值。105                raise ValueError("样本别名/来源与当前序列不一致")106            # 声明dtype只检查有效值,无效占位保留原诊断。107            sample.validate_dtype(definition.dtype, labels)108            # 同来源原序号必须真实且不能重复消费。109            if sample.sequence not in rows or sample.sequence in seen:110                # 不重编号或生成不存在的行。111                raise ValueError("样本原序号不存在或重复")112            # 定位实际行,再证明偏移映射与字段暴露事实。113            row = rows[sample.sequence]114            # 单个样本只能关联真正含该原字段的行。115            if definition.field not in row.values or sample.time_ns != checked_add_ns(row.time_ns, source.offset_ns):116                # 已归一化值可变化,但原时间/字段身份不能伪造。117                raise ValueError("样本不能定位对应原行时间/字段")118            # 来源序号只在当前序列登记,另一别名可引用同一原行。119            seen.add(sample.sequence)120            # 时间回退不排序以掩盖上游错误。121            if pending and sample.time_ns < pending[-1].time_ns:122                # 不提交回退之后的计算结果。123                raise ValueError("序列时间必须非递减")124            # 更大时间才证明上一时间组收齐。125            if pending and sample.time_ns != pending[-1].time_ns:126                # 公共校验协议处理同信号同时间冲突。127                selected, count = _collapse(self.bindings, tuple(pending))128                # 规范结果保留实际原样本,不伪造值。129                groups.append(selected)130                # 显式政策处理数量可追溯。131                conflicts += count132                # 仅结束上一组,不重置来源或会话状态。133                pending = []134            # 当前真实更新进入待规范组,无效样本同样保留。135            pending.append(sample)136        # 完整内存输入结束可以提交最后一组。137        if pending:138            # 该收尾是全序列结束,不是batch EOF。139            selected, count = _collapse(self.bindings, tuple(pending))140            # 末点仍保存为真实观测。141            groups.append(selected)142            # 最后一组冲突同样记录。143            conflicts += count144        # 未声明范围时只取实际来源首行/末行,绑定已保证来源非空。145        start = source.rows[0].time_ns if source.observation_start_ns is None else source.observation_start_ns146        # 实际末行不得加假设周期。147        end = source.rows[-1].time_ns if source.observation_end_ns is None else source.observation_end_ns148        # 构造冻结规范样本,不保留可变临时分组列表。149        object.__setattr__(self, "samples", tuple(groups))150        # 保存真实来源引用,后续两个对齐器共享该校验事实。151        object.__setattr__(self, "source", source)152        # 来源原坐标显式映射到分析时间。153        object.__setattr__(self, "observation_start_ns", checked_add_ns(start, source.offset_ns))154        # 点/连续对末端的使用由对齐入口明确指定。155        object.__setattr__(self, "observation_end_ns", checked_add_ns(end, source.offset_ns))156        # 结果数量只描述显式冲突政策,不称为旧帧去重。157        object.__setattr__(self, "conflict_count", conflicts)158159160@dataclass(frozen=True)161class StateValue:162    """保存某个查询时刻的最新观测、可用性和质量原因。163164    sample用于追溯原时间、序号和来源;最新更新无效时保留它,不回退旧值。165    没有历史时value为None。actual_update只表示查询点恰好有真实样本更新,166    有效期到期或观测结束不能伪造更新。point模式可以包含真实末点,连续模式167    排除末端,两者均在有效期右端失效。结果不会修改原样本。168    """169170    time_ns: int  # 本次查询的分析纳秒坐标。171    value: int | float | bool | str | None  # 最新观测或没有历史时None。172    valid: bool  # 质量/TTL/观测范围均满足才可使用。173    quality: QualityFlag  # 保留输入原因并叠加缺失/过期。174    sample: SignalSample | None  # 最新实际观测;不回退旧有效值。175    actual_update: bool  # 该点是否真正发生更新。176177178def align_reference(series: SignalSeries, times_ns: tuple[int, ...], *, point: bool = False,179                    strict_before: bool = False, left_limit: bool = False) -> tuple[StateValue, ...]:180    """逐查询点取得最新历史观测,再检查有效期和来源、会话范围。181182    参数:183        series (SignalSeries): 当前绑定下已经检查的规范序列。184        times_ns (tuple[int, ...]): 非递减int64纳秒查询轴,允许空轴和重复点。185        point (bool): 默认False,使用左闭右开连续范围;True包含来源及会话186            的真实末端;普通查询排除有效期右端。187        strict_before (bool): 默认False;True只取sample_time < t的样本,188            仍在当前t检查有效期,用来区分实际更新与同刻到期。189        left_limit (bool): 仅可用于 strict_before,取 t 的左侧保持;到期恰为 t 的旧值190            在左侧仍有效,来源与会话的左端无左侧保持。用于区分无间隙刷新与真正恢复。191    返回:192        tuple[StateValue, ...]: 每个查询点一个结果;无历史为MISSING,过期为STALE。193        最新无效更新不会被更早有效值替代,空轴返回空tuple。194    异常:195        TypeError: 序列、查询容器或模式标志类型错误,或者时间不是非布尔整数。196        ValueError: 查询时间回退。197        TimeOverflowError: 查询坐标超出int64范围。198    副作用:199        仅使用局部游标和结果集合,不调用NumPy对齐,不修改输入。200    """201    # 不接受未校验序列或可变查询容器。202    if (not isinstance(series, SignalSeries) or not isinstance(times_ns, tuple)203            or any(type(flag) is not bool for flag in (point, strict_before, left_limit))):204        # point不能以数值truthiness切换契约。205        raise TypeError("参考对齐需要SignalSeries、tuple时间及bool point")206    if left_limit and not strict_before:207        raise ValueError('左侧保持须排除同刻更新')208    # 只用精确Python整数,参考不调用NumPy索引/掩码内核。209    times = tuple(require_int64_ns(time, "query.time_ns") for time in times_ns)210    # 保证时间推进不回退,重复查询点允许。211    if any(right < left for left, right in zip(times, times[1:])):212        # 不排序查询掩盖执行器错误。213        raise ValueError("查询时间必须非递减")214    # 每次查询调用只有局部游标,不继承其他会话状态。215    cursor, result = -1, []216    # 当前会话范围明确,不延长来源保持。217    session = series.bindings.session218    # 有效期纳秒来自当前冻结定义,不根据消息周期推测。219    age = series.bindings.resolve(series.name).valid_for_ns220    # 小规模参考按时间逐点计算,数值/对齐独立于数组后端。221    for time in times:222        # 最新更新包含无效观测,不能只搜索有效值。223        while cursor + 1 < len(series.samples) and (series.samples[cursor + 1].time_ns < time if strict_before else series.samples[cursor + 1].time_ns <= time):224            # 精确取不晚于当前时刻的最新样本。225            cursor += 1226        # 连续域末端排除;point显式包含真实终点。227        in_domain = session.domain_start_ns <= time and (time <= session.domain_end_ns if point else time < session.domain_end_ns)228        if left_limit:229            in_domain = session.domain_start_ns < time <= session.domain_end_ns230        # 会话外或无历史没有可用观测,不能取末元素。231        if not in_domain or cursor < 0:232            # 没有可用历史时保留None和MISSING原因,不用数值0代替未知结果。233            result.append(StateValue(time, None, False, QualityFlag.MISSING, None, False))234            # 当前点没有样本参与TTL或边沿。235            continue236        # 即使最新更新无效也取它,不回退到更早有效观测。237        sample = series.samples[cursor]238        # 质量在局部结果叠加,原样本原因保持不变。239        quality = sample.quality240        # 左侧视图恰好到期仍有左邻域,普通点与右侧视图则已经过期。241        elapsed = time - sample.time_ns242        if elapsed > age or (not left_limit and elapsed == age):243            # 自动计时失效无需等待新报文。244            quality |= QualityFlag.STALE245        # 来源末端与会话一样按point/连续显式区分。246        in_source = series.observation_start_ns <= time and (time <= series.observation_end_ns if point else time < series.observation_end_ns)247        if left_limit:248            in_source = series.observation_start_ns < time <= series.observation_end_ns249        # 更长的其他来源不能延长当前来源保持。250        if not in_source:251            # 来源观测不足与输入无效/TTL原因一起保留。252            quality |= QualityFlag.MISSING253        # 所有非零质量基础策略禁止使用,正常0仍然有效。254        valid = sample.valid and quality == QualityFlag.NONE255        # 只有真正实际更新可触发后续点事件。256        result.append(StateValue(time, sample.value, valid, quality, sample, time == sample.time_ns))257    # 冻结结果容器,不暴露局部列表。258    return tuple(result)259260261def _require_series_group(series: tuple[SignalSeries, ...]) -> SignalBindings:262    """检查局部依赖结构、同绑定和同步,不分配完整时间轴。263264    参数: series为同一冻结绑定下非空tuple SignalSeries。265    返回: 当前SignalBindings;空间只与局部依赖列数有关。266    异常: TypeError为结构;ValueError为不同绑定或同步缺项。267    """268    # 只处理显式局部依赖,不建立全信号宽表。269    if not isinstance(series, tuple) or not series or not all(isinstance(item, SignalSeries) for item in series):270        # 空依赖需要由调用者明确给出纯常量轴,本入口不猜会话。271        raise TypeError("共同轴需要非空tuple SignalSeries")272    # 同一绑定身份才能形成联合任务轴。273    binding = series[0].bindings274    # 来源输入变化不能只因同别名而共用时间轴。275    if any(item.bindings.identity != binding.identity for item in series):276        # 不把不同会话串接计算。277        raise ValueError("共同轴序列必须来自同一绑定")278    # 参与联合计算的原信号必须通过公共时基检查。279    binding.require_synchronized(tuple(item.name for item in series))280    # 仅返回校验后的绑定,不遍历全历史生成索引或轴。281    return binding282283284def common_timeline(series: tuple[SignalSeries, ...], extra_ns: tuple[int, ...] = ()) -> tuple[int, ...]:285    """构造当前依赖的更新、TTL及来源/会话边界共同轴。286287    参数: series为同一绑定下所需序列tuple;extra_ns为后续规则deadline/window边界。288    返回: 排序唯一tuple int64纳秒,限制在会话闭域以保留末点上下文。289    异常: TypeError/ValueError为结构或不同绑定;时间错误传播。290    副作用: 显式分配当前依赖共同轴,归并入口不调用本生成操作。291    """292    # 校验协议与完整轴构造分开,局部归并不额外分配全历史轴。293    binding = _require_series_group(series)294    # 不接无限或可变边界容器。295    if not isinstance(extra_ns, tuple):296        # 调用侧显式提供后续规则边界。297        raise TypeError("extra_ns必须为tuple")298    # 会话闭域只保留真实终点,不增加连续持续时间。299    start, end = binding.session.domain_start_ns, binding.session.domain_end_ns300    # 当前任务必要端点都保留,纯EOF不是实际更新。301    axis = {start, end}302    # 显式规则边界使用同一精确时间校验。303    axis.update(require_int64_ns(time, "extra_ns") for time in extra_ns)304    # 每信号的保持边界进入轴,不等新更新才失效。305    for item in series:306        # 来源起止是局部覆盖事实,不能借其他列延长。307        axis.update((item.observation_start_ns, item.observation_end_ns))308        # 有效期从明示定义读取。309        age = binding.resolve(item.name).valid_for_ns310        # 更新和TTL用精确整数计算,不先转绝对float秒。311        for sample in item.samples:312            # 真实更新时刻用于原子提交及点事件。313            axis.add(sample.time_ns)314            # 超出会话的TTL不进入int64轴,先在Python整数域裁剪。315            expiry = sample.time_ns + age316            # 只保存会话内可发生的deadline。317            if start <= expiry <= end:318                # 包含与EOF并列的TTL,后续状态机可保留并列诊断。319                axis.add(expiry)320    # 排序仅作用于新核心共同轴,不排序修补来源输入。321    return tuple(sorted(time for time in axis if start <= time <= end))322323324@dataclass(frozen=True)325class TimeGroup:326    """保存一个分析时刻所有信号真实更新,供求值一次使用。327328    不在逐条样本之间求值;updates按冻结定义顺序排列,保持原样本身份。329    """330331    time_ns: int  # 该组公共分析坐标。332    updates: tuple[SignalSample, ...]  # 各信号最多一项,冲突已处理。333    conflict_count: int = 0  # 本组显式last_by_sequence处理数量。334335336class AtomicUpdateBuffer:337    """跨批收集同时间的信号更新,收齐后输出一次完整求值组。338339    看到更大时间或完整输入finish后才提交当前组,普通批末保留_pending。340    输入须已经按时间归并并完成归一化;本类不读取来源或排序。finish后实例341    关闭,新会话须创建新实例。缓冲大小取决于同刻更新数量;生产预算由上层342    执行入口约束,不能只凭分批就认为整体内存有界。343    """344345    def __init__(self, bindings: SignalBindings):346        """创建当前会话的空原子提交缓冲。347348        参数: bindings为当前真实冻结引用。349        返回: None;pending空,closed=False,无全局状态。350        异常: TypeError为非绑定输入。351        """352        # 缓冲不能关联无类型计划。353        if not isinstance(bindings, SignalBindings):354            # 不从上一个任务继承会话。355            raise TypeError("buffer需要SignalBindings")356        # 当前绑定不会随运行中值更新改变。357        self.bindings = bindings358        # 只缓存当前未证明收齐的同时间组。359        self._pending = []360        # 真正会话完成后禁止继续输入。361        self._closed = False362        # 已见时间用于拒绝跨batch回退。363        self._last_time = None364365    def _commit(self) -> TimeGroup:366        """将已收齐的pending原子规范为一次求值组。367368        参数: 无;pending须非空且同时间。369        返回: TimeGroup,信号按定义顺序,显式冲突有计数。370        异常: ValueError为默认冲突;只修改当前pending。371        """372        # 当前组各别名候选先收齐再决策,不在采集过程中求值。373        grouped = {}374        # 原观测顺序仅用于采集,冲突选择依稳定sequence。375        for sample in self._pending:376            # 不同别名更新各自收集。377            grouped.setdefault(sample.name, []).append(sample)378        # 求值组顺序由当前计划定义决定,不受批次切法影响。379        updates, conflicts = [], 0380        # 只处理本时刻确有更新的别名。381        for definition in self.bindings.signals:382            # 未更新列继续保持,不能伪造当前新样本。383            if definition.name in grouped:384                # 统一已定义冲突政策,不改变数值对齐内核独立性。385                sample, count = _collapse(self.bindings, tuple(grouped[definition.name]))386                # 一列最多一项原子更新。387                updates.append(sample)388                # 记录显式覆盖的候选数。389                conflicts += count390        # 所有原候选都处于同一个时间。391        time = self._pending[0].time_ns392        # 只在组实际完成后清空pending,不销毁会话绑定。393        self._pending = []394        # 冻结本组供后续状态驱动器求值一次。395        return TimeGroup(time, tuple(updates), conflicts)396397    def push(self, samples: tuple[SignalSample, ...]) -> tuple[TimeGroup, ...]:398        """消费一批已归并更新,返回已经收齐的更早时间组。399400        参数: samples为非递减tuple样本,可为空,batch尾可处于未完成同时间组。401        返回: tuple TimeGroup;最后时间仍留pending,不能在batch尾提前求值。402        异常: TypeError为结构;ValueError为已finish、归属/回退/冲突。403        """404        # 整个会话EOF才关闭,不把普通批次当成会话结束。405        if self._closed:406            # 不能在新会话复用旧执行状态。407            raise ValueError("buffer已finish,需新会话实例")408        # 可变或无限批次容器不进入已定义内存协议。409        if not isinstance(samples, tuple):410            # 当前入口显式有界批次。411            raise TypeError("samples batch必须为tuple")412        # 完成组结果只属于当前调用。413        result = []414        # 每条样本先校验当前归属,再推进原子组。415        for sample in samples:416            # 完整样本类型/会话检查,不允许旧引用污染新任务。417            self.bindings.session.validate_sample(sample)418            # 当前别名必须实际绑定到同来源字段。419            if sample.source_ref != self.bindings.source_ref(sample.name):420                # 绑定冲突不能被吞为无效数值。421                raise ValueError("buffer样本绑定不匹配")422            # 已规范化有效值仍须与当前声明dtype和域一致。423            definition = self.bindings.resolve(sample.name)424            # enum目标逻辑域与编码映射分开,重复标签只保留一个。425            labels = tuple(dict.fromkeys(label for _, label in definition.choices)) if definition.dtype is SignalDType.ENUM else ()426            # 无效更新允许保留原诊断占位,不参与正常数值校验。427            sample.validate_dtype(definition.dtype, labels)428            # 同组同别名不能重复消费同一稳定原样本。429            if any(item.name == sample.name and item.sequence == sample.sequence for item in self._pending):430                # 不把重复输入吞成一次正常更新。431                raise ValueError("buffer同组原序号重复")432            # 跨batch同样禁止时间回退。433            if self._last_time is not None and sample.time_ns < self._last_time:434                # 不重排上游样本。435                raise ValueError("buffer时间回退")436            # 更大时间证明上一组已收齐。437            if self._pending and sample.time_ns > self._last_time:438                # 一组的所有列先提交,调用侧随后求值一次。439                result.append(self._commit())440            # 当前样本纳入未完成组,无效更新也要覆盖旧状态。441            self._pending.append(sample)442            # 记录最新输入时间,普通batch边界保留。443            self._last_time = sample.time_ns444        # 冻结结果,最后pending保留到下一批或真正finish。445        return tuple(result)446447    def finish(self) -> tuple[TimeGroup, ...]:448        """在完整输入EOF提交最后时间组并关闭当前会话缓冲。449450        参数: 无;调用者已证明所有参与来源输入结束。451        返回: 剩余一个TimeGroup或空tuple。452        异常: ValueError为重复finish或未处理冲突;batch尾不能调用本方法。453        """454        # 不接受重复收尾,否则可能重复积分或事件。455        if self._closed:456            # 新任务必须创建独立状态对象。457            raise ValueError("buffer不能重复finish")458        # 真EOF才允许提交最终同时间组。459        result = (self._commit(),) if self._pending else ()460        # 当前会话输入不可继续复用。461        self._closed = True462        # 空输入合法,不伪造末点更新。463        return result464465466def iter_time_groups(series: tuple[SignalSeries, ...], batch_rows: int = 65536):467    """对当前局部memory序列有界归并并跨batch原子提交。468469    参数: series为同绑定、不同别名的规范序列tuple;batch_rows为正原生整数。470    返回: 逐个yield TimeGroup;完整内存输入结束后才提交最后组。471    异常: TypeError/ValueError为结构/不同绑定/重复列/批次参数或冲突。472    副作用: 只维护局部heap/一批/pending,不读取旧日志或改样本顺序。473    """474    # 纯依赖校验不生成完整共同轴,不调用任何数值对齐内核。475    _require_series_group(series)476    # 同别名的两个序列不能当成不同输入列重复消费。477    if len({item.name for item in series}) != len(series):478        # 不自动选择某一输入序列。479        raise ValueError("归并序列别名重复")480    # bool不能伪装成批次行数1。481    if type(batch_rows) is not int or batch_rows <= 0:482        # 不接受无界或无效批次参数。483        raise ValueError("batch_rows必须为正原生整数")484    # 缓冲只属于本次输入,不在批次之间重建。485    buffer = AtomicUpdateBuffer(series[0].bindings)486    # heap归并每列已经排序的规范样本,不排序修补任何来源。487    stream = merge(*(item.samples for item in series), key=lambda sample: sample.time_ns)488    # 当前局部批次最多batch_rows条,组可跨边界。489    batch = []490    # 依来源真实更新时间逐项收集。491    for sample in stream:492        # 当前样本保留原稳定身份。493        batch.append(sample)494        # 达到批次预算才将批交给同一个状态缓冲。495        if len(batch) == batch_rows:496            # 已证明收齐的时间组可逐个消费。497            yield from buffer.push(tuple(batch))498            # 普通批次完成只清批,不清pending或会话状态。499            batch = []500    # 完整输入最后不满批同样进入状态缓冲。501    if batch:502        # 同时刻更新可与上一批pending合并。503        yield from buffer.push(tuple(batch))504    # 所有memory来源结束后才执行最后组收尾。505    yield from buffer.finish()506
Chronaxis 用户手册 · 离线阅读