Virtual Embryo Challenge更新于 10-03 18:47(北京时间) / 每 5 分钟更新

总览 · ← 返回运行 20261003-153851-search-t1-D-smoke2h

节点 n6 终选程序?按该运行锁定的规则最终选出的程序;可能是候选节点,也可能由护栏回退到基线。在终选来历上

NO_CHANGE: 校准 addnz 解码(逐基因均值变化对齐 OT 意图位移)——X3 上 5 种解码/幅度组合全部低于父节点

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261003-153851-search-t1-D-smoke2h
父节点n5
子节点—
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 54.53(+0.0) · X3 55.32(+0.0) · proxy10 52.94(+0.0) · 3 次复测均分 54.31
审查通过 1 未发现问题:run.py 和 g37_common.py 只通过 view_io(load_manifest、panel_genes、stage_pair→inputs_by_time/read_stage/covered_mask)读取视图内的数据,没有绝对路径、`..`、/mnt、/home、data/raw、downloads 或评分器路径,也没有联网。增殖和凋亡基因表来自 moscot.utils.data 的库内标记表,不是数据下载。附注:g37_common.stage_pair 调用 inputs_by_time(include_external=True),所以 X3 视…
用时?从运行开始到结束(或到现在)的挂钟时间。20 分
程序版本81ee09b0904c54ee3e9a0c3d8266f8fd01edbc09 (programs.git)

方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。

来自 programs.git 81ee09b090:solution/METHOD.md

NO_CHANGE: 校准 addnz 解码(逐基因均值变化对齐 OT 意图位移)——X3 上 5 种解码/幅度组合全部低于父节点

结论

按 PLAN 实现了校准解码(步骤 1b 基因级 SNR 收缩 + 步骤 2 addnz 第一遍 + 步骤 3 在非零元上按细胞自身意图 补缺口),机制确实运行了(诊断见下),但 X3 A 半 seed 0 上 每一种幅度都比父节点差,DE 两项没有一项 被抬高。因此默认配置退回父节点解码,preds/x3_default.h5ad 与 --ablate calib 的输出 sha256 逐字节相同 (b8b200d8a4552cd9340a0e3d8465a6a317f29dc1279a3216f21a0582efeb5ac4),即本节点相对父节点零进展, 方向记为已被证否。校准解码保留在代码里,用 --calib 打开(--c、--no_gshrink、--pbeta 可调)。

查分记录(X3 A 半,seed 0,全部 5 次;父节点官方 X3 是 B 半 55.32,A 半复测 54.94)

臂说明de_score raw / ptsde_direction raw / ptsmmd_u raw / ptsvariogram raw / pts榜分
--ablate calib(=父节点)addnz 解码-0.1000 / 11.730.0182 / 12.680.02181 / 20.040.001416 / 10.5054.94
默认 --calib --c 1(PLAN 步骤 1b+2+3)缺口按 |u_jg| 权重补到非零元,逐元素上限 1·sd_nz-0.1286 / 11.520.0162 / 12.660.02250 / 19.660.001667 / 9.5553.39 (-1.55)
--ablate calib --pbeta 0.5备选机制 A:意图步除以 p_g^0.5(不做跨细胞搬运)-0.1286 / 11.52-0.0021 / 12.480.02216 / 19.840.001529 / 10.0553.89 (-1.05)
--ablate calib --pbeta 1.0备选机制 A,完整去检测率加权(p 下限 0.15)-0.1143 / 11.620.0096 / 12.590.05270 / 10.810.002526 / 7.2942.32 (-12.62)
--ablate calib --proj hvg(= --reslam 0,输出逐项相同)备选机制 B:只用模块投影步、不加全基因原始残差-0.0857 / 11.830.0200 / 12.700.02688 / 17.570.001487 / 10.2152.31 (-2.63)

未做(时间耗尽,27 分钟预算):步骤 4 的受约束开启/关闭(代码未实现)、--c ∈ {0.5, 2}、--no_gshrink、 seed 1/2 配对、proxy10 查分(proxy 是单输入,程序走照抄退路,与父节点同一路径,已用 vec-check 验证)。 共查分 7 次(额度 20,剩 13):calib c=1 查了两次(同一预测,A 半确定性相同,第二次只为取全 JSON)、父节点臂 1 次、pbeta 0.5/1.0 各 1 次、proj hvg 1 次、reslam 0 1 次。

机制生效的证据(不用真值的诊断,X3 seed 0,--calib --c 1)

  • 缺口填补真的动了输出:spearman(dp_g, D*_g) 从 0.417 → 0.664;spearman(dp_g, pb_last_g) 从 0.475 → 0.401(PLAN 预期方向正确:dp 不再由表达高低/检测率决定);缺口按 |e| 加权填补了 60.3% (其余受逐元素上限与"该基因在该细胞无非零元"限制);基因级 SNR 收缩 w 中位数 0.99,13% 的基因被压到 0。
  • 结构损伤在预算内:零值比例 0.9394 → 0.9442(+0.48pp,PLAN 要求 <1pp)。
  • 但真值侧:de_score / de_direction 都没有上升(见上表),mmd_u -0.38 分、variogram -0.95 分。
  • 修 bug 记录:e_g 是均值缺口,分配量必须求和到 n·e_g;第一版按 e_g 分配,只补了 0.1% 的缺口, 诊断把它暴露出来(gap closed 0.1%)后修正。

为什么判为证否,而不是"参数没调好"

PLAN 的 risk (1) 命中:父节点 OT 意图的基因级方向本身在 X3 上不含 DE 信息。三种把 dp 拉向 D_g 的做法 (按 u 权重跨细胞补缺口、除以 p_g^0.5、除以 p_g^1)都让 de_score raw 从 -0.100 变到 -0.114 ~ -0.129, de_direction 从 0.018 变到 -0.002 ~ 0.020,即"实现的均值变化更接近意图"这件事本身没有让排序/符号更接近真值。 备选机制 B(去掉全基因原始残差)把 de_score raw 从 -0.100 抬到 -0.086(+0.11 分),但 mmd_u 掉 2.5 分, 净 -2.63 分:说明父节点的原始残差项对 mmd 的贡献大于它对 DE 的损害。

给后续节点的建议

  1. 不要再把"实现 dp = OT 意图 dp"当作 DE 机制(本节点 3 种幅度、2 种实现路径全部证否)。要动 DE,得先 证明基因级方向本身可靠:可用两输入视图上 D_g 与"最后输入 − 前一输入"的真实伪批量差做一致性检验, 只在一致的基因上施加校准。
  2. 父节点 X3 的 de_score 低于地板(-0.100,11.73 < 12.50),而 mmd_u 收益 +5 分。"只用投影步、关掉全基因 原始残差"已经测过并且证否:--reslam 0 与 --proj hvg 输出完全相同(52.3104,分项逐项一致), DE 两项只 +0.13 分,mmd_u 掉 2.5 分。所以原始残差项是 mmd 收益的主要来源,不能为了 DE 去掉它; 想同时保住 mmd 和 DE,需要另一条正交的机制(例如只对 DE 相关基因子集、由两输入一致性门控的方向)。
  3. proxy10 是单输入照抄(52.94),任何两阶段机制都不会改变它;节点分数只能从 X3(权重 2)来。

数据与知识使用

  • 只读 view 内数据;未使用 uns.celltype_palette;未读取任何保留阶段(E9.5<E≤E13.5、E10.5、E12.5)或保留 基因型的实测数据;external/ 在两个视图里都未被本程序读取(X3 视图挂了 Qiu E8.75/E9.0,程序没用)。
  • 未使用发育事件表中的任何一行:本节点的机制是解码层面的统计校准,不含谱系/事件先验,因此无需 DOI 条目。
  • 视图无关:程序只用时间差(dt_in、dt_out、ratio),不读阶段名、路径、board/mode 字段, 不含绝对时间阈值;输出对 --seed 确定(np.random.default_rng(seed),SNR 收缩用 seed+7919 的独立流, 在 rows 抽样之后取数,因此 --ablate calib 与父节点逐字节一致)。
  • 纯 CPU(EXECUTION.json gpu=false);X3 约 14 s、峰值内存与父节点相当;final 上 _fill 按 4096 基因 分块,不额外分配整块稠密矩阵(峰值仍是父节点的两个 (n, G) 数组 + 一个 bool 掩码)。

