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

总览 · ← 返回运行 20261003-172000-search-t2-heart-extrap-chain-12h

节点 n9

坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261003-172000-search-t2-heart-extrap-chain-12h
父节点n3
子节点—
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 49.60(-0.4) · proxy_noscale 49.60(-0.4) · 3 次复测均分 49.87
审查未审查
用时?从运行开始到结束(或到现在)的挂钟时间。39 分
程序版本35edb10ff1e00e77d71f8265f3a1737e776ae47c (programs.git)
导入自20261003-105537-search-t2-heart-extrap-g24-D#9
备注re-scored at launch (origin 20261003-105537-search-t2-heart-extrap-g24-D node 9, score there 49.60)

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

来自 programs.git 35edb10ff1:solution/METHOD.md

坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。

方法

  1. 基座与父节点/copy_last 完全相同:anchor=最后观测阶段(anchor_entry),n=clip(stage.n, min_cells, max_cells),按型分层无放回抽样(src.task2_spatial.sample.take),行序 np.sort 保持稳定。代理上 n=24826(≤max_cells),输出全部细胞。
  2. 表达度量:sklearn PCA(32 维,svd_solver=randomized,random_state=seed)作用于输出的 X。
  3. 空间图:坐标上的 15-NN(kneighbors n_neighbors=16 去掉自身),与评分器 neighborhood 定义一致;图的边集由坐标点集决定,置换不改变边集,故可用 O(1) 增量更新。
  4. 贪心 2-opt:随机提议细胞对 (i,j),交换二者坐标(coords[i]↔coords[j],即"行 i 占据的坐标槽"互换);能量变化用闭式 delta = 2(e_i−e_j)·(S_p−S_q) + (a+b)‖e_i−e_j‖²(S=槽的邻居表达和,增量维护;a,b=两槽互为邻居的指示),delta<0 才接受。行序稳定:X 的行序与输出行序和 copy_last 完全一致,置换只改每行携带的坐标(PLAN 第 6 条,修复节点 7 的行序缺陷)。
  5. 参数:VEC_MAX_SWAPS 默认 0.3n(=7447,绑定约束)、VEC_MAX_PROP 默认 4,000,000(实际 1.31M 提议即达 swap 上限)、VEC_PATIENCE=10000、VEC_PCA_DIM=32、VEC_LOCAL=0(局部提议模式已实现并测试,见下)。
  6. 机制关闭对照:VEC_MAX_SWAPS=0 → 完全跳过 2-opt,输出逐位等于 copy_last。

机制生效证据(实际改变了什么)

  • 交换 7447 次,5152 行坐标改变;Dirichlet 能量 E1/E0 = 0.937(下降 6.3%)。
  • X 与对照 array_equal;坐标点集(排序后)array_equal——只有配对关系变。
  • 四组分:expression_change、cell_state 保持 50.00(DE 原始值 0、mmd_u/variogram 与对照逐位相同,因为表达完全没动);变化集中在 local_spatial(neighborhood_mmd 0.11445→0.11256)与 shape_scale(occupancy_dice,见下"噪声")。

对照与查分(proxy_noscale,8 次查分)

配置swapsE1/E0榜分local_spatialshape_scaleneighborhood_mmddice
机制关(=copy_last)01.00050.00 ✓(PLAN 预期)50.0050.000.114450.8148
全局 2-opt,500k 提议31960.97150.2350.5650.370.111940.8156
全局 2-opt,0.3n swaps(提交)74470.93751.51(seed0)50.4255.610.112560.8221
同上 seed1 / seed274470.93449.88 / 49.7450.45 / 50.6149.06 / 48.350.11243 / 0.111680.8131 / 0.8115
全局 2-opt,12k swaps120000.90449.7051.0847.700.109610.8106
全局 2-opt,25k swaps250000.83249.2851.0346.080.109830.8065

验证过 / 未验证(诚实声明)

  • 验证过:机制本身(能量下降 → neighborhood_mmd 下降)单调且跨 seed 稳健:local_spatial 在全部 3 个 seed、全部 swap 档位都 >50(50.4–51.1),swap 越多 neighborhood 越好(0.1145→0.1096,12k 档后饱和)。行序稳定成立:输出行序、X、坐标点集与 copy_last 逐位一致(array_equal),未复现节点 7 的行序缺陷。机制关闭对照 = 50.00,与 PLAN 预期一致。
  • 噪声警告(重要):置换不改变坐标点集本身,但 occupancy_dice / d2_shape 会对细胞下采样(行索引固定、坐标被置换 → 子集几何变化),所以 shape_scale 在 seed 间大幅波动(48.4–55.6),是子抽样噪声而非系统性收益或损失。seed0 的 51.51 含 dice=0.8221 的正向波动;seed 0–2 均值仅 50.38,低于终选护栏要求的 +1。0.3n 档是在 A 半 seed0 上实测最高的档位;12k/25k 档 neighborhood 更好但 dice 系统性偏低(0.8106/0.8065),总分更低。
  • 测过但未采用:VEC_LOCAL=1(只提议空间相邻槽的交换):25k swaps 只降能量 0.5%(相邻对 delta 太小),预期对 neighborhood_mmd 无实质改善,未查分。
  • 未验证:B 半分数;真实 final 视图(外推榜本地尺子历史上高估官网分,方法卡风险条款适用——本机制不含任何位移方向或尺寸假设,理论上对 final 的行为与代理一致:只做配对重排)。

知识来源

  • 评分器 neighborhood 定义(15 最近邻)来自任务书公开的评分规则简报(代码事实),用于选择 kNN 图的 K=15;无保留阶段/基因型的任何信息、无绝对时间或尺寸常数;程序只读 manifest 的相对字段,伪装视图安全(时间平移不影响任何计算:本方法根本不使用时间)。

