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

总览 · ← 返回运行 20261003-043412-search-t2-heart-interp-g24-D-s1

节点 n8 在终选来历上

在父节点簇抽样(k=25,n_clusters=10)上,把 Procrustes 对齐的地标从 exact-name 共有类型换成跨类型表达相关贪心匹配对(Pearson r≥0.3、每侧≥30 细胞),组成/表达/配额逻辑不变。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261003-043412-search-t2-heart-interp-g24-D-s1
父节点n7
子节点n11
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 63.52(+0.9) · proxy 63.52(+0.9) · 3 次复测均分 62.27
审查通过 检查项1(越界读取):未发现问题——run.py 只经 view_io(load_manifest/panel_genes/read_stage)读取 --data 视图数据,仅 import 任务库 src.task2_spatial.frame/methods/sample/transport/view_io(非打分器 src/common/evaluation),全部源码无 open()/绝对路径/'..'/mnt/home/data/raw/网络调用(grep 验证),无目标阶段文件读取。; 检查项2(硬编码目标统计量):未发现问题——代码中的数字仅为方法超参(run.py:46-4…
用时?从运行开始到结束(或到现在)的挂钟时间。21 分
程序版本dcafc254daf190702b6686a3478af5921e65e37f (programs.git)

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

来自 programs.git dcafc254da:solution/METHOD.md

在父节点簇抽样(k=25,n_clusters=10)上,把 Procrustes 对齐的地标从 exact-name 共有类型换成跨类型表达相关贪心匹配对(Pearson r≥0.3、每侧≥30 细胞),组成/表达/配额逻辑不变。

方法

  • 与父节点 7 相同的 mix 路径:括号两阶段对齐到同一帧、log 线性 RMS 缩放(scale_damp=1)、_limits 定细胞数、round(t*n) 从后侧取、每类型配额算术不变;类型内用 k-NN 贪心区域生长抽空间连续簇(k=25, n_clusters=10)。
  • 本节点机制(MATCHED_ALIGN=True,solution/matched_align.py):对齐地标不再要求类型同名。对两侧各自 ≥30 细胞的每个类型算 pseudobulk(类型均值)表达,两两 Pearson 相关,从高到低贪心配对(每个类型只用一次,r<0.3 停止),用匹配对的质心做 xy Kabsch(权重 min(两侧细胞数),允许反射),z 保持切片轴、符号由匹配质心 z 相关定。<3 对时回退 exact-name 共有质心,再回退 PCA。
  • 动机:代理括号两侧注释体系不同(E8.25_late 33 型 vs E9.5 心脏 22 型,exact-name 只有 5 对且其中同名类型未必同源——贪心把 A 的 Peri 配到 B 的 ST、A 的 JCF 配到 B 的 Peri,pPHM↔aPHM 交叉,说明两侧命名确有错位);表达相关匹配给出 22 对更可信的地标。final 括号两侧同一官方词汇(31 个共有类型),同名的同源类型相关通常最高,贪心会自然保留它们,机制退化为"同名为主 + 少数跨名补充",方向一致、低风险(PLAN risk 3)。
  • 参数开关:T2_MATCHED_ALIGN=0 / T2_CLUSTER_SAMPLING=0 / T2_MATCH_MODE / T2_CORR_THRESH / T2_N_CLUSTERS 环境变量仅用于对照实验,提交默认 greedy、r=0.3、nc=10、两个机制都打开。
  • 确定性:只用视图数据 + np.random.default_rng(seed);seed 0 重跑逐位相同(本地验证)。只用时间差 t,不用绝对时间、不看视图路径/名称。

机制生效证据(proxy,E8.25_late+E9.5→E8.75,t=0.4,seed 0,A 半)

  • 匹配对数:exact-name 5 → greedy 22 对(全部 r≥0.389,最高 0.863),Kabsch 旋转改变、z 符号翻转为 -1(父 +1),输出与父节点不逐位相同(已验证)。
  • 关闭对照(T2_MATCHED_ALIGN=0,其余不变):输出与父节点 7 逐位相同(本地 np.array_equal 验证)。
  • 四组分(对照 = 父 node 7 seed 0):shape_scale 53.5→55.35(+1.85,d2_shape 0.0339→0.0291),local_spatial 64.61→65.02(持平微升,邻域机制未破坏),expression_change 64.6→64.6、cell_state 66.8→66.8(同一批真实细胞、组成不变,不动,符合预期)。
  • 平均 5-NN 结构保持(neighborhood_mmd 0.0518 vs 父 0.0530)。

查分(vec-score A 半,proxy;父 node 7 三种子 A 半均值 62.11)

配置seed0seed1seed23种子均值
greedy r0.3, nc=10(提交)62.9461.7462.2762.32
greedy r0.5, nc=1062.94
mutual-best r0.3, nc=1062.7961.4162.1562.12
greedy r0.3, nc=1263.1561.6061.0561.93
greedy r0.3, nc=1461.72
mutual r0.3, nc=1263.11
matched-align 单独(关簇抽样)59.07=父 mix 对照 59.07
  • seed 0 满足 PLAN 门槛(shape 55.35≥53.5、local 65.02≥64、shape 提升 +1.85>1);nc=12 第二轮在 seed 0 更高但 3 种子均值更低,不取。
  • 如实说明:3 种子均值 62.32 只比父 62.11 高 +0.2,在 T2 约 1 分噪声内;波动主要来自 occupancy_dice 对簇落点的种子敏感性(0.80–0.83),matched-align 本身在代理上无系统性害处(单独关簇抽样时与父 mix 同分 59.07)。按 PLAN risk 3 提交:机制在 final(31 个共有类型、注释一致)上更可能生效,代理只 5 对同名地标、信号弱。

未验证 / 风险

  • final 括号无法本地验证;若 final 上同名地标本就可靠,greedy 跨名配对可能引入个别错配地标(缓解:同名同源类型相关通常最高,贪心优先选中;权重 min(count) 抑制小类型影响)。
  • z 符号在代理上翻转为 -1(父为 +1),shape 反而升;final 上两侧切片方向若一致,匹配质心 z 相关会给出正确符号(机制与父相同的判据,只是地标更准)。
  • 知识来源:无外部生物知识;只用视图内表达、坐标、标签与时间差。

调研员的计划

名称簇抽样 + 类型匹配对齐(matched-align)组合
动机父节点 7 最弱组 shape_scale=52.78(比 node 4 的 55.08 低 2.3 分)。ANALYSIS 明确指出簇抽样与 node 4 的 matched-align 机制正交:matched-align 提 shape_scale +1.3(node 4 对照 53.1→54.4),簇抽样提 local_spatial +11(node 7),二者分别作用于对齐质量和抽样策略,预期可叠加。node 7 的 occupancy_dice 因簇间空洞略降(0.834→0.828),更好的对齐可减小坐标框架误差、间接缓解空洞对 shape 的损害。
做法步骤:1) 在 run.py 的对齐步骤中,将 align_pair 的标签匹配从 exact-name 替换为 node 4 的 matched-align 逻辑:对两侧共有类型计算类型均值表达的 Pearson 相关,阈值 r≥0.3 且每侧 ≥30 细胞才配对,用匹配对的质心做 Procrustes(z 轴保持切片轴不变)。不匹配的类型仍按 exact-name 处理(兜底)。2) 对齐后的坐标直接传入现有 cluster_mix_indices(k=25, n_clusters=10),簇抽样逻辑不改。3) 表达、组成配额、_limits、log 线性 RMS 缩放均不变。4) 单输入阶段(b=None)走原有单阶段回退,不受影响。5) 用 vec-score 在 proxy 上查分:先跑 seed 0 确认 shape_scale 是否 ≥53.5 且 local_spatial ≥64;若 shape_scale 提升 <1 分,再试 n_clusters=12(减小簇间空洞)作为第二轮;若仍无改善则回退提交父节点配置。关键参数:匹配阈值 r≥0.3、最少细胞 30(沿用 node 4);k=25、n_clusters=10(沿用 node 7)。搜索范围极小(仅对齐方式 + 可选 n_clusters∈{10,12}),30 分钟内可完成。
风险1) matched-align 在 node 4 上仅 +1.3,接近 T2 约 1 分噪声;若叠加后提升 <1 分则不显著。Engineer 应在第一次查分后比较 shape_scale 是否 ≥53.5(对照 52.78)。2) 簇抽样依赖对齐后坐标建 k-NN 图;若 matched-align 改变了坐标分布(如旋转/缩放不同),簇的形状可能变化,local_spatial 可能微降。Engineer 应同时监控 local_spatial 是否仍 ≥63。3) 31 个共有类型(final)比代理 5 个多得多,匹配对数增加,Procrustes 更稳定,但代理上只有 5 对,信号弱。若代理上无明显提升,Engineer 仍应提交(机制合理、风险低)。

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

