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

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

节点 n14

T2HI-02:组成配额混抽基座 + OT耦合重心投影的kNN平滑坐标位移场(w=0.3,只动早期细胞坐标,表达原样)。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261003-094241-search-t2-heart-interp-g24-D-s2
父节点(种子,没有父节点)
子节点n18、n20
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。草稿
状态已打分
分数搜索目标分 64.85 · proxy 64.85 · 3 次复测均分 64.39
审查未审查
用时?从运行开始到结束(或到现在)的挂钟时间。24 分
程序版本a4764f8d051f362d0887d41b82916ef8d587575e (programs.git)

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

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

T2HI-02:组成配额混抽基座 + OT耦合重心投影的kNN平滑坐标位移场(w=0.3,只动早期细胞坐标,表达原样)。

方法

  1. 基座(= 关闭机制的对照,T2_OT_DISP=0):interp_bracket 取目标两侧输入(本视图 E8.25 + E9.5,t=0.4);procrustes 对齐(保 z,仅 xy 旋转/反射 + z 符号,共有类型仅 5 个);细胞类型组成在 log 份额空间(多类 logit = log p,几何插值)插值定配额,含伪计数 0.5/n;每类型的早期/晚期来源数用带容量约束的比例拟合对齐全局 (1−t, t) 拆分;按配额无放回抽真实细胞,表达与坐标原样携带;两朵云各自缩放到 log 线性目标 RMS;输出细胞数 log 线性后夹到 [min_cells, max_cells]。单输入/无括号退路 = 锚定阶段分层照抄。
  2. 机制(T2HI-02,默认打开):两侧输出细胞各分层抽 ≤5000 锚点,在表达 PCA(30 维, eigh 确定解) 上跑不平衡 Sinkhorn(POT,eps=0.05,reg_m=0.9,代价归一化到均值 1);早期锚点的目标位置 = 耦合行的晚期坐标重心,位移 d = (重心 − 源位置) × t × w;用坐标 kNN(k=15) 把锚点场平滑广播到全部早期输出细胞;晚期细胞不动;位移后 RMS 仅记录不回缩(回缩会抵消位移带来的收缩收益)。w 默认 0.3(实测最优的打开档位)。

机制生效证据

  • 位移中位数 43.0(w=0.3)/ 143.5(w=1.0),为坐标 RMS 的 12% / 41%(>2% 阈值);早期细胞整体向晚期云收缩,混合云 RMS 334.7 → 322.1(w=0.3)→ 301.8(w=1.0)。
  • 四组分变化(A 半,seed 0):表达两组逐位不变(de_score 0.4891、de_direction 0.6479,只动坐标,符合预期);cell_state 71.59 不变;local_spatial 66.13 → 65.93~66.07(基本持平,kNN 平滑确实保住了邻域,对照方法卡里逐细胞 OT 插值邻域暴跌到 23–41);shape_scale 50.92 → 49.18(w=0.3)→ 45.50(w=1.0)。
  • shape_scale 内部:scale_log_ratio raw 0.433 → 0.394(w=0.3)→ 0.329(w=1.0),收缩方向正确(真值更致密);但 d2_shape raw 0.046 → 0.057 → 0.106、occupancy_dice 0.812 → 0.798 → 0.793,距离分布被位移扭曲,净效应为负。

对照与查分结果(A 半,seed 0,共 9 次查分)

配置榜分shape_scalelocal_spatial
基座(机制关)64.7650.9266.13
OT w=0.3(默认提交)64.2849.1865.93
OT w=0.5 / k=30 / eps=0.02+tau=563.88 / 63.86 / 63.9347.4–47.766.0
OT w=1.063.3745.5065.98

w 从 0 → 1 单调变差:scale 的收益抵不过 d2_shape/occupancy 的损失。如实说明:机制按 PLAN 实现且确实改变了细胞坐标(见上),但在本代理上净分数为负(−0.5 ~ −1.4,w=0.3 档在 ~1 分噪声内)。按 PLAN「提交时保持打开」的要求,默认 w=0.3(实测损失最小的档位);关闭开关 T2_OT_DISP=0 即纯基座。根因推测:代理括号 E8.25↔E9.5 只有 5 个共有类型,procrustes 对齐差,OT 位移被对齐误差主导;真实括号(31 个共有类型)可能表现不同,但无法在代理上验证。

