flow: 消融到收尾、轮末解封、一轮接一轮与研究结束(任务书 17 第二块)
- ⑬–⑱:listCells 从采纳决定和冻结协议现算(不写记录);explanation 有消融才开 阶段;selectCells 照 CellRuleInput 拼输入(余额当时现算、decisionSeed 版本 2), 写种类 cells 的决定,没选中的格不另写事件;消融实现/核验按第几次编号 (r<轮>-ablationImplementation-1、verification-1、-2……从账本数),重做从上一次 这一格代码接着改,次数用完写 cellNotRunEvent 不跑不扣次;cannotRun 传 null 不核验 不运行不扣次;alsoOff 用 sharedSettings 沿用同设置的第一格;消融运行一个接一个跑, 用途 ablation,登记/扣次/恢复与正向同一入口,恢复时指标缺失策略覆盖为只记缺失 (方法.md 第五部分);settle 拼 SettleInput 调 settleAblation,结算与 chooseBet 的 bet 决定写在同一事务;wrapUp 阶段关闭后,本轮选中的每个候选在同一个事务里解封 作者自评(取最后一版提案的封存)。 - 一轮接一轮:每轮开头用 runBalance 现算余额,余额为 0 不开新轮;走到 export 停下, 原因"导出还没写"。去掉 roundsNext 里 step.id !== "listCells" 的临时写法。 - 消融中发现由 materials 的 Board.baselineOf 从结算输出读出,flow 不另写采纳记录 (已核实 materials 已实现)。 - 测试 +14:下一步分支 8 条(含收尾关闭后才解封、余额 0 走到导出停、关掉消融时 解释照方法停下)、消融步骤 6 条(选格输入逐字段手拼对账、结算四种结局、同设置 沿用、解封取最后一版封存);npm run check 通过,npm test 472 条通过。 - 全新上下文审阅一轮(只看消融重做循环、登记扣次恢复、选格/结算/押的因子输入、 解封时机、下一轮与结束条件):没有会污染账本的问题;采纳一条小修(删掉未用的 导入),其余笔记照列不改。 flow 1302 → 1662 行(上限 3500),materials 未动(1930 行)。
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { test, type TestContext } from "node:test";
|
||||
import { stepList } from "../src/config/index.ts";
|
||||
import { fullConfig as checkedFull } from "../src/flow/frozen.ts";
|
||||
import { selectCells, settle, unsealChosen } from "../src/flow/steps/ablation.ts";
|
||||
import type { Study } from "../src/flow/study.ts";
|
||||
import { Ledger } from "../src/ledger/index.ts";
|
||||
import { Board } from "../src/materials/index.ts";
|
||||
import { chooseBet, decisionSeed, runCellRule, settleAblation, type AblationModelInput, type CellRuleInput, type SettleInput } from "../src/rules/index.ts";
|
||||
import { checkTaskFacts } from "../src/tasks/index.ts";
|
||||
import { fullConfig } from "./config-helpers.ts";
|
||||
import { definition, exited, proposal, tempDir } from "./ledger-helpers.ts";
|
||||
import { judged, r, Writer } from "./materials-helpers.ts";
|
||||
|
||||
// 选格、结算与押的因子、轮末解封(任务书 17 第 2.2 节):在真实账本里写到上一步做完,跑这一步,看写下的记录。
|
||||
// 预期值按条文手拼:选格输入照 rules 的 CellRuleInput 逐项拼,结算的各格结局照方法.md 6.6 的情形拼;
|
||||
// 输出用 rules 的函数当对照(同选批测试的写法,rules 自己有独立预期值的测试)。
|
||||
|
||||
const config = fullConfig();
|
||||
const facts = checkTaskFacts(JSON.parse(readFileSync(join(import.meta.dirname, "..", "tasks", "circle_packing", "task.json"), "utf8")));
|
||||
|
||||
function study(t: TestContext): { s: Study; w: Writer } {
|
||||
const ledger = Ledger.open(join(tempDir(t), "ledger.db"));
|
||||
t.after(() => ledger.close());
|
||||
const frozen = { config: checkedFull(config), steps: stepList(config), facts, prompts: null, image: "", seed: 1 };
|
||||
return { s: { ledger, frozen } as unknown as Study, w: new Writer(ledger) };
|
||||
}
|
||||
|
||||
/** 基线 0.96、被采纳候选 r1-c1(两个因子)完整代码 1.25。 */
|
||||
function adoptedBase(w: Writer): void {
|
||||
w.l.append("code", { object: "base", version: "v-base", locations: null });
|
||||
w.run("base-1", "baseline", 0, null, judged(0.96, "main"));
|
||||
w.l.append("code", { object: "r1-c1", version: "v-full", locations: null });
|
||||
w.run("r1-forward-r1-c1-1", "forward", 1, "r1-c1", judged(1.25, "main"));
|
||||
w.protocol("r1-c1", { factors: [{ name: "因子一" }, { name: "因子二" }], safeguards: [], metrics: { defined: [], reused: [] }, body: "正文", clauses: [], round: 1, baseline: "base", proposals: ["r1-a1-1"] } as never);
|
||||
w.decision("adopt", 1, null, { adopted: "r1-c1" });
|
||||
}
|
||||
|
||||
const EXPLANATIONS = [
|
||||
{ id: "r1-a1-x1", author: "a1", cells: [{ factor: "p1", range: r(1.0, 1.1, 1.2) }, { factor: "p2", range: r(1.15, 1.2, 1.24) }] },
|
||||
{ id: "r1-a2-x1", author: "a2", cells: [{ factor: "p1", range: r(0.9, 1.05, 1.2) }, { factor: "p2", range: r(1.1, 1.18, 1.25) }] },
|
||||
] as const;
|
||||
|
||||
/** 解释阶段关闭:a1、a2 各交一条,对两格都给了三个数。 */
|
||||
function explain(w: Writer): void {
|
||||
w.open("r1-explanation", "explanation", 1, ["a1", "a2"]);
|
||||
for (const e of EXPLANATIONS) {
|
||||
w.sub("r1-explanation", e.author, "explanation", null, null, { explanations: [{ body: `机制(${e.author})`, cells: e.cells.map((c) => ({ factor: c.factor, range: c.range })) }] });
|
||||
}
|
||||
w.close("r1-explanation");
|
||||
}
|
||||
|
||||
/** 成分模型的输入:选格、结算和押的因子共用的那一份。 */
|
||||
function modelInput(): AblationModelInput {
|
||||
return { baseline: 0.96, full: 1.25, direction: "maximize", knownBest: facts.knownBest, epsilon: facts.epsilon, lambda: 0.1, factors: ["p1", "p2"], explanations: EXPLANATIONS.map((e) => ({ id: e.id, author: e.author, cells: e.cells.map((c) => ({ factor: c.factor, range: c.range })) })) };
|
||||
}
|
||||
|
||||
test("选格:输入照 CellRuleInput 拼(基线、完整分数、各条解释、余额从运行记录算、种子用选格的版本 2),规则取步骤清单", (t) => {
|
||||
const { s, w } = study(t);
|
||||
adoptedBase(w);
|
||||
explain(w);
|
||||
selectCells(s, 1);
|
||||
const record = s.ledger.records().find((x) => x.kind === "decision" && x.content.decision === "cells")!;
|
||||
const seed = decisionSeed({ studyId: "circle-full-1", round: 1, version: 2 });
|
||||
const input: CellRuleInput = {
|
||||
...modelInput(),
|
||||
// 13 次里基线和正向各确认启动 1 次,余额 11。
|
||||
balance: 11,
|
||||
batchCap: 2,
|
||||
u: 0.5,
|
||||
decay: 0.5,
|
||||
seed,
|
||||
};
|
||||
const { rule: used, ...output } = runCellRule("valueMinusCost", input);
|
||||
assert.deepEqual(record.content, { decision: "cells", round: 1, rule: used.name, ruleVersion: used.version, params: output.params, seed, input, output });
|
||||
});
|
||||
|
||||
/** 一次消融实现阶段:subs 是 [格, cannotRun, alsoOff];cannotRun 为 null 表示交了代码。 */
|
||||
function impl(w: Writer, n: number, subs: Array<[string, string | null, string[]?]>): void {
|
||||
w.open(`r1-ablationImplementation-${n}`, "ablationImplementation", 1, subs.map(([cell]) => `experimenter-${cell}`));
|
||||
for (const [cell, cannotRun, alsoOff] of subs) {
|
||||
w.sub(`r1-ablationImplementation-${n}`, `experimenter-${cell}`, "ablationImplementation", cell, null, { alsoOff: alsoOff ?? [], cannotRun, concerns: "无" });
|
||||
}
|
||||
w.close(`r1-ablationImplementation-${n}`);
|
||||
}
|
||||
|
||||
/** 结算一次:拼出 SettleInput,跑 settle,核对 settlement 和 bet 两条记录。 */
|
||||
function settled(t: TestContext, prepare: (w: Writer) => void, cells: SettleInput["cells"]): void {
|
||||
const { s, w } = study(t);
|
||||
adoptedBase(w);
|
||||
explain(w);
|
||||
w.decision("cells", 1, null, { chosen: ["p1", "p2"] });
|
||||
prepare(w);
|
||||
settle(s, 1);
|
||||
const input: SettleInput = { ...modelInput(), cells };
|
||||
const betInput = { ...modelInput(), cells };
|
||||
const settlement = s.ledger.records().find((x) => x.kind === "settlement")!;
|
||||
assert.deepEqual(settlement.content, { round: 1, result: { candidate: "r1-c1", input, output: settleAblation(input) } });
|
||||
const bet = s.ledger.records().find((x) => x.kind === "decision" && x.content.decision === "bet")!;
|
||||
assert.deepEqual(bet.content, {
|
||||
decision: "bet",
|
||||
round: 1,
|
||||
rule: "chooseBet",
|
||||
ruleVersion: 1,
|
||||
params: { direction: "maximize", epsilon: facts.epsilon, knownBest: facts.knownBest, lambda: 0.1 },
|
||||
seed: null,
|
||||
input: betInput,
|
||||
output: chooseBet(betInput),
|
||||
});
|
||||
}
|
||||
|
||||
test("结算:有效分数、没测、跑不起来、超时记证据不足,各按 6.6 的结局拼", (t) => {
|
||||
settled(
|
||||
t,
|
||||
(w) => {
|
||||
impl(w, 1, [["r1-c1-p1", null], ["r1-c1-p2", null]]);
|
||||
w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.1, "main"));
|
||||
// p2 启动了但跑崩: judged 为 null,记证据不足(扣了次数)。
|
||||
w.l.append("runPlanned", { run: "r1-ablation-r1-c1-p2-1", purpose: "ablation", round: 1, object: "r1-c1-p2", definition });
|
||||
w.l.append("runStarted", { run: "r1-ablation-r1-c1-p2-1" });
|
||||
w.l.append("runResult", { run: "r1-ablation-r1-c1-p2-1", process: exited, judged: null });
|
||||
},
|
||||
[
|
||||
{ factor: "p1", outcome: { kind: "valid", score: 1.1 } },
|
||||
{ factor: "p2", outcome: { kind: "insufficient" } },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("结算:没选中的格记没测;交了跑不起来的记跑不起来", (t) => {
|
||||
const { s, w } = study(t);
|
||||
adoptedBase(w);
|
||||
explain(w);
|
||||
w.decision("cells", 1, null, { chosen: ["p1"] });
|
||||
impl(w, 1, [["r1-c1-p1", "离了 p1 跑不起来"]]);
|
||||
settle(s, 1);
|
||||
const input: SettleInput = {
|
||||
...modelInput(),
|
||||
cells: [
|
||||
{ factor: "p1", outcome: { kind: "cannotRun" } },
|
||||
{ factor: "p2", outcome: { kind: "untested" } },
|
||||
],
|
||||
};
|
||||
const settlement = s.ledger.records().find((x) => x.kind === "settlement")!;
|
||||
assert.deepEqual(settlement.content, { round: 1, result: { candidate: "r1-c1", input, output: settleAblation(input) } });
|
||||
});
|
||||
|
||||
test("结算:核验不通过、次数用完有事件的记证据不足;设置相同的两格都记一起关,沿用第一格的分数", (t) => {
|
||||
settled(
|
||||
t,
|
||||
(w) => {
|
||||
impl(w, 1, [["r1-c1-p1", null, ["p2"]]]);
|
||||
w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.05, "main"));
|
||||
impl(w, 2, [["r1-c1-p2", null]]);
|
||||
w.l.append("event", { event: "cellNotRun", reason: "第 2 次核验仍不通过,修复次数上限 1 已用完", objects: ["r1-c1-p2"] });
|
||||
},
|
||||
[
|
||||
{ factor: "p1", outcome: { kind: "joint", off: ["p1", "p2"], score: 1.05 } },
|
||||
{ factor: "p2", outcome: { kind: "insufficient" } },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("结算:设置相同的两格都记一起关,沿用第一格的分数", (t) => {
|
||||
settled(
|
||||
t,
|
||||
(w) => {
|
||||
impl(w, 1, [["r1-c1-p1", null, ["p2"]]]);
|
||||
w.run("r1-ablation-r1-c1-p1-1", "ablation", 1, "r1-c1-p1", judged(1.05, "main"));
|
||||
impl(w, 2, [["r1-c1-p2", null, ["p1"]]]);
|
||||
},
|
||||
[
|
||||
{ factor: "p1", outcome: { kind: "joint", off: ["p1", "p2"], score: 1.05 } },
|
||||
{ factor: "p2", outcome: { kind: "joint", off: ["p2", "p1"], score: 1.05 } },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("轮末解封:解封本轮选中的候选的全部作者自评(取最后一版的封存),没选中的继续封存", (t) => {
|
||||
const { s, w } = study(t);
|
||||
w.open("r1-proposal", "proposal", 1, ["a1", "a2"]);
|
||||
w.sub("r1-proposal", "a1", "proposal", null, "r1-a1-1", proposal(r(1.0, 1.1, 1.2), "第一版"));
|
||||
const v2 = s.ledger.append("submission", {
|
||||
stageId: "r1-proposal",
|
||||
author: "a1",
|
||||
kind: "proposal",
|
||||
target: null,
|
||||
item: "r1-a1-1",
|
||||
version: 2,
|
||||
content: proposal(r(1.05, 1.15, 1.25), "第二版"),
|
||||
jev: null,
|
||||
} as never);
|
||||
w.sub("r1-proposal", "a2", "proposal", null, "r1-a2-1", proposal(r(0.9, 1.0, 1.1)));
|
||||
w.close("r1-proposal");
|
||||
w.decision("dedup", 1, null, { fresh: [{ candidate: "r1-c1", proposals: ["r1-a1-1"] }, { candidate: "r1-c2", proposals: ["r1-a2-1"] }], carried: [] });
|
||||
w.decision("batch", 1, null, { chosen: ["r1-c1"] });
|
||||
unsealChosen(s, 1);
|
||||
const unseals = s.ledger.records().filter((x) => x.kind === "unseal");
|
||||
assert.equal(unseals.length, 1);
|
||||
assert.equal(unseals[0]!.content.candidate, "r1-c1");
|
||||
// 解封的是最后一版(v2)的封存。
|
||||
const seal = s.ledger.records().find((x) => x.kind === "seal" && x.content.proposal === v2.seq)!;
|
||||
assert.deepEqual(unseals[0]!.content.seals, [seal.seq]);
|
||||
assert.deepEqual(new Board(s.ledger.records()).unsealed(), [{ candidate: "r1-c1", author: "a1", range: r(1.05, 1.15, 1.25) }]);
|
||||
});
|
||||
@@ -266,7 +266,9 @@ function roundOne(last: "proposal" | "dedupe" | "firstPrediction" | "critique" |
|
||||
]),
|
||||
response: () => stage(book, "r1-response", "response", [["researcher-1", "response", "r1-c1", null], ["researcher-2", "unification", "r1-c2", null]]),
|
||||
freezeProtocol: () => {
|
||||
for (const candidate of C) book.add("protocol", { candidate, protocol: { round: 1, baseline: "base", proposals: [] } });
|
||||
// r1-c1 一个因子(采纳它不做消融),r1-c2 两个因子(采纳它走 ⑬–⑰)。
|
||||
book.add("protocol", { candidate: "r1-c1", protocol: { round: 1, baseline: "base", proposals: [], factors: [{}] } });
|
||||
book.add("protocol", { candidate: "r1-c2", protocol: { round: 1, baseline: "base", proposals: [], factors: [{}, {}] } });
|
||||
},
|
||||
secondPrediction: () => stage(book, "r1-secondPrediction", "secondPrediction", R.flatMap((a) => C.map((c) => [a, "secondPrediction", c, null] as [string, SubmissionKind, string, null]))),
|
||||
selectBatch: () => book.add("decision", { decision: "batch", round: 1, rule: "bestExpected", ruleVersion: 1, params: null, seed: "s", input: null, output: { chosen: ["r1-c1"] } }),
|
||||
@@ -494,12 +496,190 @@ test("正向串行:上次未启动起新编号;已登记未结束必须先
|
||||
book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: null } });
|
||||
const after = new Board(book.records);
|
||||
assert.ok(after.runs().filter((r) => r.plan.purpose === "forward").every((r) => after.runPublic(r)));
|
||||
assert.deepEqual(nextStep(book.records), { do: "halt", reason: "消融还没写(任务书 17)", objects: [] });
|
||||
// 没有采纳:不开解释阶段,直接开收尾(方法.md 4.3 ⑫)。
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-wrapUp", stage: "wrapUp", round: 1, roster: R });
|
||||
});
|
||||
|
||||
test("采纳只有一个因子的候选也统一在 listCells 停下,不擅自开收尾", () => {
|
||||
test("采纳只有一个因子的候选:不做消融,直接开收尾(方法.md 4.3 ⑫)", () => {
|
||||
const book = firstVerification(batchTwo(1), true, true);
|
||||
for (const c of C) recordForward(book, c, 1, "valid");
|
||||
book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: C[0]! } });
|
||||
assert.deepEqual(nextStep(book.records), { do: "halt", reason: "消融还没写(任务书 17)", objects: [] });
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-wrapUp", stage: "wrapUp", round: 1, roster: R });
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ⑬–⑱:消融到收尾、轮末解封、下一轮和研究结束(任务书 17 第 2.2、2.3 节)。预期动作手写,不调模型、Jev 或 Docker。
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const ADOPTED = "r1-c2"; // 两个因子:p1、p2
|
||||
const CELL1 = "r1-c2-p1";
|
||||
const CELL2 = "r1-c2-p2";
|
||||
|
||||
/** 采纳了两个因子的 r1-c2。 */
|
||||
function adoptedBook(): Book {
|
||||
const book = firstVerification(batchTwo(1), true, true);
|
||||
for (const c of C) recordForward(book, c, 1, "valid");
|
||||
return book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: ADOPTED } });
|
||||
}
|
||||
|
||||
/** 解释阶段:开、三人各交一份、关。 */
|
||||
function explained(book: Book): Book {
|
||||
return stage(book, "r1-explanation", "explanation", R.map((a) => [a, "explanation", null, null]));
|
||||
}
|
||||
|
||||
/** 选格决定。 */
|
||||
function cellsChosen(book: Book, chosen: string[]): Book {
|
||||
return book.add("decision", { decision: "cells", round: 1, rule: "valueMinusCost", ruleVersion: 1, params: null, seed: "s2", input: null, output: { chosen } });
|
||||
}
|
||||
|
||||
/** 一次消融实现阶段:subs 是 [格, cannotRun, alsoOff]。 */
|
||||
function implStage(book: Book, n: number, subs: Array<[string, string | null, string[]?]>): Book {
|
||||
const stageId = `r1-ablationImplementation-${n}`;
|
||||
book.open(stageId, "ablationImplementation", 1, subs.map(([cell]) => `experimenter-${cell}`));
|
||||
for (const [cell, cannotRun, alsoOff] of subs) {
|
||||
book.add("submission", { stageId, author: `experimenter-${cell}`, kind: "ablationImplementation", target: cell, item: null, version: 1, content: { alsoOff: alsoOff ?? [], cannotRun, concerns: "无" }, jev: null });
|
||||
}
|
||||
return book.add("stageClose", { stageId });
|
||||
}
|
||||
|
||||
/** 一次消融核验阶段:subs 是 [格, [因子, 关没关]]。 */
|
||||
function verStage(book: Book, n: number, subs: Array<[string, Array<[string, boolean]>]>): Book {
|
||||
const stageId = `r1-ablationVerification-${n}`;
|
||||
book.open(stageId, "ablationVerification", 1, subs.map(([cell]) => `verifier-${cell}`));
|
||||
for (const [cell, offs] of subs) {
|
||||
book.add("submission", { stageId, author: `verifier-${cell}`, kind: "ablationVerification", target: cell, item: null, version: 1, content: { factors: offs.map(([factor, off]) => ({ factor, off, where: "initial.py:1" })) }, jev: null });
|
||||
}
|
||||
return book.add("stageClose", { stageId });
|
||||
}
|
||||
|
||||
/** 一次消融运行:没启动,或启动了、判分算数。 */
|
||||
function recordAblation(book: Book, cell: string, n: number, ending: "valid" | "notStarted"): Book {
|
||||
const run = `r1-ablation-${cell}-${n}`;
|
||||
book.add("runPlanned", { run, purpose: "ablation", round: 1, object: cell, definition });
|
||||
if (ending === "notStarted") return book.add("runResult", { run, process: { ending, reason: "Docker 未启动" }, judged: null });
|
||||
book.add("runStarted", { run });
|
||||
return book.add("runResult", { run, process: exited, judged: judged(1.2, "main") });
|
||||
}
|
||||
|
||||
test("采纳多因子改法:先开解释阶段,每人一个任务、没有对象;关闭后选格", () => {
|
||||
const book = adoptedBook();
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-explanation", stage: "explanation", round: 1, roster: R });
|
||||
book.open("r1-explanation", "explanation", 1, R);
|
||||
assert.deepEqual(nextStep(book.records), { do: "dispatch", stageId: "r1-explanation", tasks: R.map((actor) => ({ actor, kind: "explanation", object: null })) });
|
||||
for (const a of R) book.sub("r1-explanation", a, "explanation", null, null);
|
||||
assert.deepEqual(nextStep(book.records), { do: "closeStage", stageId: "r1-explanation" });
|
||||
book.add("stageClose", { stageId: "r1-explanation" });
|
||||
assert.deepEqual(nextStep(book.records), { do: "selectCells", round: 1 });
|
||||
});
|
||||
|
||||
test("选格做完:每格固定一位实验员;交了跑不起来的格不核验、不运行,没启动的下次起新编号", () => {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-ablationImplementation-1", stage: "ablationImplementation", round: 1, roster: [`experimenter-${CELL1}`, `experimenter-${CELL2}`] });
|
||||
implStage(book, 1, [[CELL1, null], [CELL2, "离了 p2 跑不起来"]]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-ablationVerification-1", stage: "ablationVerification", round: 1, roster: [`verifier-${CELL1}`] });
|
||||
verStage(book, 1, [[CELL1, [["p1", true], ["p2", false]]]]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "ablationRun", round: 1, cell: CELL1, run: `r1-ablation-${CELL1}-1` });
|
||||
recordAblation(book, CELL1, 1, "notStarted");
|
||||
assert.deepEqual(nextStep(book.records), { do: "ablationRun", round: 1, cell: CELL1, run: `r1-ablation-${CELL1}-2` });
|
||||
recordAblation(book, CELL1, 2, "valid");
|
||||
assert.deepEqual(nextStep(book.records), { do: "settle", round: 1 });
|
||||
});
|
||||
|
||||
test("核验不通过:退回原实验员重做,只重验重做的格;次数用完写不跑事件,不登记运行", () => {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
implStage(book, 1, [[CELL1, null], [CELL2, null]]);
|
||||
verStage(book, 1, [[CELL1, [["p1", false], ["p2", false]]], [CELL2, [["p1", false], ["p2", true]]]]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-ablationImplementation-2", stage: "ablationImplementation", round: 1, roster: [`experimenter-${CELL1}`] });
|
||||
implStage(book, 2, [[CELL1, null]]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-ablationVerification-2", stage: "ablationVerification", round: 1, roster: [`verifier-${CELL1}`] });
|
||||
verStage(book, 2, [[CELL1, [["p1", false], ["p2", false]]]]);
|
||||
const next = nextStep(book.records);
|
||||
assert.equal(next.do, "cellNotRun");
|
||||
if (next.do !== "cellNotRun") throw new Error("应写不跑事件");
|
||||
assert.equal(next.cell, CELL1);
|
||||
assert.match(next.reason, /第 2 次核验仍不通过,修复次数上限 1 已用完/);
|
||||
book.add("event", { event: "cellNotRun", reason: next.reason, objects: [next.cell] });
|
||||
// p1 不跑不扣次;只跑 p2。
|
||||
assert.deepEqual(nextStep(book.records), { do: "ablationRun", round: 1, cell: CELL2, run: `r1-ablation-${CELL2}-1` });
|
||||
});
|
||||
|
||||
test("几格实际关掉的因子完全相同:沿用第一格,不重复跑", () => {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
implStage(book, 1, [[CELL1, null, ["p2"]], [CELL2, null, ["p1"]]]);
|
||||
verStage(book, 1, [[CELL1, [["p1", true], ["p2", true]]], [CELL2, [["p1", true], ["p2", true]]]]);
|
||||
// 两格实际关的都是 {p1, p2}:只跑第一格。
|
||||
assert.deepEqual(nextStep(book.records), { do: "ablationRun", round: 1, cell: CELL1, run: `r1-ablation-${CELL1}-1` });
|
||||
recordAblation(book, CELL1, 1, "valid");
|
||||
assert.deepEqual(nextStep(book.records), { do: "settle", round: 1 });
|
||||
});
|
||||
|
||||
test("全部交跑不起来:不核验、不运行,直接结算", () => {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
implStage(book, 1, [[CELL1, "跑不起来一"], [CELL2, "跑不起来二"]]);
|
||||
assert.deepEqual(nextStep(book.records), { do: "settle", round: 1 });
|
||||
});
|
||||
|
||||
/** 结算和押的因子写完、收尾关闭、解封也写完的账本(余额还有 8 次)。 */
|
||||
function afterWrapUp(): Book {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
implStage(book, 1, [[CELL1, null], [CELL2, null]]);
|
||||
verStage(book, 1, [[CELL1, [["p1", true], ["p2", false]]], [CELL2, [["p1", false], ["p2", true]]]]);
|
||||
recordAblation(book, CELL1, 1, "valid");
|
||||
recordAblation(book, CELL2, 1, "valid");
|
||||
book.add("settlement", { round: 1, result: { candidate: ADOPTED, input: null, output: null } });
|
||||
book.add("decision", { decision: "bet", round: 1, rule: "chooseBet", ruleVersion: 1, params: null, seed: null, input: null, output: null });
|
||||
return stage(book, "r1-wrapUp", "wrapUp", R.map((a) => [a, "wrapUp", null, null]));
|
||||
}
|
||||
|
||||
test("结算和押的因子写完:开收尾;收尾关闭后解封本轮选中的全部候选,然后开下一轮", () => {
|
||||
const book = cellsChosen(explained(adoptedBook()), ["p1", "p2"]);
|
||||
implStage(book, 1, [[CELL1, null], [CELL2, null]]);
|
||||
verStage(book, 1, [[CELL1, [["p1", true], ["p2", false]]], [CELL2, [["p1", false], ["p2", true]]]]);
|
||||
recordAblation(book, CELL1, 1, "valid");
|
||||
assert.deepEqual(nextStep(book.records), { do: "ablationRun", round: 1, cell: CELL2, run: `r1-ablation-${CELL2}-1` });
|
||||
recordAblation(book, CELL2, 1, "valid");
|
||||
assert.deepEqual(nextStep(book.records), { do: "settle", round: 1 });
|
||||
book.add("settlement", { round: 1, result: { candidate: ADOPTED, input: null, output: null } });
|
||||
book.add("decision", { decision: "bet", round: 1, rule: "chooseBet", ruleVersion: 1, params: null, seed: null, input: null, output: null });
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r1-wrapUp", stage: "wrapUp", round: 1, roster: R });
|
||||
stage(book, "r1-wrapUp", "wrapUp", R.map((a) => [a, "wrapUp", null, null]));
|
||||
// 收尾关闭后:解封本轮选批选中的 r1-c1、r1-c2;还有一个没解封就继续。
|
||||
assert.deepEqual(nextStep(book.records), { do: "unsealChosen", round: 1 });
|
||||
book.add("unseal", { candidate: "r1-c1", seals: [1] });
|
||||
assert.deepEqual(nextStep(book.records), { do: "unsealChosen", round: 1 });
|
||||
book.add("unseal", { candidate: "r1-c2", seals: [2] });
|
||||
// 余额还有(已扣 5 次):开第 2 轮。
|
||||
assert.deepEqual(nextStep(book.records), { do: "openStage", stageId: "r2-proposal", stage: "proposal", round: 2, roster: R });
|
||||
});
|
||||
|
||||
test("余额为 0:不开下一轮,走到导出停下,原因写明", () => {
|
||||
const book = afterWrapUp();
|
||||
book.add("unseal", { candidate: "r1-c1", seals: [1] });
|
||||
book.add("unseal", { candidate: "r1-c2", seals: [2] });
|
||||
// 已扣 5 次(基线 1、正向 2、消融 2):把冻结配置的运行次数改成 5,余额为 0。
|
||||
const record = book.records.find((r) => r.kind === "config")!;
|
||||
const study = record.content as unknown as StudyRecord;
|
||||
study.config = { ...fullConfig(), runs: 5 } as unknown as Json;
|
||||
assert.deepEqual(nextStep(book.records), { do: "halt", reason: "导出还没写", objects: [] });
|
||||
});
|
||||
|
||||
test("配置关掉消融、又采纳了多因子改法:先记按配置跳过,再在解释停下", () => {
|
||||
const book = firstVerification(batchTwo(1), true, true);
|
||||
for (const c of C) recordForward(book, c, 1, "valid");
|
||||
book.add("decision", { decision: "adopt", round: 1, rule: "adopt", ruleVersion: 1, params: null, seed: null, input: null, output: { adopted: ADOPTED } });
|
||||
// 冻结的配置改为关掉消融:配置和步骤清单一起换(步骤清单在研究开始时冻结)。
|
||||
const config = fullConfig();
|
||||
if (config.flow.preset !== "full") throw new Error("测试配置应是完整流程");
|
||||
// 配置关掉消融时不写 cellRule(它只有开着消融才用得到)。
|
||||
config.flow.ablation = { enabled: false };
|
||||
const record = book.records.find((r) => r.kind === "config")!;
|
||||
const study = record.content as unknown as StudyRecord;
|
||||
study.config = config as unknown as Json;
|
||||
study.steps = stepList(config) as unknown as Json;
|
||||
const reason = "配置关掉了消融";
|
||||
assert.deepEqual(nextStep(book.records), { do: "skip", step: "r1-listCells", reason });
|
||||
book.add("event", { event: "skippedByConfig", reason, objects: ["r1-listCells"] });
|
||||
const next = nextStep(book.records);
|
||||
assert.equal(next.do, "halt");
|
||||
assert.match((next as { reason: string }).reason, /不交三个数的解释还没写/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user