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

总览 · ← 返回运行 20261003-004922-search-t2-embryo-interp-g24

节点 n5

mix + 后期抽取比例减半(t_draw=0.5·t),修复 cell_state;PLAN 的型内 Sinkhorn OT 已实现但代理上拉低分数,默认关闭。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261003-004922-search-t2-embryo-interp-g24
父节点n2
子节点n7、n9
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 60.63(+3.4) · proxy 60.63(+3.4) · 3 次复测均分 60.25
审查通过 1 越界读取:未发现。run.py 仅通过 src.task2_spatial.view_io 的 load_manifest/read_stage/panel_genes 读取 --data 视图内数据(run.py:140-150),全文无绝对路径、'..'、/mnt、/home、data/raw、打分器或 src/common/evaluation 引用,无联网代码。; 2 硬编码目标统计量:未发现。无常量比例表或细胞数写死;T_DRAW_FRAC=0.5、scale_damp=0.5、EPS_FRAC 等为通用调节系数(run.py:43-48),n 与 t 均由 manifest …
用时?从运行开始到结束(或到现在)的挂钟时间。40 分
程序版本02e5b2045e158e94ae19012a5502d474d5bf352d (programs.git)

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

来自 programs.git 02e5b2045e:solution/METHOD.md

mix + 后期抽取比例减半(t_draw=0.5·t),修复 cell_state;PLAN 的型内 Sinkhorn OT 已实现但代理上拉低分数,默认关闭。

方法

保留父节点 2 的 mix 流程:interp_bracket 取目标两侧的输入,align=procrustes3d 把两朵云放进同一帧,scale_to_rms 到 log 线性目标 RMS(scale_damp=0.5), 按类型分层抽细胞、表达与坐标一起走,细胞数夹到 [min_cells, max_cells]。

唯一改变(本节点的改进):后期(b)阶段的抽取比例从 t 降到 t_draw = T_DRAW_FRAC · t,T_DRAW_FRAC=0.5(默认)。即 mix_indices 用 t_draw 而非 t 决定从 b 抽多少细胞;early(a)阶段补足其余。所有其它步骤 (对齐、RMS、分层、坐标)完全不变。这是一个纯运行时规则:只用 t(由 manifest 的两个输入时间与目标时间现场算出)和一个常数系数,不写死任何阶段的 细胞数、比例或统计量,也不区分视图。

为什么有效:代理(E6.75 + E8.0 → E7.25,t=0.4)实测,b 阶段(较晚、较大) 细胞抽得越多,cell_state 越低(tdraw=0.7 → cell_state 17、榜 40.0; tdraw=0.55 → 24.5、48.8;父 tdraw=0.4 → 37.5、57.2)。把 tdraw 降到 0.2 (=0.5·t)时 cell_state 升到 52.6、榜 59.88;0.1 与 0.15 也 >58 但 shape_scale 略降。0.5·t 是网格里的最好点,且是「t 的固定比例」而非对代理硬调 的常数,能迁移到真实括号(t=1/3 → t_draw=1/6)。

关键参数

  • T_DRAW_FRAC=0.5(env T2_T_DRAW_FRAC 可覆盖,仅调试用)
  • PARAMS = {align: procrustes3d, scale_damp: 0.5}(同父节点)
  • OT 机制默认关闭:T2_OT_ON=0;EPS_FRAC=0.03、BETA_SCALE=1.0(β=Scale·t)仅在开启时用。

PLAN 的型内 Sinkhorn OT(family T2EI-02)——已实现,但代理上否决

按 PLAN 完整实现了机制:对每个共有类型,用早阶段被抽中细胞与晚阶段同类型 (下采样 ≤1000)细胞的 z 化表达平方欧氏代价,跑 log 域稳定化平衡 Sinkhorn (200 迭代,eps=EPS_FRAC·mean(cost)),取 barycentric 映射 m_i,早阶段细胞输出 x_i+β(m_i−x_i),坐标同权重 β 同位移(T2_OT_COORD)。单输入(b is None)时整段 跳过,行为与父节点一致。

机制生效证据:开启时 11 个共有类型全部建图(n_ot_types=11),2146/5000 个 早阶段细胞的表达被改变(mean|ΔX|≈100),位移逐细胞不同(型内位移范数 CV≈0.05–0.10,非零但偏小,说明 eps 偏大、映射偏平滑);坐标与表达同细胞同权重 位移。

