60c21ae346
rules 是纯计算,src/rules 共 1761 行:方法.md 第六部分的全部公式、第七部分的余额与扣次、4.2 的入口分配,以及种子与规则登记表。 预期值有三个来源:方法.md 的手算例;tests/fixtures/rules/ 下不看实现写的 Python 脚本;参考项目 calc15、calc14b 里与现行方法一致的部分。审阅做了两轮,每轮都换一个全新上下文的子 Agent,提出的问题都已修复或写明不修的理由。 用户确认的约定(原话"1–8 都同意"):入池、采纳、消融中发现都用严格不等号;成本用 max(0, ĝ₀);没测的格附"含它的最好组合"的净值;战绩在分数原值上判。三个数改为严格递增后,删掉了尖峰的全部专门处理(bfb96ba)。vocab 里 Range 的说明同步改为严格递增。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
167 lines
6.4 KiB
TypeScript
167 lines
6.4 KiB
TypeScript
import assert from "node:assert/strict";
|
||
import { test } from "node:test";
|
||
import {
|
||
authorVsMerged,
|
||
bitWidth,
|
||
cellIndex,
|
||
coverage,
|
||
disagreement,
|
||
placement,
|
||
surprise,
|
||
trackRecord,
|
||
worldBits,
|
||
type Mixture,
|
||
} from "../src/rules/index.ts";
|
||
|
||
// 预期值来源写在每条测试旁边。比特的例子取 ε = 25(Δ = 0.25),格边界在二进制里精确。
|
||
|
||
function close(actual: number, expected: number, tol = 1e-12): void {
|
||
assert.ok(Math.abs(actual - expected) <= tol, `实际 ${actual},预期 ${expected}`);
|
||
}
|
||
|
||
/** 很窄的范围 (y − 0.01, y, y + 0.01),连尾巴 [y − 0.02, y + 0.02]。 */
|
||
const narrow = (y: number) => ({ q1: y - 0.01, q5: y, q9: y + 0.01 });
|
||
|
||
const uniform = (lo: number, hi: number): Mixture => [
|
||
{
|
||
weight: 1,
|
||
knots: [
|
||
[lo, 0],
|
||
[hi, 1],
|
||
],
|
||
},
|
||
];
|
||
|
||
test("6.9 格宽 Δ = ε/100,跟随 ε", () => {
|
||
assert.equal(bitWidth(0.01), 0.01 / 100);
|
||
assert.equal(bitWidth(25), 0.25);
|
||
});
|
||
|
||
test("6.9 世界比特:从 b 起切;[0,1] 均匀切 4 格是 2 比特,错开 1/8 是 2.25 比特;整个落在一格里是 0", () => {
|
||
// 手算:[0,1] 均匀、b = 0、Δ = 0.25:四格各 1/4,H = 2。
|
||
close(worldBits(uniform(0, 1), 0, 0.25), 2);
|
||
// [0.125, 1.125]:五格 1/8、1/4、1/4、1/4、1/8,H = 2 × (1/8 × 3) + 3 × (1/4 × 2) = 0.75 + 1.5 = 2.25。
|
||
close(worldBits(uniform(0.125, 1.125), 0, 0.25), 2.25);
|
||
// 同一分布、b = 0.125:切口对齐,四格各 1/4,又是 2 比特。说明格从 b 起切。
|
||
close(worldBits(uniform(0.125, 1.125), 0.125, 0.25), 2);
|
||
// [0.3, 0.31] 整个在 [0.25, 0.5) 这一格里:把握 1,0 比特。
|
||
assert.equal(worldBits(uniform(0.3, 0.31), 0, 0.25), 0);
|
||
});
|
||
|
||
test("6.9 惊讶 = −log₂ p;正好在格边界上的实测归右边那格;事前把握为 0 时给明确状态", () => {
|
||
// 手算:[0,1] 均匀,实测 0.3 落在 [0.25, 0.5),p = 1/4,惊讶 2 比特。
|
||
assert.deepEqual(surprise(uniform(0, 1), 0, 0.25, 0.3), { cell: 1, prior: 0.25, bits: { status: "measured", value: 2 } });
|
||
assert.equal(surprise(uniform(0, 1), 0, 0.25, 0.25).cell, 1);
|
||
assert.deepEqual(surprise(uniform(0, 1), 0, 0.25, 2), {
|
||
cell: 8,
|
||
prior: 0,
|
||
bits: { status: "unmeasured", reason: "事前把握为 0,惊讶算不出" },
|
||
});
|
||
});
|
||
|
||
test("6.9 找实测所在格用和切格同一套边界:b + jΔ 正好落在第 j 格", () => {
|
||
// b = 2.5、Δ = 1e-4:(b + 3Δ − b)/Δ 在浮点里可能是 2.999…,按边界核对后仍是 3。
|
||
for (let j = -5; j <= 50; j++) assert.equal(cellIndex(2.5, 1e-4, 2.5 + j * 1e-4), j);
|
||
for (let j = -5; j <= 50; j++) assert.equal(cellIndex(2.635983, 1e-4, 2.635983 + j * 1e-4), j);
|
||
});
|
||
|
||
test("6.9 分歧量:大家的范围完全一样时为 0;两个窄范围各在一格时为 1 比特", () => {
|
||
const r = { q1: 0.2, q5: 0.5, q9: 0.9 };
|
||
close(
|
||
disagreement(
|
||
[
|
||
{ id: "A", author: "A", range: r },
|
||
{ id: "B", author: "B", range: r },
|
||
],
|
||
0,
|
||
0.25,
|
||
),
|
||
0,
|
||
);
|
||
// 手算:A 交 (0.09, 0.1, 0.11),连尾巴 [0.08, 0.12],整个在 [0, 0.25) 里;B 交 (0.59, 0.6, 0.61),整个在 [0.5, 0.75) 里。
|
||
// 各自 0 比特;平均后两格各一半,1 比特。
|
||
close(
|
||
disagreement(
|
||
[
|
||
{ id: "A", author: "A", range: narrow(0.1) },
|
||
{ id: "B", author: "B", range: narrow(0.6) },
|
||
],
|
||
0,
|
||
0.25,
|
||
),
|
||
1,
|
||
);
|
||
});
|
||
|
||
test("6.9 落点四段与覆盖率:边界归属;没有样本时覆盖率测不到", () => {
|
||
const r = { q1: 1, q5: 2, q9: 3 };
|
||
assert.equal(placement(r, 0.5), "belowQ1");
|
||
assert.equal(placement(r, 1), "q1ToQ5");
|
||
assert.equal(placement(r, 2), "q5ToQ9");
|
||
assert.equal(placement(r, 3), "q5ToQ9");
|
||
assert.equal(placement(r, 3.5), "aboveQ9");
|
||
const c = coverage([0.5, 1, 2, 3, 3.5].map((value) => ({ range: r, value })));
|
||
assert.deepEqual(c.counts, { belowQ1: 1, q1ToQ5: 1, q5ToQ9: 2, aboveQ9: 1 });
|
||
assert.equal(c.covered, 3);
|
||
assert.deepEqual(c.rate, { status: "measured", value: 0.6 });
|
||
assert.deepEqual(coverage([]).rate, { status: "unmeasured", reason: "没有样本" });
|
||
});
|
||
|
||
test("6.10 战绩:中位数偏高偏低、穿上下沿、包住,按人、按正向与消融分开", () => {
|
||
const r = { q1: 1, q5: 2, q9: 3 };
|
||
const t = trackRecord([
|
||
{ researcher: "B", phase: "forward", range: r, value: 2.5 },
|
||
{ researcher: "A", phase: "ablation", range: r, value: 2 },
|
||
{ researcher: "A", phase: "forward", range: r, value: 0.5 },
|
||
{ researcher: "A", phase: "forward", range: r, value: 3.5 },
|
||
{ researcher: "A", phase: "forward", range: r, value: 2.5 },
|
||
]);
|
||
assert.deepEqual(
|
||
t.map((x) => [x.researcher, x.phase]),
|
||
[
|
||
["A", "forward"],
|
||
["A", "ablation"],
|
||
["B", "forward"],
|
||
],
|
||
);
|
||
// A 正向:0.5 → 中位数偏高、穿下沿;3.5 → 偏低、穿上沿;2.5 → 偏低、包住。
|
||
assert.deepEqual(t[0]!.tally, {
|
||
total: 3,
|
||
medianHigh: 1,
|
||
medianLow: 2,
|
||
aboveTop: 1,
|
||
belowBottom: 1,
|
||
covered: 1,
|
||
placements: { belowQ1: 1, q1ToQ5: 0, q5ToQ9: 1, aboveQ9: 1 },
|
||
});
|
||
// A 消融:实测正好等于 q5,不算偏高也不算偏低。
|
||
assert.equal(t[1]!.tally.medianHigh + t[1]!.tally.medianLow, 0);
|
||
});
|
||
|
||
test("6.10 作者自评 vs 大家合并:作者一半按方向比合并一半好多少,正数是作者更乐观", () => {
|
||
const item = { candidate: "c1", author: "A", authorRange: { q1: 2, q5: 2.5, q9: 3 }, mergedRange: { q1: 1, q5: 2, q9: 3 }, value: 2.25 };
|
||
assert.deepEqual(authorVsMerged([item], "maximize"), [
|
||
{ candidate: "c1", author: "A", optimism: 0.5, authorPlacement: "q1ToQ5", mergedPlacement: "q5ToQ9" },
|
||
]);
|
||
assert.equal(authorVsMerged([item], "minimize")[0]!.optimism, -0.5);
|
||
});
|
||
|
||
test("6.9 分歧量只量人与人之间:同一人几条解释先等权平均成他的折线", () => {
|
||
// 手算:A、B 各交两条解释,都是 (0.09, 0.1, 0.11) 和 (0.59, 0.6, 0.61),各自整个落在 [0, 0.25)、[0.5, 0.75) 一格里。
|
||
// 每人的折线都是"两格各一半",各 1 比特;平均后仍是两格各一半,1 比特;分歧量 = 1 − 1 = 0。
|
||
// 若把每条解释各算比特(各 0 比特)再平均,会得出 1。
|
||
close(
|
||
disagreement(
|
||
[
|
||
{ id: "e1", author: "A", range: narrow(0.1) },
|
||
{ id: "e2", author: "A", range: narrow(0.6) },
|
||
{ id: "e3", author: "B", range: narrow(0.1) },
|
||
{ id: "e4", author: "B", range: narrow(0.6) },
|
||
],
|
||
0,
|
||
0.25,
|
||
),
|
||
0,
|
||
);
|
||
});
|