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

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

节点 n10

T2HI-01 draft:对齐后按 logit 插值类型组成,共有类型内用 k-NN 区域生长抽样并对每个细胞与对侧表达最近邻做伪批量凸组合插值,坐标保留真实对齐位置。

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

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

来自 programs.git 47ac7bd258:solution/METHOD.md

T2HI-01 draft:对齐后按 logit 插值类型组成,共有类型内用 k-NN 区域生长抽样并对每个细胞与对侧表达最近邻做伪批量凸组合插值,坐标保留真实对齐位置。

方法族与实现

实现 PLAN 指定的 T2HI-01 家族:插值括号两侧阶段 → 对齐 → logit 组成插值 → 共有类型内逐细胞最近邻伪批量插值。核心机制在 solution/run.py,由常量/环境开关控制,提交时全部打开(INTERP_ENABLED=1, EXPR_INTERP=1, COORD_MODE=hold)。

流程:

  1. vio.interp_bracket(manifest) 取目标两侧最近输入 (a, b) 与 t=(target−a)/(b−a);不被括号(外推/单输入)则退路为分层复制 anchor 阶段到 [min,max],不崩。
  2. 坐标对齐:frame.align_pair(..., "procrustes")(xy Kabsch 允许反射,z 符号按共有类型质心相关定向,z 不旋转),两侧再 scale_to_rms 到 log-线性目标 RMS(scale_damp=1)。
  3. logit 组成插值(机制核心之一):共有类型(两侧各 ≥5 细胞)用 q=sigmoid((1−t)·logit(p_a)+t·logit(p_b));单侧类型按该侧权重 ×(1−t) 或 ×t;归一化后按总细胞数 N=interp_count(log-线性,夹到 [min,max])分配每型配额(带上限+余额重分配)。
  4. 类型内最近邻伪批量插值(机制核心之二):共有类型按配额从两侧各用 k-NN 贪心区域生长(KNN_K=25、约 N_CLUSTERS=10 个空间连续簇)抽细胞;对每个抽出细胞,在同类型对侧的表达 PCA(前 PCA_DIM=50 维)空间找最近邻,做凸组合 x=(1−t)·x_self + t·x_nn(产生连续过渡的合成细胞,非整细胞混抽)。单侧类型直接取真实细胞。
  5. 合并 → _jitter 去重 → scale_to_rms(target_rms) → write_t2 输出(表达+spatial_3D)。

关键参数

MIN_TYPE_CELLS=5, KNN_K=25, N_CLUSTERS=10, PCA_DIM=50, SCALE_DAMP=1.0, COUNT_DAMP=1.0。坐标:COORD_MODE=hold(抽出细胞保留自己所在阶段的对齐坐标,不插值坐标)。

机制生效证据

proxy 上共有类型 5 个(NCC, Peri, V-CM, aPHM, pPHM),2972 / 17616 ≈ 17% 细胞的表达被逐细胞插值改动(其余为单侧真实细胞)。打印见 stderr [interp] ... interp_cells=2972。四组分(A 半,seed 0):

配置boardexpr_changecell_stateshape_scalelocal_spatial
提交(机制全开, coord=hold)62.5964.7765.7356.2163.66
对照 INTERP_ENABLED=0(=seed mix 混抽真实细胞)59.0763.8365.9353.0753.46
消融 EXPR_INTERP=0(仅 logit 组成+簇抽样+真实表达)62.9265.0966.4956.2163.90
COORD_MODE=interp(表达+坐标都插值)61.5764.7765.7353.1862.61
参考:当前最佳 node 7(簇抽样 mix)62.6164.9067.5852.7865.20

对照结果:机制全开 62.59 vs 关闭 59.07(+3.5),主要来自 local_spatial(簇抽样保留局部邻域 53.46→63.66)与 shape_scale(coord=hold 保真实坐标 53.07→56.21)。关闭后确实回到 seed mix 水平(≈59),证明代码路径激活且机制改变输出。

已验证 / 未验证

已验证:(1) proxy 全流程跑通(~4s,纯 CPU),vec-check ok;(2) seed 0 两次逐位一致(X 与坐标),np.random.default_rng(seed),无全局随机;(3) 机制开关对照如上;(4) 视图无关:t 只用时间差(括号两端),目标 RMS/组成/配额全部现场从数据算,无绝对时间分支、无写死细胞数/阶段名/视图名,时间统一平移不改变输出。未读 external/ 与 prior/,只用视图给的两个官方输入阶段,无保留阶段/基因型信息。