对照与结论:机制关闭(T2_OT_ON=0,β=0)时输出与父节点 2 逐位相同 (X 与 spatial_3D 全等,实测 X equal: True coords equal: True),得分应回到 ≈57.2。开启机制后代理分下降:β=0.4 榜 44.3(cell_state 19.4)、β=0.25 榜 54.3(cell_state 29.3)、β=0.6 榜 50.8。cell_state 不升反降,与 PLAN 预期相反。 原因:eps 即便取 0.003·mean(cost),barycentric 映射仍把早阶段细胞平滑地拉向 晚阶段类型均值,产生「不像任何真实细胞」的中间表达,正是评分器 cell_state 惩罚的 对象;逐细胞位移 CV 太低说明它退化成近似常数(型均值)位移。因此本节点默认 关闭 OT,改用上文的 t_draw 规则——后者是实测唯一稳定提分的改动。不冒充: OT 代码在仓库里、可开关、对照已验证,但提交配置不启用它。

验证过

  • 代理视图 seed 0:run.py 1.5s 跑完,vec-check ok,vec-score = 59.8837 (父 57.23,+2.65 > 噪声 ~1)。
  • 确定性:seed 0 连跑两次,X 与坐标逐位相同。
  • t_draw 网格 {0.1,0.15,0.2,0.3,0.55,0.7}×代理,0.2=0.5·t 最优。
  • 机制关闭时与父节点逐位一致。

未验证 / 风险

  • 只在代理括号(E6.75+E8.0,t=0.4)验证;真实括号 t=1/3 → t_draw=1/6, 后期细胞更少。若真实 E8.0 侧含更多目标阶段应有的新类型,t_draw 太小可能漏掉 它们、压低 expression_change。0.5 是折中,未在真实目标上验证(无权限)。
  • B 半正式分未查,A 半 +2.65 应能过 ~1 分噪声,但 shape_scale 从 74.4 降到 68.5 是代价;若 B 半 shape_scale 地板更低,收益可能缩水。
  • 未试 t_draw 与 scale_damp 的联合调参(时间预算内只固定 scale_damp=0.5)。

生物学知识来源

未使用任何保留阶段/基因型的实测信息。t_draw<1 的依据纯粹是代理评分反馈 (后期细胞比例越高 cell_state 越低),属于对本视图数据的现场测量,非硬编码先验。

调研员的计划

名称mix + 共有类型内 Sinkhorn OT 单细胞位移,修复 cell_state
动机父节点 2(mix)总分 57.23 但 cell_state 只有 37.49,显著低于 copy_last 的 49.29,是四组中最弱且唯一倒退的组;METHOD.md 自己承认两端真实细胞混抽不像中间阶段。expression_change 59.72、local_spatial 57.27 还有余量,shape_scale 74.43 最强,故本节点只针对 cell_state。兄弟节点 3(类型内表达插值)已 gen_failed 无产出,本方案改用按类型的 OT 映射做逐细胞位移而非类型内均值插值,机制与实现路径均不同。
做法保留父节点 mix 的 bracket/align(procrustes3d)/scale_damp=0.5/按类型分层抽细胞流程不动,在抽样后新增一步:对每个共有类型(n_shared_types≈11),用早阶段该类型细胞的表达(panel 基因,与坐标同源)对晚阶段该类型细胞建代价矩阵(z 化后欧氏距离),跑 log 域稳定化 Sinkhorn(≤200 次迭代),得 barycentric 映射 m_i;每个被抽中的早阶段细胞输出 x_i + β·(m_i − x_i),晚阶段细胞不动;坐标按同一细胞的同一权重 β 做相同位移,保证表达—坐标对应(k027)。参数:ε∈{0.01,0.03,0.1}×(该类型代价矩阵方差),β 初值 = t(final 时 1/3),必要时在 {0.5t, t, 1.5t} 微调;类型两侧 >1000 细胞时随机下采样建图、映射广播回全体。验证顺序:1) 先跑 β=0 对照,确认逐位复现父节点(proxy≈57.2);2) 用每类型 500 细胞的下采样 proxy 在 5 分钟内筛 ε;3) 全量 proxy 选比 57.2(A 半)高 ≥1.5 分的组合(噪声约 1 分,小于 1.5 不算赢,可查 2 次确认)。单输入阶段退路:b is None 时完全跳过 OT,行为与父节点一致。所有参数运行时由 manifest 与代价矩阵推出,不写死任何阶段的统计量。
风险1) 大类型上 Sinkhorn 慢/费内存:每侧下采样到 ≤1000 并计时,30 分钟时限内先小后大,超时就固定用下采样映射;2) ε 过大导致熵模糊、型内多样性坍缩(反而伤 cell_state):检查输出型内表达标准差较输入下降不超过 ~30%,否则换小 ε;3) 晚阶段细胞不动造成中间时刻双峰:因晚端占比仅 t≈1/3,预计影响有限,以 cell_state 是否净升为准;4) 流水线改动引入隐蔽差异:β=0 对照必须与父节点逐位一致,否则先修管线再谈机制;5) Sinkhorn 数值不稳:用 log 域稳定化,出现 NaN 时该类型退化为最近质心位移。

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

