Files
iomgaa 4eefbfdd74 feat(evolution): gate.py — CE-Gate e-process 纯函数 (#5 算法保真)
从 TRM4 core/harness/eprocess.py 逐行迁移,零逻辑变更:
- compute_e_value: 截断 Beta 混合 e 值(log 空间 + betainc 对称性)
- gate_decision: 四出口优先级链(confirmed→directional→futility→exhaustion→continue)
- probation_verdict: 试用期非对称双向结算
- 常量保真: _WALD_WIN=ln1.4, _WALD_LOSS=ln0.6, _SHRINK_PSEUDO=4

仅变更: import 路径 + GateParams/GateVerdict 移至 types.py + 中文 docstring

14 tests 覆盖: e 值数学、边界校验、四出口路径、试用期三分支

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-07-07 09:38:21 -04:00

130 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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: 判据阈值组。
返回:
GateVerdictdecision + 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"