对比:父节点版本 6739569794。改动的文件:solution/METHOD.md +26 −30、solution/matched_align.py +156 −0、solution/run.py +38 −12

diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 5e88a3e..cb8ca2e 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,41 +1,37 @@-mix 组成不变,把每类型内的 i.i.d. 随机抽样换成空间连续簇抽样(k-NN 贪心区域生长,k=25、每型约 10 簇),输出继承源阶段局部邻域结构;A 半 62.4,local_spatial 53.5→64.6。+在父节点簇抽样(k=25,n_clusters=10)上,把 Procrustes 对齐的地标从 exact-name 共有类型换成跨类型表达相关贪心匹配对(Pearson r≥0.3、每侧≥30 细胞),组成/表达/配额逻辑不变。  ## 方法 -- 路径与父节点 mix(node 2)一致:procrustes 对齐(z 保持切片轴)、log 线性 RMS 缩放(scale_damp=1)、`_limits` 定细胞数、`round(t*n)` 从后侧阶段取、每类型配额用与 `stratified_choice` 完全相同的确定性分配算术(组成不变)。父节点的 OT 位移已删除(node 5 证明单调有害)。-- 机制(`CLUSTER_SAMPLING=True`,`solution/cluster_sample.py`):在每个类型内,用 `scipy.spatial.cKDTree` 在对齐坐标上建 k-NN 图,贪心区域生长成簇——随机选种子,用最小堆反复弹出距当前簇最近的未选邻居加入,直到簇大小达到 ceil(配额/n_clusters);重复开新簇直到填满配额。只改变"选哪些细胞",表达和坐标仍来自真实细胞。-- 参数:`k=25, n_clusters=10`(代理上搜索所得)。单输入阶段(b 为 None)走原有单阶段回退,不受影响。-- 确定性:只用视图数据 + `np.random.default_rng(seed)`;seed 0 重跑输出逐位相同(本地验证)。无绝对时间、无视图路径依赖(只用 t 与时间差)。+- 与父节点 7 相同的 mix 路径:括号两阶段对齐到同一帧、log 线性 RMS 缩放(scale_damp=1)、`_limits` 定细胞数、`round(t*n)` 从后侧取、每类型配额算术不变;类型内用 k-NN 贪心区域生长抽空间连续簇(k=25, n_clusters=10)。+- 本节点机制(`MATCHED_ALIGN=True`,`solution/matched_align.py`):对齐地标不再要求类型同名。对两侧各自 ≥30 细胞的每个类型算 pseudobulk(类型均值)表达,两两 Pearson 相关,从高到低贪心配对(每个类型只用一次,r<0.3 停止),用匹配对的质心做 xy Kabsch(权重 min(两侧细胞数),允许反射),z 保持切片轴、符号由匹配质心 z 相关定。<3 对时回退 exact-name 共有质心,再回退 PCA。+- 动机:代理括号两侧注释体系不同(E8.25_late 33 型 vs E9.5 心脏 22 型,exact-name 只有 5 对且其中同名类型未必同源——贪心把 A 的 Peri 配到 B 的 ST、A 的 JCF 配到 B 的 Peri,pPHM↔aPHM 交叉,说明两侧命名确有错位);表达相关匹配给出 22 对更可信的地标。final 括号两侧同一官方词汇(31 个共有类型),同名的同源类型相关通常最高,贪心会自然保留它们,机制退化为"同名为主 + 少数跨名补充",方向一致、低风险(PLAN risk 3)。+- 参数开关:`T2_MATCHED_ALIGN=0` / `T2_CLUSTER_SAMPLING=0` / `T2_MATCH_MODE` / `T2_CORR_THRESH` / `T2_N_CLUSTERS` 环境变量仅用于对照实验,提交默认 greedy、r=0.3、nc=10、两个机制都打开。+- 确定性:只用视图数据 + `np.random.default_rng(seed)`;seed 0 重跑逐位相同(本地验证)。只用时间差 t,不用绝对时间、不看视图路径/名称。 -## 机制生效证据(proxy,E8.25_late+E9.5→E8.75,t=0.4,seed 0)+## 机制生效证据(proxy,E8.25_late+E9.5→E8.75,t=0.4,seed 0,A 半) -- 输出细胞的平均 5-NN 距离:随机 mix 21.1 → 簇抽样 14.2(−33%),局部邻域确实更连续。-- 关闭对照(`CLUSTER_SAMPLING=False`,同一程序走库 `interpolate`,即父节点 mix 路径):A 半 59.07,local_spatial 53.46;开启:62.37,local_spatial 64.61。四组分变化符合 PLAN 预期——local_spatial +11.2、neighborhood_mmd 0.083→0.053,expression_change +0.8、cell_state +0.9(同一批真实细胞、组成相同,仅集合不同带来的小幅波动),shape_scale +0.5(occupancy_dice 0.834→0.828 略降,被 d2_shape 改善抵消)。+- 匹配对数:exact-name 5 → greedy 22 对(全部 r≥0.389,最高 0.863),Kabsch 旋转改变、z 符号翻转为 -1(父 +1),输出与父节点不逐位相同(已验证)。+- 关闭对照(`T2_MATCHED_ALIGN=0`,其余不变):输出与父节点 7 逐位相同(本地 np.array_equal 验证)。+- 四组分(对照 = 父 node 7 seed 0):shape_scale 53.5→55.35(+1.85,d2_shape 0.0339→0.0291),local_spatial 64.61→65.02(持平微升,邻域机制未破坏),expression_change 64.6→64.6、cell_state 66.8→66.8(同一批真实细胞、组成不变,不动,符合预期)。+- 平均 5-NN 结构保持(neighborhood_mmd 0.0518 vs 父 0.0530)。 -## 查分(vec-score A 半,proxy;对照=父 mix 路径 59.07)+## 查分(vec-score A 半,proxy;父 node 7 三种子 A 半均值 62.11) -| 配置 (k, n_clusters, cluster_frac) | skill | expr | state | shape | local |-|---|---:|---:|---:|---:|---:|-| (25, 10, 1.0) seed0(提交) | **62.37** | 64.6 | 66.8 | 53.5 | 64.6 |-| (25, 10) seed1 / seed2 | 61.66 / 62.31 | | | | |-| (15, 10) | 62.25 | 64.8 | 66.6 | 53.5 | 64.1 |-| (8, 15) | 62.06 | 64.1 | 66.5 | 53.8 | 63.9 |-| (10, 10) | 62.00 | 64.3 | 66.9 | 52.9 | 63.9 |-| (15, 20) | 61.90 | 63.4 | 66.5 | 54.4 | 63.3 |-| (25, 7) | 61.72 | 65.0 | 66.8 | 49.8 | 65.3 |-| (15, 5) | 61.80 | 64.1 | 66.2 | 52.3 | 64.7 |-| (25, 15) | 61.41 | 64.1 | 66.2 | 52.1 | 63.4 |-| (25, 10, frac 0.85) | 62.05 | 64.4 | 66.4 | 54.0 | 63.4 |-| (25, 10, frac 0.7) | 60.87 | 63.9 | 65.8 | 52.6 | 61.2 |-| (15, 5) / (10, 5) / (40, 10) | 61.80 / 62.00* / 62.37* | | | | |+| 配置 | seed0 | seed1 | seed2 | 3种子均值 |+|---|---:|---:|---:|---:|+| greedy r0.3, nc=10(提交) | **62.94** | 61.74 | 62.27 | **62.32** |+| greedy r0.5, nc=10 | 62.94 | | | |+| mutual-best r0.3, nc=10 | 62.79 | 61.41 | 62.15 | 62.12 |+| greedy r0.3, nc=12 | 63.15 | 61.60 | 61.05 | 61.93 |+| greedy r0.3, nc=14 | 61.72 | | | |+| mutual r0.3, nc=12 | 63.11 | | | |+| matched-align 单独(关簇抽样) | 59.07 | | | =父 mix 对照 59.07 | -*k=40 与 k=25 输出逐位相同(生长顺序对 k 饱和);(10,5) 未单独查分,(10,10)=62.00。--- 趋势:n_clusters=10 附近是峰(5 与 15 都更低);k≥25 饱和;部分随机回填(cluster_frac<1)单调稀释 local_spatial 收益,不取。-- 提交配置 3 种子 A 半均值 62.11,比对照 59.07 高 +3.0,超出 T2 约 1 分噪声。+- seed 0 满足 PLAN 门槛(shape 55.35≥53.5、local 65.02≥64、shape 提升 +1.85>1);nc=12 第二轮在 seed 0 更高但 3 种子均值更低,不取。+- 如实说明:3 种子均值 62.32 只比父 62.11 高 +0.2,在 T2 约 1 分噪声内;波动主要来自 occupancy_dice 对簇落点的种子敏感性(0.80–0.83),matched-align 本身在代理上无系统性害处(单独关簇抽样时与父 mix 同分 59.07)。按 PLAN risk 3 提交:机制在 final(31 个共有类型、注释一致)上更可能生效,代理只 5 对同名地标、信号弱。  ## 未验证 / 风险 -- 只在代理括号(E8.25_late↔E9.5,5 个共有类型)上验证;final 括号(E8.25+E8.75,31 个共有类型、t=0.5)上细胞更多、类型更细,簇大小=配额/10 的绝对值会变,但机制不依赖跨阶段对齐(只用单侧坐标建图),预期方向一致。-- occupancy_dice 略降(簇间留空洞);若 final 上 shape_scale 受损明显,可增大 n_clusters 或用 frac 0.85 折中(代理上 frac 0.85 = 62.05,shape 54.0 最高)。-- 知识来源:无外部生物知识;仅用视图内坐标与标签。+- final 括号无法本地验证;若 final 上同名地标本就可靠,greedy 跨名配对可能引入个别错配地标(缓解:同名同源类型相关通常最高,贪心优先选中;权重 min(count) 抑制小类型影响)。+- z 符号在代理上翻转为 -1(父为 +1),shape 反而升;final 上两侧切片方向若一致,匹配质心 z 相关会给出正确符号(机制与父相同的判据,只是地标更准)。+- 知识来源:无外部生物知识;只用视图内表达、坐标、标签与时间差。diff --git a/solution/matched_align.py b/solution/matched_align.pynew file mode 100644index 0000000..ba8c1e2--- /dev/null+++ b/solution/matched_align.py@@ -0,0 +1,156 @@+"""Expression-correlation matched Procrustes alignment (T2HI-04, node 4 logic).++Instead of trusting every exact-name shared cell type as an alignment+landmark, keep only the shared types whose pseudobulk (type-mean) expression+profiles still correlate across the two stages (Pearson r >= ``corr_thresh``,+at least ``min_count`` cells on each side). Types whose transcriptional+identity drifted between stages are unreliable landmarks and are dropped from+the Kabsch fit; z stays the slice axis (sign from matched centroid dot),+identical in spirit to ``frame._align_z_held`` with method="procrustes".++Fallback: fewer than 3 matched pairs -> plain exact-name shared centroids+(the library behaviour).+"""++from __future__ import annotations++import numpy as np++from src.task2_spatial.frame import (+    _pca_rotation,+    _skew_flip,+    center,+    kabsch,+    shared_centroids,+    slice_counts,+)+++def _type_stats(X, labels, coords, min_count: int) -> dict:+    labs = np.asarray(labels).astype(str)+    out = {}+    for lab in np.unique(labs):+        idx = np.flatnonzero(labs == lab)+        if idx.size >= min_count:+            out[str(lab)] = (+                np.asarray(X[idx].mean(axis=0), dtype=np.float64).ravel(),+                int(idx.size),+                np.asarray(coords[idx], dtype=np.float64).mean(axis=0),+            )+    return out+++def matched_centroids(a, la, b, lb, Xa, Xb, min_count: int = 30, corr_thresh: float = 0.3,+                      match_mode: str = "greedy"):+    """Cross-type landmark pairs: greedy best expression correlation (r >= thresh).++    Type names across stages come from different annotations, so exact-name+    matching keeps few landmarks. Here every A-type with >= min_count cells is+    correlated (Pearson over the panel) with every B-type; pairs are taken+    greedily from the highest correlation down, each type used at most once,+    stopping below ``corr_thresh``. Exact-name shared types whose profiles do+    not correlate are thereby excluded automatically.+    """+    sa = _type_stats(Xa, la, a, min_count)+    sb = _type_stats(Xb, lb, b, min_count)+    ka = sorted(sa)+    kb = sorted(sb)+    if not ka or not kb:+        ca, cb, w, keys = shared_centroids(a, la, b, lb, min_count)+        return ca, cb, w, keys, [], False, []+    A = np.stack([sa[k][0] for k in ka])+    B = np.stack([sb[k][0] for k in kb])+    An = A - A.mean(axis=1, keepdims=True)+    Bn = B - B.mean(axis=1, keepdims=True)+    na = np.linalg.norm(An, axis=1)+    nb = np.linalg.norm(Bn, axis=1)+    C = (An @ Bn.T) / np.maximum(np.outer(na, nb), 1e-12)+    pairs = []+    if match_mode == "mutual":+        for i in range(len(ka)):+            j = int(np.argmax(C[i]))+            if int(np.argmax(C[:, j])) == i and float(C[i, j]) >= corr_thresh:+                pairs.append((ka[i], kb[j], float(C[i, j])))+    else:+        used_a: set[int] = set()+        used_b: set[int] = set()+        order = np.dstack(np.unravel_index(np.argsort(-C, axis=None), C.shape))[0]+        for i, j in order:+            r = float(C[i, j])+            if r < corr_thresh:+                break+            i = int(i); j = int(j)+            if i in used_a or j in used_b:+                continue+            used_a.add(i)+            used_b.add(j)+            pairs.append((ka[i], kb[j], r))+    if len(pairs) >= 3:+        ca = np.stack([sa[p[0]][2] for p in pairs])+        cb = np.stack([sb[p[1]][2] for p in pairs])+        w = np.array([min(sa[p[0]][1], sb[p[1]][1]) for p in pairs], dtype=np.float64)+        keys = [f"{p[0]}~{p[1]}" for p in pairs]+        return ca, cb, w, keys, [p[2] for p in pairs], True, pairs+    ca, cb, w, keys = shared_centroids(a, la, b, lb, min_count)+    return ca, cb, w, keys, [], False, pairs+++def align_pair_matched(coords_a, coords_b, labels_a, labels_b, Xa, Xb,+                       min_count: int = 30, corr_thresh: float = 0.3,+                       match_mode: str = "greedy"):+    """Procrustes (z held) on expression-matched shared-type centroids.++    Mirrors ``frame.align_pair(method="procrustes")``; only the landmark set+    changes. Returns (aligned_a, aligned_b, info).+    """+    a = center(coords_a)+    b = center(coords_b)+    info = {+        "align": "procrustes_matched",+        "slice_counts_a": slice_counts(coords_a),+        "slice_counts_b": slice_counts(coords_b),+        "z_axis_a": int(np.argmin(slice_counts(coords_a))),+        "z_axis_b": int(np.argmin(slice_counts(coords_b))),+    }+    ca, cb, w, keys, corrs, matched, _pairs = matched_centroids(+        a, labels_a, b, labels_b, Xa, Xb, min_count, corr_thresh, match_mode)+    info["match_corrs"] = corrs+    info["match_keys"] = keys+    info["n_matched_types"] = int(len(keys)) if matched else 0+    ay, by = a[:, :2], b[:, :2]+    az, bz = a[:, 2].copy(), b[:, 2].copy()+    z_flipped = False+    n_types = int(len(w))+    det = 1.0+    if n_types >= 3:+        r, _, _ = kabsch(cb[:, :2], ca[:, :2], w, allow_reflection=True)+        by = by @ r+        det = float(np.linalg.det(r))+        za = ca[:, 2] - np.average(ca[:, 2], weights=w)+        zb = cb[:, 2] - np.average(cb[:, 2], weights=w)+        z_dot_cent = float(np.dot(za, zb))+        if z_dot_cent < 0:+            bz = -bz+            z_flipped = True+        info["z_sign_source"] = "centroids"+        info["z_centroid_dot"] = z_dot_cent+    else:+        ra = _pca_rotation(ay)+        rb = _pca_rotation(by)+        ay = ay @ ra+        by = by @ rb+        ay, fa = _skew_flip(ay)+        by, fb = _skew_flip(by)+        if float((az ** 3).mean()) < 0:+            az = -az+        if float((bz ** 3).mean()) < 0:+            bz = -bz+            z_flipped = True+        info["z_sign_source"] = "skew"+        info["align_fallback"] = "pca"+        det = float(fa.prod() * fb.prod())+    out_a = np.column_stack([ay, az])+    out_b = np.column_stack([by, bz])+    info.update(n_shared_types=n_types, det=det, z_flipped=bool(z_flipped),+                z_dot=1.0 if not z_flipped else -1.0, matched=matched)+    return center(out_a), center(out_b), infodiff --git a/solution/run.py b/solution/run.pyindex dcdeaf7..dbcdd50 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,20 +1,26 @@ #!/usr/bin/env python3-"""Mix interpolation with spatially contiguous cluster sampling (T2HI-04).+"""Mix interpolation with cluster sampling + expression-matched alignment.  Brackets the target with the nearest inputs before and after it, puts both-clouds in one frame (procrustes, z kept as slice axis), rescales both to the-log-linear RMS exp(log r_a + scale_damp*t*(log r_b - log r_a)), and draws-cells stratified by type: round(t*n) from the later stage, the rest from the-earlier one (same composition as the parent ``mix`` method).+clouds in one frame (Procrustes on cross-type expression-correlation matched+centroids, greedy r >= 0.3, z kept as slice axis; MATCHED_ALIGN), rescales+both to the log-linear RMS exp(log r_a + scale_damp*t*(log r_b - log r_a)),+and draws cells stratified by type: round(t*n) from the later stage, the rest+from the earlier one (same composition as the parent ``mix`` method). -Mechanism (CLUSTER_SAMPLING=True, solution/cluster_sample.py): within each+Mechanism 1 (CLUSTER_SAMPLING=True, solution/cluster_sample.py): within each type the quota of cells is drawn as ~n_clusters spatially contiguous clusters (greedy k-NN region growing on the aligned coordinates) instead of i.i.d. random picks, so the output preserves the local neighbourhood structure of the source stages. Expression and coordinates still come from real cells. -Control (CLUSTER_SAMPLING=False): identical code path to parent node 2-(``mix``), bit-identical output.+Mechanism 2 (MATCHED_ALIGN=True, solution/matched_align.py): alignment+landmarks are pairs of type centroids matched greedily by Pearson correlation+of pseudobulk (type-mean) expression instead of exact type names, so+annotation vocabularies that differ across stages still yield landmarks.++Controls: T2_CLUSTER_SAMPLING=0 -> parent node 7 path; T2_MATCHED_ALIGN=0 ->+bit-identical to parent node 7 (exact-name procrustes). """  from __future__ import annotations@@ -32,10 +38,25 @@ from src.task2_spatial.transport import as_dense from src.task2_spatial.view_io import board_params, interp_bracket, load_manifest, panel_genes, read_stage, write_t2  from cluster_sample import cluster_mix_indices, grow_clusters+from matched_align import align_pair_matched  CLUSTER_SAMPLING = True+MATCHED_ALIGN = True PARAMS = {"align": "procrustes", "scale_damp": 1.0} CLUSTER_PARAMS = {"k": 25, "n_clusters": 10}+MATCH_PARAMS = {"min_count": 30, "corr_thresh": 0.3, "match_mode": "greedy"}++import os+if os.environ.get("T2_CLUSTER_SAMPLING") == "0":+    CLUSTER_SAMPLING = False+if os.environ.get("T2_MATCHED_ALIGN") == "0":+    MATCHED_ALIGN = False+if os.environ.get("T2_MATCH_MODE"):+    MATCH_PARAMS["match_mode"] = os.environ["T2_MATCH_MODE"]+if os.environ.get("T2_CORR_THRESH"):+    MATCH_PARAMS["corr_thresh"] = float(os.environ["T2_CORR_THRESH"])+if os.environ.get("T2_N_CLUSTERS"):+    CLUSTER_PARAMS["n_clusters"] = int(os.environ["T2_N_CLUSTERS"])   def main() -> None:@@ -64,8 +85,13 @@ def main() -> None:     else:         t = float(t)         rng = np.random.default_rng(int(params["seed"]))-        aligned_a, aligned_b, info = align_pair(-            stage_a.coords, stage_b.coords, stage_a.labels, stage_b.labels, str(params["align"]))+        if MATCHED_ALIGN:+            aligned_a, aligned_b, info = align_pair_matched(+                stage_a.coords, stage_b.coords, stage_a.labels, stage_b.labels,+                stage_a.X, stage_b.X, **MATCH_PARAMS)+        else:+            aligned_a, aligned_b, info = align_pair(+                stage_a.coords, stage_b.coords, stage_a.labels, stage_b.labels, str(params["align"]))         rms_a = rms_radius(stage_a.coords)         rms_b = rms_radius(stage_b.coords)         target_rms = log_interp(rms_a, rms_b, t, float(params["scale_damp"]))@@ -86,9 +112,9 @@ def main() -> None:         coords = scale_to_rms(coords, target_rms).astype(np.float32)         info.update(t=t, n=int(expr.shape[0]), rms_a=rms_a, rms_b=rms_b,                     out_rms=rms_radius(coords), n_from_a=int(ia.size), n_from_b=int(ib.size))-        ev = {"cluster": True, **{f"cl_{kk}": vv for kk, vv in CLUSTER_PARAMS.items()}}+        ev = {"cluster": True, "matched_align": MATCHED_ALIGN, **{f"cl_{kk}": vv for kk, vv in CLUSTER_PARAMS.items()}} -    keep = {k: info.get(k) for k in ("t", "n", "rms_a", "rms_b", "out_rms", "n_shared_types", "z_dot", "z_flipped", "align")}+    keep = {k: info.get(k) for k in ("t", "n", "rms_a", "rms_b", "out_rms", "n_shared_types", "n_matched_types", "match_corrs", "matched", "z_dot", "z_flipped", "align")}     print(json.dumps({"bracket": [a["stage"], b["stage"]], **keep, **ev}, default=float), file=sys.stderr)     write_t2(args.out, expr, coords, genes, seed=args.seed) 

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