验证过 / 没验证

  • 验证:seed 0 两次运行输出逐字节相同(sha256 一致);vec-check 通过;17616 细胞(= max_cells),稀疏 CSR,坐标有限。
  • 未验证:多 seed 的 B 半稳定性;真实括号(E8.25+E8.75→E8.5)上位移的方向是否仍有益(代理的晚期云与真实不同);eps/k 更细的网格(时间预算内只跑了 PLAN 网格的一角)。

知识来源

未使用任何保留阶段/禁窗的测量信息:所有统计量(组成、RMS、对齐、OT 耦合)都现场从视图输入计算;无硬编码细胞数、阶段名或尺寸。生物学知识仅用到「细胞类型标签可用于跨阶段配对/组成插值」这一通用假设(标签来自视图本身)。

调研员的计划

名称OT耦合kNN平滑位移场驱动坐标(保邻域hybrid)
动机当前最佳节点10(rank3 65.16)的shape_scale仅51.08,是四组中最弱;local_spatial 65.80仍有提升空间。方法卡显示逐细胞OT坐标插值把邻域打到23-41、总分最高45,但kNN平滑位移场可保邻域同时改善形状。节点5/8证明只动坐标的位移可改善local_spatial和shape_scale而不影响表达两组。本方案在节点10的组成配额基座上叠加OT驱动的平滑坐标位移,专攻shape_scale和local_spatial。
做法步骤:
1. 基座:procrustes对齐(保z轴,仅允许xy旋转+平移+均匀缩放)+ log线性RMS插值 + logit空间组成配额抽真实细胞(同节点10逻辑,表达/坐标原样)。
2. OT耦合:对两侧阶段细胞在表达PCA前30维上跑unbalanced Sinkhorn(正则eps=0.05,不平衡权重tau=0.9),得耦合矩阵P(n_a × n_b)。用POT库sinkhorn_unbalanced;若超时退化到scipy linear_sum_assignment取前5000对。
3. 重心投影:对输出中来自早期阶段的每个细胞i,目标位置 = Σ_j P[i,j]*pos_b[j] / Σ_j P[i,j];位移d_i = (target_pos_i - source_pos_i) × t。
4. kNN平滑:对每个待位移细胞,取其输出云内k=15最近邻的位移均值作为最终位移(一次迭代即可)。
5. 应用:early-stage细胞坐标 += smoothed_d_i;late-stage细胞坐标不动。
6. 位移后检查:若RMS偏离目标>5%则重缩放回目标RMS。
关键参数初值:sinkhorn eps=0.05(范围0.01-0.1),tau=0.9(范围0.7-1.0),k_smooth=15(范围10-25),t为插值参数。vec-score快速筛选:先跑eps∈{0.02,0.05,0.1}×k∈{10,15,20}共9组,选邻域+形状组最优再细调。
单输入退路:仅一个阶段时跳过OT,输出=copy_last。
风险1) Sinkhorn在30分钟内可能跑不完(细胞数>2万时);应尽早检查耗时,超时用linear_sum_assignment或降采样到5000。2) 位移太小(耦合接近均匀)则无改善;Engineer应打印位移范数分布,若中位数<坐标RMS的1%则机制未生效。3) kNN平滑过度可能抹平真实位移;对照:不光滑直接应用看邻域是否暴跌。4) 形状改善可能<1分噪声;需A半3-seed确认。

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

对比:这个提交的上一版(种子程序:相对空仓库)。改动的文件:solution/EXECUTION.json +1 −0、solution/METHOD.md +32 −0、solution/run.py +270 −0