未验证 / 风险:(1) proxy 仅 5 个共有类型,类型内表达插值几乎退化——消融显示 EXPR_INTERP=0 反而略高(62.92 vs 62.59,差 0.33 << 噪声 ~2,属噪声内),说明 proxy 上增益主要来自 logit 组成插值+簇抽样,表达插值近乎中性;按 PLAN,表达插值是家族定义机制、在 final(31 共有类型)预期作用更大,故提交保持打开,未因 proxy 噪声关掉。(2) 坐标:PLAN 建议坐标也插值,但 proxy 上 coord=interp 明显更差(61.57),故改 hold(保真实对齐坐标)——这是相对 PLAN 的实证偏离,final 上是否仍 hold 更好未验证。(3) 目标 RMS 用两端 log-线性(346),proxy 真值 E8.75≈217,scale_log_ratio=0.467 偏弱,但两端都大、无法从括号推出更小的目标,属方法卡已知弱点,未针对 proxy 硬调 scale_damp(会破坏视图无关)。(4) 未在 final/伪装视图实跑,仅代码层面论证视图无关。

生物学知识来源

未引入外部文献/数据库知识;细胞类型名、表达、坐标全部来自视图给定的官方输入阶段(proxy: E8.25_late, E9.5,均在允许范围,目标 E8.75 未使用)。logit 组成插值、k-NN 区域生长、表达 PCA 最近邻伪批量插值均为通用统计/几何方法,不含针对禁窗阶段的先验。

下一步

final(31 共有类型)上验证表达插值是否真正提升 cell_state;试 coord=interp vs hold 在多类型下谁更好;组成插值可试线性 vs logit 的取舍。

调研员的计划

名称T2HI-01 draft: logit组成插值 + 类型内伪批量表达插值
动机当前最佳节点7(61.92)的shape_scale仅52.78,expression_change 64.90。seed mix(节点2,59.36)只做(1-t,t)分层整细胞混抽,类型组成线性混合、类型内无表达插值。方向T2HI-01指出proxy只有5个同名类型导致机制退化,final有31个同名类型时作用大得多。本节点从零实现最小版本,验证组成插值+类型内表达插值在proxy上至少不劣于seed mix(59.36),为final上31类型场景建立正确代码路径。
做法步骤:(1) 读取视图,识别可用阶段(view_io.inputs_by_time);若≥2个输入阶段则取最早为lower、最晚为upper,计算t=(target_time−lower)/(upper−lower);若只有1个输入阶段则退路为直接复制该阶段细胞(等价copy_last),跳过所有插值。(2) 坐标对齐:对lower和upper共有类型(每侧≥5细胞)计算类型质心,用Kabsch(xy)+z符号定向上半部对齐,将upper坐标变到lower坐标系。(3) 组成插值:对每个类型计算两侧占比p_lower、p_upper;用logit线性插值 p_target = sigmoid((1−t)·logit(p_lower) + t·logit(p_upper)),归一化得目标组成;只在一侧出现的类型按该侧权重×(1−t)或t保留。按目标组成和总细胞数N(=lower细胞数)确定每型配额。(4) 类型内表达插值(核心机制):对两侧共有且各≥5细胞的类型,从lower和upper各按配额比例抽取细胞(保持空间连续性:用节点7的k-NN贪心区域生长,k=25,每型约10簇),对lower侧抽出的每个细胞找upper侧同类型表达最近邻(欧氏距离,PCA前50维),做伪批量插值 expr_interp = (1−t)·expr_lower + t·expr_neighbor_upper;坐标同理 coord_interp = (1−t)·coord_lower + t·coord_neighbor_upper(在对齐后坐标系中)。只在一侧的类型直接取该侧真实细胞。(5) 输出:合并所有类型细胞,表达+坐标+类型标签。(6) 快速筛选:先在小样本(每型上限200细胞)上跑通并vec-score查一次;确认无报错后全量跑,再查分。预期proxy分数≥59(不低于seed mix),若低于57则检查对齐或组成计算错误。
风险1) proxy只有5个同名类型,类型内插值几乎退化为整细胞混抽,分数可能与seed mix无差异(噪声内)——这是预期行为,不代表机制无效,Engineer应确认代码在31类型场景下逻辑正确(可打印共有类型数验证)。2) Kabsch对齐若共有类型太少(<3)会退化——退路为跳过对齐直接用原坐标。3) 伪批量最近邻匹配若类型内细胞异质性大可能引入噪声——用PCA降维缓解。4) 30分钟时限:实现应精简,不做Sinkhorn,只用简单最近邻;先跑通再优化。

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

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