调研员的计划

名称local_ot:校准 addnz 解码,使逐基因均值变化等于 OT 意图位移,并受约束地开启基因
动机父节点 5 的榜分是 54.53,提升几乎全部来自 cell_state:X3 mmd_u 0.0328→0.0208,+5.35 分。DE 两项没有跟上:X3 de_score 从 0.039 降到 -0.0649,得分 11.96,低于地板 12.50,de_recovery 组 -1.74;X3 de_direction 只有 0.0159(12.65 分,等于地板);X3 variogram 也略降(-0.17)。我认为这是解码造成的结构问题,不是参数问题。addnz 只把步长加到非零元上,并在 0 处截断,所以实际的伪 bulk 变化约为 dp_g ≈ p_g·step_g 减去截断损失。这有两个后果:(1) dp 按检测率 p_g 加权,而 p_g 与 pb(ref) 高度相关,正好被 de_score 的 chance 校正和 de_direction 的偏相关扣掉;(2) 低检测、应当上调的基因(开启型)几乎拿不到 dp,在小非零值上下调的基因又被截断吃掉。这样 OT 估计的基因方向在 DE 指标里被系统性压扁,与 ANALYSIS 的观察一致:位移外推能改变分布,但不改变 DE。方向库对 local_ot 的要求也是先解决两点,一是“只改非零元导致新程序无法启动”,二是“不要用全矩阵小正偏移”。节点 5 的机制 B 已证否(#3 vs #4、#6 vs #7):它按模块载荷和检测率阈值挑候选基因开启,与细胞自己的 OT 意图位移无关,而且开启了却没有校准实现的均值变化。本方案改的是解码校准,开启只是附属手段,并且以每个细胞自己的步长为依据。另外,proxy10 是单输入,退化为照抄(52.94);本节点不改这一支。
做法以节点 5 的 run.py 为底,耦合、精确祖先重心、λ=3 模块投影步、0.5 原始残差、均匀抽样都不动。只把第 3 步 addnz 换成校准解码。

步骤 1|意图步:沿用父节点,每个输出细胞 j、每个基因 g 的意图步 u_jg = step_proj + 0.5·(dt_out/dt_in)·d_j,作用在全部掩码基因上。基因层面的意图均值变化为 D_g = mean_j u_jg(不加掩码、不截断)。

步骤 1b|基因级收缩(去噪):把输出细胞随机分成两半,分别算 D_g^A、D_g^B,取 w_g = clip(1 − var_half/D_g², 0, 1),作经验贝叶斯 SNR 收缩,再算 D*_g = w_g·D_g。耦合只解一次,所以这一步只消除细胞抽样噪声。用 --no_gshrink 关闭,作为对照。

步骤 2|先按父节点方式解码:x'_jg = max(x_jg + u_jg·1[x_jg>0], 0)。实现的变化为 r_g = mean_j(x'_jg − x_jg),缺口 e_g = D*_g − r_g。

步骤 3|在非零元上补缺口,按细胞自身意图加权,不加常数:
- e_g>0 时,把 e_g 分配给 x'_jg>0 且 u_jg>0 的细胞,权重 ∝ u_jg。
- e_g<0 时,分配给 x'_jg>0 且 u_jg<0 的细胞,权重 ∝ |u_jg|,同时每个元素的下调不超过 x'_jg 本身,余量顺延给同组其他细胞。
- 每个元素的补量上限 c·sd_nz(g),sd_nz(g) 是该基因在最新阶段非零值的标准差,c 初值 1,搜索 {0.5, 1, 2}。补不完的部分记为 e'_g。

步骤 4|受约束开启,只处理 e'_g>0:
- 候选:x_jg=0 且 u_jg ≥ τ_g 的细胞,τ_g 取该基因 u 在零元细胞中的 90 分位,并且 >0。
- 开启数上限 n_g = min(当前缺口/均值非零值, Δp_g·N)。Δp_g = 局部检测率趋势,即 kNN30 平滑的最新阶段检测率减去耦合加权的祖先检测率,只取正值,乘 dt_out/dt_in。
- 每细胞开启总数上限 M=20(搜索 {10, 20, 40})。
- 开启值从细胞 kNN30 邻居里该基因的非零值中抽样。
- 开启优先给 u_jg 最大的细胞,所以开启是细胞特异的,不是稠密小正偏移。
- 与节点 5 机制 B 的区别:候选由细胞自身 OT 意图步加上局部 Δp 决定;数量由缺口决定,缺口为 0 就不开;不按模块载荷挑基因。
- 对称地,下调缺口在步骤 3 补完后,如果还剩 e'_g<0,可以关闭 u_jg 最负、x 最小的细胞,数量上限 Δp_g(负)·N。默认开启,可单独关闭(--ablate closing)。

…
风险(1) OT 基因级方向本身可能不对。父节点 2 的 X3 de_direction 是 -0.023,节点 5 是 0.016,都接近 0。如果意图 D_g 与真值方向无关,校准只会把噪声放大,DE 两项不升反而可能下降。尽早发现的办法:步骤 2 第一次查分时只看 calib 臂的 de_direction;如果低于父节点超过 1 分,就停止,并在 METHOD 中如实报告 OT 基因方向不可靠。(2) 开启或补量损害 mmd_u 和 variogram,节点 5 机制 B 出现过这种情况。监控两项:零值比例变化要小于 1pp;X3 mmd_u 得分下降超过 1 分时,退回只做步骤 3 并降低 c。(3) 补量集中在少数细胞,形成离群值,用每元素上限和离群诊断检查(最大值、99 分位不超过最新阶段的 1.2 倍)。(4) 收益只在 A 半上出现。必须做 seed 1、2 配对;配对均值提升小于 2 分时不要宣称进步。(5) 伪装视图:只用时间差,不读阶段名。重跑后要求输出逐字节一致。

代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。