用到的知识库条目

编号标题出处
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md
k026Canonicalise predicted 3D coordinates before submissionnotes/pitfalls/04_scorer_invariance.md
k025Spateo: rigid + non-rigid alignment and morphometric vector fields10.1016/j.cell.2024.10.011

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

改了什么在父节点 7(k-NN 簇抽样 mix,k=25/n_clusters=10)之上,新增 solution/matched_align.py 并把 run.py 的对齐地标从 exact-name 共有类型质心换成跨类型 pseudobulk 表达 Pearson 相关贪心匹配对(r>=0.3、每侧>=30 细胞、权重 min(细胞数)、z 保持切片轴且符号由匹配质心 z 相关决定,<3 对回退 exact-name 再回退 PCA);组成、配额、log 线性 RMS 缩放、表达与簇抽样逻辑均未改,另加 T2_MATCHED_ALIGN / T2_CLUSTER_SAMPLING / T2_MATCH_MODE / T2_CORR_THRESH / T2_N_CLUSTERS 环境变量对照开关。
各组分数的变化cell_state:不变:67.58 -> 67.58(+0.00),符合预期(组成与配额算术未改)
expression_change:不变:64.90 -> 64.90(+0.00),符合预期(同一批真实细胞、表达逻辑未改)
local_spatial:噪声内:65.20 -> 65.67(+0.47),说明 matched-align 改变坐标框架没有破坏簇抽样的邻域结构(父节点机制保留)
shape_scale:变好:52.78 -> 55.93(+3.15),远超 T2 约 1 分噪声,且是本节点全部增益来源(代理上 shape 也是唯一明显上升的组分:53.5 -> 55.35)
family_idT2HI-04
假设是否成立是
经验
  1. 两个正交机制(对齐质量 x 抽样策略)可以叠加:本节点只改对齐地标,shape_scale +3.15 而 local_spatial/expression_change/cell_state 不动,说明簇抽样收益未被破坏,收益归属清晰。
  2. 代理括号上的信号强度会低估 final/真实括号的收益:Engineer 报告代理 3 种子均值仅 62.32 vs 父 62.11(+0.2,噪声内),但实跑榜分 +0.91、shape_scale +3.15;当机制依赖地标数量(代理仅 5 个同名共有类型 vs 本节点贪心 22 对)时,代理低分不足以否决提交。
  3. 对齐地标的选取判据(表达相关)比同名判据更稳:exact-name 在注释体系不同的两侧会保留错位地标(A 的 Peri 对 B 的 ST 之类),贪心相关匹配 r>=0.3 + min_count=30 直接把它们剔除,shape 提升即来源于此。
  4. 参数选择要用多种子均值而非单种子:nc=12 在 seed0 更高(63.15)但 3 种子均值更低(61.93 vs 62.32);shape_scale 的种子间波动主要来自 occupancy_dice 对簇落点的敏感(0.80-0.83),单种子查分容易误判。
  5. 关闭对照是判定机制生效的关键:T2_MATCHED_ALIGN=0 时输出与父节点逐位相同(np.array_equal 验证),说明改动确实只走新对齐路径,没有夹带其他变化;matched-align 单独关簇抽样时 59.07 = 父 mix 对照,代理上无系统性害处。