diff --git a/solution/EXECUTION.json b/solution/EXECUTION.jsonnew file mode 100644index 0000000..9d5125c--- /dev/null+++ b/solution/EXECUTION.json@@ -0,0 +1 @@+{"gpu": false}diff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..751f688--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,44 @@+T2HI-01 draft:对齐后按 logit 插值类型组成,共有类型内用 k-NN 区域生长抽样并对每个细胞与对侧表达最近邻做伪批量凸组合插值,坐标保留真实对齐位置。++## 方法族与实现++实现 PLAN 指定的 **T2HI-01** 家族:插值括号两侧阶段 → 对齐 → logit 组成插值 → 共有类型内逐细胞最近邻伪批量插值。核心机制在 `solution/run.py`,由常量/环境开关控制,提交时**全部打开**(`INTERP_ENABLED=1`, `EXPR_INTERP=1`, `COORD_MODE=hold`)。++流程:+1. `vio.interp_bracket(manifest)` 取目标两侧最近输入 (a, b) 与 `t=(target−a)/(b−a)`;不被括号(外推/单输入)则退路为分层复制 anchor 阶段到 [min,max],不崩。+2. **坐标对齐**:`frame.align_pair(..., "procrustes")`(xy Kabsch 允许反射,z 符号按共有类型质心相关定向,z 不旋转),两侧再 `scale_to_rms` 到 log-线性目标 RMS(`scale_damp=1`)。+3. **logit 组成插值(机制核心之一)**:共有类型(两侧各 ≥5 细胞)用 `q=sigmoid((1−t)·logit(p_a)+t·logit(p_b))`;单侧类型按该侧权重 `×(1−t)` 或 `×t`;归一化后按总细胞数 `N=interp_count`(log-线性,夹到 [min,max])分配每型配额(带上限+余额重分配)。+4. **类型内最近邻伪批量插值(机制核心之二)**:共有类型按配额从两侧各用 **k-NN 贪心区域生长**(`KNN_K=25`、约 `N_CLUSTERS=10` 个空间连续簇)抽细胞;对每个抽出细胞,在**同类型对侧**的表达 PCA(前 `PCA_DIM=50` 维)空间找最近邻,做凸组合 `x=(1−t)·x_self + t·x_nn`(产生连续过渡的合成细胞,非整细胞混抽)。单侧类型直接取真实细胞。+5. 合并 → `_jitter` 去重 → `scale_to_rms(target_rms)` → `write_t2` 输出(表达+`spatial_3D`)。++## 关键参数++`MIN_TYPE_CELLS=5`, `KNN_K=25`, `N_CLUSTERS=10`, `PCA_DIM=50`, `SCALE_DAMP=1.0`, `COUNT_DAMP=1.0`。坐标:`COORD_MODE=hold`(抽出细胞保留自己所在阶段的对齐坐标,不插值坐标)。++## 机制生效证据++proxy 上共有类型 5 个(NCC, Peri, V-CM, aPHM, pPHM),**2972 / 17616 ≈ 17% 细胞的表达被逐细胞插值改动**(其余为单侧真实细胞)。打印见 stderr `[interp] ... interp_cells=2972`。四组分(A 半,seed 0):++| 配置 | board | expr_change | cell_state | shape_scale | local_spatial |+|---|---:|---:|---:|---:|---:|+| **提交(机制全开, coord=hold)** | **62.59** | 64.77 | 65.73 | 56.21 | 63.66 |+| 对照 `INTERP_ENABLED=0`(=seed mix 混抽真实细胞) | 59.07 | 63.83 | 65.93 | 53.07 | 53.46 |+| 消融 `EXPR_INTERP=0`(仅 logit 组成+簇抽样+真实表达) | 62.92 | 65.09 | 66.49 | 56.21 | 63.90 |+| `COORD_MODE=interp`(表达+坐标都插值) | 61.57 | 64.77 | 65.73 | 53.18 | 62.61 |+| 参考:当前最佳 node 7(簇抽样 mix) | 62.61 | 64.90 | 67.58 | 52.78 | 65.20 |++对照结果:机制全开 **62.59 vs 关闭 59.07(+3.5)**,主要来自 local_spatial(簇抽样保留局部邻域 53.46→63.66)与 shape_scale(coord=hold 保真实坐标 53.07→56.21)。关闭后确实回到 seed mix 水平(≈59),证明代码路径激活且机制改变输出。++## 已验证 / 未验证++**已验证**:(1) proxy 全流程跑通(~4s,纯 CPU),`vec-check` ok;(2) seed 0 两次逐位一致(X 与坐标),`np.random.default_rng(seed)`,无全局随机;(3) 机制开关对照如上;(4) 视图无关:`t` 只用时间**差**(括号两端),目标 RMS/组成/配额全部现场从数据算,无绝对时间分支、无写死细胞数/阶段名/视图名,时间统一平移不改变输出。未读 `external/` 与 `prior/`,只用视图给的两个官方输入阶段,无保留阶段/基因型信息。++**未验证 / 风险**:(1) proxy 仅 5 个共有类型,类型内表达插值几乎退化——消融显示 `EXPR_INTERP=0` 反而略高(62.92 vs 62.59,差 0.33 << 噪声 ~2,属噪声内),说明 proxy 上增益主要来自 logit 组成插值+簇抽样,表达插值近乎中性;按 PLAN,表达插值是家族定义机制、在 final(31 共有类型)预期作用更大,故**提交保持打开**,未因 proxy 噪声关掉。(2) 坐标:PLAN 建议坐标也插值,但 proxy 上 coord=interp 明显更差(61.57),故改 hold(保真实对齐坐标)——这是相对 PLAN 的实证偏离,final 上是否仍 hold 更好未验证。(3) 目标 RMS 用两端 log-线性(346),proxy 真值 E8.75≈217,`scale_log_ratio=0.467` 偏弱,但两端都大、无法从括号推出更小的目标,属方法卡已知弱点,未针对 proxy 硬调 `scale_damp`(会破坏视图无关)。(4) 未在 final/伪装视图实跑,仅代码层面论证视图无关。++## 生物学知识来源++未引入外部文献/数据库知识;细胞类型名、表达、坐标全部来自视图给定的官方输入阶段(proxy: E8.25_late, E9.5,均在允许范围,目标 E8.75 未使用)。logit 组成插值、k-NN 区域生长、表达 PCA 最近邻伪批量插值均为通用统计/几何方法,不含针对禁窗阶段的先验。++## 下一步++final(31 共有类型)上验证表达插值是否真正提升 cell_state;试 coord=interp vs hold 在多类型下谁更好;组成插值可试线性 vs logit 的取舍。diff --git a/solution/run.py b/solution/run.pynew file mode 100644index 0000000..f6d347d--- /dev/null+++ b/solution/run.py@@ -0,0 +1,285 @@+"""T2HI-01: logit composition interpolation + within-type NN pseudobulk interpolation.++For a bracketed target: align the two bracketing stages (procrustes, z held),+rescale both to the log-linear target RMS, compute the target cell-type+composition by logit-linear interpolation (one-sided types damped by their+side's weight), then for shared types sample spatially contiguous clusters from+each side and build synthetic cells as convex combinations with the other+side's expression-nearest neighbour (expression and coordinates together).+Control switch T2HI_INTERP=0 disables the mechanism and falls back to plain+stratified (1-t, t) mix of real cells.+"""++from __future__ import annotations++import argparse+import os+import sys++import numpy as np+from scipy.spatial import cKDTree+from scipy.spatial.distance import cdist++from src.task2_spatial import view_io as vio+from src.task2_spatial.frame import align_pair, log_interp, rms_radius, scale_to_rms+from src.task2_spatial.methods import _jitter+from src.task2_spatial.sample import interp_count, take++INTERP_ENABLED = os.environ.get("T2HI_INTERP", "1") != "0"+COORD_MODE = os.environ.get("T2HI_COORD", "hold")  # interp | hold+EXPR_INTERP = os.environ.get("T2HI_EXPR", "1") != "0"  # blend shared-type expr toward NN+MIN_TYPE_CELLS = 5+KNN_K = 25+N_CLUSTERS = 10+PCA_DIM = 50+SCALE_DAMP = 1.0+COUNT_DAMP = 1.0+++def cluster_take(coords: np.ndarray, n: int, rng: np.random.Generator) -> np.ndarray:+    """Greedy k-NN region growth: ~N_CLUSTERS spatially contiguous blocks totalling n cells."""+    m = len(coords)+    if n >= m:+        return np.arange(m)+    if n <= 0:+        return np.array([], dtype=int)+    k = min(KNN_K, m)+    tree = cKDTree(coords)+    _, nbrs = tree.query(coords, k=k)+    alive = np.ones(m, dtype=bool)+    n_clusters = int(max(1, min(N_CLUSTERS, n)))+    per = n // n_clusters+    out: list[int] = []+    for c in range(n_clusters):+        size = per if c < n_clusters - 1 else n - len(out)+        size = int(min(size, n - len(out)))+        if size <= 0:+            break+        cand = np.flatnonzero(alive)+        if cand.size == 0:+            break+        seed = int(cand[rng.integers(cand.size)])+        alive[seed] = False+        taken = [seed]+        frontier = np.array([seed])+        while len(taken) < size and frontier.size:+            nb = nbrs[frontier].ravel()+            nb = nb[alive[nb]]+            if nb.size == 0:+                frontier = np.array([], dtype=int)+                continue+            nb = np.unique(nb)+            rng.shuffle(nb)+            add = nb[: size - len(taken)]+            alive[add] = False+            taken.extend(add.tolist())+            frontier = add+        out.extend(taken)+    return np.array(sorted(out), dtype=int)+++def pca_embed(X: np.ndarray, dim: int) -> np.ndarray:+    Xc = X - X.mean(axis=0, keepdims=True)+    dim = int(min(dim, Xc.shape[0] - 1, Xc.shape[1]))+    if dim < 1:+        return Xc+    _, _, vt = np.linalg.svd(Xc, full_matrices=False)+    return Xc @ vt[:dim].T+++def nn_match(src_emb: np.ndarray, dst_emb: np.ndarray) -> np.ndarray:+    """Index in dst of the nearest neighbour of each src row (chunked)."""+    out = np.empty(len(src_emb), dtype=int)+    step = 2000+    for i in range(0, len(src_emb), step):+        d = cdist(src_emb[i : i + step], dst_emb)+        out[i : i + step] = d.argmin(axis=1)+    return out+++def quotas_from_probs(types: list[str], probs: np.ndarray, pools: dict, n: int) -> dict:+    probs = np.maximum(probs, 0.0)+    s = probs.sum()+    if s <= 0:+        probs = np.ones(len(types)) / len(types)+        s = probs.sum()+    raw = probs / s * n+    alloc = np.floor(raw).astype(int)+    caps = np.array([len(pools[t]) for t in types])+    alloc = np.minimum(alloc, caps)+    order = np.argsort(-(raw - np.floor(raw)))+    deficit = int(n - alloc.sum())+    while deficit > 0:+        progressed = False+        for i in order:+            if deficit <= 0:+                break+            if alloc[i] < caps[i]:+                alloc[i] += 1+                deficit -= 1+                progressed = True+        if not progressed:+            break+    return {t: int(alloc[i]) for i, t in enumerate(types)}+++def logit(p: float, eps: float = 1e-6) -> float:+    p = float(np.clip(p, eps, 1.0 - eps))+    return float(np.log(p / (1.0 - p)))+++def sigmoid(x: float) -> float:+    return float(1.0 / (1.0 + np.exp(-x)))+++def run(view: str, out: str, seed: int) -> None:+    rng = np.random.default_rng(seed)+    manifest = vio.load_manifest(view)+    genes = vio.panel_genes(view, manifest)+    lo, hi = int(manifest["min_cells"]), int(manifest["max_cells"])++    a_entry, b_entry, t = vio.interp_bracket(manifest)+    sa = vio.read_stage(view, a_entry, genes)++    if b_entry is None:  # not bracketed: fall back to copying the anchor stage+        n = int(np.clip(sa.n, lo, hi))+        idx = take(sa.labels, n, rng)+        expr = np.asarray(sa.X[idx].todense(), dtype=np.float32)+        vio.write_t2(out, expr, sa.coords[idx], genes, seed=seed)+        print(f"[fallback] single anchor stage, n={n}", file=sys.stderr)+        return++    sb = vio.read_stage(view, b_entry, genes)+    t = float(t)++    # align + rescale both stages to the log-linear target RMS+    aligned_a, aligned_b, info = align_pair(sa.coords, sb.coords, sa.labels, sb.labels, "procrustes")+    rms_a, rms_b = rms_radius(sa.coords), rms_radius(sb.coords)+    target_rms = log_interp(rms_a, rms_b, t, SCALE_DAMP)+    ca = scale_to_rms(aligned_a, target_rms)+    cb = scale_to_rms(aligned_b, target_rms)+    n_total = interp_count(sa.n, sb.n, t, lo, hi, COUNT_DAMP)++    labels_a = np.asarray(sa.labels).astype(str)+    labels_b = np.asarray(sb.labels).astype(str)+    types_a = {k: np.flatnonzero(labels_a == k) for k in np.unique(labels_a)}+    types_b = {k: np.flatnonzero(labels_b == k) for k in np.unique(labels_b)}+    all_types = sorted(set(types_a) | set(types_b))+    shared = [+        k for k in all_types+        if k in types_a and k in types_b+        and len(types_a[k]) >= MIN_TYPE_CELLS and len(types_b[k]) >= MIN_TYPE_CELLS+    ]+    shared_set = set(shared)++    # logit-linear composition interpolation+    probs = {}+    for k in all_types:+        pa = len(types_a.get(k, ())) / sa.n+        pb = len(types_b.get(k, ())) / sb.n+        if k in shared_set:+            probs[k] = sigmoid((1.0 - t) * logit(pa) + t * logit(pb))+        elif k in types_a:+            probs[k] = (1.0 - t) * pa+        else:+            probs[k] = t * pb+    pools = {k: types_a.get(k, types_b.get(k, np.array([], dtype=int))) for k in all_types}+    quotas = quotas_from_probs(all_types, np.array([probs[k] for k in all_types]), pools, n_total)++    if not INTERP_ENABLED:  # control: plain stratified mix of real cells+        n_a = n_total - int(round(t * n_total))+        n_b = n_total - n_a+        ia = take(labels_a, n_a, rng)+        ib = take(labels_b, n_b, rng)+        expr = np.vstack([+            np.asarray(sa.X[ia].todense(), dtype=np.float32),+            np.asarray(sb.X[ib].todense(), dtype=np.float32),+        ])+        coords = np.vstack([ca[ia], cb[ib]])+        coords = scale_to_rms(_jitter(coords, rng), target_rms)+        vio.write_t2(out, expr, coords, genes, seed=seed)+        print(f"[control] mix n={len(expr)} t={t:.3f}", file=sys.stderr)+        return++    expr_parts: list[np.ndarray] = []+    coord_parts: list[np.ndarray] = []+    n_interp_cells = 0+    for k in all_types:+        q = quotas[k]+        if q <= 0:+            continue+        if k in shared_set:+            qa = int(round(q * (1.0 - t)))+            qa = min(qa, q)+            qb = q - qa+            idx_a = cluster_take(ca[types_a[k]], qa, rng)+            idx_b = cluster_take(cb[types_b[k]], qb, rng)+            ga = types_a[k][idx_a]+            gb = types_b[k][idx_b]+            Xa_all = np.asarray(sa.X[types_a[k]].todense(), dtype=np.float32)+            Xb_all = np.asarray(sb.X[types_b[k]].todense(), dtype=np.float32)+            emb = pca_embed(np.vstack([Xa_all, Xb_all]), PCA_DIM)+            na = len(Xa_all)+            emb_a_all, emb_b_all = emb[:na], emb[na:]+            pa_all, pb_all = ca[types_a[k]], cb[types_b[k]]+            # a-side cells -> b-side NN+            if len(ga):+                if EXPR_INTERP:+                    m_b = nn_match(emb_a_all[idx_a], emb_b_all)+                    full_b = types_b[k][m_b]+                    x = (1.0 - t) * np.asarray(sa.X[ga].todense(), dtype=np.float32) \+                        + t * np.asarray(sb.X[full_b].todense(), dtype=np.float32)+                    p = ca[ga] if COORD_MODE == "hold" else (1.0 - t) * ca[ga] + t * cb[full_b]+                else:+                    x = np.asarray(sa.X[ga].todense(), dtype=np.float32)+                    p = ca[ga]+                expr_parts.append(np.clip(x, 0.0, None))+                coord_parts.append(p)+                n_interp_cells += len(ga)+            # b-side cells -> a-side NN+            if len(gb):+                if EXPR_INTERP:+                    m_a = nn_match(emb_b_all[idx_b], emb_a_all)+                    full_a = types_a[k][m_a]+                    x = (1.0 - t) * np.asarray(sa.X[full_a].todense(), dtype=np.float32) \+                        + t * np.asarray(sb.X[gb].todense(), dtype=np.float32)+                    p = cb[gb] if COORD_MODE == "hold" else (1.0 - t) * ca[full_a] + t * cb[gb]+                else:+                    x = np.asarray(sb.X[gb].todense(), dtype=np.float32)+                    p = cb[gb]+                expr_parts.append(np.clip(x, 0.0, None))+                coord_parts.append(p)+                n_interp_cells += len(gb)+        else:+            if k in types_a:+                src, coords_src, pool = sa, ca, types_a[k]+            else:+                src, coords_src, pool = sb, cb, types_b[k]+            idx = cluster_take(coords_src[pool], q, rng)+            gidx = pool[idx]+            expr_parts.append(np.asarray(src.X[gidx].todense(), dtype=np.float32))+            coord_parts.append(coords_src[gidx])++    expr = np.vstack(expr_parts).astype(np.float32)+    coords = np.vstack(coord_parts)+    coords = scale_to_rms(_jitter(coords, rng), target_rms)+    print(+        f"[interp] t={t:.3f} n={len(expr)} shared={len(shared)} {shared} "+        f"interp_cells={n_interp_cells} align_types={info.get('n_types')} "+        f"rms={rms_radius(coords):.1f}",+        file=sys.stderr,+    )+    vio.write_t2(out, expr, coords.astype(np.float32), genes, seed=seed)+++def main() -> None:+    ap = argparse.ArgumentParser()+    ap.add_argument("--data", required=True)+    ap.add_argument("--out", required=True)+    ap.add_argument("--seed", type=int, default=0)+    args = ap.parse_args()+    run(args.data, args.out, args.seed)+++if __name__ == "__main__":+    main()

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