diff --git a/solution/EXECUTION.json b/solution/EXECUTION.jsonnew file mode 100644index 0000000..6d8012e--- /dev/null+++ b/solution/EXECUTION.json@@ -0,0 +1 @@+{"gpu": false}\ No newline at end of filediff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..fe309c4--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,32 @@+T2HI-02:组成配额混抽基座 + OT耦合重心投影的kNN平滑坐标位移场(w=0.3,只动早期细胞坐标,表达原样)。++## 方法++1. **基座(= 关闭机制的对照,T2_OT_DISP=0)**:`interp_bracket` 取目标两侧输入(本视图 E8.25 + E9.5,t=0.4);`procrustes` 对齐(保 z,仅 xy 旋转/反射 + z 符号,共有类型仅 5 个);细胞类型组成在 log 份额空间(多类 logit = log p,几何插值)插值定配额,含伪计数 0.5/n;每类型的早期/晚期来源数用带容量约束的比例拟合对齐全局 (1−t, t) 拆分;按配额无放回抽真实细胞,表达与坐标原样携带;两朵云各自缩放到 log 线性目标 RMS;输出细胞数 log 线性后夹到 [min_cells, max_cells]。单输入/无括号退路 = 锚定阶段分层照抄。+2. **机制(T2HI-02,默认打开)**:两侧输出细胞各分层抽 ≤5000 锚点,在表达 PCA(30 维, eigh 确定解) 上跑不平衡 Sinkhorn(POT,eps=0.05,reg_m=0.9,代价归一化到均值 1);早期锚点的目标位置 = 耦合行的晚期坐标重心,位移 d = (重心 − 源位置) × t × w;用坐标 kNN(k=15) 把锚点场平滑广播到全部早期输出细胞;晚期细胞不动;位移后 RMS 仅记录不回缩(回缩会抵消位移带来的收缩收益)。w 默认 0.3(实测最优的打开档位)。++## 机制生效证据++- 位移中位数 43.0(w=0.3)/ 143.5(w=1.0),为坐标 RMS 的 12% / 41%(>2% 阈值);早期细胞整体向晚期云收缩,混合云 RMS 334.7 → 322.1(w=0.3)→ 301.8(w=1.0)。+- 四组分变化(A 半,seed 0):表达两组逐位不变(de_score 0.4891、de_direction 0.6479,只动坐标,符合预期);cell_state 71.59 不变;local_spatial 66.13 → 65.93~66.07(基本持平,kNN 平滑确实保住了邻域,对照方法卡里逐细胞 OT 插值邻域暴跌到 23–41);shape_scale 50.92 → 49.18(w=0.3)→ 45.50(w=1.0)。+- shape_scale 内部:scale_log_ratio raw 0.433 → 0.394(w=0.3)→ 0.329(w=1.0),收缩方向正确(真值更致密);但 d2_shape raw 0.046 → 0.057 → 0.106、occupancy_dice 0.812 → 0.798 → 0.793,距离分布被位移扭曲,净效应为负。++## 对照与查分结果(A 半,seed 0,共 9 次查分)++| 配置 | 榜分 | shape_scale | local_spatial |+|---|---:|---:|---:|+| 基座(机制关) | **64.76** | 50.92 | 66.13 |+| OT w=0.3(默认提交) | 64.28 | 49.18 | 65.93 |+| OT w=0.5 / k=30 / eps=0.02+tau=5 | 63.88 / 63.86 / 63.93 | 47.4–47.7 | 66.0 |+| OT w=1.0 | 63.37 | 45.50 | 65.98 |++w 从 0 → 1 单调变差:scale 的收益抵不过 d2_shape/occupancy 的损失。**如实说明:机制按 PLAN 实现且确实改变了细胞坐标(见上),但在本代理上净分数为负(−0.5 ~ −1.4,w=0.3 档在 ~1 分噪声内)**。按 PLAN「提交时保持打开」的要求,默认 w=0.3(实测损失最小的档位);关闭开关 T2_OT_DISP=0 即纯基座。根因推测:代理括号 E8.25↔E9.5 只有 5 个共有类型,procrustes 对齐差,OT 位移被对齐误差主导;真实括号(31 个共有类型)可能表现不同,但无法在代理上验证。++## 验证过 / 没验证++- 验证:seed 0 两次运行输出逐字节相同(sha256 一致);vec-check 通过;17616 细胞(= max_cells),稀疏 CSR,坐标有限。+- 未验证:多 seed 的 B 半稳定性;真实括号(E8.25+E8.75→E8.5)上位移的方向是否仍有益(代理的晚期云与真实不同);eps/k 更细的网格(时间预算内只跑了 PLAN 网格的一角)。++## 知识来源++未使用任何保留阶段/禁窗的测量信息:所有统计量(组成、RMS、对齐、OT 耦合)都现场从视图输入计算;无硬编码细胞数、阶段名或尺寸。生物学知识仅用到「细胞类型标签可用于跨阶段配对/组成插值」这一通用假设(标签来自视图本身)。diff --git a/solution/run.py b/solution/run.pynew file mode 100644index 0000000..a026e84--- /dev/null+++ b/solution/run.py@@ -0,0 +1,270 @@+"""T2 heart interpolation: composition-quota real-cell mix + OT-coupled kNN-smoothed+coordinate displacement field (PLAN family T2HI-02).++Base (mechanism off, env T2_OT_DISP=0):+  - bracket stages (a<t<b) via view_io.interp_bracket; procrustes alignment (z held);+  - per-celltype proportions interpolated in log space (logit/geometric), quotas;+  - per-(stage,type) source counts by IPF against global (1-t, t) stage split;+  - real cells sampled without replacement, expression and coordinates carried as-is;+  - both clouds scaled to log-linear target RMS; output count log-linear, clipped.+Mechanism (on by default):+  - unbalanced Sinkhorn (POT) between expression-PCA(30) embeddings of stratified+    anchor subsamples of the two output sides;+  - barycentric projection of late-stage positions -> per-anchor displacement x t;+  - kNN(k=15)-smoothed broadcast of the anchor field onto every early output cell;+  - late cells held; RMS restored to target if drifted >5%.+Fallbacks: single input / unbracketed target -> stratified copy of anchor stage.+"""++from __future__ import annotations++import argparse+import os+import sys++import numpy as np+from scipy import sparse+from scipy.spatial import cKDTree++from src.task1_temporal.view_io import write_prediction+from src.task2_spatial import view_io+from src.task2_spatial.frame import align_pair, log_interp, rms_radius, scale_to_rms+from src.task2_spatial.transport import stratified_choice++EPS = float(os.environ.get("T2_OT_EPS", "0.05"))+TAU = float(os.environ.get("T2_OT_TAU", "0.9"))+W_DISP = float(os.environ.get("T2_OT_W", "0.3"))+K_SMOOTH = int(os.environ.get("T2_OT_K", "15"))+OT_ANCHORS = 5000+PCA_DIM = 30+SCALE_DAMP = 1.0+++def log(*a):+    print(*a, file=sys.stderr, flush=True)+++def quota_counts(fracs: np.ndarray, n: int, pool_total: np.ndarray) -> np.ndarray:+    """Integer quotas summing to n, capped by available pool per type."""+    raw = fracs * n+    counts = np.floor(raw).astype(np.int64)+    counts = np.minimum(counts, pool_total)+    deficit = int(n - counts.sum())+    if deficit > 0:+        spare = pool_total - counts+        order = np.argsort(-(raw - np.floor(raw)))+        guard = 0+        while deficit > 0 and spare.sum() > 0 and guard < 4 * len(counts) + 10:+            progressed = False+            for i in order:+                if deficit <= 0:+                    break+                if spare[i] > 0:+                    counts[i] += 1+                    spare[i] -= 1+                    deficit -= 1+                    progressed = True+            guard += 1+            if not progressed:+                break+    return counts+++def stage_split(quotas, pool_a, pool_b, n_b_target):+    """Per-type counts from stage b, targeting a global column sum n_b_target."""+    k = len(quotas)+    nb = np.minimum(np.round(quotas * (n_b_target / max(quotas.sum(), 1))).astype(np.int64),+                    np.minimum(quotas, pool_b))+    na = quotas - nb+    over = np.maximum(na - pool_a, 0)+    if over.sum() > 0:+        room = np.minimum(over, quotas - nb)+        nb += room+        na = quotas - nb+    diff = int(n_b_target - nb.sum())+    if diff > 0:+        spare = np.minimum(quotas - nb, pool_b - nb)+        order = np.argsort(-spare)+        for i in order:+            if diff <= 0:+                break+            take = int(min(diff, spare[i]))+            if take > 0:+                nb[i] += take+                diff -= take+    elif diff < 0:+        order = np.argsort(-nb)+        for i in order:+            if diff >= 0:+                break+            give = int(min(-diff, nb[i], quotas[i] - nb[i] + nb[i]))+            give = int(min(give, nb[i]))+            if give > 0 and (quotas[i] - (nb[i] - give)) <= pool_a[i]:+                nb[i] -= give+                diff += give+    nb = np.clip(nb, 0, quotas)+    return nb, quotas - nb+++def sample_pool(pool_idx, n, rng):+    if n <= 0:+        return np.array([], dtype=np.int64)+    if n >= len(pool_idx):+        return np.asarray(pool_idx, dtype=np.int64)+    return rng.choice(np.asarray(pool_idx), int(n), replace=False)+++def ot_displacement(X_e, X_l, coords_e, coords_l, labels_e, labels_l, t, rng):+    """Unbalanced-Sinkhorn coupling on expression PCA -> smoothed displacement for early cells."""+    n_e, n_l = len(coords_e), len(coords_l)+    ia = np.arange(n_e) if n_e <= OT_ANCHORS else stratified_choice(labels_e, OT_ANCHORS, rng)+    ib = np.arange(n_l) if n_l <= OT_ANCHORS else stratified_choice(labels_l, OT_ANCHORS, rng)++    A = np.asarray(X_e[ia].todense(), dtype=np.float64)+    B = np.asarray(X_l[ib].todense(), dtype=np.float64)+    if A.shape[0] + B.shape[0] < 10:+        return np.zeros((n_e, 3))+    Z = np.vstack([A, B])+    mu = Z.mean(0)+    Zc = Z - mu+    dim = min(PCA_DIM, Zc.shape[1], Zc.shape[0] - 1)+    if dim < 2:+        return np.zeros((n_e, 3))+    # randomized-free deterministic SVD on the small gene dimension+    cov = Zc.T @ Zc+    w, V = np.linalg.eigh(cov)+    V = V[:, ::-1][:, :dim]+    Ea = Zc[: len(A)] @ V+    Eb = Zc[len(A):] @ V++    aa = np.einsum("ij,ij->i", Ea, Ea)[:, None]+    bb = np.einsum("ij,ij->i", Eb, Eb)[None, :]+    M = np.maximum(aa + bb - 2.0 * (Ea @ Eb.T), 0.0)+    m = M.mean()+    if not np.isfinite(m) or m <= 0:+        return np.zeros((n_e, 3))+    M /= m++    import ot++    wa = np.full(len(ia), 1.0 / len(ia))+    wb = np.full(len(ib), 1.0 / len(ib))+    P = ot.sinkhorn_unbalanced(wa, wb, M, reg=EPS, reg_m=TAU)+    P = np.nan_to_num(P, nan=0.0, posinf=0.0, neginf=0.0)+    rs = P.sum(1)+    ok = rs > 1e-12+    if ok.sum() < 10:+        log("OT: degenerate coupling, skipping displacement")+        return np.zeros((n_e, 3))+    bary = (P[ok] @ coords_l[ib]) / rs[ok][:, None]+    d_anchor = (bary - coords_e[ia][ok]) * float(t)++    # kNN-smoothed broadcast of the anchor field onto every early cell+    tree = cKDTree(coords_e[ia][ok])+    _, nbr = tree.query(coords_e, k=min(K_SMOOTH, ok.sum()))+    if nbr.ndim == 1:+        nbr = nbr[:, None]+    d = d_anchor[nbr].mean(axis=1)+    d[~np.isfinite(d).any(1)] = 0.0+    return d * W_DISP+++def copy_last(view, manifest, genes, seed, out):+    entry = view_io.anchor_entry(manifest)+    S = view_io.read_stage(view, entry, genes)+    rng = np.random.default_rng(seed)+    hi = int(manifest["max_cells"])+    idx = np.arange(S.n) if S.n <= hi else stratified_choice(S.labels, hi, rng)+    write_prediction(S.X[idx], genes, out, coords=np.asarray(S.coords[idx], dtype=np.float64), seed=seed)+++def main():+    ap = argparse.ArgumentParser()+    ap.add_argument("--data", required=True)+    ap.add_argument("--out", required=True)+    ap.add_argument("--seed", type=int, required=True)+    args = ap.parse_args()+    view, out, seed = args.data, args.out, args.seed+    rng = np.random.default_rng(seed)++    manifest = view_io.load_manifest(view)+    genes = view_io.panel_genes(view, manifest)+    lo, hi = int(manifest["min_cells"]), int(manifest["max_cells"])++    a_entry, b_entry, t = view_io.interp_bracket(manifest)+    if b_entry is None:+        log("no bracket -> stratified copy of anchor stage")+        copy_last(view, manifest, genes, seed, out)+        return++    A = view_io.read_stage(view, a_entry, genes)+    B = view_io.read_stage(view, b_entry, genes)+    log(f"bracket {a_entry['stage']} + {b_entry['stage']} -> t={t:.3f}; "+        f"n_a={A.n} n_b={B.n}")++    # aligned, centred frames (z held)+    ca, cb, info = align_pair(A.coords, B.coords, A.labels, B.labels, method="procrustes")+    rms_t = log_interp(rms_radius(A.coords), rms_radius(B.coords), t, SCALE_DAMP)+    ca = scale_to_rms(ca, rms_t)+    cb = scale_to_rms(cb, rms_t)+    log(f"align: shared_types={info.get('n_shared_types')} z_flipped={info.get('z_flipped')} "+        f"rms_target={rms_t:.1f}")++    n_out = int(np.clip(round(log_interp(A.n, B.n, t)), lo, hi))++    # logit(log-share) composition interpolation with pseudocount+    types = sorted(set(map(str, A.labels)) | set(map(str, B.labels)))+    la = np.asarray(A.labels).astype(str)+    lb = np.asarray(B.labels).astype(str)+    cnt_a = np.array([np.sum(la == k) for k in types], dtype=np.float64)+    cnt_b = np.array([np.sum(lb == k) for k in types], dtype=np.float64)+    eps = 0.5 / max(A.n, B.n)+    pa = cnt_a / A.n + eps+    pb = cnt_b / B.n + eps+    pa /= pa.sum()+    pb /= pb.sum()+    pt = np.exp((1.0 - t) * np.log(pa) + t * np.log(pb))+    pt /= pt.sum()++    idx_a = {k: np.flatnonzero(la == k) for k in types}+    idx_b = {k: np.flatnonzero(lb == k) for k in types}+    pool_a = np.array([len(idx_a[k]) for k in types])+    pool_b = np.array([len(idx_b[k]) for k in types])+    quotas = quota_counts(pt, n_out, pool_a + pool_b)+    n_b_target = int(round(t * n_out))+    nb, na = stage_split(quotas, pool_a, pool_b, n_b_target)+    log(f"n_out={n_out} from_a={na.sum()} from_b={nb.sum()}")++    picks_a, picks_b = [], []+    for i, k in enumerate(types):+        picks_a.append(sample_pool(idx_a[k], na[i], rng))+        picks_b.append(sample_pool(idx_b[k], nb[i], rng))+    sel_a = np.concatenate(picks_a) if picks_a else np.array([], dtype=np.int64)+    sel_b = np.concatenate(picks_b) if picks_b else np.array([], dtype=np.int64)+    sel_a = np.sort(sel_a)+    sel_b = np.sort(sel_b)++    X_out = sparse.vstack([A.X[sel_a], B.X[sel_b]], format="csr")+    coords_out = np.vstack([ca[sel_a], cb[sel_b]]) if len(sel_a) else cb[sel_b].copy()++    if os.environ.get("T2_OT_DISP", "1") != "0" and len(sel_a) >= 20 and len(sel_b) >= 20:+        d = ot_displacement(A.X[sel_a], B.X[sel_b], coords_out[: len(sel_a)],+                            coords_out[len(sel_a):], np.asarray(A.labels)[sel_a],+                            np.asarray(B.labels)[sel_b], t, rng)+        norms = np.linalg.norm(d, axis=1)+        log(f"OT displacement: median={np.median(norms):.2f} p95={np.percentile(norms, 95):.2f} "+            f"rms={rms_t:.1f} rel_median={np.median(norms) / max(rms_t, 1e-9):.4f}")+        coords_out[: len(sel_a)] += d+        log(f"RMS after displacement: {rms_radius(coords_out):.1f} (target was {rms_t:.1f})")+    else:+        log("mechanism OFF (T2_OT_DISP=0): pure composition-quota mix")++    coords_out = np.asarray(coords_out, dtype=np.float64)+    if not np.isfinite(coords_out).all():+        raise RuntimeError("non-finite coordinates")+    write_prediction(X_out, genes, out, coords=coords_out, seed=seed)+    log(f"wrote {X_out.shape[0]} cells")+++if __name__ == "__main__":+    main()

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