对比:父节点版本 b616d893bf。改动的文件:solution/METHOD.md +72 −0、solution/run.py +165 −14

diff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..a116904--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,72 @@+mix + 后期抽取比例减半(t_draw=0.5·t),修复 cell_state;PLAN 的型内 Sinkhorn OT 已实现但代理上拉低分数,默认关闭。++## 方法++保留父节点 2 的 mix 流程:`interp_bracket` 取目标两侧的输入,`align=procrustes3d`+把两朵云放进同一帧,`scale_to_rms` 到 log 线性目标 RMS(`scale_damp=0.5`),+按类型分层抽细胞、表达与坐标一起走,细胞数夹到 `[min_cells, max_cells]`。++**唯一改变(本节点的改进)**:后期(b)阶段的抽取比例从 t 降到+`t_draw = T_DRAW_FRAC · t`,`T_DRAW_FRAC=0.5`(默认)。即 `mix_indices` 用+`t_draw` 而非 t 决定从 b 抽多少细胞;early(a)阶段补足其余。所有其它步骤+(对齐、RMS、分层、坐标)完全不变。这是一个纯运行时规则:只用 t(由+manifest 的两个输入时间与目标时间现场算出)和一个常数系数,不写死任何阶段的+细胞数、比例或统计量,也不区分视图。++为什么有效:代理(E6.75 + E8.0 → E7.25,t=0.4)实测,b 阶段(较晚、较大)+细胞抽得越多,`cell_state` 越低(tdraw=0.7 → cell_state 17、榜 40.0;+tdraw=0.55 → 24.5、48.8;父 tdraw=0.4 → 37.5、57.2)。把 tdraw 降到 0.2+(=0.5·t)时 `cell_state` 升到 52.6、榜 59.88;0.1 与 0.15 也 >58 但+`shape_scale` 略降。0.5·t 是网格里的最好点,且是「t 的固定比例」而非对代理硬调+的常数,能迁移到真实括号(t=1/3 → t_draw=1/6)。++## 关键参数++- `T_DRAW_FRAC=0.5`(env `T2_T_DRAW_FRAC` 可覆盖,仅调试用)+- `PARAMS = {align: procrustes3d, scale_damp: 0.5}`(同父节点)+- OT 机制默认关闭:`T2_OT_ON=0`;`EPS_FRAC=0.03`、`BETA_SCALE=1.0`(β=Scale·t)仅在开启时用。++## PLAN 的型内 Sinkhorn OT(family T2EI-02)——已实现,但代理上否决++按 PLAN 完整实现了机制:对每个共有类型,用早阶段被抽中细胞与晚阶段同类型+(下采样 ≤1000)细胞的 z 化表达平方欧氏代价,跑 log 域稳定化平衡 Sinkhorn+(200 迭代,eps=EPS_FRAC·mean(cost)),取 barycentric 映射 m_i,早阶段细胞输出+x_i+β(m_i−x_i),坐标同权重 β 同位移(`T2_OT_COORD`)。单输入(b is None)时整段+跳过,行为与父节点一致。++**机制生效证据**:开启时 11 个共有类型全部建图(n_ot_types=11),2146/5000 个+早阶段细胞的表达被改变(mean|ΔX|≈100),位移逐细胞不同(型内位移范数+CV≈0.05–0.10,非零但偏小,说明 eps 偏大、映射偏平滑);坐标与表达同细胞同权重+位移。++**对照与结论**:机制关闭(`T2_OT_ON=0`,β=0)时输出与父节点 2 **逐位相同**+(X 与 spatial_3D 全等,实测 `X equal: True coords equal: True`),得分应回到+≈57.2。开启机制后代理分**下降**:β=0.4 榜 44.3(cell_state 19.4)、β=0.25 榜+54.3(cell_state 29.3)、β=0.6 榜 50.8。cell_state 不升反降,与 PLAN 预期相反。+原因:eps 即便取 0.003·mean(cost),barycentric 映射仍把早阶段细胞平滑地拉向+晚阶段类型均值,产生「不像任何真实细胞」的中间表达,正是评分器 cell_state 惩罚的+对象;逐细胞位移 CV 太低说明它退化成近似常数(型均值)位移。因此本节点**默认+关闭 OT**,改用上文的 t_draw 规则——后者是实测唯一稳定提分的改动。不冒充:+OT 代码在仓库里、可开关、对照已验证,但提交配置不启用它。++## 验证过++- 代理视图 seed 0:`run.py` 1.5s 跑完,`vec-check` ok,`vec-score` = 59.8837+  (父 57.23,+2.65 > 噪声 ~1)。+- 确定性:seed 0 连跑两次,X 与坐标逐位相同。+- t_draw 网格 {0.1,0.15,0.2,0.3,0.55,0.7}×代理,0.2=0.5·t 最优。+- 机制关闭时与父节点逐位一致。++## 未验证 / 风险++- 只在代理括号(E6.75+E8.0,t=0.4)验证;真实括号 t=1/3 → t_draw=1/6,+  后期细胞更少。若真实 E8.0 侧含更多目标阶段应有的新类型,t_draw 太小可能漏掉+  它们、压低 expression_change。0.5 是折中,未在真实目标上验证(无权限)。+- B 半正式分未查,A 半 +2.65 应能过 ~1 分噪声,但 shape_scale 从 74.4 降到 68.5+  是代价;若 B 半 shape_scale 地板更低,收益可能缩水。+- 未试 t_draw 与 scale_damp 的联合调参(时间预算内只固定 scale_damp=0.5)。++## 生物学知识来源++未使用任何保留阶段/基因型的实测信息。t_draw<1 的依据纯粹是代理评分反馈+(后期细胞比例越高 cell_state 越低),属于对本视图数据的现场测量,非硬编码先验。diff --git a/solution/run.py b/solution/run.pyindex 7ba73af..e6a41f3 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,28 +1,133 @@ #!/usr/bin/env python3-"""mix (T2 interpolation): real cells from both bracketing inputs, drawn (1−t, t).--Brackets the target with the nearest inputs before and after it, puts both in-one frame (``ALIGN``), rescales both clouds to the log-linear RMS-exp(log r_a + SCALE_DAMP·t·Δlog r), and draws cells stratified by type:-round(t·n) from the later stage, the rest from the earlier one. Expression and-coordinates travel together. n is log-linear in t, clipped to the board range.-Parameters are the T2 card's choice for this board (selected_params.json).-If the target is not bracketed, falls back to the latest input before it.+"""mix + per-type Sinkhorn OT single-cell displacement (T2 interpolation).++Base pipeline is the parent's mix: bracket the target with the nearest inputs,+align frames (procrustes3d), rescale both clouds to the damped log-linear RMS,+draw cells stratified by type: T_DRAW_FRAC*t from the later stage (default+T_DRAW_FRAC=0.5, chosen on the proxy where later-stage-heavy mixes collapse+cell_state), the rest from the earlier one.++Added mechanism (family T2EI-02): for every cell type shared by the two+bracketing stages, build a cost matrix between the drawn earlier-stage cells+and a subsample (<=1000) of the later-stage cells of the same type+(z-scored expression, squared Euclidean), solve an entropy-regularised+balanced OT with log-domain Sinkhorn (<=200 iters, eps = EPS_FRAC * var(cost)),+and take the barycentric map m_i of each earlier cell. Each drawn earlier+cell outputs x_i + beta*(m_i - x_i); later cells stay untouched. Coordinates+get the same per-cell weight beta toward the barycentric coordinate of the+matched later cells, so expression and position move together.+beta = BETA_SCALE * t when enabled. The OT step is DISABLED by default+(T2_OT_ON=1 to enable): on the proxy every beta tested lowered the board score+(54.3 / 50.8 / 44.3 vs parent 57.2), see METHOD.md. With the mechanism off the+output is bit-for-bit the parent pipeline plus the t_draw change.++All parameters are derived at runtime from the manifest and the cost matrices;+nothing about any stage is hardcoded. """  from __future__ import annotations  import argparse import json+import os import sys  import numpy as np -from src.task2_spatial.methods import interpolate-from src.task2_spatial.sample import take+from src.task2_spatial.frame import align_pair, log_interp, rms_radius, scale_to_rms+from src.task2_spatial.methods import _jitter, _limits, _pack_expr+from src.task2_spatial.sample import mix_indices, take+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  PARAMS = {"align": "procrustes3d", "scale_damp": 0.5}+EPS_FRAC = float(os.environ.get("T2_OT_EPS", "0.03"))+BETA_SCALE = float(os.environ.get("T2_OT_BETA", "1.0"))+OT_ON = os.environ.get("T2_OT_ON", "0") == "1"  # PLAN mechanism, rejected by proxy scores (see METHOD.md)+OT_COORD = os.environ.get("T2_OT_COORD", "1") == "1"+T_DRAW_FRAC = float(os.environ.get("T2_T_DRAW_FRAC", "0.5"))  # later-stage draw fraction = T_DRAW_FRAC * t+OT_MAX_SIDE = 1000+SINKHORN_ITERS = 200+++def _sqdist(a: np.ndarray, b: np.ndarray) -> np.ndarray:+    aa = np.einsum("ij,ij->i", a, a)[:, None]+    bb = np.einsum("ij,ij->i", b, b)[None, :]+    return np.maximum(aa + bb - 2.0 * (a @ b.T), 0.0)+++def _sinkhorn_log(cost: np.ndarray, eps: float) -> np.ndarray:+    n, m = cost.shape+    log_a = np.full(n, -np.log(n))+    log_b = np.full(m, -np.log(m))+    k = -cost / max(eps, 1e-12)+    k -= k.max()+    f = np.zeros(n)+    g = np.zeros(m)+    for _ in range(SINKHORN_ITERS):+        f = -np.log(np.exp(k + g[None, :]).sum(axis=1) + 1e-300) + log_a+        g = -np.log(np.exp(k + f[:, None]).sum(axis=0) + 1e-300) + log_b+    log_plan = k + f[:, None] + g[None, :]+    log_plan -= log_plan.max()+    plan = np.exp(log_plan)+    plan /= max(plan.sum(), 1e-300)+    if not np.isfinite(plan).all() or plan.sum() <= 0:+        return np.outer(np.full(n, 1.0 / n), np.full(m, 1.0 / m))+    return plan+++def type_ot_displacement(labels_a_drawn, stage_b, cb, xa, pa, beta, eps_frac, rng):+    """Per-shared-type barycentric displacement of drawn earlier-stage cells."""+    dx = np.zeros_like(xa, dtype=np.float64)+    dp = np.zeros_like(pa, dtype=np.float64)+    lb = np.asarray(stage_b.labels).astype(str)+    la = np.asarray(labels_a_drawn).astype(str)+    n_ot_types = 0+    cv_list = []+    for typ in sorted(set(la) & set(lb)):+        rows = np.flatnonzero(la == typ)+        pool = np.flatnonzero(lb == typ)+        if rows.size == 0 or pool.size == 0:+            continue+        cols = pool if pool.size <= OT_MAX_SIDE else np.sort(rng.choice(pool, OT_MAX_SIDE, replace=False))+        Xa = xa[rows].astype(np.float64)+        Xb = as_dense(stage_b.X, cols).astype(np.float64)+        Pb = np.asarray(cb[cols], dtype=np.float64)+        if rows.size > OT_MAX_SIDE:+            ot_rows = np.sort(rng.choice(rows.size, OT_MAX_SIDE, replace=False))+        else:+            ot_rows = np.arange(rows.size)+        Zo = ot_rows+        M = np.vstack([Xa[Zo], Xb])+        mu = M.mean(axis=0)+        sd = M.std(axis=0) + 1e-8+        Za = (Xa[Zo] - mu) / sd+        Zb = (Xb - mu) / sd+        cost = _sqdist(Za, Zb)+        eps = eps_frac * float(cost.mean() + 1e-12)+        plan = _sinkhorn_log(cost, eps)+        mass = plan.sum(axis=1, keepdims=True) + 1e-12+        mx = (plan @ Xb) / mass+        mp = (plan @ Pb) / mass+        dx_ot = beta * (mx - Xa[Zo])+        dp_ot = beta * (mp - pa[rows][Zo])+        if not (np.isfinite(dx_ot).all() and np.isfinite(dp_ot).all()):+            continue+        if Zo.size == rows.size:+            dx[rows] = dx_ot+            dp[rows] = dp_ot+        else:+            # broadcast the subsampled map to the remaining cells of this type+            rest = np.setdiff1d(np.arange(rows.size), Zo)+            nn = np.argmin(_sqdist((Xa[rest] - mu) / sd, Za), axis=1)+            dx[rows[rest]] = dx_ot[nn]+            dp[rows[rest]] = dp_ot[nn]+        norms = np.linalg.norm(dx_ot, axis=1)+        if norms.mean() > 1e-9:+            cv_list.append(float(norms.std() / (norms.mean() + 1e-12)))+        n_ot_types += 1+    cv = float(np.mean(cv_list)) if cv_list else 0.0+    return dx.astype(np.float32), dp, n_ot_types, cv   def main() -> None:@@ -44,10 +149,56 @@ def main() -> None:     stage_a = read_stage(args.data, a, genes)     stage_b = read_stage(args.data, b, genes)     params = board_params(manifest, "mix", PARAMS, args.seed)-    expr, coords, info = interpolate(stage_a, stage_b, t, params)-    keep = {k: info.get(k) for k in ("t", "n", "rms_a", "rms_b", "out_rms", "n_shared_types", "z_dot", "z_flipped", "align")}+    t = float(t)+    damp = float(params.get("scale_damp", 1.0))+    align = str(params.get("align", "procrustes"))+    rng = np.random.default_rng(int(params.get("seed", 0)))++    aligned_a, aligned_b, info = align_pair(stage_a.coords, stage_b.coords, stage_a.labels, stage_b.labels, align)+    rms_a = rms_radius(stage_a.coords)+    rms_b = rms_radius(stage_b.coords)+    target_rms = log_interp(rms_a, rms_b, t, damp)+    ca = scale_to_rms(aligned_a, target_rms)+    cb = scale_to_rms(aligned_b, target_rms)+    n = _limits(params, stage_a.n, stage_b.n, t, "interp")+    t_draw = float(np.clip(T_DRAW_FRAC * t, 0.0, 1.0))+    ia, ib = mix_indices(stage_a.labels, stage_b.labels, t_draw, n, rng)++    xa = as_dense(stage_a.X, ia)+    pa = np.asarray(ca[ia], dtype=np.float64)+    xb = as_dense(stage_b.X, ib) if ib.size else np.zeros((0, len(genes)), dtype=np.float32)+    pb = np.asarray(cb[ib], dtype=np.float64) if ib.size else np.zeros((0, 3))++    beta = BETA_SCALE * t if OT_ON else 0.0+    n_ot_types, disp_cv = 0, 0.0+    if beta > 0.0 and ia.size and ib.size:+        rng_ot = np.random.default_rng(int(params.get("seed", 0)) + 777)+        dx, dp, n_ot_types, disp_cv = type_ot_displacement(+            stage_a.labels[ia], stage_b, cb, xa, pa, beta, EPS_FRAC, rng_ot+        )+        xa = np.clip(xa.astype(np.float64) + dx, 0.0, None).astype(np.float32)+        if OT_COORD:+            pa = pa + dp++    expr_parts = []+    coord_parts = []+    if ia.size:+        expr_parts.append(xa)+        coord_parts.append(pa)+    if ib.size:+        expr_parts.append(xb)+        coord_parts.append(pb)+    expr = _pack_expr(expr_parts)+    coords = _jitter(np.vstack(coord_parts), rng)+    coords = scale_to_rms(coords, target_rms)+    info.update(t=t, n=int(expr.shape[0]), rms_a=rms_a, rms_b=rms_b, out_rms=rms_radius(coords),+                align=align, scale_damp=damp, beta=beta, eps_frac=EPS_FRAC,+                n_ot_types=n_ot_types, disp_cv=disp_cv, n_from_a=int(ia.size), n_from_b=int(ib.size))+    keep = {k: info.get(k) for k in ("t", "n", "rms_a", "rms_b", "out_rms", "n_shared_types", "z_dot",+                                     "z_flipped", "align", "beta", "eps_frac", "n_ot_types", "disp_cv",+                                     "n_from_a", "n_from_b")}     print(json.dumps({"bracket": [a["stage"], b["stage"]], **keep}, default=float), file=sys.stderr)-    write_t2(args.out, expr, coords, genes, seed=args.seed)+    write_t2(args.out, expr, coords.astype(np.float32), genes, seed=args.seed)   if __name__ == "__main__":

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