下一步建议

  • shape 组的 seed 间波动源于评分器下采样与行索引的耦合,属尺子噪声;若继续本机制,应看 rank3(3 seed 均值)而非单 seed。要突破 +1 护栏,local_spatial 的稳健收益(~+0.5)不够,需要叠加 expression/cell_state 组的真实改进(如 PLAN 外的型内组成插值),或接受本节点作为"机制验证成功但净收益 <1"的中间结果。

调研员的计划

名称T2HX-06 坐标置换(稳定行序版):表达kNN引导2-opt重排细胞-坐标配对
动机父节点3输出逐位等于copy_last(50.00),机制完全未激活(代理无外部前向样本、速度回退反相关已关)。全树唯一超过地板的是节点5(52.74),核心机制为表达kNN引导的坐标2-opt置换(local_spatial +0.98)。节点7从同一父节点复制该机制却得49.60,原因是置换后行序改变导致评分器按索引抽样时occupancy_dice碰到不利子集(0.8066→0.8025,−1.67)。本方案复制节点5已验证的机制,同时修复节点7的行序缺陷,目标稳定获得local_spatial收益而不赔shape_scale。
做法在父节点3的copy_last基座上新增坐标置换层,步骤:
1. 细胞选取与父节点相同(anchor=末观测阶段,n≤max_cells分层抽样),坐标点集与表达矩阵均不改。
2. 表达空间:对X做PCA取前32主成分(sklearn PCA,whiten=False),作为细胞表达距离度量。
3. 空间图:用坐标建15-NN图(与评分器neighborhood_mmd的邻域定义一致,sklearn NearestNeighbors,n_neighbors=16含自身,取后15)。
4. 目标函数:Dirichlet能量 E=Σ_{(i,j)∈edges}‖pca_i−pca_j‖²。初始E0记录。
5. 贪心2-opt:随机提议配对(i,j),若交换坐标i↔j后E下降则接受;上限VEC_MAX_PROP(默认500000,搜索范围200k–1M);提前终止:连续10000次无接受则停。
6. 行序稳定(关键修复):置换只改坐标赋值(coords_new[i]=coords[j], coords_new[j]=coords[i]),输出行序与输入完全一致,避免评分器按索引抽样时碰到不同细胞子集。验证:输出行序与输入array_equal。
7. 输出:X不变、坐标为重排后值、行序不变。
8. 环境变量:VEC_MAX_SWAPS(最大交换次数,默认=细胞数×0.3)、VEC_MAX_PROP(最大提议数)、VEC_PCA_DIM(默认32)。VEC_MAX_SWAPS=0时不做任何交换,输出逐位等于copy_last(对照)。
9. 快速筛选:先500细胞子集跑通验证E下降且swap>0(<1min),再全量;vec-score查VEC_MAX_SWAPS=0应得50.00,查全量看neighborhood_mmd原始值是否下降(越小越好)。
10. 单输入阶段退路:置换不依赖时间信息,单输入同样适用。
风险1) occupancy_dice仍可能因坐标重排后点云局部密度变化而微波动(±0.5分内),但行序稳定后不应出现节点7的−1.67;若occupancy_dice skill<0.48,检查是否抽样仍受影响。2) 2-opt在30min内可能未充分收敛(提议数不够),Dirichlet能量下降不足→neighborhood_mmd改善小;Engineer应在小样本上确认能量下降>10%再全量。3) 若细胞数很大(>5000),500k提议可能不够覆盖,可调高至1M但注意时间。4) 行序验证失败(输出行序≠输入行序)说明实现有bug,必须修复后再查分。

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

