Chronaxis用户手册English
本页目录

chronaxis.core.stability

源码基线:8904c2f744951857659abd40aaa3535d57faf53c

1"""把进入/退出条件转换为带迟滞和双向防抖的活动。23输入为同一会话、同一时刻的两组ConditionTick和非负纳秒防抖时长。4先结算此前已持续满足的条件,再处理当前更新;中间区间保持已确定状态,5UNKNOWN清除候选和此前状态。普通批次/文件边界不参与状态重置。6输出沿用活动Event,起点等于实际进入确认时刻;退出等待期间仍属于活动。7本模块仅实现有序状态转换,活动组装委托既有独立参考/NumPy检测器;8不声称状态循环是数值数组化,不读取文件,也不保存全量信号历史。9"""1011from dataclasses import dataclass, replace12from .temporal import ConditionTick13from .temporal_reference import ReferenceTemporalDetector, require_batch, require_detector14from .types import QualityFlag as Q, Truth15from ..timebase.conversion import require_int64_ns161718@dataclass(frozen=True)19class StabilitySpec:20    """保存进入和退出的独立等待时长;0表示条件满足即可转换。2122    状态属于运行实例,本声明不保存已报警标志或候选计时。23    """2425    enter_ns: int = 0  # 进入条件连续满足多久才切换为活动。26    exit_ns: int = 0  # 退出条件连续满足多久才切换为非活动。27    track_duration: bool = False  # 仅显式需求累计,普通规则不增加逐步统计。2829    def __post_init__(self):30        """拒绝负值、bool及超int64时长。参数: 无。返回: None。异常: 类型/范围错误。"""31        if type(self.track_duration) is not bool:32            raise TypeError('track_duration必须bool')33        for name in ('enter_ns', 'exit_ns'):34            value = require_int64_ns(getattr(self, name), name)35            if value < 0:36                raise ValueError('防抖时长不能为负')37            object.__setattr__(self, name, value)383940def invert_tick(tick):41    """取得普通防抖条件的退出证据,UNKNOWN保留原原因。4243    参数: tick为ConditionTick。返回: 点/连续状态分别取反的新证据。44    异常: TypeError为非ConditionTick;不修改输入。45    """46    if not isinstance(tick, ConditionTick):47        raise TypeError('防抖条件需要ConditionTick')48    opposite = {Truth.TRUE: Truth.FALSE, Truth.FALSE: Truth.TRUE, Truth.UNKNOWN: Truth.UNKNOWN}49    return replace(tick, state=opposite[tick.state], point_state=opposite[tick.point_state])505152class StabilityDetector:53    """维护一个规则/会话的已确定状态、单个待转换候选及活动输出。5455    未知起点不猜未报警;两条件都假时保持前态,二者同时为真则报告配置冲突。56    一个条件未知会中断活动;恢复时从未知开始重新确认,不能跨缺口累计防抖。57    每次push可有多组,只有真实terminal才能finish;drain只取走输出。58    """5960    def __init__(self, rule, start_ns, end_ns, spec, source_refs=(), *, backend='reference', record_clock=False):61        """建立独立迟滞状态,复用零时长活动检测器。6263        参数: rule为condition_duration=0规则;start_ns/end_ns为会话域;spec为StabilitySpec;64            source_refs为来源定位;backend为reference或numpy。65        返回: None;初始化实例状态。异常: 规则、域、类型或后端错误。66        """67        require_detector(rule, start_ns, end_ns, source_refs)68        if not isinstance(spec, StabilitySpec) or rule.rule_type != 'condition_duration' or rule.minimum_duration_ns != 0:69            raise ValueError('迟滞需要StabilitySpec及duration=0活动规则')70        if backend not in ('reference', 'numpy'):71            raise ValueError('迟滞后端需reference或numpy')72        # 参考入口不导入NumPy;两者共用状态转换协议,活动核独立执行。73        detector_type = ReferenceTemporalDetector74        if backend == 'numpy':75            from .temporal_numpy import NumpyTemporalDetector76            detector_type = NumpyTemporalDetector77        self.rule, self.spec = rule, spec78        self.start_ns, self.end_ns = int(start_ns), int(end_ns)79        self.state, self.quality = Truth.UNKNOWN, Q.MISSING80        self.pending, self.deadline = None, None81        self.record_clock, self.remaining = record_clock, 082        self.last_time, self.ended, self.finished = None, False, False83        self._core = detector_type(rule, self.start_ns, self.end_ns, source_refs, record_clock=record_clock)84        # 固定三个整数/一个状态,不保存逐点轨迹或额外扫描。UNKNOWN时间不会算成未报警。85        self._duration_time, self._duration_state = self.start_ns, Truth.UNKNOWN86        self._active_ns = self._known_ns = 08788    def _push_core(self, tick, advance=None):89        """把已确认状态交给活动核,并按需累计左侧半开区间。9091        参数: tick为状态转换后的证据,advance为记录模式本组累计量。返回: 原活动核Event tuple。92        异常: 原活动核校验错误;失败实例不能继续使用。93        """94        if self.spec.track_duration:95            duration = advance if self.record_clock else int(tick.time_ns) - self._duration_time96            if self._duration_state is not Truth.UNKNOWN:97                self._known_ns += duration98                if self._duration_state is Truth.TRUE:99                    self._active_ns += duration100            self._duration_time, self._duration_state = int(tick.time_ns), tick.state101        return self._core.push((tick,), advances=(advance,) if self.record_clock else None)102103    @property104    def duration_counts(self):105        """读取完成后的已知报警/已知观测整数纳秒。106107        参数: 无。返回: (active_ns,known_ns)或None(未请求统计)。108        异常: ValueError表示请求统计但尚未正常finish。109        """110        if not self.spec.track_duration:111            return None112        if not self.finished:113            raise ValueError('迟滞累计只能在finish后读取')114        return self._active_ns, self._known_ns115116    @property117    def next_deadline_ns(self):118        """读取域内待转换时间。参数: 无。返回: int或None。异常: 无。119120        加法使用Python整数,域外结果不转换为int64或回绕。121        """122        return self.deadline if not self.record_clock and self.deadline is not None and self.deadline <= self.end_ns else None123124    @staticmethod125    def _target(enter, exit, quality):126        """区分明确方向、中间区间、未知与矛盾条件。127128        参数: enter/exit为Truth;quality为两个条件原因并集。129        返回: Truth或None(中间区间)。异常: ValueError为同时满足进出条件。130        """131        if enter is Truth.TRUE and exit is Truth.TRUE:132            raise ValueError('迟滞进入和退出条件同时成立,请设置不重叠的条件')133        if enter is Truth.UNKNOWN or exit is Truth.UNKNOWN:134            return Truth.UNKNOWN135        if enter is Truth.TRUE:136            return Truth.TRUE137        if exit is Truth.TRUE:138            return Truth.FALSE139        return None140141    def _settle(self):142        """兑现已持续达到门槛的转换。参数: 无。返回: None。异常: 无。"""143        self.state, self.quality = self.pending, Q.NONE144        self.pending, self.deadline = None, None145        self.remaining = 0146147    def _point(self, target, quality):148        """判断当前点状态,不把零长度点累计到等待时间。149150        参数: target为目标或None,quality为点原因。返回: (Truth,Q)。异常: 无。151        """152        if target is Truth.UNKNOWN:153            return Truth.UNKNOWN, quality154        if target is not None:155            delay = self.spec.enter_ns if target is Truth.TRUE else self.spec.exit_ns156            if delay == 0:157                return target, Q.NONE158        return self.state, self.quality159160    def _continuous(self, target, quality, time_ns):161        """更新右侧持续状态和唯一候选,取消不连续的计时。162163        参数: target为目标/None;quality为原因;time_ns为当前纳秒。164        返回: None,修改实例。异常: 无;超域deadline仅用于比较。165        """166        if target is Truth.UNKNOWN:167            self.state, self.quality = Truth.UNKNOWN, quality168            self.pending, self.deadline = None, None169        elif target is None or target is self.state:170            # 返回中间区间仍保留已确认状态,但未满时长的进/出请求撤销。171            self.pending, self.deadline = None, None172        else:173            delay = self.spec.enter_ns if target is Truth.TRUE else self.spec.exit_ns174            if delay == 0:175                self.state, self.quality = target, Q.NONE176                self.pending, self.deadline = None, None177            elif self.pending is not target:178                self.pending = target179                self.deadline = None if self.record_clock else time_ns + delay180                self.remaining = delay181182    def push(self, ticks, exit_ticks=None, *, advances=None):183        """推进成对条件组,可在两次实际更新之间精确兑现防抖计时。184185        参数: ticks为进入条件tuple;exit_ticks为同刻退出tuple,省略时为not进入;186            advances仅record_clock模式传入本组周期增量,无驱动边界为0。187        返回: 本次新Event tuple;活动起点为进入转换时刻,不回填等待区间。188        异常: 输入/顺序/同刻冲突/finish后推进错误;发生错误后调用方应丢弃实例。189        """190        if self.finished:191            raise ValueError('迟滞检测器已finish')192        require_batch(ticks, self.start_ns, self.end_ns, self.last_time, self.ended)193        from .temporal_clock import require_clock_advances194        require_clock_advances(ticks, advances, self.record_clock, self.last_time)195        if exit_ticks is None:196            exit_ticks = tuple(invert_tick(tick) for tick in ticks)197        require_batch(exit_ticks, self.start_ns, self.end_ns, self.last_time, self.ended)198        if len(ticks) != len(exit_ticks) or any(a.time_ns != b.time_ns for a, b in zip(ticks, exit_ticks)):199            raise ValueError('迟滞两个条件必须在相同的完整时间组求值')200        output = []201        for index, (enter, exit) in enumerate(zip(ticks, exit_ticks)):202            time_ns = int(enter.time_ns)203            advance = None if advances is None else advances[index]204            # 先结算前一个半开区间,即使当前点恰好变假或失效也不抹去已得时长。205            if self.record_clock and self.pending is not None:206                self.remaining = max(0, self.remaining - advance)207            settled = (self.pending is not None and self.remaining == 0 if self.record_clock208                       else self.deadline is not None and self.deadline <= time_ns)209            if settled:210                deadline = time_ns if self.record_clock else self.deadline211                self._settle()212                if deadline < time_ns:213                    output.extend(self._push_core(ConditionTick(deadline, self.state)))214            diagnostic = enter.diagnostic_quality | exit.diagnostic_quality | enter.quality | exit.quality215            diagnostic |= enter.point_quality | exit.point_quality216            point_quality = enter.point_quality | exit.point_quality217            point_target = self._target(enter.point_state, exit.point_state, point_quality)218            point_state, point_quality = self._point(point_target, point_quality)219            # 退出等待已在左侧完整满足;同刻EOF/失效不能把这个已知退出改成缺观测。220            if settled and self.state is Truth.FALSE and point_state is Truth.UNKNOWN:221                point_state, point_quality = Truth.FALSE, Q.NONE222            if enter.terminal:223                # 末点能解除旧活动,但不能开始新的正长度活动或假造末点后的保持。224                state, quality = Truth.UNKNOWN, enter.quality | exit.quality | Q.MISSING225                self.pending, self.deadline = None, None226            else:227                quality = enter.quality | exit.quality228                target = self._target(enter.state, exit.state, quality)229                self._continuous(target, quality, time_ns)230                state, quality = self.state, self.quality231            tick = ConditionTick(time_ns, state, quality,232                actual_update=enter.actual_update or exit.actual_update or settled,233                observation_end=enter.observation_end or exit.observation_end, terminal=enter.terminal,234                point_state=point_state, point_quality=point_quality,235                actual_unknown=(enter.actual_unknown or exit.actual_unknown) and point_state is Truth.UNKNOWN,236                diagnostic_quality=diagnostic)237            output.extend(self._push_core(tick, advance))238            self.last_time, self.ended = time_ns, enter.terminal239        return tuple(output)240241    def drain_events(self):242        """取走最终活动而不清计时。参数: 无。返回: Event tuple。异常: 分配错误。"""243        return self._core.drain_events()244245    def finish(self):246        """只在真实会话末尾封闭。参数: 无。返回: 未取走活动。异常: 缺terminal/重复。"""247        result = self._core.finish()248        self.finished = True249        return result250
Chronaxis 用户手册 · 离线阅读