用到的知识库条目

编号标题出处
k003Fused Gromov-Wasserstein mapping for spatial snapshots10.1038/s41586-024-08453-2
k002moscot.time: scalable temporal OT with growth rates and low-rank solvers10.1038/s41586-024-08453-2
k025Spateo: rigid + non-rigid alignment and morphometric vector fields10.1016/j.cell.2024.10.011

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

改了什么从零实现 T2HI-02:log 份额组成配额 + IPF 来源拆分的真实细胞混抽基座(procrustes 保 z 对齐),叠加表达 PCA30 上不平衡 Sinkhorn 耦合、重心投影、kNN(k=15) 平滑的坐标位移场(w=0.3,只动早期细胞坐标,带 T2_OT_DISP=0 关闭开关)。
各组分数的变化cell_state:变好 +5.71(66.70→72.41):mmd_u 0.0322→0.0253(得分 +0.67),variogram 0.0254→0.0190(得分 +0.76);同样归因于基座(真实细胞原样携带表达)。
expression_change:变好 +6.50(63.90→70.40):de_score 0.330→0.479(得分 +0.77),de_direction 0.516→0.651(得分 +0.85);来自基座的组成配额混抽,与 OT 位移无关(只动坐标,表达两组按 Engineer 消融逐位不变)。
local_spatial:变好 +12.86(54.03→66.89):neighborhood_mmd 0.0829→0.0486(得分 +3.21),主要来自基座的真实细胞混抽保住了表达-位置配对。
shape_scale:变坏 -3.68(53.37→49.69):d2_shape 0.0401→0.0576(得分 -0.80),occupancy_dice 0.834→0.810(得分 -0.47),scale_log_ratio 0.469→0.396(得分 +0.35,收缩方向正确但抵不过前两项)。
family_idT2HI-02
假设是否成立否
经验
  1. 在共有细胞类型少(本例代理括号仅 5 个)、procrustes 对齐质量差的条件下,对早期细胞施加 OT 重心投影位移(w=0.3~1.0)会扭曲距离分布:d2_shape/occupancy_dice 变差压过 scale_log_ratio 的收缩收益,榜分随 w 单调下降(基座 64.76 → w=0.3 64.28 → w=1.0 63.37)。
  2. kNN(k=15) 平滑广播的位移场能保住 neighborhood_mmd(local_spatial ~66 持平),验证了『平滑场位移』与『逐细胞 OT 插值』(方法卡上邻域暴跌到 23–41)的关键区别:位移必须局部整块移动才不破坏表达-位置配对。
  3. 组成配额混抽真实细胞的基座本身就是强配置:相对 best_seed(节点 2)四组中三组大涨(local_spatial +12.86、expression +6.50、cell_state +5.71),榜分 64.85 与节点 10 的 65.25 在噪声内相当。
  4. Engineer 的消融与榜分一致且如实报告:机制打开在代理上净负(-0.5~-1.4),提交按 PLAN 保持打开(w=0.3,损失最小档),本节点 +5.35 的提升实际由基座而非所声称的 OT 机制贡献。