对比:父节点版本 a0271985a8。改动的文件:solution/METHOD.md +31 −21、solution/run.py +115 −211

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 6640572..0c5d66d 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,32 +1,42 @@-T2HX-01 位移层:外推时优先用视图内"晚于末输入、不晚于目标"的外部样本按伪批量相关匹配细胞型做阻尼位移(保坐标);两输入速度回退在代理上验证有害、默认关;无可用方向时逐位等于 copy_last。+坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。  ## 方法 -在父节点(copy_last)之上加一个位移层,管线(全部只读 manifest 的相对时间与区间字段,无绝对阶段/尺寸常数,伪装视图安全):+1. 基座与父节点/copy_last 完全相同:anchor=最后观测阶段(`anchor_entry`),n=clip(stage.n, min_cells, max_cells),按型分层无放回抽样(`src.task2_spatial.sample.take`),行序 `np.sort` 保持稳定。代理上 n=24826(≤max_cells),输出全部细胞。+2. 表达度量:sklearn PCA(32 维,svd_solver=randomized,random_state=seed)作用于输出的 X。+3. 空间图:坐标上的 15-NN(kneighbors n_neighbors=16 去掉自身),与评分器 neighborhood 定义一致;图的边集由坐标点集决定,置换不改变边集,故可用 O(1) 增量更新。+4. 贪心 2-opt:随机提议细胞对 (i,j),交换二者坐标(coords[i]↔coords[j],即"行 i 占据的坐标槽"互换);能量变化用闭式 delta = 2(e_i−e_j)·(S_p−S_q) + (a+b)‖e_i−e_j‖²(S=槽的邻居表达和,增量维护;a,b=两槽互为邻居的指示),delta<0 才接受。**行序稳定**:X 的行序与输出行序和 copy_last 完全一致,置换只改每行携带的坐标(PLAN 第 6 条,修复节点 7 的行序缺陷)。+5. 参数:VEC_MAX_SWAPS 默认 0.3n(=7447,绑定约束)、VEC_MAX_PROP 默认 4,000,000(实际 1.31M 提议即达 swap 上限)、VEC_PATIENCE=10000、VEC_PCA_DIM=32、VEC_LOCAL=0(局部提议模式已实现并测试,见下)。+6. 机制关闭对照:VEC_MAX_SWAPS=0 → 完全跳过 2-opt,输出逐位等于 copy_last。 -1. 细胞选择与坐标和父节点完全相同:anchor=最后观测阶段,n≤max_cells 整份输出,超过则按型分层抽样;坐标一律不动(保 neighborhood_mmd 与结构门)。-2. 位移方向按优先级:-   - (a) **外部前向靶**:`manifest.external` 中存在区间 (e_lo, e_hi) 满足 e_lo > 末输入时间且 e_hi ≤ 目标时间的样本时,加载并对齐到面板(<50 共有基因则放弃;X 若像原始计数则重算 log1p(CP10k));anchor 各细胞型与外部型在共有基因伪批量上算 Pearson r,r≥0.5 才配对;每细胞 shift = α·[β·型级差 + (1−β)·(匹配型内最近邻外部细胞 − 本细胞)],α=0.5、β=1(默认)。-   - (b) 两输入速度回退:型级伪批量差 × α_vel × dt_ratio。**默认 α_vel=0(关)**,理由见下。-   - (c) 都没有 → 输出逐位等于 copy_last。-3. shift 逐基因截断在 ±2·std(基因 in anchor),X+shift 后截 0。异常时 (a) 路径 try/except 回退,绝不崩。+## 机制生效证据(实际改变了什么) -## 机制对照与验证(proxy_noscale,5 次查分)+- 交换 7447 次,5152 行坐标改变;Dirichlet 能量 E1/E0 = 0.937(下降 6.3%)。+- X 与对照 array_equal;坐标点集(排序后)array_equal——只有配对关系变。+- 四组分:expression_change、cell_state 保持 50.00(DE 原始值 0、mmd_u/variogram 与对照逐位相同,因为表达完全没动);变化集中在 local_spatial(neighborhood_mmd 0.11445→0.11256)与 shape_scale(occupancy_dice,见下"噪声")。 -- **α=0 对照(mechanism_off_control)**:默认配置在代理上的输出与父节点逐位相同(X、坐标 array_equal 验证),vec-score 实测 **50.00**,四组全 50。✓-- **速度回退 (b) 的方向质量**(这是代理上唯一可测的方向):-  - 型级伪批量速度 α_eff=0.45/0.9/1.5:**49.0 / 48.7 / 48.3**。de_score 原始值 −0.056(低于 chance),de_direction ≈ −0.003~−0.007:E8.25→E8.75 的型级速度与真值 E8.75→E9.5 变化**不相关甚至反相关**,且 DE 两项对 α 近似不变(秩/符号指标),所以任何 α 都拿不到 DE 收益,只赔 variogram(skill 0.36–0.44)。与方法卡"α 网格单调低于地板"一致。-  - 去掉增殖模块(GO BP 名字含 cell cycle/mitotic/DNA replication 等的 44 个面板基因置零,通用基因功能注释,来自视图 prior/go)α_eff=0.45:**49.05**。de_direction 转微正(+0.015,skill 0.504)但 de_score 更差(−0.139),variogram 仍赔 0.8 分。-- 结论:代理上速度外推方向无信号,(b) 默认关(α_vel=0);代理行为 = copy_last = 50.00。-- **外部路径 (a) 冒烟测试**(把末输入时间假装提前以触发):加载 qiu E8.75、27883 基因对齐、33 个 anchor 型中 1 个(心脏谱系)r≥0.5 配对成功、shift 逐型非零——机械上可运行;代理视图内没有真正"晚于末输入"的外部样本,故该路径的科学效果**未能在本地验证**(这正是方法卡指出的外推盲区:本地尺子无法检验方向,且外推榜本地分历史上高估官网分)。-- vec-check 对本 board 返回 "not allowed for this client"(客户端限制,非格式问题);vec-score 正常接收并打分同一文件,格式合规。+## 对照与查分(proxy_noscale,8 次查分)++| 配置 | swaps | E1/E0 | 榜分 | local_spatial | shape_scale | neighborhood_mmd | dice |+|---|---:|---:|---:|---:|---:|---:|---:|+| 机制关(=copy_last) | 0 | 1.000 | **50.00** ✓(PLAN 预期) | 50.00 | 50.00 | 0.11445 | 0.8148 |+| 全局 2-opt,500k 提议 | 3196 | 0.971 | 50.23 | 50.56 | 50.37 | 0.11194 | 0.8156 |+| **全局 2-opt,0.3n swaps(提交)** | 7447 | 0.937 | **51.51**(seed0) | 50.42 | 55.61 | 0.11256 | 0.8221 |+| 同上 seed1 / seed2 | 7447 | 0.934 | 49.88 / 49.74 | 50.45 / 50.61 | 49.06 / 48.35 | 0.11243 / 0.11168 | 0.8131 / 0.8115 |+| 全局 2-opt,12k swaps | 12000 | 0.904 | 49.70 | 51.08 | 47.70 | 0.10961 | 0.8106 |+| 全局 2-opt,25k swaps | 25000 | 0.832 | 49.28 | 51.03 | 46.08 | 0.10983 | 0.8065 |++## 验证过 / 未验证(诚实声明)++- **验证过**:机制本身(能量下降 → neighborhood_mmd 下降)单调且跨 seed 稳健:local_spatial 在全部 3 个 seed、全部 swap 档位都 >50(50.4–51.1),swap 越多 neighborhood 越好(0.1145→0.1096,12k 档后饱和)。行序稳定成立:输出行序、X、坐标点集与 copy_last 逐位一致(array_equal),未复现节点 7 的行序缺陷。机制关闭对照 = 50.00,与 PLAN 预期一致。+- **噪声警告(重要)**:置换不改变坐标点集本身,但 occupancy_dice / d2_shape 会对细胞下采样(行索引固定、坐标被置换 → 子集几何变化),所以 shape_scale 在 seed 间大幅波动(48.4–55.6),是子抽样噪声而非系统性收益或损失。seed0 的 51.51 含 dice=0.8221 的正向波动;**seed 0–2 均值仅 50.38**,低于终选护栏要求的 +1。0.3n 档是在 A 半 seed0 上实测最高的档位;12k/25k 档 neighborhood 更好但 dice 系统性偏低(0.8106/0.8065),总分更低。+- **测过但未采用**:VEC_LOCAL=1(只提议空间相邻槽的交换):25k swaps 只降能量 0.5%(相邻对 delta 太小),预期对 neighborhood_mmd 无实质改善,未查分。+- **未验证**:B 半分数;真实 final 视图(外推榜本地尺子历史上高估官网分,方法卡风险条款适用——本机制不含任何位移方向或尺寸假设,理论上对 final 的行为与代理一致:只做配对重排)。  ## 知识来源 -- GO BP 注释(视图 prior/go/gene_sets_bp.gmt):增殖模块(细胞周期/有丝分裂/DNA 复制)是瞬态程序,其早期轨迹不可靠外推——通用基因功能知识,非任何保留阶段测量。-- 细胞型配对用表达相关而非标签名(外部数据标签词汇可能不同)——PLAN T2HX-01 规定。+- 评分器 neighborhood 定义(15 最近邻)来自任务书公开的评分规则简报(代码事实),用于选择 kNN 图的 K=15;无保留阶段/基因型的任何信息、无绝对时间或尺寸常数;程序只读 manifest 的相对字段,伪装视图安全(时间平移不影响任何计算:本方法根本不使用时间)。 -## 已知弱点 / 未验证+## 下一步建议 -- (a) 路径在真实 final 视图(可能挂 MOSTA E9.5 等前向外部样本)上的效果无法在本地检验;若方向错会低于地板。终选护栏(proxy_noscale 种子均值需超 copy_last 1 分)在本节点上不会通过,正式提交会回落到基线预测。-- 未做:STRING/通路平滑速度、t 统计加权、逐细胞 OT 位移(方法卡已证 OT 伤邻域)。+- shape 组的 seed 间波动源于评分器下采样与行索引的耦合,属尺子噪声;若继续本机制,应看 rank3(3 seed 均值)而非单 seed。要突破 +1 护栏,local_spatial 的稳健收益(~+0.5)不够,需要叠加 expression/cell_state 组的真实改进(如 PLAN 外的型内组成插值),或接受本节点作为"机制验证成功但净收益 <1"的中间结果。diff --git a/solution/run.py b/solution/run.pyindex 196e6b2..d0b3cb4 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,22 +1,22 @@ #!/usr/bin/env python3-"""T2 extrapolation: expression-matched pseudobulk displacement on the anchor stage.--Mechanism (family T2HX-01): shift each anchor-stage cell's expression toward a-forward target while keeping coordinates untouched (preserves local spatial-pairing). Target priority, computed only from view data and relative times:--  (a) an external sample whose whole stage interval lies strictly after the-      last input and at/before the target: per-cell type of the anchor is-      matched to external types by pseudobulk Pearson correlation on shared-      panel genes (r >= 0.5); shift blends the matched type-level difference-      (weight beta) with a nearest-neighbour cell-level difference (1 - beta).-  (b) else with >= 2 inputs: per-type pseudobulk velocity between the last two-      inputs, extrapolated over (target - last) time; type-level shift.-  (c) else: copy_last exactly (alpha also forces this when set to 0).--Shifts are per-gene capped at cap * std(gene in anchor), values clipped at 0.-No absolute stage names, times or sizes are hardcoded; only relative time-differences from the manifest are used, so the program is view-independent.+"""T2 extrapolation: copy_last base + expression-guided coordinate permutation.++Mechanism (family T2HX-06): keep the anchor (last observed) stage's cells,+expression matrix and the coordinate point set bitwise unchanged; only re-pair+cells with coordinates. A greedy 2-opt search on the fixed 15-NN graph of the+coordinate slots minimizes the Dirichlet energy of the cells' expression PCA+embedding, so that spatial neighbours become expression-similar. This targets+neighborhood_mmd (expression-location pairing) without touching any metric+that depends on the point set or the expression values.++Row order is kept identical to the copy_last output: a swap exchanges the+coordinate rows assigned to two cells (coords[i] <-> coords[j]) and never+reorders X or the output rows, so any index-based subsampling by the scorer+sees the same cell subset as the copy_last baseline.++No absolute stage names, times or sizes are used; only the manifest's inputs,+so the program is view-independent. VEC_MAX_SWAPS=0 disables the mechanism+and the output equals copy_last bitwise (mechanism-off control). """  from __future__ import annotations@@ -24,158 +24,90 @@ from __future__ import annotations import argparse import os import sys+import time  import numpy as np  from src.task2_spatial.sample import take from src.task2_spatial.view_io import (     anchor_entry,-    extrap_step,     load_manifest,     panel_genes,     read_stage,-    target_time,     write_t2, ) -ALPHA = float(os.environ.get("VEC_ALPHA", "0.5"))  # external-forward path-ALPHA_VEL = float(os.environ.get("VEC_ALPHA_VEL", "0.0"))  # two-input velocity fallback (proxy-validated: harmful, kept off)-BETA = float(os.environ.get("VEC_BETA", "1.0"))-CAP = float(os.environ.get("VEC_CAP", "2.0"))-TIME_SCALE = bool(int(os.environ.get("VEC_TS", "1")))-CC_ZERO = bool(int(os.environ.get("VEC_CC", "0")))-MATCH_R = 0.5-MIN_SHARED = 50--_CC_KEYWORDS = ("cell cycle", "mitotic", "meiotic", "dna replication", "chromosome segregation", "cytokinesis")---def _cell_cycle_genes(view: str) -> set[str]:-    """Genes in GO BP terms whose name indicates proliferation programs (generic-    gene-function annotation from the view's prior/; transient modules whose-    early trajectory is not trusted for forward extrapolation)."""-    out: set[str] = set()-    try:-        path = os.path.join(view, "prior", "go", "gene_sets_bp.gmt")-        with open(path) as fh:-            for line in fh:-                parts = line.rstrip("\n").split("\t")-                if len(parts) < 3:-                    continue-                name = parts[1].lower()-                if any(k in name for k in _CC_KEYWORDS):-                    out.update(parts[2:])-    except OSError:-        pass-    return out---def _pb(X: np.ndarray, labels: np.ndarray) -> tuple[list[str], np.ndarray]:-    types = sorted(set(labels.tolist()))-    out = np.zeros((len(types), X.shape[1]), dtype=np.float64)-    for k, t in enumerate(types):-        m = labels == t-        if m.any():-            out[k] = X[m].mean(axis=0)-    return types, out---def _external_forward_entry(manifest: dict, last_time: float, tt: float) -> dict | None:-    for e in manifest.get("external") or []:-        try:-            lo = float(e.get("e_lo", -np.inf))-            hi = float(e.get("e_hi", lo))-        except (TypeError, ValueError):+MAX_SWAPS_ENV = os.environ.get("VEC_MAX_SWAPS", "")  # default: 0.3 * n+MAX_PROP = int(os.environ.get("VEC_MAX_PROP", "4000000"))+PCA_DIM = int(os.environ.get("VEC_PCA_DIM", "32"))+PATIENCE = int(os.environ.get("VEC_PATIENCE", "10000"))+LOCAL = bool(int(os.environ.get("VEC_LOCAL", "0")))  # restrict proposals to spatially adjacent slots+K = 15  # neighbours, matching the scorer's neighborhood definition+++def _dirichlet_energy(E: np.ndarray, nbrs: np.ndarray, nrm2: np.ndarray, S: np.ndarray) -> float:+    # sum_i sum_{u in nbrs[i]} ||e_i - e_u||^2 (asymmetric kNN sum)+    return float(15.0 * nrm2.sum() + np.add.reduceat(nrm2[nbrs.ravel()], np.arange(0, nbrs.size, K)).sum()+                 - 2.0 * np.einsum("ij,ij->i", E, S).sum())+++def _permute_coords(coords0: np.ndarray, E: np.ndarray, nbrs: np.ndarray, rev: list[list[int]],+                    nbrset: list[set], max_swaps: int, max_prop: int, patience: int,+                    rng: np.random.Generator) -> tuple[np.ndarray, dict]:+    n = coords0.shape[0]+    S = E[nbrs].sum(axis=1)  # slot -> sum of neighbour expressions (slot p holds row p initially)+    nrm2 = np.einsum("ij,ij->i", E, E)+    e0 = _dirichlet_energy(E, nbrs, nrm2, S)++    pos = np.arange(n)  # row -> slot currently holding its coordinates+    occ = np.arange(n)  # slot -> row currently occupying it+    swaps = 0+    props = 0+    rejects = 0+    t0 = time.time()+    # For LOCAL mode, candidate partners occupy slots spatially adjacent to the+    # row's current slot, so a swap moves a coordinate only a short distance:+    # the subsampled point cloud geometry (d2_shape / occupancy_dice) stays+    # close to the anchor while expression-position pairing (neighborhood_mmd)+    # is smoothed locally.+    while swaps < max_swaps and props < max_prop and rejects < patience:+        i = int(rng.integers(n))+        p = int(pos[i])+        if LOCAL:+            q = int(nbrs[p][rng.integers(K)])+            j = int(occ[q])+        else:+            j = int(rng.integers(n))+        if i == j:             continue-        if lo > last_time + 1e-6 and hi <= tt + 1e-6:-            return e-    return None---def _load_external(view: str, entry: dict, genes: list[str]):-    import anndata as ad-    from scipy import sparse--    ad_obj = ad.read_h5ad(os.path.join(view, entry["path"]))-    ext_genes = [str(g) for g in ad_obj.var_names]-    pos = {g: i for i, g in enumerate(ext_genes)}-    shared = [g for g in genes if g in pos]-    if len(shared) < MIN_SHARED:-        return None, None-    cols = np.array([pos[g] for g in shared])-    X = ad_obj.X-    X = X.toarray() if sparse.issparse(X) else np.asarray(X)-    X = np.nan_to_num(np.asarray(X, dtype=np.float64))-    if "log1p" not in ad_obj.uns and X.max() > 30.0:  # looks like raw counts-        tot = X.sum(axis=1, keepdims=True)-        tot[tot <= 0] = 1.0-        X = np.log1p(X / tot * 10000.0)-    X = X[:, cols]-    labels = (-        np.asarray(ad_obj.obs["celltype"]).astype(str)-        if "celltype" in ad_obj.obs-        else np.array(["ext"] * X.shape[0])-    )-    return shared, (X, labels)---def _direction_external(view, manifest, genes, X_a, lab_a, pb_a, last_time, tt):-    entry = _external_forward_entry(manifest, last_time, tt)-    if entry is None:-        return None-    try:-        shared, loaded = _load_external(view, entry, genes)-        if loaded is None:-            return None-        X_e, lab_e = loaded-        gidx = np.array([genes.index(g) for g in shared])-        types_e, pb_e = _pb(X_e, lab_e)-        # match anchor types to external types by pseudobulk Pearson r on shared genes-        A = pb_a[:, gidx]-        B = pb_e-        A = A - A.mean(axis=1, keepdims=True)-        B = B - B.mean(axis=1, keepdims=True)-        na = np.linalg.norm(A, axis=1, keepdims=True)-        nb = np.linalg.norm(B, axis=1, keepdims=True)-        na[na == 0] = 1.0-        nb[nb == 0] = 1.0-        C = (A / na) @ (B / nb).T-        best = C.argmax(axis=1)-        ok = C[np.arange(len(best)), best] >= MATCH_R-        print(f"[diag] external target {entry.get('id')}: matched {ok.sum()}/{len(ok)} anchor types", file=sys.stderr)-        if not ok.any():-            return None-        # type-level difference on full panel (external genes only)-        diff_t = np.zeros_like(pb_a)-        for k in range(len(ok)):-            if ok[k]:-                diff_t[k, gidx] = pb_e[best[k]] - pb_a[k, gidx]-        # cell-level: nearest external neighbour within matched type-        from sklearn.neighbors import NearestNeighbors--        shift = np.zeros_like(X_a)-        d_sub = X_a[:, gidx]-        types_a = sorted(set(lab_a.tolist()))-        for k in range(len(ok)):-            if not ok[k]:-                continue-            m = lab_a == types_a[k]-            if not m.any():-                continue-            tgt_m = lab_e == types_e[best[k]]-            if not tgt_m.any():-                continue-            nn = NearestNeighbors(n_neighbors=1).fit(X_e[tgt_m][:, : len(shared)])-            _, ind = nn.kneighbors(d_sub[m])-            cell_diff = np.zeros((m.sum(), X_a.shape[1]))-            cell_diff[:, gidx] = X_e[tgt_m][ind[:, 0]] - d_sub[m]-            type_diff = np.zeros((m.sum(), X_a.shape[1]))-            type_diff[:, gidx] = diff_t[k, gidx]-            shift[m] = BETA * type_diff + (1.0 - BETA) * cell_diff-        return shift-    except Exception as exc:  # never crash on the optional external path-        print(f"[diag] external path failed: {exc}", file=sys.stderr)-        return None+        props += 1+        q = int(pos[j])+        d = E[i] - E[j]+        a = q in nbrset[p]+        b = p in nbrset[q]+        dd = float(d @ d)+        delta = 2.0 * float(d @ (S[p] - S[q])) + (a + b) * dd+        if delta < -1e-12:+            # accept: row i takes slot q, row j takes slot p+            pos[i], pos[j] = q, p+            occ[p], occ[q] = j, i+            dv = -d  # e_j - e_i+            for u in rev[p]:+                S[u] += dv+            for u in rev[q]:+                S[u] -= dv+            if a:+                S[p] -= dv+            if b:+                S[q] += dv+            swaps += 1+            rejects = 0+        else:+            rejects += 1+    e1 = _dirichlet_energy(E[occ], nbrs, nrm2[occ], S)+    diag = {"E0": e0, "E1": e1, "ratio": e1 / e0 if e0 > 0 else float("nan"),+            "swaps": swaps, "props": props, "secs": round(time.time() - t0, 1)}+    return coords0[pos], diag   def main() -> None:@@ -187,8 +119,6 @@ def main() -> None:      manifest = load_manifest(args.data)     genes = panel_genes(args.data, manifest)-    prev, last, dt_ratio = extrap_step(manifest)-    tt = target_time(manifest)     stage = read_stage(args.data, anchor_entry(manifest), genes)     n = int(np.clip(stage.n, manifest["min_cells"], manifest["max_cells"]))     rng = np.random.default_rng(args.seed)@@ -199,59 +129,33 @@ def main() -> None:      X = stage.X[rows].toarray().astype(np.float32)     coords = stage.coords[rows]+    n = X.shape[0] -    if ALPHA > 0.0 or ALPHA_VEL > 0.0:-        lab = stage.labels[rows]-        types, pb_a = _pb(X.astype(np.float64), lab)-        shift = None-        src = "none"-        if ALPHA > 0.0:-            shift = _direction_external(-                args.data, manifest, genes, X.astype(np.float64), lab, pb_a, float(last["time"]), tt-            )-            if shift is not None:-                shift *= ALPHA-                src = "external"-        if shift is None and prev is not None and ALPHA_VEL > 0.0:-            src = "velocity"-            pstage = read_stage(args.data, prev, genes)-            Xp = pstage.X.toarray().astype(np.float64)-            _, pb_p = _pb(Xp, pstage.labels)-            tmap_p = {t: i for i, t in enumerate(_pb(Xp, pstage.labels)[0])}-            glob = pb_a.mean(axis=0) - Xp.mean(axis=0)-            v = np.tile(glob, (len(types), 1))-            for k, t in enumerate(types):-                if t in tmap_p:-                    v[k] = pb_a[k] - pb_p[tmap_p[t]]-            scale = ALPHA_VEL * (dt_ratio if (TIME_SCALE and dt_ratio) else 1.0)-            per_type = {t: scale * v[k] for k, t in enumerate(types)}-            shift = np.zeros_like(X, dtype=np.float64)-            for t in types:-                m = lab == t-                if m.any():-                    shift[m] = per_type[t]-            del Xp-        if shift is None:-            src = "copy_last(no usable forward direction)"-        if shift is not None:-            if CC_ZERO:-                cc = _cell_cycle_genes(args.data)-                idx = [i for i, g in enumerate(genes) if g in cc]-                if idx:-                    shift[:, idx] = 0.0-                print(f"[diag] cc-zero: {len(idx)} genes zeroed", file=sys.stderr)-            sd = X.astype(np.float64).std(axis=0)-            capv = CAP * np.maximum(sd, 1e-3)-            shift = np.clip(shift, -capv, capv)-            X = np.clip(X.astype(np.float64) + shift, 0.0, None).astype(np.float32)-            norms = np.linalg.norm(shift, axis=1)-            print(f"[diag] src={src} shift norm mean/p50/p95: {norms.mean():.3f} "-                  f"{np.percentile(norms,50):.3f} {np.percentile(norms,95):.3f}", file=sys.stderr)-            gmag = np.abs(shift).mean(axis=0)-            top = np.argsort(-gmag)[:20]-            print("[diag] top shifted genes: " + ", ".join(f"{genes[i]}({gmag[i]:.2f})" for i in top), file=sys.stderr)-        else:-            print(f"[diag] src={src}: no shift applied", file=sys.stderr)+    max_swaps = int(float(MAX_SWAPS_ENV) if MAX_SWAPS_ENV else 0.3 * n)++    if max_swaps > 0 and n > K + 1:+        from sklearn.decomposition import PCA+        from sklearn.neighbors import NearestNeighbors++        dim = int(min(PCA_DIM, X.shape[0] - 1, X.shape[1]))+        pca = PCA(n_components=dim, svd_solver="randomized", random_state=args.seed)+        E = np.ascontiguousarray(pca.fit_transform(X.astype(np.float64)), dtype=np.float64)++        idx = NearestNeighbors(n_neighbors=K + 1).fit(coords).kneighbors(coords, return_distance=False)+        nbrs = np.empty((n, K), dtype=np.int64)+        for i in range(n):+            kept = [int(j) for j in idx[i] if j != i][:K]+            while len(kept) < K:+                kept.append(i)+            nbrs[i] = kept+        rev: list[list[int]] = [[] for _ in range(n)]+        for u in range(n):+            for p in nbrs[u]:+                rev[int(p)].append(u)+        nbrset = [set(int(v) for v in nbrs[i]) for i in range(n)]++        coords, diag = _permute_coords(coords, E, nbrs, rev, nbrset, max_swaps, MAX_PROP, PATIENCE, rng)+        print(f"[diag] permute: {diag}", file=sys.stderr)      write_t2(args.out, X, coords, genes, seed=args.seed) 

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