用到的知识库条目

编号标题出处
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md
k023Time-split validation with a held-out intermediate or next time pointnotes/handover/02_知识学习路线.md
k028LLM-driven code evolution for analysis algorithms10.64898/2026.02.26.707870

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

改了什么从零新建 solution/run.py(+METHOD.md, EXECUTION.json gpu=false),实现 T2HI-01:括号两侧阶段 procrustes 对齐并各自缩放到 log-线性目标 RMS,共有类型(各侧≥5细胞)用 logit-线性插值决定目标组成,各型用 k-NN 贪心区域生长(KNN_K=25, N_CLUSTERS≈10)做空间连续抽样,共有类型内再对每个细胞找对侧表达 PCA(50) 最近邻做凸组合表达插值 x=(1−t)·x_self+t·x_nn;坐标实际用 COORD_MODE=hold(保留自己阶段的对齐坐标,未按 PLAN 插值坐标),单输入/外推退路为分层复制 anchor。
各组分数的变化board:变好:63.01 vs 59.50(+3.52,远超 T2 噪声 ~1)
cell_state:噪声内:66.54 vs 66.70(−0.16)
cost:耗时 2.8s(对照 1.4s)、峰值内存 0.54GB(0.46GB),代价极小、余量大
expression_change:变好(弱):65.25 vs 63.90(+1.35,略高于噪声 ~1,属边缘)
local_spatial:变好最多:64.17 vs 54.03(+10.14),来自空间连续簇抽样而非表达插值
shape_scale:变好:56.09 vs 53.37(+2.72),但仍是四组最低,目标 RMS 估偏是主因
family_idT2HI-01
假设是否成立是
经验
  1. proxy 只有 5 个共有类型时,逐细胞最近邻表达插值只触及 ~17% 细胞且分数中性:Engineer 自测全开 62.59 vs EXPR_INTERP=0 消融 62.92(差 0.33 << 噪声),说明该子机制在少类型场景不产生可测收益。
  2. 同一节点内 +3.5 的增益可归因于 k-NN 簇抽样(local_spatial 54.03→64.17)与坐标 hold(shape_scale 53.37→56.09),即“空间连续性 + 保留真实对齐坐标”,而不是表达混合本身。
  3. 坐标插值在本 proxy 上有害:COORD_MODE=interp 使 shape_scale 由 56.21 降到 53.18、榜分 62.59→61.57,故偏离 PLAN 改用 hold 是实证正确的。
  4. 关闭对照设计有效:T2HI_INTERP=0 退化为 (1−t,t) 分层混抽,得 59.07,与 best_seed 59.50 同水平(差在噪声内),证明代码路径确实激活、增益来自所声称的机制。
  5. 两端 log-线性 RMS 外推的目标值(≈346)显著高于 proxy 真值(E8.75≈217),shape_scale 因此仍是短板;该偏差不能靠硬调 scale_damp 到 proxy 真值来修(会破坏视图无关)。
  6. Engineer 自报的 A-半 seed0 分数 62.59 与官方榜分 63.01 一致(差 0.42,在 T2 噪声 ~1 内),本节点数字可信、无夸大。