对比:父节点版本 8bf3e4f97f。改动的文件:solution/METHOD.md +58 −74、solution/calib.py +109 −0、solution/run.py +45 −4

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex dde565a..305c614 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,79 +1,63 @@-OT 全细胞耦合的精确祖先位移:投到 30 个输入 PCA 程序模块并阻尼 λ=3 外推,再叠加 0.5×原始全基因位移,addnz 解码,均匀抽样输出,关闭生长重采样。+NO_CHANGE: 校准 addnz 解码(逐基因均值变化对齐 OT 意图位移)——X3 上 5 种解码/幅度组合全部低于父节点 -# 节点 5(improve,父节点 2 ot_moscot):local_ot — 模块投影阻尼位移 + 弱原始残差+## 结论 -## 最终方法(run.py 默认配置)+按 PLAN 实现了校准解码(步骤 1b 基因级 SNR 收缩 + 步骤 2 addnz 第一遍 + 步骤 3 在非零元上按细胞自身意图+补缺口),机制确实运行了(诊断见下),但 X3 A 半 seed 0 上 **每一种幅度都比父节点差**,DE 两项没有一项+被抬高。因此默认配置退回父节点解码,`preds/x3_default.h5ad` 与 `--ablate calib` 的输出 **sha256 逐字节相同**+(`b8b200d8a4552cd9340a0e3d8465a6a317f29dc1279a3216f21a0582efeb5ac4`),即本节点相对父节点零进展,+方向记为已被证否。校准解码保留在代码里,用 `--calib` 打开(`--c`、`--no_gshrink`、`--pbeta` 可调)。 -两输入阶段(X3: E8.75+E9.0 → E9.5;final: E8.5+E9.5 → E10.5):+## 查分记录(X3 A 半,seed 0,全部 5 次;父节点官方 X3 是 B 半 55.32,A 半复测 54.94) -1. **输出细胞**:从最新阶段均匀无放回抽取 `target_n_cells` 个。父节点的 g^dt 生长重采样**默认关闭**(在本管线里它降低 X3,见下表;`--ablate growth_on` 保留为对照)。-2. **联合嵌入**:沿用父节点 `g37_common.embed`(两阶段合并,只在两阶段都实测的基因里选 HVG 2000,z-score clip10,30 PCs)。30 个 PCA 载荷 = 数据驱动的“程序模块” W(只用输入阶段拟合)。-3. **OT 耦合**:moscot TemporalProblem,全部细胞(非父节点之外的子集),ε=1e-3,τ_a=0.95,τ_b=1,scale_cost="mean",源边际 = Waddington-OT 增殖/凋亡生长先验(同父节点)。每个输出细胞 j 用它自己的耦合列(精确祖先重心 q_j),不做近邻平滑映射。-4. **位移**:d_j = kNN30 平滑的最新阶段位置 s_j − 祖先重心 a_j(原始 log 空间)。-   - 模块投影步:c_j = W·d_j/sd /(1+α)(W 正交,岭回归解析解,α=1),step_proj = λ·(dt_out/dt_in)·sd·Wᵀc_j,λ=3;-   - 混合步:step = 0.5·(dt_out/dt_in)·d_j(**全部**两阶段实测基因)+ step_proj(HVG 上叠加);-   - 只加到该细胞的非零元上(addnz),clip ≥0,零模式保留细胞自身残差。-5. **单输入退路**(proxy10):耦合需要两个阶段 → 均匀照抄最新阶段(与 copy_last 同流程)。分支只看输入阶段个数,不看视图身份/绝对时间。--实现但**默认关闭**的机制(PLAN 机制 B):按局部检测率趋势的受约束基因开启/关闭(候选 = 细胞 top-3 模块的 top-50 载荷基因、|Δp|≥0.05、每细胞上限 M、开启值从近邻非零值抽取)。默认 `M_CAP=0`,`--mcap 30` 可启用。关闭原因见“被证否的部分”。--实现但默认不用的稳定性机制(PLAN 机制 A 的收缩部分):`--subset` 跑两个 80% 子集耦合并按 SNR_k=|两次平均|/|两次差| 收缩模块系数。X3 上精确全细胞耦合一致更好(下表 #16 vs #8),故默认 `--exact`(w_k 全部=1,收缩退化为恒等)。--## 查分记录(全部 20 次额度;X3 = A 半,seed 0,均按 vec-score score_parts)--| # | 配置(X3) | de_score | de_dir | mmd_u | variogram | 总分 |+| 臂 | 说明 | de_score raw / pts | de_direction raw / pts | mmd_u raw / pts | variogram raw / pts | 榜分 | |---|---|---|---|---|---|---|-| 1 | 父节点原码复跑 | (截断) | 12.26 | 14.96 | 10.69 | ≈50.6 |-| 2 | 父节点原码 @proxy10 | 12.40* | 13.61* | 14.25* | 9.41* | ≈49.7* |-| 3 | 子集耦合 hvg λ0.5 M30(PLAN 默认) | 11.23 | 12.57 | 15.08 | 9.95 | 48.83 |-| 4 | 同上 M0(关开关) | 11.04 | 12.58 | 15.25 | 10.00 | 48.88 |-| 5 | `--ablate displacement`(均匀照抄) | 10.86 | 12.53 | 15.16 | 10.05 | 48.60 |-| 6 | 子集 hvg λ1 M0 | 11.32 | 12.63 | 15.36 | 9.99 | 49.30 |-| 7 | 子集 hvg λ1 M30 | 11.62 | 12.63 | 14.94 | 9.88 | 49.07 |-| 8 | 子集 hvg λ2 M0 | 11.62 | 12.65 | 15.62 | 9.95 | 49.84 |-| 9 | 子集 full λ1(原始全基因步) | 11.62 | 12.39 | 15.82 | 9.30 | 49.13 |-| 10 | 子集 full λ2 | 11.83 | 12.24 | 15.41 | 8.81 | 48.30 |-| 11 | 子集 hvg λ2 M0 + growth | 11.83 | 12.66 | 15.53 | 9.19 | 49.20 |-| 12 | 子集 hvg λ0.5 M30 + growth | 11.42 | 12.58 | 14.95 | 9.17 | 48.12 |-| 13 | 子集 full λ1 + growth | 11.72 | 12.46 | 15.44 | 8.54 | 48.17 |-| 14 | 精确 full λ1 + growth(≈父节点机制) | 11.94 | 12.19 | 13.52 | 10.10 | 47.74 |-| 15 | 精确 full λ1 | 12.15 | 12.25 | 14.29 | 10.72 | 49.42 |-| 16 | 精确 hvg λ2 M0 | 11.62 | 12.71 | 15.65 | 10.14 | 50.12 |-| 17 | 精确 hvg λ3 M0 | 11.83 | 12.70 | 17.57 | 10.21 | 52.31 |-| 18 | 精确 hybrid λ2 + 0.5·raw | 11.83 | 12.67 | 17.73 | 10.59 | 52.82 |-| 19-20 | **精确 hybrid λ3 + 0.5·raw(最终默认)** | 11.73 | 12.68 | **20.03**(raw 0.0218) | 10.50 | **54.94** |--(* #2 的 score_parts 取自父节点变化量表 proxy10 行。#19 与 #20 是同一预测文件:一次输出解析失败、一次重查确认,额度因此耗尽。)--λ 方向在 hvg 投影步上单调向好(48.88 → 49.30 → 49.84/50.12 → 52.31),加 0.5 倍原始残差再 +0.5~2.6。**未验证 λ4**(额度耗尽),最终配置取已验证的最优点而不是外推点。--## 机制证据(PLAN mechanism_evidence 对应项)--1. **型内位移方差占比 0.965**(精确模式,X3 seed 0,stderr 诊断;标签仅用于诊断):位移在类型内部高度异质,不是每型常向量。-2. 开关机制(默认关):λ0.5 时每细胞开启 7.2 / 关闭 6.7 个基因,λ1 时 12.8 / 12.1 个,上限 M=30 未触发;模块 w_k(子集模式)0.57–0.85。-3. 零值比例:最新阶段 0.9394 → 输出 0.9422(+0.28pp < 2pp 护栏);方差/离群未塌缩(100% 细胞被位移触及,step max 9.4 但中位小)。-4. 被改动细胞比例:100%(addnz 步作用于每个细胞的非零元)。-5. **消融对照**(`--ablate`,harness 用第一个标识符 `displacement`):-   - `displacement` → 均匀照抄,输出显著不同(#5: 48.60 vs 默认 54.94)→ mechanism_active 应判 yes;-   - `opening` → M=0,与默认输出**逐位相同**(默认即 M=0,如实说明:开关机制不在最终配置中);-   - `growth_on` → 恢复 g^dt 重抽样,输出不同(#11/#14:降分)。--## 被证否的部分(如实报告)--- **PLAN 机制 B(检测率开关基因)在已测幅度下有害或无效**:同幅度 M30 vs M0 两次对照(#3 vs #4:48.83/48.88;#6 vs #7:49.30/49.07),mmd_u 与 variogram 均被拉低,de_score 无稳定收益。最终配置 M=0,代码保留可启用。-- **生长重采样在本管线的 X3 上有害**(#11 vs #8、#12 vs #3、#14 vs #15:variogram/mmd 下降),默认关闭,只留对照。-- **子集耦合 + 近邻映射的祖先平滑不如精确耦合列**(#9 vs #15、#8 vs #16),SNR 收缩在精确模式下不可辨识(单次耦合,w≡1)。PLAN 的收缩机制被更强的“精确祖先 + 模块投影去噪”替代。--## 验证过 / 未验证--- 已验证:X3 与 proxy 两视图 `vec-check` ok;seed 0 重跑逐字节相同(确定性);seed 1、2 正常运行且诊断量接近;**伪装视图**(时间统一 +1 天、manifest 键序打乱、路径更换)输出与真实视图逐位相同(view-independent);X3 全程 13–15 s、CPU-only(EXECUTION.json gpu=false)。-- 未验证(额度耗尽):proxy10 上默认配置的查分(退路 = 均匀照抄,预期 ≈ 节点 1 copy_last 的 52.75,抽样差异 ≤0.5);最终配置在 seed 1/2 的分数;λ=4;开关机制在 λ3 混合步下的表现;final 视图实跑(父节点同构型全耦合在 final 约 70 s / 6.7 GB,本方法内存轮廓与父节点相当)。-- B 半风险:X3 A 半 54.94,正式分用 B 半,噪声约 ±2;但相对父节点 A 半复跑(≈50.6)的 +4.3 超出噪声,方向由 8 个配置的单调趋势支持。--## 知识来源--- 未使用任何保留阶段/保留基因型的测量值;未使用 `uns.celltype_palette`;无硬编码类型名单、阶段名、细胞数、比例。程序只用视图内表达、标签集合外的时间**差**(dt_in、dt_out)。-- 发育事件表(G42.2 附件):**未使用**(本方法是纯数据驱动的 OT+线性模块外推,不含标记程序或事件顺序)。-- Waddington-OT 生长先验(增殖/凋亡基因评分 → 出生/死亡率):Schiebinger et al. 2019, Cell, DOI 10.1016/j.cell.2019.01.006(经 moscot 实现;marker 列表是通用细胞状态标记,非阶段特异测量)。父节点 2 继承。-- 熵正则非平衡 OT(Sinkhorn,τ_a/τ_b 边际松弛):Peyré & Cuturi 2019(moscot/otter 实现)。父节点继承。-- 评分规则简报 §3 事实 5(组成/位移需与真实变化同向才得分)用于解读:λ 增大时 mmd_u/de_dir 同向改善,说明外推方向与真值一致。+| `--ablate calib`(=父节点) | addnz 解码 | -0.1000 / 11.73 | 0.0182 / 12.68 | 0.02181 / 20.04 | 0.001416 / 10.50 | **54.94** |+| 默认 `--calib --c 1`(PLAN 步骤 1b+2+3) | 缺口按 \|u_jg\| 权重补到非零元,逐元素上限 1·sd_nz | -0.1286 / 11.52 | 0.0162 / 12.66 | 0.02250 / 19.66 | 0.001667 / 9.55 | 53.39 (-1.55) |+| `--ablate calib --pbeta 0.5` | 备选机制 A:意图步除以 p_g^0.5(不做跨细胞搬运) | -0.1286 / 11.52 | -0.0021 / 12.48 | 0.02216 / 19.84 | 0.001529 / 10.05 | 53.89 (-1.05) |+| `--ablate calib --pbeta 1.0` | 备选机制 A,完整去检测率加权(p 下限 0.15) | -0.1143 / 11.62 | 0.0096 / 12.59 | 0.05270 / 10.81 | 0.002526 / 7.29 | 42.32 (-12.62) |+| `--ablate calib --proj hvg`(= `--reslam 0`,输出逐项相同) | 备选机制 B:只用模块投影步、不加全基因原始残差 | -0.0857 / 11.83 | 0.0200 / 12.70 | 0.02688 / 17.57 | 0.001487 / 10.21 | 52.31 (-2.63) |++未做(时间耗尽,27 分钟预算):步骤 4 的受约束开启/关闭(代码未实现)、`--c ∈ {0.5, 2}`、`--no_gshrink`、+seed 1/2 配对、proxy10 查分(proxy 是单输入,程序走照抄退路,与父节点同一路径,已用 vec-check 验证)。+共查分 7 次(额度 20,剩 13):calib c=1 查了两次(同一预测,A 半确定性相同,第二次只为取全 JSON)、父节点臂 1 次、pbeta 0.5/1.0 各 1 次、proj hvg 1 次、reslam 0 1 次。++## 机制生效的证据(不用真值的诊断,X3 seed 0,`--calib --c 1`)++- 缺口填补真的动了输出:`spearman(dp_g, D*_g)` 从 **0.417 → 0.664**;`spearman(dp_g, pb_last_g)` 从+  **0.475 → 0.401**(PLAN 预期方向正确:dp 不再由表达高低/检测率决定);缺口按 |e| 加权填补了 **60.3%**+  (其余受逐元素上限与"该基因在该细胞无非零元"限制);基因级 SNR 收缩 w 中位数 0.99,13% 的基因被压到 0。+- 结构损伤在预算内:零值比例 0.9394 → 0.9442(+0.48pp,PLAN 要求 <1pp)。+- 但真值侧:de_score / de_direction 都没有上升(见上表),mmd_u -0.38 分、variogram -0.95 分。+- 修 bug 记录:`e_g` 是**均值**缺口,分配量必须求和到 `n·e_g`;第一版按 `e_g` 分配,只补了 0.1% 的缺口,+  诊断把它暴露出来(`gap closed 0.1%`)后修正。++## 为什么判为证否,而不是"参数没调好"++PLAN 的 risk (1) 命中:父节点 OT 意图的**基因级方向本身在 X3 上不含 DE 信息**。三种把 dp 拉向 D_g 的做法+(按 u 权重跨细胞补缺口、除以 p_g^0.5、除以 p_g^1)都让 de_score raw 从 -0.100 变到 -0.114 ~ -0.129,+de_direction 从 0.018 变到 -0.002 ~ 0.020,即"实现的均值变化更接近意图"这件事本身没有让排序/符号更接近真值。+备选机制 B(去掉全基因原始残差)把 de_score raw 从 -0.100 抬到 -0.086(+0.11 分),但 mmd_u 掉 2.5 分,+净 -2.63 分:说明父节点的原始残差项对 mmd 的贡献大于它对 DE 的损害。++## 给后续节点的建议++1. **不要再把"实现 dp = OT 意图 dp"当作 DE 机制**(本节点 3 种幅度、2 种实现路径全部证否)。要动 DE,得先+   证明基因级方向本身可靠:可用两输入视图上 `D_g` 与"最后输入 − 前一输入"的真实伪批量差做一致性检验,+   只在一致的基因上施加校准。+2. 父节点 X3 的 de_score 低于地板(-0.100,11.73 < 12.50),而 mmd_u 收益 +5 分。**"只用投影步、关掉全基因+   原始残差"已经测过并且证否**:`--reslam 0` 与 `--proj hvg` 输出完全相同(52.3104,分项逐项一致),+   DE 两项只 +0.13 分,mmd_u 掉 2.5 分。所以原始残差项是 mmd 收益的主要来源,不能为了 DE 去掉它;+   想同时保住 mmd 和 DE,需要另一条正交的机制(例如只对 DE 相关基因子集、由两输入一致性门控的方向)。+3. proxy10 是单输入照抄(52.94),任何两阶段机制都不会改变它;节点分数只能从 X3(权重 2)来。++## 数据与知识使用++- 只读 view 内数据;未使用 `uns.celltype_palette`;未读取任何保留阶段(E9.5<E≤E13.5、E10.5、E12.5)或保留+  基因型的实测数据;`external/` 在两个视图里都未被本程序读取(X3 视图挂了 Qiu E8.75/E9.0,程序没用)。+- 未使用发育事件表中的任何一行:本节点的机制是解码层面的统计校准,不含谱系/事件先验,因此无需 DOI 条目。+- 视图无关:程序只用时间差(`dt_in`、`dt_out`、`ratio`),不读阶段名、路径、`board`/`mode` 字段,+  不含绝对时间阈值;输出对 `--seed` 确定(`np.random.default_rng(seed)`,SNR 收缩用 `seed+7919` 的独立流,+  在 `rows` 抽样之后取数,因此 `--ablate calib` 与父节点逐字节一致)。+- 纯 CPU(`EXECUTION.json` `gpu=false`);X3 约 14 s、峰值内存与父节点相当;final 上 `_fill` 按 4096 基因+  分块,不额外分配整块稠密矩阵(峰值仍是父节点的两个 (n, G) 数组 + 一个 bool 掩码)。diff --git a/solution/calib.py b/solution/calib.pynew file mode 100644index 0000000..aafd109--- /dev/null+++ b/solution/calib.py@@ -0,0 +1,109 @@+"""Calibrated decoding of an intended per-cell displacement (node 6, family local_ot).++The parent (node 5) decodes an intended step ``u`` with "addnz": the step is added to the cell's non-zero+entries only and clipped at 0. The realised per-gene mean change is then ``r_g ~ p_g * u_g`` (weighted by the+gene's detection rate), and ``p_g`` is strongly correlated with ``pb(ref)`` -- exactly the component that the+``de_score`` chance correction and the ``de_direction`` partial correlation remove. Low-detection genes that+should go up get almost no ``dp``, and genes that should go down lose their step to the clip at 0.++``calibrate_from`` takes the addnz pass as given and closes the per-gene gap ``e_g = D*_g - r_g`` between the+intended mean change ``D*_g`` (SNR-shrunk) and the realised one, distributing it over the cells whose own+intended step has the same sign and whose entry is already non-zero, with weight proportional to ``|u_jg|``+and a per-element cap ``c * sd_nz(g)``. No cell receives a constant or type-level vector: every correction+follows that cell's own coupling column.+"""++from __future__ import annotations++import numpy as np+++def gene_shrink(U: np.ndarray, rng: np.random.Generator):+    """Positive-part empirical-Bayes shrinkage of the intended per-gene mean change.++    ``D_g`` is the mean of ``u`` over the output cells; splitting the cells in two halves estimates its+    sampling variance, and ``w_g = clip(1 - var/D_g^2, 0, 1)`` removes genes whose mean change is+    indistinguishable from cell-sampling noise. The coupling is solved once, so this only removes that noise.+    """+    n = U.shape[0]+    half = rng.permutation(n)+    a, b = half[: n // 2], half[n // 2:]+    da = U[a].mean(axis=0)+    db = U[b].mean(axis=0)+    d = 0.5 * (da + db)+    var_d = 0.5 * ((da - db) * 0.5) ** 2  # variance of the mean of the two halves+    w = np.clip(1.0 - var_d / np.maximum(d ** 2, 1e-24), 0.0, 1.0)+    return (w * d).astype(np.float32), w.astype(np.float32)+++def nonzero_sd(Xl, mask: np.ndarray) -> np.ndarray:+    """Per-gene standard deviation of the NON-ZERO values of the latest stage (0 where undefined)."""+    G = Xl.shape[1]+    cnt = np.asarray((Xl > 0).sum(axis=0)).ravel().astype(np.float64)+    s = np.asarray(Xl.sum(axis=0)).ravel().astype(np.float64)+    s2 = np.asarray(Xl.multiply(Xl).sum(axis=0)).ravel().astype(np.float64)+    out = np.zeros(G, dtype=np.float32)+    ok = cnt >= 8+    mu = s[ok] / cnt[ok]+    out[ok] = np.sqrt(np.maximum(s2[ok] / cnt[ok] - mu ** 2, 0.0))+    out[~mask] = 0.0+    return out+++def _fill(Xp: np.ndarray, U: np.ndarray, e: np.ndarray, cap: np.ndarray, chunk: int) -> np.ndarray:+    """Close the gap on existing non-zero entries, weighted by the cell's own intended step (in place)."""+    n, G = Xp.shape+    e = e * np.float32(n)   # e_g is a MEAN gap; the amounts distributed must sum to n * e_g+    left = e.copy()+    for s in range(0, G, chunk):+        t = min(s + chunk, G)+        Xc, Uc, ec, cc = Xp[:, s:t], U[:, s:t], e[s:t], cap[s:t]+        up = np.maximum(ec, 0.0)+        if up.any():+            W = np.where((Xc > 0) & (Uc > 0), Uc, 0.0)+            tw = W.sum(axis=0)+            amt = np.zeros_like(W)+            good = (tw > 1e-12) & (up > 0)+            if good.any():+                amt[:, good] = W[:, good] * (up[good] / tw[good])+                np.minimum(amt, cc, out=amt)+                Xc += amt+                up = up - amt.sum(axis=0)+            del W, tw, amt+        dn = np.minimum(ec, 0.0)+        if dn.any():+            W = np.where((Xc > 0) & (Uc < 0), -Uc, 0.0)+            tw = W.sum(axis=0)+            amt = np.zeros_like(W)+            good = (tw > 1e-12) & (dn < 0)+            if good.any():+                amt[:, good] = -W[:, good] * (-dn[good] / tw[good])+                np.maximum(amt, -cc, out=amt)+                np.maximum(amt, -Xc, out=amt)+                Xc += amt+                dn = dn - amt.sum(axis=0)+            del W, tw, amt+        left[s:t] = np.where(ec > 0, up, np.where(ec < 0, dn, 0.0))+    return left / np.float32(n)+++def calibrate_from(X: np.ndarray, U: np.ndarray, mean0: np.ndarray, r0: np.ndarray, mask: np.ndarray,+                   sd_nz: np.ndarray, c: float, gshrink: bool, seed: int, chunk: int = 4096):+    """In-place calibrated decode. ``X`` is the addnz pass, ``r0`` its realised per-gene mean change."""+    from scipy.stats import spearmanr++    D = U.mean(axis=0).astype(np.float32)+    if gshrink:+        Dstar, w = gene_shrink(U, np.random.default_rng(seed + 7919))+    else:+        Dstar, w = D.copy(), np.ones_like(D)+    Dstar[~mask] = 0.0+    rho_before = float(spearmanr(r0[mask], Dstar[mask]).statistic)+    e = (Dstar - r0).astype(np.float32)+    left = _fill(X, U, e, np.float32(c) * sd_nz, chunk)+    r1 = X.mean(axis=0) - mean0+    rho_after = float(spearmanr(r1[mask], Dstar[mask]).statistic)+    diag = dict(D=D, Dstar=Dstar, w=w, r0=r0, r1=r1, rho_before=rho_before, rho_after=rho_after,+                left=left,+                frac_gap_closed=float(np.abs(r1 - r0)[mask].sum() / max(np.abs(e[mask]).sum(), 1e-12)))+    return X, diagdiff --git a/solution/run.py b/solution/run.pyindex 536dfce..babb186 100644--- a/solution/run.py+++ b/solution/run.py@@ -55,6 +55,7 @@ SUB_FRAC = 0.8     # always keep two DIFFERENT subsets so the SNR shrinkage is e RIDGE_ALPHA = 1.0 LAMBDA = 3.0       # damping of the module-projected extrapolated step (chosen on X3: 0.5/1/2/3 monotone better) RES_LAMBDA = 0.5   # weight of the raw (all-masked-gene) displacement added to the projected step+C_CAP = 1.0        # per-element calibration cap in units of sd_nz(g) M_CAP = 0          # per-cell cap on openings/closings; 0 = mechanism B off (harmful at tested amplitudes, METHOD.md) TOP_MODS = 3       # modules whose top-loading genes are opening/closing candidates TOP_LOAD = 50      # genes per module@@ -153,16 +154,30 @@ def main() -> None:                         help="single full-cell coupling with exact per-cell ancestor barycentres (default)")     parser.add_argument("--subset", action="store_true",                         help="dev only: two 80%%-subset couplings with SNR shrinkage instead of --exact")+    # node 6: calibrated decoding (calib.py)+    parser.add_argument("--calib", action="store_true",+                        help="dev only: calibrated decode (PLAN of node 6). OFF by default: every tested "+                             "amplitude scored below the node-5 addnz decode on X3 (see METHOD.md)")+    parser.add_argument("--c", type=float, default=C_CAP, help="dev only: per-element cap in units of sd_nz(g)")+    parser.add_argument("--no_gshrink", action="store_true", help="dev only: no gene-level SNR shrinkage of D_g")+    parser.add_argument("--pbeta", type=float, default=0.0,+                        help="dev only: divide the intended step by the gene detection rate p_g^beta before decoding")+    parser.add_argument("--pfloor", type=float, default=0.15, help="dev only: floor on p_g for --pbeta")     args = parser.parse_args()      lam = LAMBDA if args.lam is None else float(args.lam)     m_cap = M_CAP if args.mcap is None else int(args.mcap)     eps = EPSILON if args.epsilon is None else float(args.epsilon)     res_lam = RES_LAMBDA if args.reslam is None else float(args.reslam)+    c_cap = float(args.c)     args.exact = args.exact and not args.subset      ablate = args.ablate-    displacement_off = ablate is not None and ablate not in ("opening", "growth_on")+    # `calib` (also accepted as `mechanism`) turns the calibrated decode off and restores the node-5 addnz+    # decode byte-for-byte; `displacement` still removes the step entirely.+    calib_off = (not args.calib) or ablate in ("calib", "mechanism", "opening", "closing")+    displacement_off = ablate is not None and ablate not in ("opening", "growth_on", "calib", "mechanism",+                                                             "closing")     growth_on = ablate == "growth_on"      manifest = load_manifest(args.data)@@ -332,10 +347,36 @@ def main() -> None:     else:         step_mat = np.zeros((n, len(genes)), dtype=np.float32)         step_mat[:, hvg] = step+    if args.pbeta > 0:+        p_det = np.asarray((last.X[rows] > 0).mean(axis=0)).ravel().astype(np.float32)+        step_mat /= np.maximum(p_det, np.float32(args.pfloor)) ** np.float32(args.pbeta)+        _log(f"diag pbeta={args.pbeta}: step |median| {np.median(np.abs(step_mat)):.4f} "+             f"max {np.abs(step_mat).max():.3f}", t0)     X = Xout.toarray()-    step_mat *= X > 0-    X += step_mat-    np.maximum(X, 0.0, out=X)+    if calib_off:+        step_mat *= X > 0+        X += step_mat+        np.maximum(X, 0.0, out=X)+    else:+        from calib import calibrate_from, nonzero_sd+        sd_nz = nonzero_sd(last.X, mask)+        mean0 = X.mean(axis=0)+        nzmask = X > 0+        X += step_mat                       # node-5 addnz first pass+        X[~nzmask] = 0.0                    # zeros keep the cell's own pattern+        del nzmask+        np.maximum(X, 0.0, out=X)+        r0 = X.mean(axis=0) - mean0+        X, cdiag = calibrate_from(X, step_mat, mean0, r0, mask, sd_nz, c_cap, not args.no_gshrink, args.seed)+        _log(f"diag calib: spearman(dp, D*) {cdiag['rho_before']:.3f} -> {cdiag['rho_after']:.3f}; "+             f"gshrink w med {np.median(cdiag['w']):.2f} frac>0 {(cdiag['w'] > 0).mean():.2f}; "+             f"gap closed {cdiag['frac_gap_closed'] * 100:.1f}%; "+             f"|D*| med {np.median(np.abs(cdiag['Dstar'][mask])):.4f} |r0| med "+             f"{np.median(np.abs(cdiag['r0'][mask])):.4f} -> |r1| med {np.median(np.abs(cdiag['r1'][mask])):.4f}", t0)+        pb_last = np.asarray(last.X.mean(axis=0)).ravel()+        from scipy.stats import spearmanr+        _log(f"diag calib: partial-ish corr(dp, pb_last) {spearmanr(cdiag['r0'][mask], pb_last[mask]).statistic:.3f}"+             f" -> {spearmanr(cdiag['r1'][mask], pb_last[mask]).statistic:.3f}", t0)      # ---- diagnostics (mechanism evidence) ----     moved = np.abs(step_mat).sum(axis=1) > 0