用到的知识库条目

编号标题出处
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md
k016Degenerate-solution checks for population predictionsnotes/handover/02_知识学习路线.md
k024World-model evaluation dimensions for state-transition predictorsnotes/competition/07_biomedical_world_models.md

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

改了什么在 copy_last 基座(父节点 3)上把 T2HX-01 位移层整体换成 T2HX-06 坐标置换层:X、坐标点集、行序逐位不动,只在坐标 15-NN 图上做贪心 2-opt(闭式 delta + 增量维护邻居表达和)最小化表达 PCA-32 的 Dirichlet 能量,重排细胞与坐标的配对;提交档 0.3n=7447 次交换、能量降 6.3%,VEC_MAX_SWAPS=0 时逐位等于 copy_last。耗时 2.3s→10.8s,内存 0.42→0.54GB。
各组分数的变化cell_state:完全没动:mmd_u 0.05832→0.05832、variogram 0.05806→0.05806,skill 均 0.500,组 +0.00(符合设计)。
expression_change:完全没动:de_score/de_direction 原始值均 0(无变化保护),skill 0.500→0.500,组 50.00→50.00,+0.00(符合设计:表达矩阵未改)。
local_spatial:机制方向正确但收益在噪声边缘:neighborhood_mmd 0.11261→0.11066,skill 0.500→0.504,得分 +0.11;组 50.00→50.44,+0.44(T2 噪声约 1 分,单项收益小于噪声)。
shape_scale:变坏且超出噪声:occupancy_dice 0.8066→0.8016,skill 0.500→0.440,得分 −0.50;d2_shape 0.04911→0.04938,得分 −0.01;scale_log_ratio 未动(−0.4334)。组 50.00→47.96,−2.04。结构门未触发(邻域 skill 0.504>0.5)。
family_idT2HX-06
假设是否成立否
经验
  1. 坐标置换即使做到行序/X/点集逐位不变(array_equal 验证通过),occupancy_dice 仍会掉(0.8066→0.8016,shape −2.04):评分器按行索引下采样,置换改变的是被抽中子集携带的几何,所以父节点 7 的 dice 损失并非行序缺陷造成,本节点的"关键修复"诊断是错的。
  2. 置换类机制的净期望为负:local_spatial 的稳健收益只有 +0.4(neighborhood_mmd 降约 0.002),而它诱发的 shape_scale 波动达 ±2,1 次采样的期望 < 0,靠调 seed 无法变成正收益。
  3. swap 数是单调 trade-off 而非甜点:0.3n→12k→25k,neighborhood_mmd 0.1126→0.1096→0.1098 持续变好,但 dice 0.8148→0.8106→0.8065 系统性变差,总分 51.5(seed0)→49.70→49.28;加大置换强度只会更差。
  4. 只提议空间相邻槽的局部 2-opt(VEC_LOCAL=1)在 25k 次交换下只降能量 0.5%,对 Dirichlet 能量最小化无效;要降能量必须允许远距离交换,而这恰好是破坏 dice 的部分。
  5. 隔离重跑会落到 Engineer 未测过的 seed 上:本节点 neighborhood_mmd(0.1107)优于其报告的三个 seed(0.1117–0.1126),dice(0.8016)却低于全部三个(0.8115–0.8221),榜分 49.60 低于其 3-seed 均值 50.38;与下采样耦合的机制必须按多 seed 均值+最差值评估,不能引用单 seed 最好值(seed0 的 51.51)。
  6. 外推榜本地尺子历史上高估官网分(心脏外推本地 54.2→官网 49.6),本地 +0.5 量级的收益一律不能当作真实收益。
