总览 · ← 返回运行 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(envT2_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.py1.5s 跑完,vec-checkok,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__":
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k007 | Interval staging and held-out-window filtering of external data | notes/official/来件/virtualembryo.ai/rules.md |
| k028 | LLM-driven code evolution for analysis algorithms | 10.64898/2026.02.26.707870 |
| k027 | Joint expression-geometry generation with relative geometry | notes/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_id | other |
| 假设是否成立 | 否 |
| 经验 |
|
| mechanism_active | 否 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、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 |