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>
This commit is contained in:
@@ -0,0 +1,129 @@
|
|||||||
|
"""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"
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
"""CE-Gate e-process 纯函数单元测试。
|
||||||
|
|
||||||
|
覆盖 compute_e_value、gate_decision、probation_verdict 三个公共函数
|
||||||
|
的核心路径与边界条件。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from core.evolution.gate import compute_e_value, gate_decision, probation_verdict
|
||||||
|
from core.evolution.types import GateParams, GateVerdict
|
||||||
|
|
||||||
|
_PARAMS = GateParams(
|
||||||
|
e_confirm=20.0,
|
||||||
|
e_provisional=3.0,
|
||||||
|
w_net_min=2,
|
||||||
|
delta_min=0.02,
|
||||||
|
lambda_dir=-0.642,
|
||||||
|
e_rollback=10.0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestComputeEValue:
|
||||||
|
"""compute_e_value 的数学正确性与边界校验。"""
|
||||||
|
|
||||||
|
def test_zero_zero_returns_one(self) -> None:
|
||||||
|
"""W=L=0 时 e 值应为 1(无证据,中性)。"""
|
||||||
|
assert compute_e_value(0, 0) == pytest.approx(1.0)
|
||||||
|
|
||||||
|
def test_negative_w_raises(self) -> None:
|
||||||
|
"""负 W 应立即报错。"""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
compute_e_value(-1, 0)
|
||||||
|
|
||||||
|
def test_negative_l_raises(self) -> None:
|
||||||
|
"""负 L 应立即报错。"""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
compute_e_value(0, -1)
|
||||||
|
|
||||||
|
def test_heavy_loss_returns_near_zero(self) -> None:
|
||||||
|
"""重度失败时 e 值趋近于零。"""
|
||||||
|
assert compute_e_value(0, 20) < 0.05
|
||||||
|
|
||||||
|
def test_heavy_win_returns_large(self) -> None:
|
||||||
|
"""重度胜利时 e 值远大于 100。"""
|
||||||
|
assert compute_e_value(10, 0) > 100
|
||||||
|
|
||||||
|
def test_symmetric(self) -> None:
|
||||||
|
"""W>L 时 e 值应大于 W<L 时。"""
|
||||||
|
assert compute_e_value(5, 3) > compute_e_value(3, 5)
|
||||||
|
|
||||||
|
|
||||||
|
class TestGateDecision:
|
||||||
|
"""gate_decision 四出口优先级链测试。"""
|
||||||
|
|
||||||
|
def test_confirmed_needs_both_e_and_delta(self) -> None:
|
||||||
|
"""高 e 值 + 足够 delta → accept_confirmed。"""
|
||||||
|
v = gate_decision(10, 0, 10, 10, params=_PARAMS)
|
||||||
|
assert v.decision == "accept_confirmed"
|
||||||
|
|
||||||
|
def test_continue_on_balanced(self) -> None:
|
||||||
|
"""平衡局面且题未尽 → continue。"""
|
||||||
|
v = gate_decision(3, 3, 6, 20, params=_PARAMS)
|
||||||
|
assert v.decision == "continue"
|
||||||
|
|
||||||
|
def test_reject_inertia_on_exhaustion(self) -> None:
|
||||||
|
"""题尽且证据不足 → reject_inertia。"""
|
||||||
|
v = gate_decision(1, 1, 2, 0, params=_PARAMS)
|
||||||
|
assert v.decision == "reject_inertia"
|
||||||
|
|
||||||
|
def test_n_used_zero_raises(self) -> None:
|
||||||
|
"""n_used=0 应立即报错。"""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
gate_decision(0, 0, 0, 10, params=_PARAMS)
|
||||||
|
|
||||||
|
def test_n_remaining_negative_raises(self) -> None:
|
||||||
|
"""n_remaining<0 应立即报错。"""
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
gate_decision(1, 0, 1, -1, params=_PARAMS)
|
||||||
|
|
||||||
|
|
||||||
|
class TestProbationVerdict:
|
||||||
|
"""probation_verdict 试用期结算测试。"""
|
||||||
|
|
||||||
|
def test_strong_win_confirmed(self) -> None:
|
||||||
|
"""强烈胜利 → confirmed 转正。"""
|
||||||
|
assert probation_verdict(10, 0, params=_PARAMS) == "confirmed"
|
||||||
|
|
||||||
|
def test_strong_loss_rollback(self) -> None:
|
||||||
|
"""强烈失败 → rollback 回滚。"""
|
||||||
|
assert probation_verdict(0, 10, params=_PARAMS) == "rollback"
|
||||||
|
|
||||||
|
def test_balanced_unverified(self) -> None:
|
||||||
|
"""平衡局面 → unverified 惯性转正。"""
|
||||||
|
assert probation_verdict(3, 3, params=_PARAMS) == "unverified"
|
||||||
Reference in New Issue
Block a user