"""CE-Gate 统计核心:截断 Beta 混合 e-process 的纯函数实现。 配对不一致检验:候选与基线跑同一题,只数翻转(基线错->候选对 = W; 基线对->候选错 = L)。H0(候选不优)下翻转方向精确五五开, E = 2^(W+L+1)*B(W+1,L+1)*[1-I_1/2(W+1,L+1)] 为 H0 下非负上鞅, Ville 不等式给出任意停时 P(E >= 1/alpha) <= alpha。 设计规格见 research-wiki/designs/2026-07-03-ce-gate-formal-design.md。 仅依赖 scipy.special,无 I/O、无状态,便于单测与历史回放复用。 """ from __future__ import annotations import math from scipy.special import betainc, betaln from core.evolution.types import GateParams, GateVerdict # Wald 方向游走步长(theta_1=0.70 固定设计常量,不入配置): # 胜 +ln(2*theta_1)=ln1.4,负 ln(2*(1-theta_1))=ln0.6。 _WALD_WIN = math.log(1.4) _WALD_LOSS = math.log(0.6) # delta_shrunk 的伪计数(Agresti-Coull 风格收缩,只作观测输出不进判据)。 _SHRINK_PSEUDO = 4 def compute_e_value(w: int, l: int) -> float: # noqa: E741 """截断 Beta 混合 e 值:E = 2^(W+L+1)*B(W+1,L+1)*[1-I_1/2(W+1,L+1)]。 参数: w: 基线错->候选对的翻转数。 l: 基线对->候选错的翻转数。 返回: e 值(W=L=0 时为 1)。 异常: ValueError: 翻转计数为负时抛出。 关键实现细节: log 空间计算在 n_max<=40 的设计工作区间内数值稳定(数百级计数 亦可);极大计数(>1000)时最终 exp 仍可能溢出。用正则化不完全 Beta 的对称性 1-I_1/2(a,b) = I_1/2(b,a) 避免 1-x 的灾难性精度损失。 """ if w < 0 or l < 0: raise ValueError(f"翻转计数不能为负: w={w}, l={l}") a, b = w + 1, l + 1 tail = betainc(b, a, 0.5) # = 1 - I_1/2(a, b) if tail <= 0.0: return 0.0 log_e = (w + l + 1) * math.log(2.0) + betaln(a, b) + math.log(tail) return math.exp(log_e) def gate_decision( w: int, l: int, # noqa: E741 n_used: int, n_remaining: int, *, params: GateParams, ) -> GateVerdict: """块间四出口判定(每块结束时调用一次)。 出口优先级:CONFIRMED(有证书先走)-> 方向拒绝 -> futility 拒绝 -> 题尽(provisional / inertia)-> continue。 参数: w: 累计 W。 l: 累计 L。 n_used: 已消费的阶梯题数(含一致题)。 n_remaining: 阶梯剩余可用题数(min(阶梯长, n_max) - n_used)。 params: 判据阈值组。 返回: GateVerdict(decision + e 值/游走/效应量诊断)。 异常: ValueError: n_used <= 0 或 n_remaining < 0 时抛出。 """ if n_used <= 0: raise ValueError(f"gate_decision 须在至少消费一块后调用: n_used={n_used}") if n_remaining < 0: raise ValueError(f"n_remaining 不能为负: {n_remaining}") e_value = compute_e_value(w, l) wald = w * _WALD_WIN + l * _WALD_LOSS delta_hat = (w - l) / n_used delta_shrunk = (w - l) / (n_used + _SHRINK_PSEUDO) if e_value >= params.e_confirm and delta_hat >= params.delta_min: decision = "accept_confirmed" elif wald <= params.lambda_dir: decision = "reject_directional" elif n_remaining > 0 and compute_e_value(w + n_remaining, l) < params.e_provisional: # futility 只在题未尽时有意义;题尽后的弱证据归 inertia 出口。 decision = "reject_futility" elif n_remaining <= 0: if ( e_value >= params.e_provisional and (w - l) >= params.w_net_min and delta_hat >= params.delta_min ): decision = "accept_provisional" else: decision = "reject_inertia" else: decision = "continue" return GateVerdict(decision, e_value, wald, delta_hat, delta_shrunk) def probation_verdict(w: int, l: int, *, params: GateParams) -> str: # noqa: E741 """试用期一次性结算:固定样本 e 值双向检验。 参数: w: 结算配对的 W(锚快照错->候选重跑对)。 l: 结算配对的 L(锚快照对->候选重跑错)。 params: 判据阈值组(用 e_confirm / e_rollback)。 返回: "confirmed"(E>=e_confirm 转正)/ "rollback"(对称 E'>=e_rollback 回滚) / "unverified"(证据不足,elitist 惯性转正)。 """ if compute_e_value(w, l) >= params.e_confirm: return "confirmed" if compute_e_value(l, w) >= params.e_rollback: return "rollback" return "unverified"