mechanism_active是
下一步建议
  1. 放弃继续单独优化置换层(针对 local_spatial):收益上限约 +0.5,被 shape_scale 的 ±2 波动吃掉;若仍要保留,必须先给出"能量下降但 dice 不变"的约束式实现(如只接受 ‖coord_p−coord_q‖ 小于点云 RMS 半径 0.2 倍的交换,并在 ≥5 seed 上同时报 neighborhood_mmd 与 dice 的均值和最差值),否则不投入查分额度。
  2. 把资源转到 expression_change / cell_state(两组仍卡在地板 50.00,de_score/de_direction 原始值恒为 0):这两组各 25 分、上限远高于 local_spatial 的 +0.5;具体做型级组成/表达的方向性改变——用两个观测阶段的型级伪批量差做阻尼外推并只保留秩与符号正确的基因子集(|Δ| 排序取 top-k,k≈真值上调基因数量级),目标是让 std(dp)>1%·std(dt) 脱离无变化保护,再查 de_score 原始值是否 >0。
  3. 对任何会改变点云几何或被下采样的机制,节点验收标准改为"3–5 seed 的组分均值超 copy_last 且最差 seed 不低于 −0.5",而不是单 seed 榜分;本节点若按此标准在提交前就应判为不通过。

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

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

分析员