用到的知识库条目

编号标题出处
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md
k028LLM-driven code evolution for analysis algorithms10.64898/2026.02.26.707870
k027Joint expression-geometry generation with relative geometrynotes/competition/03_solution_landscape.md

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

改了什么PLAN 的共有类型内 Sinkhorn OT 位移已完整实现但默认关闭(T2_OT_ON=0,工程师代理实测所有 β 都降分);实际提交的改动是把后期阶段抽取比例从 t 降为 t_draw=0.5·t(运行时由 manifest 推出),其余 mix 流程(procrustes3d、scale_damp=0.5、分层抽样)不变。
各组分数的变化cell_state:显著变好:37.49 → 53.88(+16.39),远超噪声,是本节点主要收益来源
expression_change:噪声内:59.72 → 60.08(+0.36,T2 噪声约 1 分)
local_spatial:变好:57.27 → 59.48(+2.21),略超噪声
shape_scale:变坏:74.43 → 69.09(-5.34),减少后期(较大)细胞抽取的代价
family_idother
假设是否成立否
经验
  1. 在 T2 embryo mix 基线上,barycentric Sinkhorn OT 把早阶段细胞平滑拉向晚阶段类型均值(位移范数 CV 仅 0.05–0.10,近似常数位移),产生非真实的中间表达,代理上所有 β(0.25/0.4/0.6)都使 cell_state 不升反降(榜分 44.3–54.3 vs 父 57.2),即使 eps 低至 0.003·mean(cost) 也无法避免——熵正则 OT 的重心映射不适合直接生成过渡态细胞。
  2. 降低 mix 中后期阶段抽取比例(t_draw=T_DRAW_FRAC·t,T_DRAW_FRAC=0.5)在代理和真实目标上一致大幅提升 cell_state(真实 +16.39),代价是 shape_scale -5.34,净榜分 +3.40(57.23 → 60.63),超过噪声;该规则用 t 的固定比例而非硬编码常数,可跨括号迁移。
  3. 机制关闭(β=0)对照输出与父节点逐位一致,说明管线重写(把 interpolate 拆成 align/scale/mix_indices 手工组装)没有引入隐蔽差异,是可信的验证流程。
  4. 工程师自报 proxy 59.88,实际正式分 60.63,方向一致、幅度略大,代理筛选结论在本例可迁移到真实目标。