调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。

用到的知识库条目

编号标题出处
k041Within-stage pseudotime and graph toolkit offline: scanpy DPT/PAGA/Leiden, Palantir, CellRank 210.1186/s13059-019-1663-x (PAGA); 10.1038/s41587-019-0068-4 (Palantir); 10.1038/s41592-024-02303-9 (CellRank 2)
k031Offline OT toolkit in the sandbox: moscot TemporalProblem, wot OTModel, POT, geomloss10.1038/s41586-024-08453-2 (moscot); 10.1016/j.cell.2019.01.006 (Waddington-OT)
k018Damped per-type shift: shrinkage alpha on the observed deltanotes/plan/cards/T1.md

分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。

改了什么新增 calib.py(基因级 SNR 收缩、addnz 第一遍、按细胞自身 |u_jg| 加权把均值缺口补到非零元,逐元素上限 c·sd_nz),并给 run.py 加了 --calib、--c、--no_gshrink、--pbeta 等开发开关。默认不开 --calib,因此默认输出与节点 5 逐字节相同(sha256 一致)。PLAN 步骤 4 的受约束开启/关闭没有实现。
各组分数的变化cell_state:无变化:+0.00(proxy10 mmd_u 0.04135,15.27 分;X3 0.02075,20.37 分)
covariation:无变化:+0.00(proxy10 variogram 0.001252,9.82 分;X3 0.001456,10.34 分)
de_recovery:无变化:+0.00(proxy10 de_score 0.04237,12.86 分;X3 de_score -0.0649,11.96 分,仍低于地板 12.50)。输出与父节点逐字节相同。
direction:无变化:+0.00(proxy10 0.201,15.00 分;X3 0.0159,12.65 分,约等于地板)
family_idlocal_ot
假设是否成立否
经验
  1. 在两输入 local_ot 位移管线里,把 addnz 解码校准成“实现的逐基因均值变化 ≈ OT 意图 D_g”,确实提高了 spearman(dp, D*)(0.417→0.664,Engineer 自报的诊断),但 X3 A 半 seed 0 上 DE 两项没有上升:de_score raw -0.100→-0.129,de_direction 0.018→0.016;mmd_u 降 0.38 分,variogram 降 0.95 分,榜分 54.94→53.39。说明 OT 意图的基因级方向不含 DE 信息,校准只是把噪声放大。
  2. 把意图步除以检测率 p_g^β 去掉检测率加权同样无效:β=0.5 时榜分 -1.05;β=1(p 下限 0.15)时 mmd_u 从 20.04 掉到 10.81 分,variogram 掉 3.2 分,榜分 -12.62。在稀疏低检测基因上放大步长会严重破坏细胞分布和共变结构。
  3. 去掉 0.5×全基因原始残差、只保留模块投影步(--proj hvg,与 --reslam 0 输出相同),de_score 只多 0.1 分,mmd_u 却掉 2.5 分(净 -2.63)。父节点在 X3 上的 mmd_u 收益主要来自原始残差项,不能为了 DE 去掉它。
  4. 以上所有臂的分数都是 Engineer 在 X3 A 半、单 seed 上自报的,没有 seed 1/2 配对;变化量表只证明默认输出与父节点相同(官方 B 半 55.32,A 半复测 54.94)。负面结论在方向上一致(5 个臂全部更差),但幅度要按噪声内外区分来读:-1.05 和 -1.55 落在 T1 约 2 分的噪声内,只有 -2.63 和 -12.62 明显超出。
  5. 用“均值缺口”分配时要乘以 n 再分配:第一版漏乘,只补了 0.1% 的缺口,是靠 gap-closed 诊断发现的。缺口闭合比例这类无真值诊断应作为每次查分前的必检项。