mechanism_active是
下一步建议
  1. 针对 shape_scale:在 cluster_sample 的种子选取上做覆盖约束(最远点/凸包边缘优先播种,或按 z 层分配各型 n_clusters 配额),减少簇间空洞以提升 occupancy_dice(代理 0.828、种子间 0.80-0.83),参数从 n_clusters=10、k=25 出发只改播种规则,并用 3 种子均值判优。
  2. 针对 shape_scale:在 matched-align 内做地标加权/筛选的细调——把 Kabsch 权重从 min(count) 改为 min(count)*r(或 min(count)*r^2),并试 corr_thresh in {0.2, 0.3, 0.4} 与 greedy+同名优先(同名且 r>=阈值时强制配对,再用跨名补充),预期在共有类型多的括号上进一步降 d2_shape。
  3. 针对 local_spatial(当前 65.67,仅 +0.47 在噪声内):不要再调 n_clusters/k(父节点已证明 nc=10 附近是峰、k>=25 饱和),改试簇内回填比例 frac in {0.9, 1.0} 与簇大小上限(避免单簇过大挤占邻域多样性),并以 neighborhood_mmd(父 0.0530 -> 本 0.0518)为主指标、3 种子均值判优。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数43
工具调用共 47 次:bash 30、edit 12、read 3、write 2
用时19 分
token 数输入 87,274 · 输出 14,840 · 思考 15,822
任务(第一行)按计划修改程序:簇抽样 + 类型匹配对齐(matched-align)组合
最后的回答(摘录)
Done. Summary:

**做了什么**:在父节点 7(簇抽样 mix,A 半 62.37)上按 PLAN 实现对齐改进——`solution/matched_align.py` 把 Procrustes 地标从 exact-name 共有类型(代理上仅 5 对)换成跨类型 pseudobulk 表达相关贪心匹配(r≥0.3、每侧≥30 细胞,代理上 22 对),z 保持切片轴;簇抽样(k=25, nc=10)、组成、表达、配额全部不变。对照验证:`T2_MATCHED_ALIGN=0` 输出与父节点逐位相同;seed 0 重跑确定;vec-check 通过;运行 2s。

**查分**(A 半,proxy,用了 8/20 次):greedy r0.3 seed0 **62.94**(shape 53.5→55.35,local 65.0,满足 PLAN 门槛);3 种子均值 62.32 vs 父 62.11(+0.2,噪声内)。nc=12 seed0 更高(63.15)但 3 种子均值 61.93 更低,不取;mutual-best 62.12、r0.5 与 r0.3 持平。matched-align 单独(关簇抽样)= 59.07,与父 mix 对照同分,代理上无系统性害处。按 PLAN risk 3 提交(final 有 31 个共有类型,代理信号弱)。

**下一步最值得试**:shape_scale 的种子间波动主要来自 occupancy_dice 对簇落点的敏感(0.80–0.83)——可试簇生长时偏向覆盖类型凸包边缘/按 z 层配额分布,减少簇间空洞;或在 final 括号(同词汇、地标更多)验证 matched-align 是否真正兑现 +1~2 shape。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/engineer.jsonl 254 KB
/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时不到 1 分
token 数输入 10,533 · 输出 1,124 · 思考 1,372
任务(第一行)为节点 n8(改进,父节点 n7)写改进计划 PLAN.json
最后的回答(摘录)
 shape_scale +1.3(node 4 对照 53.1→54.4),簇抽样提 local_spatial +11(node 7),二者分别作用于对齐质量和抽样策略,预期可叠加。node 7 的 occupancy_dice 因簇间空洞略降(0.834→0.828),更好的对齐可减小坐标框架误差、间接缓解空洞对 shape 的损害。",
  "approach": "步骤:1) 在 run.py 的对齐步骤中,将 align_pair 的标签匹配从 exact-name 替换为 node 4 的 matched-align 逻辑:对两侧共有类型计算类型均值表达的 Pearson 相关,阈值 r≥0.3 且每侧 ≥30 细胞才配对,用匹配对的质心做 Procrustes(z 轴保持切片轴不变)。不匹配的类型仍按 exact-name 处理(兜底)。2) 对齐后的坐标直接传入现有 cluster_mix_indices(k=25, n_clusters=10),簇抽样逻辑不改。3) 表达、组成配额、_limits、log 线性 RMS 缩放均不变。4) 单输入阶段(b=None)走原有单阶段回退,不受影响。5) 用 vec-score 在 proxy 上查分:先跑 seed 0 确认 shape_scale 是否 ≥53.5 且 local_spatial ≥64;若 shape_scale 提升 <1 分,再试 n_clusters=12(减小簇间空洞)作为第二轮;若仍无改善则回退提交父节点配置。关键参数:匹配阈值 r≥0.3、最少细胞 30(沿用 node 4);k=25、n_clusters=10(沿用 node 7)。搜索范围极小(仅对齐方式 + 可选 n_clusters∈{10,12}),30 分钟内可完成。",
  "expected_groups": ["shape_scale", "local_spatial"],
  "risks": "1) matched-align 在 node 4 上仅 +1.3,接近 T2 约 1 分噪声;若叠加后提升 <1 分则不显著。Engineer 应在第一次查分后比较 shape_scale 是否 ≥53.5(对照 52.78)。2) 簇抽样依赖对齐后坐标建 k-NN 图;若 matched-align 改变了坐标分布(如旋转/缩放不同),簇的形状可能变化,local_spatial 可能微降。Engineer 应同时监控 local_spatial 是否仍 ≥63。3) 31 个共有类型(final)比代理 5 个多得多,匹配对数增加,Procrustes 更稳定,但代理上只有 5 对,信号弱。若代理上无明显提升,Engineer 仍应提交(机制合理、风险低)。",
  "family_id": "T2HI-04",
  "mechanism": "在簇抽样(保邻域)之前,用类型均值表达相关性匹配质心做 Procrustes 对齐(替代 exact-name 匹配),提高对齐精度,使簇抽样的 k-NN 图建立在更准确的坐标框架上。",
  "vs_constant_shift": "matched-align 改变的是跨阶段坐标对齐的旋转/反射矩阵(由类型相关匹配驱动),不是对每个类型加一个常矢量;簇抽样改变的是类型内选哪些细胞(空间连续簇而非 i.i.d.),二者都不是全局平移或组成重加权。",
  "mechanism_evidence": "1) 对齐质量:输出 info 中的 z_dot / n_shared_types 变化,以及匹配对数(应 ≥3 对)。2) shape_scale 组分:预期从 52.78 提升至 ≥53.5(+1 以上)。3) local_spatial 组分:应保持 ≥64(簇抽样机制未变)。4) 输出细胞的平均 5-NN 距离应与父节点相当(~14),若明显变大说明对齐改变破坏了簇结构。5) 四组分变化模式:shape_scale 升、local_spatial 持平或微升、cell_state/expression_change 不动(组成和表达逻辑未改)。",
  "mechanism_off_control": "设 MATCHED_ALIGN=False(回退到 exact-name 对齐),其余代码不变,输出应逐位等同于父节点 7(A 半 62.37)。若关闭后分数不变,说明 matched-align 未生效。另设 CLUSTER_SAMPLING=False 回退到 node 4 的 matched-align mix 路径(预期 ~59.9),可分离两个机制各自的贡献。",
  "sources": []
}
```
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/researcher.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/researcher.stderr

审查员

角色审查员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数7
工具调用共 14 次:read 9、bash 3、glob 1、write 1
用时5 分
token 数输入 23,962 · 输出 1,783 · 思考 4,228
任务(第一行)审查节点 n8 的程序是否越界读取、写死目标、钻评分器漏洞
最后的回答(摘录)
REVIEW.json written
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/reviewer.jsonl 109 KB
/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/8/reviewer.stderr