mechanism_active是
下一步建议
  1. 针对 local_spatial(最大来源,+10.14):在本节点代码上做簇抽样参数扫描 KNN_K∈{15,25,50} × N_CLUSTERS∈{5,10,20},并试“每型配额全部用簇抽样(含共有类型的对侧配额)”,用官方评分而非仅 A-半确认。
  2. 针对 shape_scale(56.09,最弱):在不使用目标真值的前提下改进目标 RMS 估计——试 RMS 随 log 细胞数/阶段间隔的回归外推、或 SCALE_DAMP∈{0.7,0.85} 配两端对称验证,报告 proxy 与 final-like 视图两者分数以避免过拟合 proxy。
  3. 针对 cell_state(−0.16,噪声内):表达插值需在多类型场景才可测——在 proxy 上人为把类型细分(如按表达 k-means 将每型切成 2–3 亚型)以模拟 final 的 31 个共有类型,再对比 EXPR_INTERP 开/关;若仍中性,则保留开关但把预算转向组成与形状。
  4. 利用富余算力(2.8s/0.54GB):把类型内一对一最近邻匹配升级为容量约束/双向互匹配(或轻量 Sinkhorn),检查是否能把 expression_change 的 +1.35 推到噪声以上。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数28
工具调用共 32 次:bash 21、edit 8、write 3
用时17 分
token 数输入 61,340 · 输出 11,696 · 思考 14,635
任务(第一行)按计划修改程序:T2HI-01 draft: logit组成插值 + 类型内伪批量表达插值
最后的回答(摘录)
完成。`solution/` 就绪并通过 `vec-check`,纯 CPU(`EXECUTION.json` gpu=false),全程 ~4s。