mechanism_active是
下一步建议
  1. 针对 shape_scale/local_spatial:在真实括号(共有类型 ~31 个、对齐质量应更好)上重测 OT 位移 w∈{0, 0.3},若仍为负则以 T2_OT_DISP=0 的纯基座为准提交,并用多 seed B 半确认基座稳定性。
  2. 针对 shape_scale:把跨阶段对齐从类型质心 procrustes 换成表达空间软对齐(如对 PCA30 嵌入做 entropic OT 再对齐坐标),或把位移限制在类型内质心残差上,减少对齐误差主导的位移失真。
  3. 针对 shape_scale:在基座上叠加节点 8 式的向心收缩型位移(只改径向尺度不改相对结构),专攻 d2_shape/occupancy_dice 而非依赖 OT 配对。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数23
工具调用共 31 次:bash 26、edit 3、write 2
用时21 分
token 数输入 64,730 · 输出 10,819 · 思考 18,751
任务(第一行)按计划修改程序:OT耦合kNN平滑位移场驱动坐标(保邻域hybrid)
最后的回答(摘录)
**做了什么**:从零实现 T2HI-02——log 份额空间组成配额 + IPF 来源拆分的真实细胞混抽基座(procrustes 保 z 对齐),叠加 OT 耦合(不平衡 Sinkhorn,表达 PCA30)重心投影的 kNN(k=15) 平滑坐标位移场,带 T2_OT_DISP 关闭开关;seed 确定(双跑 sha256 一致)、vec-check 通过、纯 CPU 35s。