mechanism_active否
下一步建议
  1. 针对 de_recovery 和 direction:先做不用真值的一致性门控。在两输入视图上比较 D_g 与“最后输入 − 前一输入”的真实伪批量差 Δobs_g,只在符号一致且 |Δobs_g| 超过半样本噪声的基因上施加额外步长(例如 ×2),其余基因保持节点 5 的 addnz。还要用 seed 0/1/2 与父节点配对比较 de_score + de_direction 之和;如果门控后基因集合在 X3 上不超过约 100 个就放弃这条路。
  2. 针对 de_recovery:X3 de_score 低于地板(-0.065,11.96 分)。可以试一个与 OT 无关的方向来源:用两输入阶段的伪批量差 Δobs_g × (dt_out/dt_in) 作为逐基因方向,只在 top-k(k∈{200, 500})基因的非零元上加一个小幅度、按细胞自身表达比例分配的位移,同时保留节点 5 的位移和原始残差,以保住 mmd_u 的 20 分。
  3. 针对 cell_state:节点 5 的 λ 只验证到 3,且趋势单调(λ2→λ3 时 mmd_u 17.57→20.03)。可以在原始残差 0.5 不变的前提下测 λ∈{4, 5} 和 RES_LAMBDA∈{0.75, 1.0},用 seed 0/1/2 配对,并监控 variogram 不低于 10.0 分。