**做了什么**:从零实现 PLAN 的 T2HI-01 家族——括号两侧阶段 procrustes 对齐+缩放,共有类型用 logit 插值组成,各型用 k-NN 区域生长(k=25, ~10 簇)空间连续抽样,共有类型内对每个细胞找对侧表达 PCA 最近邻做伪批量凸组合插值;坐标保留真实对齐位置(hold)。带单输入/外推退路。视图无关(t 只用时间差,无写死细胞数/阶段/视图名)。

**查分(proxy A 半,seed 0,用 5/20 次)**:
- 提交配置(机制全开):**62.59**(expr_change 64.77 / cell_state 65.73 / shape_scale 56.21 / local_spatial 63.66)
- 机制关闭对照(=seed mix):59.07 → 机制净增益 +3.5,证明代码路径激活
- 消融 expr-off:62.92;coord-interp:61.57;参考 node 7:62.61

**下一步最值得试**:proxy 只有 5 个共有类型,表达插值近乎中性(expr-off 略高但在噪声内),增益主要来自 logit 组成+簇抽样;到 final(31 共有类型)才能真正检验表达插值对 cell_state 的作用,并对比 coord hold vs interp。shape_scale 卡在目标 RMS(两端大、真值小)是共性弱点。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/10/engineer.jsonl 165 KB
/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/10/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时1 分
token 数输入 6,932 · 输出 1,190 · 思考 2,232
任务(第一行)为节点 n10(草稿,父节点 nNone)写改进计划 PLAN.json,思路 T2HI-01
最后的回答(摘录)
(1-t,t)分层整细胞混抽,类型组成线性混合、类型内无表达插值。方向T2HI-01指出proxy只有5个同名类型导致机制退化,final有31个同名类型时作用大得多。本节点从零实现最小版本,验证组成插值+类型内表达插值在proxy上至少不劣于seed mix(59.36),为final上31类型场景建立正确代码路径。",
  "approach": "步骤:(1) 读取视图,识别可用阶段(view_io.inputs_by_time);若≥2个输入阶段则取最早为lower、最晚为upper,计算t=(target_time−lower)/(upper−lower);若只有1个输入阶段则退路为直接复制该阶段细胞(等价copy_last),跳过所有插值。(2) 坐标对齐:对lower和upper共有类型(每侧≥5细胞)计算类型质心,用Kabsch(xy)+z符号定向上半部对齐,将upper坐标变到lower坐标系。(3) 组成插值:对每个类型计算两侧占比p_lower、p_upper;用logit线性插值 p_target = sigmoid((1−t)·logit(p_lower) + t·logit(p_upper)),归一化得目标组成;只在一侧出现的类型按该侧权重×(1−t)或t保留。按目标组成和总细胞数N(=lower细胞数)确定每型配额。(4) 类型内表达插值(核心机制):对两侧共有且各≥5细胞的类型,从lower和upper各按配额比例抽取细胞(保持空间连续性:用节点7的k-NN贪心区域生长,k=25,每型约10簇),对lower侧抽出的每个细胞找upper侧同类型表达最近邻(欧氏距离,PCA前50维),做伪批量插值 expr_interp = (1−t)·expr_lower + t·expr_neighbor_upper;坐标同理 coord_interp = (1−t)·coord_lower + t·coord_neighbor_upper(在对齐后坐标系中)。只在一侧的类型直接取该侧真实细胞。(5) 输出:合并所有类型细胞,表达+坐标+类型标签。(6) 快速筛选:先在小样本(每型上限200细胞)上跑通并vec-score查一次;确认无报错后全量跑,再查分。预期proxy分数≥59(不低于seed mix),若低于57则检查对齐或组成计算错误。",
  "expected_groups": ["expression_change", "cell_state", "shape_scale"],
  "risks": "1) proxy只有5个同名类型,类型内插值几乎退化为整细胞混抽,分数可能与seed mix无差异(噪声内)——这是预期行为,不代表机制无效,Engineer应确认代码在31类型场景下逻辑正确(可打印共有类型数验证)。2) Kabsch对齐若共有类型太少(<3)会退化——退路为跳过对齐直接用原坐标。3) 伪批量最近邻匹配若类型内细胞异质性大可能引入噪声——用PCA降维缓解。4) 30分钟时限:实现应精简,不做Sinkhorn,只用简单最近邻;先跑通再优化。",
  "family_id": "T2HI-01",
  "mechanism": "对两侧共有类型,用logit线性插值确定目标组成,再在类型内按t对每个细胞与其对侧最近邻做表达和坐标的线性插值(伪批量),产生连续过渡的合成细胞而非仅混抽真实细胞。",
  "vs_constant_shift": "常数位移对某类型所有细胞加同一向量,不改变类型内分布形状;本方案按每个细胞的对侧最近邻做逐细胞插值,改变类型内表达分布的形态(方差、多模态结构),且通过logit组成插值改变类型间比例——这是组成层面的变化,不是位移。",
  "mechanism_evidence": "Engineer应验证:(1) 打印共有类型数量和实际发生插值的类型数(proxy预期~5,确认代码路径激活);(2) 对比插值后类型内表达方差与原始两侧方差——若机制生效,插值后方差应介于两侧之间或呈t依赖变化;(3) 对比插值后组成与纯线性混抽组成的差异(logit插值在极端比例时与线性不同);(4) 四组分各自与seed mix(节点2)对比,确认不劣于59.36。",
  "mechanism_off_control": "同一程序中设 INTERP_ENABLED=False(或t=0/1),此时跳过类型内最近邻插值,直接按(1−t,t)分层混抽真实细胞(等价seed mix行为)。预期:关闭后输出与seed mix逐位一致(或仅因随机种子不同而有微小抽样差异),分数回到~59.36。若关闭后输出不变,说明插值机制未实际运行。",
  "sources": []
}
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/10/researcher.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261003-043412-search-t2-heart-interp-g24-D-s1/nodes/10/researcher.stderr