角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。分析员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数?大模型一共回复了几轮(每轮可以调用多个工具)。3
工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。共 3 次:—
用时1 分
token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。输入 21,569 · 输出 1,650 · 思考 2,158
任务(第一行)分析节点 n9 的打分结果,写 ANALYSIS.json
最后的回答(摘录)
ANALYSIS.json written
原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。/home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/analyst.jsonl (文件不在)

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数30
工具调用共 33 次:—
用时26 分
token 数输入 75,511 · 输出 12,331 · 思考 24,589
任务(第一行)按计划修改程序:T2HX-06 坐标置换(稳定行序版):表达kNN引导2-opt重排细胞-坐标配对
最后的回答(摘录)
Done. Summary:

**做了什么**:按 PLAN T2HX-06 在 copy_last 基座上实现"表达kNN引导坐标置换(稳定行序版)"——X、坐标点集、行序逐位不变,仅用贪心 2-opt 在坐标 15-NN 图上最小化 PCA-32 表达 Dirichlet 能量,重排细胞-坐标配对(闭式 delta + 增量维护邻居表达和,全量 24826 细胞约 20s)。

**查分结果**(8/20 次额度):机制关闭对照 = 50.00 ✓(PLAN 预期)。提交配置(0.3n=7447 swaps,E↓6.3%):seed0 = **51.51**(local_spatial 50.42,dice 0.8221 正向波动);seed1/2 = 49.88/49.74。neighborhood_mmd 改善跨 seed、跨档位稳健(local_spatial 全部 >50,0.1145→0.1096),但 shape 组因评分器按行索引下采样与置换耦合而大幅波动(48.4–55.6),3-seed 均值仅 50.38,低于终选 +1 护栏。12k/25k swaps 档 neighborhood 更好但 dice 系统性偏低,总分更低。

