import type { Content } from "../../ledger/index.ts"; import { Board, cellId, CELL_NOT_RUN, explanationId, protocolMetrics, splitCell, validScore, } from "../../materials/index.ts"; import { chooseBet, decisionSeed, runBalance, runCellRule, settleAblation, sharedSettings, type AblationExplanation, type AblationModelInput, type CellOutcome, type CellRuleInput, type CellRuleName, type SettleInput, } from "../../rules/index.ts"; import type { HypothesisMetric, Json } from "../../vocab/index.ts"; import { formalRun, type RunOutcome } from "../formal-run.ts"; import { ablationOf, aliveCells, lastAblationImpl } from "../next-step.ts"; import { SEED_VERSIONS } from "./round.ts"; import type { Study } from "../study.ts"; // ⑮ 选格、⑯ 消融运行、⑰ 结算与押的因子、⑱ 轮末解封(任务书 17 第 2.2 节)。选格、结算和押的因子各写一条记录 // (结算和押的因子同一个事务),写下之前被打断就从账本重新算一遍;写下之后恢复时照原样用,不重算(方法.md 第九部分)。 /** * ⑮ 选格(方法.md 6.7):基线、完整分数、方向、已知最好、ε、λ、因子、各条解释,加余额、每批上限、u、衰减和种子, * 交给步骤清单里 selectCells 那一步的规则,写一条选格决定。种子用决定的版本 2(选批已用版本 1,方法.md 第九部分)。 */ export function selectCells(s: Study, round: number): void { const board = new Board(s.ledger.records()); const { config, steps } = s.frozen; const seed = decisionSeed({ studyId: config.studyId, round, version: SEED_VERSIONS.cells, }); const input: CellRuleInput = { ...modelInput(board, s, round), balance: runBalance(config.runs, board.runStates()).balance, batchCap: config.batchLimit, u: config.flow.knowledgeUse, decay: config.flow.decay, seed, }; const rule = steps.round.find((x) => x.id === "selectCells")!.rule! .name as CellRuleName; const { rule: used, ...output } = runCellRule(rule, input); // SAFETY: rules 是纯计算,输入输出都是纯 Json 值;账本在写入时校验 Json。 s.ledger.append("decision", { decision: "cells", round, rule: used.name, ruleVersion: used.version, params: output.params, seed, input: input as unknown as Json, output: output as unknown as Json, }); } /** ⑯ 消融运行:用核验过的这一格快照,复用正式运行的登记、扣次、判分入口。完整代码和基线的分数取本批已有的运行结果,不重跑。 */ export function ablationRun( s: Study, round: number, cell: string, run: string, ): Promise { const board = new Board(s.ledger.records()); return formalRun( s, { run, purpose: "ablation", round, object: cell }, board.code(cell).version, ablationMetrics(board, splitCell(cell).candidate), ); } /** 消融格正式运行登记的指标:照被采纳候选的冻结协议取;消融格里假设专用指标缺失一律只记缺失、不作废这一格(方法.md 第五部分"指标")。 */ export function ablationMetrics( board: Board, candidate: string, ): HypothesisMetric[] { return protocolMetrics(board, board.protocol(candidate)).map((m) => ({ ...m, missing: "recordMissing", })); } /** * ⑰ 结算(方法.md 4.3 ⑰、6.6):选中的格全部有结果、"跑不起来"或"不跑"事件后,照 SettleInput 拼输入, * 写一条结算记录;押的因子(6.8)在同一个事务里写成种类 bet 的决定。 * "消融中发现"在 settleAblation 的输出里:materials 的 Board.baselineOf 据此把那一格的代码当成下一轮的基线,不另写采纳记录。 */ export function settle(s: Study, round: number): void { const board = new Board(s.ledger.records()); const { candidate, keys } = ablationOf(board, round)!; const events = s.ledger .records() .flatMap((r) => (r.kind === "event" ? [r.content] : [])); const model = modelInput(board, s, round); const input: SettleInput = { ...model, cells: keys.map((key) => ({ factor: key, outcome: outcomeOf(board, events, round, candidate, key), })), }; const output = settleAblation(input); const betInput = { ...model, cells: input.cells }; const bet = chooseBet(betInput); // SAFETY: rules 是纯计算,结算和决定的输入输出都是纯 Json 值;账本在写入时校验 Json。 s.ledger.transaction(() => { s.ledger.append("settlement", { round, result: { candidate, input, output } as unknown as Json, }); s.ledger.append("decision", { decision: "bet", round, rule: "chooseBet", ruleVersion: 1, params: { direction: model.direction, epsilon: model.epsilon, knownBest: model.knownBest, lambda: model.lambda, }, seed: null, input: betInput as unknown as Json, output: bet as unknown as Json, }); }); } /** 一格的结局(方法.md 6.6"一格的几种结局"):没测、跑不起来、证据不足、一起关、有效分数。 */ function outcomeOf( board: Board, events: readonly Content<"event">[], round: number, candidate: string, key: string, ): CellOutcome { if (!board.decision("cells", round)!.output.chosen.includes(key)) return { kind: "untested" }; const cell = cellId(candidate, key); const impl = lastAblationImpl(board, cell)!; if (impl.content.cannotRun !== null) return { kind: "cannotRun" }; if (events.some((e) => e.event === CELL_NOT_RUN && e.objects.includes(cell))) return { kind: "insufficient" }; const off = [key, ...impl.content.alsoOff]; // 几格实际关掉的因子完全相同时,沿用同设置第一格的运行结果(不重跑,方法.md 6.6"一起关")。 const alive = aliveCells(board, events, round); const sameAs = new Map( sharedSettings( alive.map((x) => ({ factor: splitCell(x.cell).key, off: x.off })), ).map((x) => [x.factor, x.sameAs]), ); const first = sameAs.get(key); const ranCell = first === null || first === undefined ? cell : cellId(candidate, first); const run = board .runs() .filter( (r) => r.plan.purpose === "ablation" && r.plan.round === round && r.plan.object === ranCell, ) .at(-1)!; const score = validScore(run.result!.judged); if (off.length > 1) return { kind: "joint", off, score }; return score === null ? { kind: "insufficient" } : { kind: "valid", score }; } /** * ⑱ 轮末解封(方法.md 4.3 ⑱):本轮选批选中的每个候选(跑过的、入选了却没跑成的)解封作者自评, * 全部写在同一个事务里。只在收尾阶段关闭之后走到这里("下一步做什么"保证),提前写会泄露。 * "入选了却没跑成的候选离开候选池"不写记录:Board.carried 只留从没进过任何一批的候选。 */ export function unsealChosen(s: Study, round: number): void { const board = new Board(s.ledger.records()); const records = s.ledger.records(); const chosen = board.decision("batch", round)!.output.chosen; s.ledger.transaction(() => { for (const candidate of chosen) { if ( records.some( (r) => r.kind === "unseal" && r.content.candidate === candidate, ) ) continue; const seals = board.proposalsOf(candidate).map((id) => { const seq = board.proposal(id).seq; const seal = records.find( (r) => r.kind === "seal" && r.content.proposal === seq, ); if (seal === undefined) throw new Error( `提案 ${id}(记录 ${seq})没有封存记录:提案的作者自评在写入提案时一并封存`, ); return seal.seq; }); s.ledger.append("unseal", { candidate, seals }); } }); } /** 成分模型的输入(方法.md 6.7):选格、结算和押的因子共用。完整代码和本批基线的分数取本批已有的运行结果。 */ function modelInput(board: Board, s: Study, round: number): AblationModelInput { const { candidate, keys } = ablationOf(board, round)!; const { config, facts } = s.frozen; return { baseline: board.baselineScore(board.baselineOf(round)), full: board.baselineScore(candidate), direction: facts.finalMetric.direction, knownBest: facts.knownBest, epsilon: facts.epsilon, lambda: config.flow.openReserve, factors: keys, explanations: explanationsOf(board, round), }; } /** 这一轮已冻结的各条解释:作者的第 i 条(materials 的 explanationId),每条对每一格给的三个数。 */ function explanationsOf(board: Board, round: number): AblationExplanation[] { return board .published("explanation") .filter((x) => x.round === round) .flatMap((x) => x.content.explanations.map((e, i) => ({ id: explanationId(round, x.author, i + 1), author: x.author, cells: e.cells!, })), ); }