对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。

只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。

分析员

角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。分析员 vec-relay-claude-cli/claude-opus-5-5
调用次数1 次
轮数?大模型一共回复了几轮(每轮可以调用多个工具)。1
工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。共 0 次:—
用时不到 1 分
token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。输入 2 · 输出 2,091 · 思考 0
任务(第一行)分析节点 n6 的打分结果,写 ANALYSIS.json
最后的回答(摘录)
与节点 5 逐字节相同(sha256 一致)。PLAN 步骤 4 的受约束开启/关闭没有实现。",
 "effect_by_group": {"de_recovery": "无变化:+0.00(proxy10 de_score 0.04237,12.86 分;X3 de_score -0.0649,11.96 分,仍低于地板 12.50)。输出与父节点逐字节相同。", "direction": "无变化:+0.00(proxy10 0.201,15.00 分;X3 0.0159,12.65 分,约等于地板)", "cell_state": "无变化:+0.00(proxy10 mmd_u 0.04135,15.27 分;X3 0.02075,20.37 分)", "covariation": "无变化:+0.00(proxy10 variogram 0.001252,9.82 分;X3 0.001456,10.34 分)"},
 "hypothesis_supported": "no",
 "failure_reason": null,
 "lessons": ["在两输入 local_ot 位移管线里,把 addnz 解码校准成“实现的逐基因均值变化 ≈ OT 意图 D_g”,确实提高了 spearman(dp, D*)(0.417→0.664,Engineer 自报的诊断),但 X3 A 半 seed 0 上 DE 两项没有上升:de_score raw -0.100→-0.129,de_direction 0.018→0.016;mmd_u 降 0.38 分,variogram 降 0.95 分,榜分 54.94→53.39。说明 OT 意图的基因级方向不含 DE 信息,校准只是把噪声放大。", "把意图步除以检测率 p_g^β 去掉检测率加权同样无效:β=0.5 时榜分 -1.05;β=1(p 下限 0.15)时 mmd_u 从 20.04 掉到 10.81 分,variogram 掉 3.2 分,榜分 -12.62。在稀疏低检测基因上放大步长会严重破坏细胞分布和共变结构。", "去掉 0.5×全基因原始残差、只保留模块投影步(--proj hvg,与 --reslam 0 输出相同),de_score 只多 0.1 分,mmd_u 却掉 2.5 分(净 -2.63)。父节点在 X3 上的 mmd_u 收益主要来自原始残差项,不能为了 DE 去掉它。", "以上所有臂的分数都是 Engineer 在 X3 A 半、单 seed 上自报的,没有 seed 1/2 配对;变化量表只证明默认输出与父节点相同(官方 B 半 55.32,A 半复测 54.94)。负面结论在方向上一致(5 个臂全部更差),但幅度要按噪声内外区分来读:-1.05 和 -1.55 落在 T1 约 2 分的噪声内,只有 -2.63 和 -12.62 明显超出。", "用“均值缺口”分配时要乘以 n 再分配:第一版漏乘,只补了 0.1% 的缺口,是靠 gap-closed 诊断发现的。缺口闭合比例这类无真值诊断应作为每次查分前的必检项。"],
 "next_suggestions": ["针对 de_recovery 和 direction:先做不用真值的一致性门控。在两输入视图上比较 D_g 与“最后输入 − 前一输入”的真实伪批量差 Δobs_g,只在符号一致且 |Δobs_g| 超过半样本噪声的基因上施加额外步长(例如 ×2),其余基因保持节点 5 的 addnz。还要用 seed 0/1/2 与父节点配对比较 de_score + de_direction 之和;如果门控后基因集合在 X3 上不超过约 100 个就放弃这条路。", "针对 de_recovery:X3 de_score 低于地板(-0.065,11.96 分)。可以试一个与 OT 无关的方向来源:用两输入阶段的伪批量差 Δobs_g × (dt_out/dt_in) 作为逐基因方向,只在 top-k(k∈{200, 500})基因的非零元上加一个小幅度、按细胞自身表达比例分配的位移,同时保留节点 5 的位移和原始残差,以保住 mmd_u 的 20 分。", "针对 cell_state:节点 5 的 λ 只验证到 3,且趋势单调(λ2→λ3 时 mmd_u 17.57→20.03)。可以在原始残差 0.5 不变的前提下测 λ∈{4, 5} 和 RES_LAMBDA∈{0.75, 1.0},用 seed 0/1/2 配对,并监控 variogram 不低于 10.0 分。"],
 "family_id": "local_ot",
 "mechanism_active": "no"}