mechanism_active否
下一步建议
  1. 针对 shape_scale:在 t_draw ∈ {0.4·t, 0.5·t, 0.6·t} × scale_damp ∈ {0.4, 0.5, 0.65} 上做联合网格(工程师已列为未试项),目标在保住 cell_state ≥50 的前提下把 shape_scale 拉回 72+。
  2. 针对 expression_change(本次仅 +0.36,噪声内):检查 t_draw=1/6(真实 t=1/3)是否漏掉仅存在于晚阶段的类型;对晚阶段独有类型保留全部/更高比例的细胞,只对共有类型降抽样,验证 expression_change 是否上升。
  3. 放弃 OT barycentric 位移路线(已实测否决);若再试逐细胞位移,改用硬匹配(最近邻或 eps→0 极限)避免向类型均值坍缩,并先在代理上以 cell_state 不降为门槛再全量跑。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数30
工具调用共 34 次:bash 29、edit 3、write 2
用时21 分
token 数输入 66,295 · 输出 11,946 · 思考 19,694
任务(第一行)按计划修改程序:mix + 共有类型内 Sinkhorn OT 单细胞位移,修复 cell_state
最后的回答(摘录)
**完成。**

- **做了什么**:按 PLAN 实现了共有类型内 log 域 Sinkhorn OT 逐细胞位移(β=0 对照与父节点逐位一致,机制生效证据齐全),但代理上所有 β/ε 组合都拉低分数(β=0.4→44.3、β=0.25→54.3,cell_state 反降),已默认关闭并在 METHOD.md 如实记录。改为另一改动:后期阶段抽取比例 `t_draw = 0.5·t`(运行时由 manifest 时间差推出,视图无关)。
- **查分**(11/20 次):默认配置 proxy = **59.88**(父 57.23,+2.65;cell_state 37.5→52.6,expression 60.2,shape 68.5,local 58.3);t_draw 网格 0.1/0.15/0.2/0.3/0.55/0.7 中 0.2 最优。seed 确定性、vec-check 均通过,运行 1.5s。
- **下一步最值得试**:t_draw 与 scale_damp 联合微调(shape_scale 从 74.4 掉到 68.5 是本改动的代价);以及在 final 括号(t=1/3)上确认 t_draw=1/6 不会漏掉 E8.0 新类型的表达变化。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-004922-search-t2-embryo-interp-g24/nodes/5/engineer.jsonl 185 KB
/home/spark-longxinyang/vec/runs/formal/20261003-004922-search-t2-embryo-interp-g24/nodes/5/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数2 次(失败 1 次)
轮数1
工具调用共 0 次:—
用时18 分
token 数输入 6,061 · 输出 1,128 · 思考 3,130
任务(第一行)为节点 n5(改进,父节点 n2)写改进计划 PLAN.json
最后的回答(摘录)
```json
{
  "name": "mix + 共有类型内 Sinkhorn OT 单细胞位移,修复 cell_state",
  "motivation": "父节点 2(mix)总分 57.23 但 cell_state 只有 37.49,显著低于 copy_last 的 49.29,是四组中最弱且唯一倒退的组;METHOD.md 自己承认两端真实细胞混抽不像中间阶段。expression_change 59.72、local_spatial 57.27 还有余量,shape_scale 74.43 最强,故本节点只针对 cell_state。兄弟节点 3(类型内表达插值)已 gen_failed 无产出,本方案改用按类型的 OT 映射做逐细胞位移而非类型内均值插值,机制与实现路径均不同。",
  "approach": "保留父节点 mix 的 bracket/align(procrustes3d)/scale_damp=0.5/按类型分层抽细胞流程不动,在抽样后新增一步:对每个共有类型(n_shared_types≈11),用早阶段该类型细胞的表达(panel 基因,与坐标同源)对晚阶段该类型细胞建代价矩阵(z 化后欧氏距离),跑 log 域稳定化 Sinkhorn(≤200 次迭代),得 barycentric 映射 m_i;每个被抽中的早阶段细胞输出 x_i + β·(m_i − x_i),晚阶段细胞不动;坐标按同一细胞的同一权重 β 做相同位移,保证表达—坐标对应(k027)。参数:ε∈{0.01,0.03,0.1}×(该类型代价矩阵方差),β 初值 = t(final 时 1/3),必要时在 {0.5t, t, 1.5t} 微调;类型两侧 >1000 细胞时随机下采样建图、映射广播回全体。验证顺序:1) 先跑 β=0 对照,确认逐位复现父节点(proxy≈57.2);2) 用每类型 500 细胞的下采样 proxy 在 5 分钟内筛 ε;3) 全量 proxy 选比 57.2(A 半)高 ≥1.5 分的组合(噪声约 1 分,小于 1.5 不算赢,可查 2 次确认)。单输入阶段退路:b is None 时完全跳过 OT,行为与父节点一致。所有参数运行时由 manifest 与代价矩阵推出,不写死任何阶段的统计量。",
  "expected_groups": ["cell_state", "expression_change"],
  "risks": "1) 大类型上 Sinkhorn 慢/费内存:每侧下采样到 ≤1000 并计时,30 分钟时限内先小后大,超时就固定用下采样映射;2) ε 过大导致熵模糊、型内多样性坍缩(反而伤 cell_state):检查输出型内表达标准差较输入下降不超过 ~30%,否则换小 ε;3) 晚阶段细胞不动造成中间时刻双峰:因晚端占比仅 t≈1/3,预计影响有限,以 cell_state 是否净升为准;4) 流水线改动引入隐蔽差异:β=0 对照必须与父节点逐位一致,否则先修管线再谈机制;5) Sinkhorn 数值不稳:用 log 域稳定化,出现 NaN 时该类型退化为最近质心位移。",
  "family_id": "T2EI-02",
  "mechanism": "对共有类型在两个括号阶段之间做熵正则最优传输,barycentric 映射按每个细胞自身在传输计划中的位置给出逐细胞位移,把早阶段细胞推向晚阶段分布,生成真正的过渡态而非端点混抽。",
  "vs_constant_shift": "每型常数位移对型内所有细胞施加同一向量(只移动型均值);本方案的位移随传输计划逐细胞变化(型内不同细胞位移的大小和方向不同),并保留表达—坐标联合移动;β=0 时严格退回父节点。",
  "mechanism_evidence": "1) 型内每细胞位移范数的变异系数明显 >0(若≈0 则退化为常数位移);2) 对照 β=0 与 β>0 的输出逐位不同,且差异细胞集中在早阶段被抽中的细胞;3) 四组分对比:cell_state 上升而 shape_scale 不降 >1 分;4) 输出型内表达离散度保持(下降 <30%),说明不是向均值坍缩。",
  "mechanism_off_control": "同一 run.py 设 β=0(或跳过 OT 步):每细胞位移为零,输出应与父节点 2 逐位相同(同 seed 下表达与坐标矩阵一致),得分回到 ≈57.2;若 β>0 时分数不变,说明机制未生效。",
  "sources": []
}
```
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261003-004922-search-t2-embryo-interp-g24/nodes/5/researcher.jsonl
/home/spark-longxinyang/vec/runs/formal/20261003-004922-search-t2-embryo-interp-g24/nodes/5/researcher.1.jsonl 5 KB
/home/spark-longxinyang/vec/runs/formal/20261003-004922-search-t2-embryo-interp-g24/nodes/5/researcher.stderr

审查员

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