Chronaxis用户手册English
本页目录

chronaxis.core.temporal_reference

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""逐时间组检测持续条件和真实边沿,输出已经确认的活动或点事件。23输入为完整ConditionTick批次、会话范围和TemporalRule。持续候选跨批保留:4先结算此前TRUE区间的时长,再处理当前点更新和连续状态,最后处理观测结束。5确认时间记录为精确阈值到期时刻,不等待下一报文。空批次不重置状态,finish6只能在真正会话终点之后调用。默认保存全部事件;主动drain_events只转交并清空7已产生输出,不重置业务状态,此后finish返回尚未取走的事件。8这是独立的标准库参考状态机,只共享模型、身份和协议检查,不调用NumPy9检测算法。不读取来源、不持有全局状态,每个检测器对应一个规则和会话。10"""1112# 模型/来源校验可共享,参考状态不导入NumPy。13from .temporal import ConditionTick, ConditionTimeline, TemporalRule, make_event14# 来源必须属于当前真实session。15from .models import Interval, SourceRef16# 三值严格比较枚举,不隐式bool化UNKNOWN。17from .types import QualityFlag as Q, Truth181920def require_batch(ticks, start_ns, end_ns, previous_time_ns, ended):21    """验证完整时刻组的批次顺序/边界,不检测状态。2223    参数: ticks tuple、完整domain ns、前一时刻previous_time_ns或None、ended标志。24    返回: None,空批次允许但不产生EOF。25    异常: TypeError/ValueError为错类型/回退/中途terminal/边界不符。26    """27    # 已归并条件组必须typed,不接半组原信号更新。28    if not isinstance(ticks, tuple) or any(not isinstance(item, ConditionTick) for item in ticks):29        # AtomicUpdateBuffer在上游处理跨batch同时间样本。30        raise TypeError('检测batch需tuple ConditionTick')31    # terminal之后不能继续添加非空证据。32    if ticks and ended:33        # session结束与普通batch尾严格分开。34        raise ValueError('真实terminal之后不能push')35    # 首组要覆盖明示domain起点,不猜遗漏区间。36    if ticks and previous_time_ns is None and ticks[0].time_ns != start_ns:37        # 可以显式UNKNOWN起点,不能省略范围。38        raise ValueError('首组必须为domain start')39    # 静态协议逐组检查,没有truth检测逻辑。40    for index, tick in enumerate(ticks):41        # 前一个完整时间组只用于严格单调检查。42        prior = previous_time_ns if index == 0 else ticks[index-1].time_ns43        # 不排序或合并重复条件组来掩盖调用侧错误。44        if prior is not None and tick.time_ns <= prior or not start_ns <= tick.time_ns <= end_ns:45            # 同时间完整组只能提交一次。46            raise ValueError('条件组重复/回退/超domain')47        # 会话终点必须terminal,terminal必须最后且在真实end_ns。48        if tick.terminal != (tick.time_ns == end_ns) or tick.terminal and index != len(ticks)-1:49            # 单文件/batch EOF不允许当session EOF。50            raise ValueError('terminal与真实domain end不符')515253def require_detector(rule, start_ns, end_ns, refs):54    """校验规则/域/来源范围,不初始化算法状态。5556    参数: rule真实TemporalRule、start_ns/end_ns ns、refs SourceRef tuple。57    返回: None,span/int64/会话一致则通过。58    异常: TypeError/ValueError/OverflowError为配置或来源冲突。59    """60    # 每次运行的独立规则不可无类型占位。61    if not isinstance(rule, TemporalRule) or not isinstance(refs, tuple):62        # 后端不能默认某个全局规则。63        raise TypeError('检测需要TemporalRule及tuple refs')64    # 先证明域跨度,数组差值后续才可安全减。65    Interval(start_ns, end_ns)66    # 真来源定位必须同当前会话。67    for ref in refs:68        # 跨会话同名来源不是同一输入。69        if not isinstance(ref, SourceRef) or ref.session_id != rule.session_id:70            # 不静默换session或清空来源。71            raise ValueError('检测来源session不一致')727374class ReferenceTemporalDetector:75    """保存一个规则在一个会话内的候选活动、确认时间和边沿前态。7677    candidate_start与confirmed分别保存起点和确认时刻;last_truth与last_time78    保存上一完整组,seen_known控制首次已知条件。全部状态属于当前实例,普通79    批次边界不清理;finish只在真实终点之后关闭实例。未确认候选不作为Event输出。80    events默认累计;drain_events可增量取走输出,候选、边沿前态和抑制状态不变。81    start_only额外保存blocked_until_ns;只接受真实rising_edge或明确首次初始化,82    固定窗口内触发丢弃,到期不排队或自动重触发,右端真实边沿允许再次接受。83    """8485    def __init__(self, rule, start_ns, end_ns, source_refs=(), *, record_clock=False):86        """检查规则、会话范围和来源后,创建空的检测状态。8788        参数:89            rule (TemporalRule): 当前规则,不继承其他实例配置。90            start_ns、end_ns (int): 完整会话起止坐标,单位为纳秒。91            source_refs (tuple[SourceRef, ...]): 当前来源引用,默认空tuple。92        返回:93            None: 候选、确认时间、边沿前态和事件集合均从初始状态开始。94        异常:95            TypeError、ValueError或OverflowError: 规则、时间范围或来源归属不合法。96        副作用:97            初始化本实例状态,没有全局缓存或文件操作。98        """99        # 校验与状态算法分开,后端只共享该协议。100        # record_clock仅接受调用方显式周期增量;实际坐标仍用于事件。101        require_detector(rule, start_ns, end_ns, source_refs)102        if type(record_clock) is not bool:103            raise TypeError('record_clock需要bool')104        self.record_clock = record_clock105        self.candidate_elapsed = self.blocked_remaining = 0106        # 当前规则/范围/来源不从全局缓存继承。107        self.rule, self.start_ns, self.end_ns, self.refs = rule, start_ns, end_ns, source_refs108        # 尚未消费任何完整时间组。109        self.last_time, self.last_truth = None, Truth.UNKNOWN110        # 新session不继承初始化/候选状态。111        self.seen_known, self.candidate_start, self.confirmed = False, None, None112        # 当前活动期间的所有依赖诊断。113        self.diagnostic = Q.NONE114        # 未主动排空时累计最终事件;输出容器与真正EOF/finish生命周期分开。115        self.events, self.ended, self.finished = [], False, False116        # 固定抑制跨普通batch保留,仅真实接受触发才更新,不排队被抑制边沿。117        self.blocked_until_ns = None118119    @property120    def next_deadline_ns(self):121        """给出待确认候选的精确下一deadline,不等待新报文时间。122123        参数: 无;读取候选/规则/完整domain。124        返回: int ns或None;超domain门槛永不误cast/wrap。125        异常: 无主动异常;状态局部。126        """127        # 没有候选/已确认/edge规则没有持续deadline。128        if self.record_clock or self.candidate_start is None or self.confirmed is not None:129            # 不能创建虚假计时器。130            return None131        # Python整数域先算再比较;不发生int64 wrap。132        deadline = self.candidate_start + self.rule.qualifying_duration_ns133        # 域外候选不能在EOF之后确认。134        return deadline if deadline <= self.end_ns else None135136    def _consume(self, tick, advance=None):137        """结算旧TRUE区间,再处理当前更新及连续状态,最后处理会话终点。138139        参数:140            tick (ConditionTick): 已经过批次协议检查的完整时间组。141        返回:142            None: 更新本实例候选、确认时间、诊断、边沿前态和最终事件。143        异常:144            TypeError、ValueError或OverflowError: 构造事件时模型检查失败,错误原样传播。145        边界:146            阈值恰好等于当前结束时间仍可确认;真实FALSE优先闭合,真实无效更新147            优先中断。单纯观测结束截断活动;会话终点不创建新持续候选。148        """149        # 记录模式只减少必要的剩余等待,不改写任何实际端点。150        if self.record_clock:151            self.blocked_remaining = max(0, self.blocked_remaining - advance)152        # 旧TRUE状态到tick之前仍属于已观测半开区间。153        if self.rule.rule_type == 'condition_duration' and self.candidate_start is not None:154            # 精确门槛在旧状态中确认,False/invalid在同刻不能抹除。155            if self.record_clock:156                self.candidate_elapsed = min(self.rule.qualifying_duration_ns, self.candidate_elapsed + advance)157                deadline = tick.time_ns if self.candidate_elapsed >= self.rule.qualifying_duration_ns else None158            else:159                deadline = self.candidate_start + self.rule.qualifying_duration_ns160            # 即使t跳到5ns仍保存3ns确认,轴构造器会插入deadline独立tick。161            if self.confirmed is None and deadline is not None and deadline <= tick.time_ns:162                # 不把确认当onset或延长区间。163                self.confirmed = deadline164            # 包含当前结束证据的诊断,但不修改真实truth。165            self.diagnostic |= tick.diagnostic_quality | tick.quality | tick.point_quality166        # 边沿仅由真实更新与已知旧状态决定。167        if self.rule.rule_type in ('rising_edge', 'falling_edge', 'start_only') and tick.actual_update:168            # 默认从UNKNOWN变为TRUE或FALSE不构成已知边沿。169            edge = self.rule.rule_type in ('rising_edge', 'start_only') and self.last_truth is Truth.FALSE and tick.point_state is Truth.TRUE or self.rule.rule_type == 'falling_edge' and self.last_truth is Truth.TRUE and tick.point_state is Truth.FALSE170            # 显式首次known TRUE允许初始化点,后续unknown恢复不重复initial。171            initial = self.rule.rule_type in ('rising_edge', 'start_only') and self.rule.emit_initial and not self.seen_known and tick.point_state is Truth.TRUE172            # 半开抑制区间右端真实边沿可触发;到期本身不制造候选或自动重触发。173            allowed = (self.rule.rule_type != 'start_only' or174                (self.blocked_remaining == 0 if self.record_clock else175                 self.blocked_until_ns is None or tick.time_ns >= self.blocked_until_ns))176            # 没有真实变化/初始化证据不输出point。177            if (edge or initial) and allowed:178                # 点start=confirmed,end=None,不增加任何持续面积。179                self.events.append(make_event(self.rule, tick.time_ns, tick.time_ns, None, 'point', Q.NONE,180                                              tick.diagnostic_quality, self.refs, trigger='initial' if initial else self.rule.rule_type))181                # 宽整数deadline可超观测域,只用于比较,不能wrap为早期时间。182                if self.rule.rule_type == 'start_only':183                    if self.record_clock:184                        self.blocked_remaining = self.rule.window_duration_ns185                    else:186                        self.blocked_until_ns = tick.time_ns + self.rule.window_duration_ns187        # condition_duration看到真实结束/连续非TRUE或真正terminal,立即结束候选。188        if self.rule.rule_type == 'condition_duration' and self.candidate_start is not None and (tick.terminal or tick.state is not Truth.TRUE):189            # 实际更新明确为FALSE时优先闭合,观测结束或有效期到期仍保留诊断。190            if tick.actual_update and tick.point_state is Truth.FALSE:191                # 真实结束证据不能降级truncated。192                status, quality = 'closed', Q.NONE193            # 实际更新导致未知优先interrupted,包括同EOF真实invalid。194            elif tick.actual_unknown:195                # 不能仅凭任一列更新误判同刻TTL因果。196                status, quality = 'interrupted', tick.point_quality197            # 纯EOF缺观测保留truncated,同刻TTL原因仍在diagnostic。198            elif tick.terminal or tick.observation_end:199                # 没有实际结束不伪造False。200                status, quality = 'truncated', tick.quality | tick.point_quality201            # 明确连续已知False同样结束,显式span可给出该证据。202            elif tick.state is Truth.FALSE:203                # known条件结束是closed。204                status, quality = 'closed', Q.NONE205            else:206                # TTL早于EOF或UNKNOWN缺口中断真实连续性。207                status, quality = 'interrupted', tick.quality208            # 不够门槛的pending不输出已确认事实。209            if self.confirmed is not None:210                # 已确认activity区间不会因post/pre片段填平缺口。211                self.events.append(make_event(self.rule, self.candidate_start, self.confirmed, tick.time_ns,212                                              status, quality, self.diagnostic, self.refs))213            # 候选彻底结束,后续TRUE从新onset开始。214            self.candidate_start, self.confirmed, self.diagnostic = None, None, Q.NONE215        # 非终点TRUE才建立持续候选,condition_duration=0也不建末点活动。216        if self.rule.rule_type == 'condition_duration' and not tick.terminal and tick.state is Truth.TRUE and self.candidate_start is None:217            # onset保留真实活动开始,不等待确认才设起点。218            self.candidate_start = tick.time_ns219            self.candidate_elapsed = 0220            # 零门槛立即确认;正门槛由精确deadline处理。221            self.confirmed = tick.time_ns if self.rule.qualifying_duration_ns == 0 else None222            # 起点与后续组按同一规则累计;连续为真时,点证据仍可能含未知原因。223            self.diagnostic = tick.diagnostic_quality | tick.quality | tick.point_quality224        # 每组point/continuous可不同,后续边沿旧状态采用连续区间truth。225        self.last_truth, self.last_time = tick.state, tick.time_ns226        # 初始化策略只允许首次known,恢复TRUE不能反复initial。227        self.seen_known |= tick.point_state is not Truth.UNKNOWN228        # 只有真实会话terminal才能结束流,不处理batch/file尾EOF。229        self.ended |= tick.terminal230231    def push(self, ticks: tuple[ConditionTick, ...], *, advances=None):232        """消费完整组批次,保留候选/边沿并返回本次新最终事件。233234        参数: ticks已归并tuple,允许空batch不重置。235        返回: 本批新增Event tuple,未结束pending/active不写伪事件。236        异常: ValueError为finish后/回退/边界错,typed协议异常传播。237        """238        # advances仅在记录计时模式提供;各项是本组推进量,不是另一套时间坐标。239        # finish生命周期不能隐式resume。240        if self.finished:241            # 新会话要新实例,不复用旧状态。242            raise ValueError('检测器已finish')243        # 完整组顺序校验与检测状态分开。244        require_batch(ticks, self.start_ns, self.end_ns, self.last_time, self.ended)245        from .temporal_clock import require_clock_advances246        require_clock_advances(ticks, advances, self.record_clock, self.last_time)247        # 记录已产生数量,只返回新增事实。248        previous = len(self.events)249        # 独立参考逐组状态循环,不称为NumPy数组化。250        for index, tick in enumerate(ticks):251            # 内部按先结算时长、再处理实际更新、最后处理观测结束的顺序推进。252            self._consume(tick, None if advances is None else advances[index])253        # 空batch自然返回空且不修改状态。254        return tuple(self.events[previous:])255256    def drain_events(self):257        """转交当前未取走的最终事件,并清空输出容器,不推进或重置业务状态。258259        参数: 无,读取本实例已经产生的events。260        返回: Event tuple;未产生输出或已经排空时为空tuple。261        异常: MemoryError为tuple构造失败,此时原输出保持未排空。262        副作用: 成功后只清空events,候选/确认/前态/初始化/抑制及生命周期均保持。263        """264        # 先成功构造冻结转交结果,再清空原列表,分配失败不会丢失已产生事件。265        events = tuple(self.events)266        self.events.clear()267        return events268269    def finish(self):270        """在已处理真正terminal后关闭并返回当前尚未取走的稳定事件。271272        参数: 无,不制造EOF或读取额外输入。273        返回: 尚未drain的Event tuple;未调用drain时仍返回全部事件,保持原API。274        异常: ValueError为缺terminal或重复finish。275        """276        # 未到domain end不能把batch尾当EOF收尾。277        if not self.ended or self.finished:278            # 调用者须先收齐真正末组。279            raise ValueError('finish需已消费真正terminal且不能重复')280        # 标记封闭实例,防跨任务继承状态。281        self.finished = True282        # 所有pending已在terminal处理;只返回尚未主动drain取走的稳定输出。283        return tuple(self.events)284285286def detect_reference(rule: TemporalRule, timeline: ConditionTimeline, *, batch_rows: int = 65536):287    """按任意合法批大小消费完整条件轴,不重置状态。288289    参数: rule/timeline;batch_rows正原生int,只切组,不切原子同刻事实。290    返回: 独立参考Event tuple,ID与分割无关。291    异常: TypeError/ValueError为模型/批大小,检测协议错误传播。292    """293    # 不接受自由轴或bool批大小。294    if not isinstance(timeline, ConditionTimeline) or type(batch_rows) is not int or batch_rows <= 0:295        # 不能通过广播凑批次或丢最后行。296        raise TypeError('detect需ConditionTimeline及正int batch_rows')297    # 每次独立实例,完整源关联与域。298    detector = ReferenceTemporalDetector(rule, timeline.start_ns, timeline.end_ns, timeline.source_refs)299    # batch边界不会触发finish/reset。300    for start in range(0, len(timeline.ticks), batch_rows):301        # 只有已完整收齐的ConditionTick进入状态机。302        detector.push(timeline.ticks[start:start+batch_rows])303    # timeline模型保证唯一真正末组已消费。304    return detector.finish()305
Chronaxis 用户手册 · 离线阅读