原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。/home/spark-longxinyang/vec/runs/test/20261003-153851-search-t1-D-smoke2h/nodes/6/analyst.jsonl 4 KB

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数39
工具调用共 43 次:bash 33、edit 7、write 3
用时17 分
token 数输入 92,543 · 输出 17,608 · 思考 15,009
任务(第一行)按计划修改程序:local_ot:校准 addnz 解码,使逐基因均值变化等于 OT 意图位移,并受约束地开启基因
最后的回答(摘录)
提交状态:`solution/run.py` 默认输出与父节点逐字节相同(sha256 已验证),proxy 与 X3 两视图均过 `vec-check`;`METHOD.md` 首行为 `NO_CHANGE:`。

- 按 PLAN 实现了校准解码(基因级 SNR 收缩 + addnz 第一遍 + 缺口按细胞自身 `u_jg` 权重补到非零元,逐元素上限 `c·sd_nz`),机制确认生效:`spearman(dp, D*)` 0.417→0.664,`spearman(dp, pb_last)` 0.475→0.401,缺口填补 60.3%,零值比例 +0.48pp。
- X3 A 半 seed 0 查分 7 次:父节点臂 54.94;calib c=1 → 53.39;`--pbeta 0.5` → 53.89;`--pbeta 1.0` → 42.32;`--proj hvg`(= `--reslam 0`)→ 52.31。DE 两项在所有臂上都没有超过父节点(de_score raw -0.100 → -0.086…-0.129),PLAN 的 risk (1) 命中:OT 意图的基因级方向不含 DE 信息。
- 下一步最值得试:不要再用"实现 dp = 意图 dp"作 DE 机制;先用两输入的真实伪批量差对 `D_g` 做一致性门控,只在方向可验证的基因子集上施加校准,同时保留全基因原始残差(它是 X3 mmd_u +5 分的主要来源,去掉即 -2.5 分)。
原始记录位置/home/spark-longxinyang/vec/runs/test/20261003-153851-search-t1-D-smoke2h/nodes/6/engineer.jsonl 211 KB
/home/spark-longxinyang/vec/runs/test/20261003-153851-search-t1-D-smoke2h/nodes/6/engineer.stderr

调研员

