feat: 研究规则 rules 模块(任务书 02)

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>
This commit is contained in:
2026-10-02 01:50:21 -04:00
parent bfb96ba7d7
commit 60c21ae346
37 changed files with 14067 additions and 3 deletions
+136
View File
@@ -244,3 +244,139 @@
- `Range`(字段定义):三个数 q1、q5、q9,即一个分数有一成、一半、九成可能低于多少。
- `SUBMISSION_SCHEMAS`:每种提交的字段定义,键与 `SUBMISSION_KINDS` 相同;同一份定义是类型、收件格式检查和提交工具参数。
- `Submission<K>`(类型):某种提交的内容类型,例如 `Submission<"proposal">`。
### rules
下面是 `src/rules/index.ts` 导出的全部内容。rules 是纯计算:输入数字,输出数字,不碰文件、数据库和模型,也不用全局随机数。λ、ε、已知最好、每批上限、余额这些参数都由调用方传进来,rules 里只写死方法.md 6.11 标"固定"的几个常数。三个数一律用 vocab 的 `Range`。编号(候选、研究者、入口、因子、解释)一律按自然序比较:数字部分按数值大小比,所以 c2 排在 c10 前面。
几个反复出现的词:**折线**是把一个人的三个数画成"分数低于 x 的把握"的分段直线(6.2);**合并分布**是几条折线按权重加起来的结果(6.3);**改进量**是分数比基线好了多少,正数表示变好,越小越好的任务也换算成正数为好(6.1)。决定类函数的返回值里带着写决定记录要用的中间数,不只返回"选了谁"。
**常数与基础**
- `INTERACTION_CUT`:配合关系标签里相对交互量的界限 0.2(6.6"配合关系标签")。
- `INDISPENSABLE_SUPPORT`:判"缺一不可"的撑几成界限 0.9(6.6)。
- `NET_TOLERANCE`:选格时比较净值的容差 1e-12,差距在它以内的组合当作一样好(6.7"选格")。
- `GRID_COMBINATION_CAP`:只测一部分格时,网格组合总数的上限 4,000,000(6.7"信息价值")。
- `GRID_SUBDIVISIONS`:网格里每段最多再等分 20 份(6.7"信息价值")。
- `epsilonFloor(ε)`:算 ε/100;采纳线下限、剩余空间下限、比特格宽三处共用这一个函数,保证末位一致(6.11"跟随 ε")。
- `improvement(from, to, direction)`:从 from 到 to 按指标方向好了多少,越大越好时是 to − from,越小越好时是 from − to(6.1)。
- `compareIds(a, b)`:按自然序比较两个编号,供所有排序和"编号最小"的判断使用;p01 和 p1 这类逐段相同的编号再按整串定先后,不会判成相等。
- `combinations(n, k)`:列出从 0..n−1 里选 k 个的全部组合,按字典序排,选批和选格列组合时用。
**范围与合并分布(6.1–6.3)**
- `Point`、`Knots`(类型):折线上的一个点(横坐标,低于它的把握),以及按横坐标严格递增排列的一串点。
- `Component`、`Mixture`(类型):合并分布的一个成分(一条折线加它的权重),以及成分的列表,权重加起来是 1。
- `personKnots(range)`:把一个人的三个数变成五个点的折线,两端各补一段与相邻半宽相同的尾巴(6.2)。
- `below(knots, x)`:读折线在 x 处"低于 x 的把握"。
- `knotsMean(knots)`:折线所代表的分数的平均值(期望)。
- `density(knots, x)`:折线在 x 处的斜率,也就是把握在这一点有多集中(密度);押的因子更新权重时用(6.8)。
- `mapKnots(knots, f, decreasing)`:把折线的横坐标换到另一把刻度上;换算是反向时点序倒过来、把握换成 1 减原把握。
- `reserveKnots(lines)`:开放预留,即给"大家都没想到的结果"留的那一份:取所有折线的最低点到最高点,左右各再放宽一半,在上面均匀铺开(6.3)。
- `authorShares(authors)`:每条解释的占比,研究者之间等权,同一人的几条平分(6.3"合并"、6.7"成分与权重")。
- `mixtureBelow(m, x)`:合并分布在 x 处"低于 x 的把握"。
- `breakpoints(m)`:合并分布全部折点的横坐标,去重、升序。
- `mapMixture(m, f, decreasing)`:把合并分布的每个成分都换到另一把刻度上,权重不变。
- `forwardMixtures(candidates, λ)`:正向每个候选的合并分布,(1 − λ) × 各人折线平均 + λ × 开放预留,开放预留由这次选批的全部候选、全部人共用(6.3"正向")。
- `ablationCellMixture(entries, λ)`:消融一格的合并分布,各条解释按 `authorShares` 分 1 − λ,这一格自己的开放预留占 λ(6.3"消融")。
- `toImprovement(m, b, direction)`:把合并分布从分数原值换算成相对基线 b 的改进量;越小越好时刻度反向,一成和九成随之对调(6.1)。
- `mixtureQuantile(m, p)`:合并分布的分位点,即"有 p 的把握低于多少";只给人看,不参与决定。
- `mixtureRange(m)`:合并分布的一成、一半、九成,按分数原值,消融格显示"大家合起来的范围"用。
- `forwardMergedRange(imp, b, direction)`:正向候选"大家合起来的范围",在改进量上读三个数再换回分数原值。
- `integrateUpperTail(ds, from)`:按段精确算 ∫[1 − ∏F] dt,正向的批内最好期望和消融全测时的信息价值共用这一个积分。
**正向:入池、选批、采纳(6.4、6.5,4.3 ⑫)**
- `lossProbability(imp, ε)`:候选"比基线低 ε 以上"的把握;严格小于一半才入池(6.4"入池")。
- `batchValue(imps)`:一组候选的批内最好期望 V(S),即这批里最好的那个预计比基线好多少,低于基线按 0 算(6.4)。
- `BatchInput`(类型):两条选批规则共用的输入:候选及各人的三个数、基线、方向、ε、λ、每批上限、余额、种子。
- `BatchDecision`(类型):选批结果,含 6.4"选批记录写入"的全部内容:参数、批量 k、每个候选的合并范围和是否入池、各组合的 V、选中的组合及其 V、候选池的 V、分数尺读数。
- `selectBestExpected(input)`:选批规则"批内最好期望",在候选池里列出全部组合取 V 最大的,V 完全相等时取编号小的组合(6.4)。
- `selectUniformRandom(input)`:选批规则"均匀随机",用种子从全部候选里抽,不看入池门槛;入池、合并范围、分数尺读数照样算出来记下(第十一部分)。
- `AdoptionLineInput`(类型):算采纳线要的基线 B、已知最好 K\*(没有时为 null)、ε 和方向。
- `adoptionLine(input)`:采纳线(6.5),返回线的值和 (K\* − B) ÷ 4 的原值;没有已知最好时是 ε,结果不低于 ε/100。
- `adopt(input)`:采纳判断,只看有有效分数的候选,最好的那个比基线高出采纳线以上才采纳;返回采纳线、最好的候选及其改进量和采纳结果,够写采纳记录。
**消融结算(6.6,4.3 ⑬ ⑰)**
- `CellOutcome`(类型):一格的五种结局:有效分数、证据不足、没测、跑不起来、一起关(带实际关掉的因子和可能的分数)。
- `AblationCell`(类型):一格,即一个因子和它的结局。
- `AblationExplanation`(类型):一条解释,带作者和它对每一格给的三个数。
- `listCells(factors)`:列格,被采纳改法的每个因子各一格,按因子编号排(4.3 ⑬)。
- `totalGain(baseline, full, direction)`:总提升 G = f(完整) − f(基线);G ≤ 0 说明上游选错了改法,直接报错(6.6"撑几成")。
- `supportOf(baseline, full, off, direction)`:某个因子撑几成,即关掉它后损失的分数占总提升的比例,不截断(6.6)。
- `SynergyLabel`(类型):配合关系标签的代号,六种:关掉反而更好、不全、缺一不可、互补、替代、各管各;中文说明以后由 materials 写。
- `synergyLabel(supports)`:按 6.6 那张表从上往下判出标签,同时返回各格撑几成之和与相对交互量(有格没测出有效分数时这两个数为 null)。
- `sharedSettings(cells)`:判断哪些"一起关"的格其实是同一种设置(关的因子集合相同),给出该沿用哪一格的结果(6.6)。
- `ExplanationVerdict`(类型):一条解释在一格上的判定,确认或否定。
- `judgeExplanations(explanations, cells)`:每条解释在每个有效格上,实测落在它的一成到九成之间(含两端)记确认,否则记否定(6.6)。
- `inseparableGroups(judgments)`:找出在所有有效格上确认与否定完全相同的解释,归成"现有测法分不开"的组(6.6)。
- `ablationFinding(input)`:消融中发现,找出比当前基线高出采纳线以上的格,几格都过线时取分数最好的、同分取因子编号小的;返回采纳线、(K\* − B) ÷ 4、全部过线的格和选中的格(4.3 ⑰)。
- `SettleInput`(类型):一次消融结算的输入:基线、完整分数、方向、ε、已知最好、λ、被采纳改法的因子清单、各格结局、各条解释。清单里每个因子都要有一格,缺了直接报错。
- `settleAblation(input)`:一次消融的完整结算,给出 G、每格的撑几成和合并范围、有效格的世界比特、惊讶和分歧量、配合关系标签、确认与否定、分不开的组、消融中发现(4.3 ⑰、6.6、6.9);押的因子另由 `chooseBet` 算。
**信息价值与选格(6.7、6.8)**
- `OPEN_RESERVE`:成分名单里开放预留的名字,排在最后。
- `AblationModelInput`(类型):建成分模型要的输入:基线、完整分数、方向、已知最好、ε、λ、因子编号、各条解释。
- `AblationModel`(类型):成分模型:因子、成分(各条解释加开放预留)、起始权重、G、剩余空间 h、每格每成分的折线(分数原值和撑几成两种刻度)及期望、每格的事前期望撑几成。
- `remainingSpace(full, knownBest, ε, direction)`:剩余空间 h,有已知最好时是 max(K\* − f(完整), ε/100),没有时是 1(6.7"方向收益")。
- `ablationModel(input)`:建成分模型,每条解释一个成分、开放预留一个成分,解释合起来占 1 − λ(研究者等权、同一人平分),开放预留占 λ(6.7"成分与权重")。
- `gridSize(k)`:测 k 格时每格的小段数 m,即满足 m^k ≤ 4,000,000 的最大整数(6.7"信息价值")。
- `cellGrid(knots, k)`:把一格按折点切成小段,返回各小段中点和每个成分在各小段上的把握;小段数的规则按 6.7 原文。
- `measuredGrid(model, c, k)`:模型里第 c 格的网格,在分数原值上切段,再把中点换算成撑几成。
- `informationValueExact(model)`:全部格都测时的信息价值,逐段精确积分。
- `informationValueGrid(model, S)`:用网格算的信息价值,不论测几格都走网格,主要用来和精确算法互相对照。
- `informationValue(model, S)`:信息价值 VOI(S),即测了 S 这几格之后,挑出最该押的因子能比现在多挣多少(6.7);全测走精确积分,只测一部分走网格,空集是 0。
- `ComboParams`(类型):算价值与成本要的余额、每批上限、知识被用上的概率 u、衰减 r。
- `ComboValue`(类型):一个测格组合的 VOI、R̂、m′、价值、成本、净值(6.7"价值减成本")。
- `evaluateCombos(model, params)`:列出格数不超过"余额 − 1"的全部非空组合并算价值、成本、净值,另给 ĝ₀ 的原值。
- `CellRuleInput`(类型):四条选格规则共用的输入,即成分模型的输入加 `ComboParams` 和种子。
- `UntestedReason`(类型):一格没测的原因:剩余次数不够、信息价值不足以抵过成本、随机没抽中、规则是"都不测"。
- `CellDecision`(类型):选格结果,含 6.7 要求附上的全部数字:参数、种子、G、h、成分与起始权重、各因子的事前期望收益、ĝ₀、全部组合、选中的格及其净值,以及每一格测不测、没测的原因和含它的净值最大组合。
- `topCombo(combos)`:在给定的组合里挑净值最大的,差在 1e-12 以内算一样好,再先取格数少的、后按因子编号比(6.7"选格"第 2 条)。它不管净值是否大于 1e-12,所以也用来挑"含这一格的最好组合"。
- `pickByNet(combos)`:按净值挑要测的组合。先只留净值大于 1e-12 的组合,一个都没有就一格都不测;再在留下的组合里用 `topCombo` 挑。
- `selectByValue(input)`:选格规则"信息价值减成本"(6.7"选格")。
- `selectAll(input)`:选格规则"全测",按因子编号测前"余额 − 1"格(第十一部分)。
- `selectRandomSameCount(input)`:选格规则"随机抽同样多的格",格数和"信息价值减成本"会选的一样,用种子抽(第十一部分)。
- `selectNone(input)`:选格规则"都不测"(第十一部分)。
- `BetResult`(类型):押的因子的结果:更新后的成分权重、权重是否无从更新、各因子的方向收益(实测换算的或估计的)、挑出的因子及其预测收益、押或不押的两种情况、关掉反而更好的格。
- `chooseBet(input)`:押的因子,用有效格的实测更新各成分的权重,再挑方向收益最大的因子;有格关掉反而更好或估计收益不为正时不押,实测落在所有成分之外时保持起始权重(6.8)。
**信息账(6.9)**
这一组是方法里"唯一的尺子",不随选批、选格规则变,一律在分数原值上从本批基线 b 起按格宽切小格。
- `bitWidth(ε)`:格宽 Δ = ε/100。
- `entropyBits(ps)`:一组把握的拿不准程度 −Σ p log₂ p,单位是比特。
- `worldBits(m, origin, width)`:世界比特,即合并分布在宽 Δ 的小格上有多拿不准。
- `cellIndex(origin, width, v)`:实测 v 落在第几个小格,和切格用同一套边界。
- `surprise(m, origin, width, value)`:惊讶 = −log₂(事前给实测所在小格的把握),返回格号、事前把握和惊讶值;事前把握为 0 时惊讶记"测不到"并写明原因。
- `disagreement(entries, origin, width)`:分歧量,大家平均后的世界比特减去各人世界比特的平均,量的是人与人之间的差别,不掺开放预留,只记录。
- `Placement`(类型):落点四段:低于一成、一成到一半、一半到九成、高于九成。
- `placement(range, v)`:实测落在一个人范围的哪一段。
- `covers(range, v)`:实测是否落在一成到九成之间(含两端),也是 6.6 里"确认"的判据。
- `coverage(samples)`:落点四段的计数和覆盖率;一条样本都没有时覆盖率记"测不到",不记 0。
**余额与战绩(第七部分、6.10 第 4 条)**
- `RunState`(类型):一次正式运行在账上的三种状态:已预留还没核对出结果、确认启动、确认没启动。
- `runBalance(total, runs)`:从运行记录数出已扣次数、占用名额和余额;确认启动的扣一次,已预留的先占着名额,确认没启动的退还。只数运行次数,不算计算时间。
- `forwardBatchSize(batchCap, balance)`:本轮正向批量 k = min(每批上限, 余额)。
- `ablationCellLimit(balance)`:消融最多测"余额 − 1"格,余额为 0 或 1 时是 0。
- `Phase`(类型):正向或消融。
- `Tally`(类型):一个人的战绩:总数、中位数偏高几次、偏低几次、被穿过上沿几次、下沿几次、包住几次、落点四段计数。
- `trackRecord(samples)`:按研究者、按正向与消融分开数战绩,只用来显示,不改任何权重(6.9、6.10)。
- `authorVsMerged(items, direction)`:"作者自评 vs 大家合并",给出作者比大家乐观多少,以及实测落在两个范围的哪一段;只收已经跑过、有有效成绩的候选(6.10 第 4 条)。
**入口分配、种子与规则登记表(4.2、第九部分、第十一部分)**
- `EntryAssignment`(类型):一个研究者分到的入口,以及它是第几志愿(按剩余入口分到的为 null)。
- `assignEntries(input)`:第零轮的入口分配,按志愿一轮一轮分,争同一个入口时先给作者、再给编号最小的人,志愿用完的人按编号拿剩下的入口;结果里带志愿序号,够写分配记录(4.2"分配规则")。
- `decisionSeed(input)`:决定的种子,由研究编号、轮次、版本经 vocab 的 `hash` 算出(第九部分)。
- `seededRandom(seed)`:从种子确定地展开一串随机整数,同一个种子永远得到同一串数,不用 `Math.random`。
- `seededSample(items, size, seed)`:用种子从一组编号里不放回地均匀抽 size 个,结果只取决于种子和编号集合,与传入次序无关;候选和因子都这样抽。
- `BATCH_RULES`、`BatchRuleName`:选批规则登记表,名字 → 函数和版本号,两条:批内最好期望、均匀随机。
- `CELL_RULES`、`CellRuleName`:选格规则登记表,四条:信息价值减成本、全测、随机抽同样多的格、都不测。
- `runBatchRule(name, input)`:按名字跑一条选批规则,结果带上规则名和版本,flow 写决定记录时从这里取。
- `runCellRule(name, input)`:按名字跑一条选格规则,结果带上规则名和版本。