**下一步最值得试**:置换机制已验证但净收益 <1;要过护栏需在 expression_change/cell_state 组叠加真实改进(如型内组成插值或先验驱动的秩级方向调整),或与本置换层组合以保住 local_spatial 的稳健 +0.5。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/engineer.jsonl (文件不在)

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时7 分
token 数输入 15,107 · 输出 6,383 · 思考 8,192
任务(第一行)为节点 n9(改进,父节点 n3)写改进计划 PLAN.json
最后的回答(摘录)
_dice碰到不利子集(0.8066→0.8025,−1.67)。本方案复制节点5已验证的机制,同时修复节点7的行序缺陷,目标稳定获得local_spatial收益而不赔shape_scale。",
  "approach": "在父节点3的copy_last基座上新增坐标置换层,步骤:\n1. 细胞选取与父节点相同(anchor=末观测阶段,n≤max_cells分层抽样),坐标点集与表达矩阵均不改。\n2. 表达空间:对X做PCA取前32主成分(sklearn PCA,whiten=False),作为细胞表达距离度量。\n3. 空间图:用坐标建15-NN图(与评分器neighborhood_mmd的邻域定义一致,sklearn NearestNeighbors,n_neighbors=16含自身,取后15)。\n4. 目标函数:Dirichlet能量 E=Σ_{(i,j)∈edges}‖pca_i−pca_j‖²。初始E0记录。\n5. 贪心2-opt:随机提议配对(i,j),若交换坐标i↔j后E下降则接受;上限VEC_MAX_PROP(默认500000,搜索范围200k–1M);提前终止:连续10000次无接受则停。\n6. **行序稳定(关键修复)**:置换只改坐标赋值(coords_new[i]=coords[j], coords_new[j]=coords[i]),输出行序与输入完全一致,避免评分器按索引抽样时碰到不同细胞子集。验证:输出行序与输入array_equal。\n7. 输出:X不变、坐标为重排后值、行序不变。\n8. 环境变量:VEC_MAX_SWAPS(最大交换次数,默认=细胞数×0.3)、VEC_MAX_PROP(最大提议数)、VEC_PCA_DIM(默认32)。VEC_MAX_SWAPS=0时不做任何交换,输出逐位等于copy_last(对照)。\n9. 快速筛选:先500细胞子集跑通验证E下降且swap>0(<1min),再全量;vec-score查VEC_MAX_SWAPS=0应得50.00,查全量看neighborhood_mmd原始值是否下降(越小越好)。\n10. 单输入阶段退路:置换不依赖时间信息,单输入同样适用。",
  "expected_groups": ["local_spatial", "shape_scale"],
  "risks": "1) occupancy_dice仍可能因坐标重排后点云局部密度变化而微波动(±0.5分内),但行序稳定后不应出现节点7的−1.67;若occupancy_dice skill<0.48,检查是否抽样仍受影响。2) 2-opt在30min内可能未充分收敛(提议数不够),Dirichlet能量下降不足→neighborhood_mmd改善小;Engineer应在小样本上确认能量下降>10%再全量。3) 若细胞数很大(>5000),500k提议可能不够覆盖,可调高至1M但注意时间。4) 行序验证失败(输出行序≠输入行序)说明实现有bug,必须修复后再查分。",
  "family_id": "T2HX-06",
  "mechanism": "在坐标15-NN图上用贪心2-opt最小化表达Dirichlet能量,重排细胞与坐标的配对关系,使表达相似的细胞成为空间邻居,从而改善neighborhood_mmd(表达-位置配对质量)。表达矩阵和坐标点集本身均不变。",
  "vs_constant_shift": "不做任何表达位移:每个细胞的表达向量原封不动,每个坐标点也原封不动;只改变哪个细胞对应哪个坐标。常数位移是对每型加一个表达向量,改变表达值本身;本机制不改变任何表达值,只改变表达与空间位置的配对。",
  "mechanism_evidence": "1) 交换计数>0且Dirichlet能量下降百分比(E_after/E_before<1);2) neighborhood_mmd原始值下降(0.1126→更低);3) local_spatial组分>50;4) 表达矩阵与坐标点集逐位不变(array_equal验证),只有配对关系改变;5) 四组分中expression_change和cell_state保持50(表达未动),变化集中在local_spatial。",
  "mechanism_off_control": "设VEC_MAX_SWAPS=0,程序跳过2-opt循环,输出逐位等于copy_last(X和坐标array_equal验证),vec-score应得50.00、四组全50。与开启置换的输出对比:差异仅在坐标列的排列和local_spatial/shape_scale组分。",
  "sources": []
}
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/researcher.jsonl (文件不在)