**查分(A 半,9/20 次)**:基座 64.76(≈节点10 的 65.25,噪声内);OT 位移 w=0.3→64.28、w=0.5→63.88、w=1.0→63.37,单调变差——位移收缩云使 scale_log_ratio 改善(0.433→0.329)且 kNN 平滑保住了邻域(66),但 d2_shape/occupancy 被对齐误差主导的位移扭曲。按 PLAN 要求提交时机制保持打开(w=0.3),METHOD.md 如实报告对照更高。

**下一步最值得试**:代理括号只有 5 个共有细胞类型导致 procrustes 对齐差、OT 位移方向失真;改进方向是先用表达空间(而非类型质心)做跨阶段软对齐再算位移,或把位移限制在类型内质心残差上;另一条路是基座本身(cell_state 71.6)叠加节点 8 式的收缩型 NN 位移专攻 shape_scale。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/14/engineer.jsonl 142 KB
/home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/14/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时1 分
token 数输入 10,500 · 输出 1,036 · 思考 2,533
任务(第一行)为节点 n14(草稿,父节点 nNone)写改进计划 PLAN.json,思路 T2HI-02
最后的回答(摘录)
{
  "name": "OT耦合kNN平滑位移场驱动坐标(保邻域hybrid)",
  "motivation": "当前最佳节点10(rank3 65.16)的shape_scale仅51.08,是四组中最弱;local_spatial 65.80仍有提升空间。方法卡显示逐细胞OT坐标插值把邻域打到23-41、总分最高45,但kNN平滑位移场可保邻域同时改善形状。节点5/8证明只动坐标的位移可改善local_spatial和shape_scale而不影响表达两组。本方案在节点10的组成配额基座上叠加OT驱动的平滑坐标位移,专攻shape_scale和local_spatial。",
  "approach": "步骤:\n1. 基座:procrustes对齐(保z轴,仅允许xy旋转+平移+均匀缩放)+ log线性RMS插值 + logit空间组成配额抽真实细胞(同节点10逻辑,表达/坐标原样)。\n2. OT耦合:对两侧阶段细胞在表达PCA前30维上跑unbalanced Sinkhorn(正则eps=0.05,不平衡权重tau=0.9),得耦合矩阵P(n_a × n_b)。用POT库sinkhorn_unbalanced;若超时退化到scipy linear_sum_assignment取前5000对。\n3. 重心投影:对输出中来自早期阶段的每个细胞i,目标位置 = Σ_j P[i,j]*pos_b[j] / Σ_j P[i,j];位移d_i = (target_pos_i - source_pos_i) × t。\n4. kNN平滑:对每个待位移细胞,取其输出云内k=15最近邻的位移均值作为最终位移(一次迭代即可)。\n5. 应用:early-stage细胞坐标 += smoothed_d_i;late-stage细胞坐标不动。\n6. 位移后检查:若RMS偏离目标>5%则重缩放回目标RMS。\n关键参数初值:sinkhorn eps=0.05(范围0.01-0.1),tau=0.9(范围0.7-1.0),k_smooth=15(范围10-25),t为插值参数。vec-score快速筛选:先跑eps∈{0.02,0.05,0.1}×k∈{10,15,20}共9组,选邻域+形状组最优再细调。\n单输入退路:仅一个阶段时跳过OT,输出=copy_last。",
  "expected_groups": ["shape_scale", "local_spatial"],
  "risks": "1) Sinkhorn在30分钟内可能跑不完(细胞数>2万时);应尽早检查耗时,超时用linear_sum_assignment或降采样到5000。2) 位移太小(耦合接近均匀)则无改善;Engineer应打印位移范数分布,若中位数<坐标RMS的1%则机制未生效。3) kNN平滑过度可能抹平真实位移;对照:不光滑直接应用看邻域是否暴跌。4) 形状改善可能<1分噪声;需A半3-seed确认。",
  "family_id": "T2HI-02",
  "mechanism": "OT耦合给出跨阶段细胞对应关系,重心投影将早期细胞坐标推向其对应的晚期位置,kNN平滑保证局部邻域整块移动而非逐细胞散乱插值。",
  "vs_constant_shift": "常数位移对所有同类型细胞施加相同向量;本方案的位移由OT耦合逐细胞决定(取决于该细胞在表达空间中与对侧的匹配),且经kNN平滑后是空间连续的场,不同位置、不同表达状态的细胞获得不同位移。",
  "mechanism_evidence": "1) 打印位移范数分布(中位数、P95),应>坐标RMS的2%才有意义;2) 位移后neighborhood_mmd raw应≤无位移基座(节点10的0.0510);3) d2_shape raw应下降;4) 表达两组(de_score、de_direction)raw应与基座逐位相同(只动坐标);5) 位移方向与细胞类型迁移方向的一致性(定性检查)。",
  "mechanism_off_control": "设环境变量T2_OT_DISP=0(或位移权重=0),跳过步骤3-5,输出退化为纯组成配额mix(等同节点10逻辑)。预期差别:shape_scale和local_spatial回落到节点10水平(~51/66),表达两组不变。",
  "sources": []
}
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/14/researcher.jsonl 4 KB
/home/spark-longxinyang/vec/runs/formal/20261003-094241-search-t2-heart-interp-g24-D-s2/nodes/14/researcher.stderr