角色调研员 vec-relay-claude-cli/claude-opus-5-5
调用次数1 次
轮数1
工具调用共 0 次:—
用时1 分
token 数输入 2 · 输出 7,658 · 思考 0
任务(第一行)为节点 n6(改进,父节点 n5)写改进计划 PLAN.json
最后的回答(摘录)
std 都接近照抄,否则先降 c 或 M。\n2. X3 seed 0:父节点复测 1 次;calib(只做步骤 3)1 次;calib+opening 1 次;calib+opening+closing 1 次。共 4 次。\n3. 在最好的一臂上做小网格:c ∈ {0.5, 2},M ∈ {10, 40},`--no_gshrink`,约 5 次。\n4. 最佳配置做 X3 seed 1、2 和父节点 seed 1、2 的配对比较,共 4 次,判断提升是否超过约 2 分噪声,以 de_score + de_direction 的配对差为主要判据。\n5. proxy10 查 1 次,确认仍等于照抄。\n6. 余量留作复查。\n\n不改 λ,避免把 cell_state 的变化与 DE 的变化混在一起。",
 "expected_groups": ["de_recovery", "direction"],
 "risks": "(1) OT 基因级方向本身可能不对。父节点 2 的 X3 de_direction 是 -0.023,节点 5 是 0.016,都接近 0。如果意图 D_g 与真值方向无关,校准只会把噪声放大,DE 两项不升反而可能下降。尽早发现的办法:步骤 2 第一次查分时只看 calib 臂的 de_direction;如果低于父节点超过 1 分,就停止,并在 METHOD 中如实报告 OT 基因方向不可靠。(2) 开启或补量损害 mmd_u 和 variogram,节点 5 机制 B 出现过这种情况。监控两项:零值比例变化要小于 1pp;X3 mmd_u 得分下降超过 1 分时,退回只做步骤 3 并降低 c。(3) 补量集中在少数细胞,形成离群值,用每元素上限和离群诊断检查(最大值、99 分位不超过最新阶段的 1.2 倍)。(4) 收益只在 A 半上出现。必须做 seed 1、2 配对;配对均值提升小于 2 分时不要宣称进步。(5) 伪装视图:只用时间差,不读阶段名。重跑后要求输出逐字节一致。",
 "family_id": "local_ot",
 "mechanism": "把父节点 OT 精确祖先位移的解码由“只加非零元并截断”改为“校准解码”:每个基因实现的均值变化等于 OT 意图均值变化(经 SNR 收缩)。缺口先按每个细胞自身的意图步长分配到非零元上;仍补不完时,在细胞自身意图步大、且局部检测率上升的细胞里有上限地开启(对称地关闭)基因。",
 "vs_constant_shift": "没有任何细胞收到每型或全局的常向量。基础步来自每个细胞自己的耦合列,节点 5 测得型内位移方差占比 0.965。校准的补量按细胞自身的 u_jg 加权,只分给本身就想往同一方向移动的细胞;开启只发生在 u_jg 超过阈值、且局部 Δp>0 的细胞,数量受缺口和 Δp 双重约束。不改细胞组成,抽样仍是均匀抽样。",
 "mechanism_evidence": "不用真值的诊断,打印到 stderr:\n(a) 全部掩码基因上,实现的 dp_g(输出均值 − 最新阶段均值)与意图 D*_g 的 Spearman 相关,校准前(父节点解码)与校准后对比,预期明显升高,例如从约 0.5 升到 >0.9。\n(b) dp_g 与 pb(last) 秩的偏相关,以及 dp 与检测率 p_g 的相关,预期校准后下降,说明 dp 不再由表达高低决定。\n(c) 开启/关闭的数量(总数、每细胞中位数),被开启基因的 Δp 分布,以及开启集中在哪些基因、哪些细胞。\n(d) 被补量改动的元素比例,补量在型内的离散程度(只用标签做诊断)。\n(e) 零值比例、方差、99 分位的变化。\n用真值的证据:X3 score_parts 中 de_score 和 de_direction 相对 `--ablate calib` 上升,mmd_u 与 variogram 的变化在 1 分以内;并在 seed 0/1/2 上配对重复。",
 "mechanism_off_control": "`--ablate calib`:跳过步骤 2–4,恢复节点 5 的 addnz 解码,要求输出与节点 5 逐字节相同(harness 的 `--ablate mechanism` 映射到它)。预期差别:X3 上 DE 两项下降回父节点水平,mmd_u 基本不变;proxy10 两臂完全相同(单输入照抄)。附加对照:`--ablate opening` 只校准非零元,用来分离开启的贡献;`--no_gshrink` 关掉基因级 SNR 收缩。如果 calib 打开后,诊断 (a) 没有升高,或输出与关闭时相同,说明机制没有运行,应先修复再查分。"}
```
原始记录位置/home/spark-longxinyang/vec/runs/test/20261003-153851-search-t1-D-smoke2h/nodes/6/researcher.jsonl 9 KB

审查员

角色审查员 vec-relay-claude-cli/claude-opus-5-5
调用次数1 次
轮数1
工具调用共 0 次:—
用时不到 1 分
token 数输入 2 · 输出 1,809 · 思考 0
任务(第一行)审查节点 n6 的程序是否越界读取、写死目标、钻评分器漏洞
最后的回答(摘录)
{"verdict": "pass", "reasons": ["1 未发现问题:run.py 和 g37_common.py 只通过 view_io(load_manifest、panel_genes、stage_pair→inputs_by_time/read_stage/covered_mask)读取视图内的数据,没有绝对路径、`..`、/mnt、/home、data/raw、downloads 或评分器路径,也没有联网。增殖和凋亡基因表来自 moscot.utils.data 的库内标记表,不是数据下载。附注:g37_common.stage_pair 调用 inputs_by_time(include_external=True),所以 X3 视图里清单列出的 external(Qiu E8.75/E9.0)可能被纳入输入阶段,这与 METHOD.md 中“external/ 未被本程序读取”的说法不一致。但这些数据在视图清单之内,阶段不在禁窗内,不构成越界。", "2 未发现问题:run.py 的常量只有超参数(N_HVG、N_PCS、EPSILON、LAMBDA、RES_LAMBDA 等)。细胞数来自 target_n_cells(manifest, …),所有统计量都由输入现场计算,没有写死的比例表、表达量或类型名单。", "3 未发现问题:没有利用行序、缩放或平均细胞之类的技巧。weighted_rows 返回 np.sort 后的行,与父节点相同,是均匀无放回抽样。T1 无坐标,不涉及 occupancy_dice 手性。", "4 未发现问题:run.py 使用 np.random.default_rng(args.seed);PCA random_state=seed,score_genes random_state=seed;calib.gene_shrink 使用 default_rng(seed+7919);JAX_PLATFORMS=cpu。没有发现时间或全局随机状态依赖。", "5 未发现问题:程序只读取视图输入阶段(X3 为 E8.75/E9.0,proxy10 为 E8.5),没有读取禁窗阶段或保留基因型数据。METHOD.md 声明未使用发育事件表条目;growth_rates 注释给出了 Schiebinger et al. 2019 Cell 作为方法来源,这是统计先验,不是事件或标记程序。", "6 未发现问题:程序只使用时间差(dt_in = last-prev,dt_out = target-last),不读阶段名或文件名。单输入时走 prev is None 的照抄退路,任意数量的输入阶段由 stage_pair 取最后两个。", "7 未发现问题:PLAN.json 中没有 [compliance: removed] 标记或 _compliance 字段,METHOD.md 和代码中没有重新引入保留阶段的测量值。", "附注(非合规项):默认配置下 calib_off=True(--calib 默认关闭),输出与父节点 5 逐字节相同,harness 也报告 mechanism_active=no、全部分数变化 +0.00。METHOD.md 第一行如实标注 NO_CHANGE。本节点没有超过父节点,不应视为新进展。"]}
原始记录位置/home/spark-longxinyang/vec/runs/test/20261003-153851-search-t1-D-smoke2h/nodes/6/reviewer.jsonl 3 KB