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

总览 · ← 返回运行 20261002-204523-search-t2-heart-interp-g24q

节点 n4 在终选来历上

实现 T2HI-01 类型内配对插值并对照:五个变体在代理上均不优于父 mix,提交为机制关闭(=父节点 mix,逐位一致)。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261002-204523-search-t2-heart-interp-g24q
父节点n2
子节点n5
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 59.50(+0.0) · proxy 59.50(+0.0) · 3 次复测均分 59.36
审查通过 1 越界读取:未发现问题。run.py 只通过 src.task2_spatial.view_io 的 load_manifest/read_stage/panel_genes 读 --data 视图数据,无绝对路径、'..'、/mnt、/home、data/raw、downloads、打分器路径,无联网代码。; 2 硬编码目标统计量:未发现问题。代码中无细胞类型比例/数量/基因列表常量;PARAMS 与 PCA_DIM=30 等为方法超参;类型比例 (1-t)*frac_a+t*frac_b 和 n 均由输入现场统计(run.py L196-L209)。METHOD.md 中的 NCC/P…
用时?从运行开始到结束(或到现在)的挂钟时间。22 分
程序版本414f874ab93dab0330042f81bcda5eedb602dbfc (programs.git)

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

来自 programs.git 414f874ab9:solution/METHOD.md

实现 T2HI-01 类型内配对插值并对照:五个变体在代理上均不优于父 mix,提交为机制关闭(=父节点 mix,逐位一致)。

方法族与实现

PLAN 指定 T2HI-01:对每个共有细胞类型,在表达 PCA(30 维,全局拟合两阶段)空间用 KDTree 最近邻把 E8.25_late 与 E9.5 的细胞配对(数量不等时对少的一侧有放回采样补齐),生成 expr=(1−t)·a+t·b、coords=(1−t)·ca+t·cb(z 同插值)的合成中间态细胞;组成按 (1−t)·frac_a+t·frac_b,n 沿用 log-linear 夹到 [min,max];Procrustes 对齐 + log-linear RMS 缩放与父节点相同。单输入退路(b=None)与父节点一致。

实现了三个变体(run.py 内,环境变量开关,提交默认关闭):

  1. pair(PAIR_FRAC=1.0/0.5):合成插值池(可选与真实端点细胞混合成池)。
  2. hybrid_bernoulli(提交默认 MODE):父 mix 的真实细胞与坐标不变,每个细胞的表达与其类型内 NN 伙伴做逐基因 Bernoulli 混合(a 侧以概率 t 取伙伴值),保留单细胞稀疏结构。
  3. hybrid_convex:同上但表达做凸组合 (1−w)·self+w·partner。

对照结果(vec-score,proxy,seed 0,A 半)

变体榜分cell_stateexpression_changelocal_spatialshape_scale
父 mix(=机制关闭,逐位一致)59.5066.7063.9054.0353.37
pair 全合成58.5063.6366.2853.4250.66
pair 半合成58.8564.5266.2553.2751.36
hybrid_bernoulli59.3166.4364.7053.0453.07
hybrid_bernoulli 仅 a 侧58.9765.9764.0452.7953.07
hybrid_convex59.0665.6164.6952.8853.07

机制关闭对照:T2HI_INTERPOLATE=0 时输出与父节点 run.py(node 2 的 mix)在 proxy seed 0 上 .X 与 obsm 逐位相等(本地 cmp 验证),四组分应与父节点完全相同。

机制生效的证据(pair 变体,stderr diag)

  • 输出 RMS 346.3,介于 rms_a=354.1 与 rms_b=335.0 对应的 target_rms(log-linear,scale_damp=1)。
  • 每个共有类型内插值坐标总方差远小于端点混合方差(NCC 2.96e4 vs 9.85e4;Peri 3.88e4 vs 8.33e4;V-CM 1.38e4 vs 3.65e4;aPHM 1.73e4 vs 6.54e4;pPHM 1.28e4 vs 7.48e4)→ 配对确实产生了更紧凑的中间态邻域,机制按设计改变了这 5 个共有类型的全部细胞(proxy 上共有类型仅 NCC/Peri/V-CM/aPHM/pPHM)。
  • 配对距离中位数:NCC 19.2、aPHM 17.4、pPHM 18.9 超过 2× 型内 a 侧中位距离(~7.4–8.7),已按 PLAN 风险 1 输出警告——proxy 两端相隔 1.25 天且标签词汇不同,跨阶段表达配对质量差;Peri 11.7、V-CM 10.8 未触发警告。
  • 四组分变化方向:expression_change 升(+2.4 全合成 / +0.8 hybrid),cell_state 降(凸组合过平滑伤分布真实性;Bernoulli 保稀疏基本恢复 66.4),shape_scale 降(合成坐标填在两云之间,d2_shape 变差),local_spatial 持平或略降。净效应为负。

结论与提交

与方法卡一致(mix 家族在该榜是平台,OT 类更差):类型内配对插值把 expression_change 的提升被 cell_state/shape_scale/local_spatial 的损失抵消,5 个变体全部 ≤ 父节点。提交为机制关闭状态(T2HI_INTERPOLATE 默认 "0"),逐位等于父 mix(proxy seed 0 本地验证 .X 与 obsm 逐位相等;预期 proxy 59.50,rank3 ≈59.36)。vec-score 共用掉 6 次额度。

验证过 / 未验证

  • 验证:proxy seed 0 上 5 个变体的 vec-score;关闭态与父程序输出逐位一致;vec-check 通过;运行时间(关闭态 ~3s,机制开 ~60s,均远低于 30min/28GB 限制)。
  • 未验证:final 视图(31 个共有类型、t=0.5、两端更近,配对质量应好于 proxy——若后续节点重试 T2HI-01,final 上结论可能不同,但本节点无法查证);seed 1/2 上机制开的复跑(差距为负,不值得额度);坐标加权配对(表达+坐标联合特征)未试。
  • 生物学知识来源:无外部知识;仅使用视图内数据(表达、标签、时间差)与任务书/方法卡中的通用说明(z 为切片轴、心脏尺度非单调)。未使用任何保留阶段/保留基因型信息。
  • 视图无关性:程序只读 manifest 数据与时间差,不读绝对时间/路径/榜名;伪装视图(时间平移)下输出不变(t、Δlog RMS、类型比例均平移不变)。

调研员的计划

名称mix + 类型内配对插值(表达与坐标联合)
动机父节点 mix(59.50)的 local_spatial=54.03 和 shape_scale=53.37 最弱。当前方法只从两端各取整细胞混合,不做真正的细胞级插值,导致空间邻域和尺度形态只是端点的并集而非中间态。兄弟节点 3 的类型质心位移(T2HI-02)已证明单调有害,不可重复。方向库 T2HI-01 中'类型内表达插值'尚未尝试。
做法在父节点 run.py 基础上修改 interpolate 逻辑:
1. 保留 Procrustes 对齐与 log-linear RMS 缩放(scale_damp=1)不变。
2. 对每个共有类型,取 stage_a 和 stage_b 的该类型细胞;若数量不等,对少的一侧有放回采样使数量相等。
3. 在表达空间内用贪心最近邻(scipy KDTree,维度=基因数,若基因>50 则先 PCA 到 30 维)配对 a、b 细胞。
4. 生成插值细胞:expr_i=(1−t)·expr_a_i+t·expr_b_i,coords_i=(1−t)·coords_a_i+t·coords_b_i(z 同样插值)。
5. 总细胞数 n 沿用 log-linear 公式并夹到 [min_cells, max_cells];从插值池中按类型比例抽取 n 个。
6. 单输入阶段退路:若 interp_bracket 返回 b=None,保持父节点行为(直接取最近输入)。
7. vec-score 快速筛选:先在 proxy 上跑 seed 0,若总分 ≤58.5(低于噪声下界)立即回退到父节点逻辑;否则再跑 seed 1 确认。
关键参数:配对用表达空间(不用坐标,避免对齐残差影响);PCA 维度 30(范围 20–50);无额外超参。
风险1) 类型内 a、b 细胞表达差异大时配对质量差,插值产生不真实细胞——Engineer 应在 stderr 输出每类型配对距离中位数,若 >2×该型内 a 侧中位距离则警告。2) 基因维度高时 KDTree 慢——用 PCA 降维解决,30 min 时限内可行(17616 细胞 × 30 维)。3) 提升可能 <1 分噪声——需两次查分(seed 0,1)确认。

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

对比:父节点版本 ab33f143fe。改动的文件:solution/METHOD.md +42 −0、solution/README.md +6 −5、solution/run.py +317 −13

diff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..0109467--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,42 @@+实现 T2HI-01 类型内配对插值并对照:五个变体在代理上均不优于父 mix,提交为机制关闭(=父节点 mix,逐位一致)。++## 方法族与实现++PLAN 指定 T2HI-01:对每个共有细胞类型,在表达 PCA(30 维,全局拟合两阶段)空间用 KDTree 最近邻把 E8.25_late 与 E9.5 的细胞配对(数量不等时对少的一侧有放回采样补齐),生成 expr=(1−t)·a+t·b、coords=(1−t)·ca+t·cb(z 同插值)的合成中间态细胞;组成按 (1−t)·frac_a+t·frac_b,n 沿用 log-linear 夹到 [min,max];Procrustes 对齐 + log-linear RMS 缩放与父节点相同。单输入退路(b=None)与父节点一致。++实现了三个变体(`run.py` 内,环境变量开关,提交默认关闭):++1. `pair`(PAIR_FRAC=1.0/0.5):合成插值池(可选与真实端点细胞混合成池)。+2. `hybrid_bernoulli`(提交默认 MODE):父 mix 的真实细胞与坐标不变,每个细胞的表达与其类型内 NN 伙伴做逐基因 Bernoulli 混合(a 侧以概率 t 取伙伴值),保留单细胞稀疏结构。+3. `hybrid_convex`:同上但表达做凸组合 (1−w)·self+w·partner。++## 对照结果(vec-score,proxy,seed 0,A 半)++| 变体 | 榜分 | cell_state | expression_change | local_spatial | shape_scale |+|---|---:|---:|---:|---:|---:|+| 父 mix(=机制关闭,逐位一致) | 59.50 | 66.70 | 63.90 | 54.03 | 53.37 |+| pair 全合成 | 58.50 | 63.63 | 66.28 | 53.42 | 50.66 |+| pair 半合成 | 58.85 | 64.52 | 66.25 | 53.27 | 51.36 |+| hybrid_bernoulli | 59.31 | 66.43 | 64.70 | 53.04 | 53.07 |+| hybrid_bernoulli 仅 a 侧 | 58.97 | 65.97 | 64.04 | 52.79 | 53.07 |+| hybrid_convex | 59.06 | 65.61 | 64.69 | 52.88 | 53.07 |++机制关闭对照:`T2HI_INTERPOLATE=0` 时输出与父节点 run.py(node 2 的 mix)在 proxy seed 0 上 `.X` 与 `obsm` 逐位相等(本地 cmp 验证),四组分应与父节点完全相同。++## 机制生效的证据(pair 变体,stderr diag)++- 输出 RMS 346.3,介于 rms_a=354.1 与 rms_b=335.0 对应的 target_rms(log-linear,scale_damp=1)。+- 每个共有类型内插值坐标总方差远小于端点混合方差(NCC 2.96e4 vs 9.85e4;Peri 3.88e4 vs 8.33e4;V-CM 1.38e4 vs 3.65e4;aPHM 1.73e4 vs 6.54e4;pPHM 1.28e4 vs 7.48e4)→ 配对确实产生了更紧凑的中间态邻域,机制按设计改变了这 5 个共有类型的全部细胞(proxy 上共有类型仅 NCC/Peri/V-CM/aPHM/pPHM)。+- 配对距离中位数:NCC 19.2、aPHM 17.4、pPHM 18.9 超过 2× 型内 a 侧中位距离(~7.4–8.7),已按 PLAN 风险 1 输出警告——proxy 两端相隔 1.25 天且标签词汇不同,跨阶段表达配对质量差;Peri 11.7、V-CM 10.8 未触发警告。+- 四组分变化方向:expression_change 升(+2.4 全合成 / +0.8 hybrid),cell_state 降(凸组合过平滑伤分布真实性;Bernoulli 保稀疏基本恢复 66.4),shape_scale 降(合成坐标填在两云之间,d2_shape 变差),local_spatial 持平或略降。净效应为负。++## 结论与提交++与方法卡一致(mix 家族在该榜是平台,OT 类更差):类型内配对插值把 expression_change 的提升被 cell_state/shape_scale/local_spatial 的损失抵消,5 个变体全部 ≤ 父节点。提交为机制关闭状态(`T2HI_INTERPOLATE` 默认 "0"),逐位等于父 mix(proxy seed 0 本地验证 `.X` 与 `obsm` 逐位相等;预期 proxy 59.50,rank3 ≈59.36)。vec-score 共用掉 6 次额度。++## 验证过 / 未验证++- 验证:proxy seed 0 上 5 个变体的 vec-score;关闭态与父程序输出逐位一致;vec-check 通过;运行时间(关闭态 ~3s,机制开 ~60s,均远低于 30min/28GB 限制)。+- 未验证:final 视图(31 个共有类型、t=0.5、两端更近,配对质量应好于 proxy——若后续节点重试 T2HI-01,final 上结论可能不同,但本节点无法查证);seed 1/2 上机制开的复跑(差距为负,不值得额度);坐标加权配对(表达+坐标联合特征)未试。+- 生物学知识来源:无外部知识;仅使用视图内数据(表达、标签、时间差)与任务书/方法卡中的通用说明(z 为切片轴、心脏尺度非单调)。未使用任何保留阶段/保留基因型信息。+- 视图无关性:程序只读 manifest 数据与时间差,不读绝对时间/路径/榜名;伪装视图(时间平移)下输出不变(t、Δlog RMS、类型比例均平移不变)。diff --git a/solution/README.md b/solution/README.mdindex 3cb16cd..eebfede 100644--- a/solution/README.md+++ b/solution/README.md@@ -1,6 +1,7 @@-# mix(T2:heart:val_interp)+# node 4 (improve, parent=2 mix): T2HI-01 类型内配对插值 —— 对照后提交为关闭态 -T2 方法卡的心脏插值选择:`mix`,align=procrustes(xy 用共有类型质心 Kabsch,z 保持为切片轴,只定符号),scale_damp=1。取目标前后最近的两个输入,t=(目标−a)/(b−a),把两朵云缩放到 log 线性 RMS,按 (1−t, t) 分层抽真实细胞,表达和坐标一起走;细胞数 log 线性后夹到 [1000, 17616]。代码在 `modeling/src/task2_spatial/`(`view_io.py` + `methods.interpolate`)。-proxy(E8.25_late + E9.5 → E8.75,t=0.4)预期 59.12(seed 0 实测 59.116,与方法卡一致;表达 63.7 / 状态 66.5 / 形状 53.2 / 邻域 53.0)。-final(E8.25_late + E8.75 → E8.5,t=0.5):n=17616,RMS 277.1,共有类型 31,与 `data/processed/t2/T2__heart__val_interp__mix.h5ad` 逐位相同。-已知弱点:代理两端 RMS 都大(354/335),测不到心脏尺度的非单调;proxy 只有 5 个共有类型,对齐在 final 上更可靠。+提交默认 = 父节点 mix(`T2HI_INTERPOLATE=0`,输出与 node 2 逐位一致,proxy 59.50)。+机制实现(配对插值 / hybrid Bernoulli / hybrid convex)保留在 `run.py` 中,用+`T2HI_INTERPOLATE=1`(可选 `T2HI_MODE=pair|hybrid_bernoulli`、`T2HI_BLEND=bernoulli|convex`、+`T2HI_SIDE=both|a|b`、`T2HI_PAIR_FRAC`)复现;全部变体在 proxy 上 58.5–59.3,低于父节点。+证据、对照与结论见 METHOD.md。diff --git a/solution/run.py b/solution/run.pyindex 8c61767..11ae220 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,28 +1,325 @@ #!/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 + within-type paired interpolation (T2HI-01) for T2 heart interpolation.++Bracket the target with the nearest inputs before/after, align both clouds in+one frame (procrustes: xy Kabsch on shared-type centroids, z kept as slice+axis, sign fixed), rescale both to the log-linear RMS. Then, instead of only+mixing whole endpoint cells:++* for every cell type present in BOTH stages, pair a-cells with b-cells by+  nearest neighbour in expression-PCA space (30 dims, fitted on both stages)+  within the type, drawing with replacement on the smaller side to equalise+  counts, and synthesise cells+      expr   = (1-t) * expr_a + t * expr_b+      coords = (1-t) * ca_pair + t * cb_pair   (z interpolated too)+* cells of types present in only one stage stay real (expression carried,+  coordinates from that stage's aligned+scaled cloud);+* the output composition matches the parent mix expectation: per-type+  fraction (1-t)*frac_a + t*frac_b, total n log-linear in t and clipped to+  the board range.++Set INTERPOLATE=False (or env T2HI_INTERPOLATE=0) to fall back bit-for-bit to+the parent seed mix path (same params, same rng usage) as a mechanism-off+control. """  from __future__ import annotations  import argparse import json+import os import sys  import numpy as np +from src.task2_spatial.frame import align_pair, log_interp, rms_radius, scale_to_rms from src.task2_spatial.methods import interpolate-from src.task2_spatial.sample import take-from src.task2_spatial.view_io import board_params, interp_bracket, load_manifest, panel_genes, read_stage, write_t2+from src.task2_spatial.sample import interp_count, 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": "procrustes", "scale_damp": 1.0}+INTERPOLATE = os.environ.get("T2HI_INTERPOLATE", "0") == "1"+PAIR_FRAC = float(os.environ.get("T2HI_PAIR_FRAC", "1.0"))+MODE = os.environ.get("T2HI_MODE", "hybrid_bernoulli")+BLEND = os.environ.get("T2HI_BLEND", "bernoulli")+SIDE = os.environ.get("T2HI_SIDE", "both")+PCA_DIM = 30+PCA_FIT_CELLS = 20000+++def _jitter(coords: np.ndarray, rng: np.random.Generator) -> np.ndarray:+    if len(coords) < 2:+        return coords+    rounded = np.round(coords, 5)+    _, inv, counts = np.unique(rounded, axis=0, return_inverse=True, return_counts=True)+    if counts.max() <= 1:+        return coords+    rms = rms_radius(coords) + 1e-8+    noise = rng.normal(0.0, 1e-4 * rms, size=coords.shape)+    out = coords.copy()+    dup = counts[inv] > 1+    out[dup] = out[dup] + noise[dup]+    return out+++def _largest_remainder(fracs: dict, n: int, caps: dict) -> dict:+    types = sorted(fracs)+    raw = np.array([fracs[t] * n for t in types], dtype=np.float64)+    alloc = np.floor(raw).astype(int)+    caps_arr = np.array([caps[t] for t in types], dtype=int)+    alloc = np.minimum(alloc, caps_arr)+    rem = int(n - alloc.sum())+    order = np.argsort(-(raw - np.floor(raw)))+    i = 0+    guard = 0+    while rem > 0 and guard < 10 * len(types) + 100:+        j = order[i % len(order)]+        if alloc[j] < caps_arr[j]:+            alloc[j] += 1+            rem -= 1+        i += 1+        guard += 1+        if i > 0 and i % len(order) == 0 and all(alloc >= caps_arr):+            break+    return {t: int(alloc[k]) for k, t in enumerate(types)}+++def pair_interpolate(stage_a, stage_b, t: float, params: dict):+    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)))+    from scipy.spatial import cKDTree+    from sklearn.decomposition import PCA++    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)+    lo = int(params["min_cells"])+    hi = int(params["max_cells"])+    n = interp_count(stage_a.n, stage_b.n, t, lo, hi, 1.0)++    la = np.asarray(stage_a.labels).astype(str)+    lb = np.asarray(stage_b.labels).astype(str)+    types_a = {t_: np.flatnonzero(la == t_) for t_ in np.unique(la)}+    types_b = {t_: np.flatnonzero(lb == t_) for t_ in np.unique(lb)}+    shared = sorted(set(types_a) & set(types_b))+    only_a = sorted(set(types_a) - set(types_b))+    only_b = sorted(set(types_b) - set(types_a))++    # global expression PCA for pairing features+    sub_a = rng.choice(stage_a.n, size=min(PCA_FIT_CELLS, stage_a.n), replace=False)+    sub_b = rng.choice(stage_b.n, size=min(PCA_FIT_CELLS, stage_b.n), replace=False)+    fit_mat = np.vstack([as_dense(stage_a.X, sub_a), as_dense(stage_b.X, sub_b)]).astype(np.float32)+    dim = min(PCA_DIM, fit_mat.shape[1], fit_mat.shape[0] - 1)+    pca = PCA(n_components=max(dim, 2), svd_solver="full")+    pca.fit(fit_mat)+    del fit_mat+    Za = pca.transform(as_dense(stage_a.X).astype(np.float32))+    Zb = pca.transform(as_dense(stage_b.X).astype(np.float32))++    Xa_full = None+    Xb_full = None+    if shared:+        Xa_full = as_dense(stage_a.X)+        Xb_full = as_dense(stage_b.X)++    expr_parts = []+    coord_parts = []+    pool_type = []+    pool_sizes = {}+    diag = {"pair_dist": {}, "var_interp": {}, "var_mix": {}}++    for typ in shared + only_a + only_b:+        ia = types_a.get(typ)+        ib = types_b.get(typ)+        if ia is not None and ib is not None:+            m = int(max(ia.size, ib.size))+            sa = ia if ia.size == m else rng.choice(ia, m, replace=True)+            sb = ib if ib.size == m else rng.choice(ib, m, replace=True)+            tree = cKDTree(Zb[sb])+            dist, partner = tree.query(Za[sa], k=1)+            # pairing-quality diagnostics (PLAN risk 1)+            within, _ = cKDTree(Za[ia]).query(Za[ia], k=2)+            med_pair = float(np.median(dist))+            med_within = float(np.median(within[:, 1]))+            diag["pair_dist"][typ] = {"pair": med_pair, "within_a": med_within,+                                      "warn": bool(med_pair > 2.0 * med_within)}+            pa = sa+            pb = sb[partner]+            xe_s = np.clip((1.0 - t) * Xa_full[pa] + t * Xb_full[pb], 0.0, None).astype(np.float32)+            xc_s = (1.0 - t) * ca[pa] + t * cb[pb]+            mix_c = np.vstack([ca[pa], cb[pb]])+            diag["var_interp"][typ] = float(xc_s.var(axis=0).sum())+            diag["var_mix"][typ] = float(mix_c.var(axis=0).sum())+            if PAIR_FRAC >= 1.0:+                xe, xc = xe_s, xc_s+            else:+                k = int(round(PAIR_FRAC * m))+                keep_s = np.zeros(m, dtype=bool)+                keep_s[rng.choice(m, k, replace=False)] = True+                slots = np.flatnonzero(~keep_s)+                n_real = slots.size+                n_ra = int(round((1.0 - t) * n_real))+                ra = slots[rng.choice(n_real, n_ra, replace=False)] if n_ra else np.array([], int)+                rb = slots[rng.choice(n_real, n_real - n_ra, replace=False)] if n_real - n_ra else np.array([], int)+                xe = np.vstack([xe_s[keep_s], Xa_full[pa[ra]], Xb_full[pb[rb]]]).astype(np.float32)+                xc = np.vstack([xc_s[keep_s], ca[pa[ra]], cb[pb[rb]]])+        elif ia is not None:+            xe = Xa_full[ia].astype(np.float32) if Xa_full is not None else as_dense(stage_a.X, ia)+            xc = ca[ia]+        else:+            xe = Xb_full[ib].astype(np.float32) if Xb_full is not None else as_dense(stage_b.X, ib)+            xc = cb[ib]+        expr_parts.append(xe)+        coord_parts.append(np.asarray(xc, dtype=np.float64))+        pool_type.append(np.full(xe.shape[0], len(pool_sizes), dtype=int))+        pool_sizes[len(pool_sizes)] = typ++    expr_pool = np.clip(np.vstack(expr_parts), 0.0, None).astype(np.float32)+    coord_pool = np.vstack(coord_parts)+    pool_type = np.concatenate(pool_type)++    # composition = (1-t)*frac_a + t*frac_b (matches parent mix expectation)+    na_tot = float(stage_a.n)+    nb_tot = float(stage_b.n)+    all_types = shared + only_a + only_b+    fr = {}+    caps = {}+    for k, typ in enumerate(all_types):+        fa = len(types_a.get(typ, [])) / na_tot+        fb = len(types_b.get(typ, [])) / nb_tot+        fr[typ] = (1.0 - t) * fa + t * fb+        caps[typ] = int((pool_type == k).sum())+    s = sum(fr.values())+    fr = {k_: v / s for k_, v in fr.items()}+    counts = _largest_remainder(fr, n, caps)++    picks = []+    for k, typ in enumerate(all_types):+        c = counts[typ]+        if c <= 0:+            continue+        idx = np.flatnonzero(pool_type == k)+        if c <= idx.size:+            picks.append(rng.choice(idx, c, replace=False))+        else:+            picks.append(rng.choice(idx, c, replace=True))+    sel = np.sort(np.concatenate(picks)) if picks else np.array([], dtype=int)++    expr = expr_pool[sel]+    coords = _jitter(coord_pool[sel], 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,+        target_rms=target_rms, out_rms=rms_radius(coords),+        scale_damp=damp, align=align, n_shared_types=len(shared),+        method="mix_pair_interp", diag=diag,+    )+    return expr, coords.astype(np.float32), info+++def hybrid_bernoulli(stage_a, stage_b, t: float, params: dict):+    """Parent mix selection (real cells + real coords), expression blended with+    the within-type nearest-neighbour partner from the other stage by a+    per-gene Bernoulli draw (keeps single-cell sparsity, shifts levels to t)."""+    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)))+    from scipy.spatial import cKDTree+    from sklearn.decomposition import PCA+    from src.task2_spatial.sample import mix_indices+    from src.task2_spatial.methods import _jitter as _jit++    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)+    lo = int(params["min_cells"])+    hi = int(params["max_cells"])+    n = interp_count(stage_a.n, stage_b.n, t, lo, hi, 1.0)+    ia, ib = mix_indices(stage_a.labels, stage_b.labels, t, n, rng)++    Xa = as_dense(stage_a.X)+    Xb = as_dense(stage_b.X)+    la = np.asarray(stage_a.labels).astype(str)+    lb = np.asarray(stage_b.labels).astype(str)++    sub_a = rng.choice(stage_a.n, size=min(PCA_FIT_CELLS, stage_a.n), replace=False)+    sub_b = rng.choice(stage_b.n, size=min(PCA_FIT_CELLS, stage_b.n), replace=False)+    fit_mat = np.vstack([Xa[sub_a], Xb[sub_b]])+    dim = min(PCA_DIM, fit_mat.shape[1], fit_mat.shape[0] - 1)+    pca = PCA(n_components=max(dim, 2), svd_solver="full")+    pca.fit(fit_mat)+    del fit_mat+    Za = pca.transform(Xa)+    Zb = pca.transform(Xb)++    expr = np.vstack([Xa[ia], Xb[ib]]).astype(np.float64)+    other = np.empty_like(expr)+    has_partner = np.zeros(expr.shape[0], dtype=bool)+    weights = np.empty(expr.shape[0], dtype=np.float64)+    weights[: ia.size] = t          # a-side cells take b value with prob t+    weights[ia.size :] = 1.0 - t    # b-side cells take a value with prob 1-t+    if SIDE == "a":+        weights[ia.size :] = 0.0+    elif SIDE == "b":+        weights[: ia.size] = 0.0+    diag = {"pair_dist": {}}+    for side, sel, Zself, Zoth, Xoth, labs_self, labs_oth, base in (+        ("a", ia, Za, Zb, Xb, la, lb, 0),+        ("b", ib, Zb, Za, Xa, lb, la, ia.size),+    ):+        if sel.size == 0:+            continue+        for typ in np.unique(labs_self[sel]):+            rows = np.flatnonzero(labs_self[sel] == typ)+            self_all = np.flatnonzero(labs_self == typ)+            oth_all = np.flatnonzero(labs_oth == typ)+            if oth_all.size == 0:+                continue+            tree = cKDTree(Zoth[oth_all])+            dist, part = tree.query(Zself[sel[rows]], k=1)+            diag["pair_dist"][f"{side}:{typ}"] = float(np.median(dist))+            gi = base + rows+            other[gi] = Xoth[oth_all[part]]+            has_partner[gi] = True+    if BLEND == "convex":+        w = weights[:, None]+        expr = np.where(has_partner[:, None], (1.0 - w) * expr + w * other, expr)+    else:+        u = rng.random(size=expr.shape)+        take_other = has_partner[:, None] & (u < weights[:, None])+        expr = np.where(take_other, other, expr)+    expr = np.clip(expr, 0.0, None).astype(np.float32)++    coords = _jit(np.vstack([ca[ia], cb[ib]]), 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,+        target_rms=target_rms, out_rms=rms_radius(coords),+        scale_damp=damp, align=align, method="hybrid_bernoulli",+        n_paired=int(has_partner.sum()), diag=diag,+    )+    return expr, coords.astype(np.float32), info   def main() -> None:@@ -44,9 +341,16 @@ 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")}+    if INTERPOLATE and MODE == "hybrid_bernoulli":+        expr, coords, info = hybrid_bernoulli(stage_a, stage_b, t, params)+    elif INTERPOLATE:+        expr, coords, info = pair_interpolate(stage_a, stage_b, t, params)+    else:+        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", "method")}     print(json.dumps({"bracket": [a["stage"], b["stage"]], **keep}, default=float), file=sys.stderr)+    if "diag" in info:+        print(json.dumps({"diag": info["diag"]}, default=float), file=sys.stderr)     write_t2(args.out, expr, coords, genes, seed=args.seed)  

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

用到的知识库条目

编号标题出处
k027Joint expression-geometry generation with relative geometrynotes/competition/03_solution_landscape.md
k003Fused Gromov-Wasserstein mapping for spatial snapshots10.1038/s41586-024-08453-2
k007Interval staging and held-out-window filtering of external datanotes/official/来件/virtualembryo.ai/rules.md

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

改了什么实现了 T2HI-01 类型内表达-PCA 最近邻配对插值(全合成/半合成)及两个 hybrid 变体(真实细胞+坐标,表达与 NN 伙伴做逐基因 Bernoulli 或凸组合混合);proxy 上 5 个变体全部 ≤ 父节点(58.50–59.31 vs 59.50),最终按 T2HI_INTERPOLATE=0 提交关闭态,输出与父节点 mix 逐位一致(变化量表四组 delta 全为 0.00 印证)。
各组分数的变化cell_state:噪声内(+0.00,66.70=父);机制开变体在 proxy 上降到 63.6–66.4
expression_change:噪声内(+0.00,63.90=父);提交态即父 mix,机制开的 proxy 测试中曾 +0.8~+2.4 但未提交
local_spatial:噪声内(+0.00,54.03=父);机制开变体在 proxy 上持平或略降(52.8–53.4)
shape_scale:噪声内(+0.00,53.37=父);机制开变体在 proxy 上降到 50.7–53.1
family_idT2HI-01
假设是否成立否
经验
  1. 在该榜(proxy 两端相隔 1.25 天、标签词汇不同)做类型内表达-PCA 最近邻配对时,NCC/aPHM/pPHM 的配对距离中位数超过 2× 型内 a 侧中位距离,跨阶段配对质量差,插值细胞不真实,全合成变体榜分 −1.0。
  2. 类型内配对插值的机制本身确实生效(插值坐标类型内方差比端点混合低 2–6 倍、输出 RMS 介于两端之间、expression_change +0.8~+2.4),但收益被 cell_state(凸组合过平滑破坏单细胞稀疏分布)和 shape_scale(合成坐标填在两云之间损害 d2_shape)的损失抵消,净效应为负。
  3. 保留真实细胞与坐标、只对表达做逐基因 Bernoulli 混合(保稀疏)是最接近打平的变体(59.31 vs 59.50),说明混合表达时保住单细胞稀疏结构能几乎完全恢复 cell_state(66.43 vs 66.70),但 shape/local 的微小损失仍使其略负。
  4. 机制关闭对照(T2HI_INTERPOLATE=0 逐位等于父输出)+ 提交前多 seed/多变体 proxy 快筛的流程有效:花 6 次查分额度避免了一次负分提交,值得沿用。
  5. mix 家族在该榜已两次(node 3 质心位移、node 4 配对插值)确认是平台,对表达/坐标做任何逐细胞改造的边际收益都被其它组的损失抵消。
mechanism_active否
下一步建议
  1. 针对 expression_change/cell_state:若在 final 括号(31 个共有类型、两端时间更近、配对质量应更好)重试,只提交 hybrid_bernoulli(T2HI_MODE=hybrid_bernoulli, SIDE=both),并先在 proxy seed 0+1 双 seed 确认 ≥59.5 再上 final;否则不要再投入 T2HI-01。
  2. 针对 shape_scale/local_spatial:放弃表达侧改造,改试按类型分别调 RMS 缩放(每型 scale 到 (1−t)·rms_a(type)+t·rms_b(type) 而非全局单一 target_rms),proxy 快筛同流程。
  3. 针对 local_spatial:试坐标加权配对(表达 PCA 与对齐坐标拼接作 NN 特征,坐标权重 0.3–0.5),仅在决定重试 T2HI-01 时作为 hybrid_bernoulli 的配对改进,不单独提交全合成。

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

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

分析员

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

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数36
工具调用共 46 次:bash 24、edit 15、read 5、write 2
用时20 分
token 数输入 72,428 · 输出 14,274 · 思考 12,731
任务(第一行)按计划修改程序:mix + 类型内配对插值(表达与坐标联合)
最后的回答(摘录)
Done. Final state verified: `solution/run.py` (default = mechanism off) reproduces parent mix bit-for-bit on proxy seed 0, passes `vec-check`, runtime ~3s.

**做了什么**:按 PLAN 实现 T2HI-01(类型内表达-PCA 最近邻配对 + (1−t,t) 联合插值表达/坐标),并额外实现 hybrid 变体(真实细胞与坐标、逐基因 Bernoulli / 凸组合与 NN 伙伴混合表达、仅 a 侧混合);`T2HI_INTERPOLATE=0` 关闭对照与父节点输出逐位一致。

**查分结果**(proxy seed 0,A 半;父 59.50):全合成配对 58.50、半合成 58.85、hybrid Bernoulli 59.31、hybrid convex 59.06、仅 a 侧 58.97。机制证据成立(插值坐标类型内方差比端点混合低 2–6 倍,expression_change +0.8~+2.4),但 cell_state/shape_scale/local_spatial 的损失抵消收益——凸组合过平滑伤 cell_state,合成坐标伤 shape。5 个变体全部 ≤ 父节点,故按节点 3 先例提交关闭态(=父 mix,预期 59.50)。用了 5 次查分额度。

**下一步最值得试**:(1) 保稀疏的表达混合已在 hybrid_bernoulli 里接近打平(59.31),若配对质量更好(final 上 31 个共有类型 vs proxy 5 个、时间跨度更短)结论可能反转,值得在 final 相邻括号上复验;(2) local_spatial/shape_scale 是平台,建议换靶点:坐标加权配对(表达+坐标联合特征)或按类型分别调 RMS 缩放而非全局缩放。
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-204523-search-t2-heart-interp-g24q/nodes/4/engineer.jsonl 235 KB
/home/spark-longxinyang/vec/runs/formal/20261002-204523-search-t2-heart-interp-g24q/nodes/4/engineer.stderr

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数1
工具调用共 0 次:—
用时1 分
token 数输入 6,276 · 输出 935 · 思考 2,357
任务(第一行)为节点 n4(改进,父节点 n2)写改进计划 PLAN.json
最后的回答(摘录)
{
  "name": "mix + 类型内配对插值(表达与坐标联合)",
  "motivation": "父节点 mix(59.50)的 local_spatial=54.03 和 shape_scale=53.37 最弱。当前方法只从两端各取整细胞混合,不做真正的细胞级插值,导致空间邻域和尺度形态只是端点的并集而非中间态。兄弟节点 3 的类型质心位移(T2HI-02)已证明单调有害,不可重复。方向库 T2HI-01 中'类型内表达插值'尚未尝试。",
  "approach": "在父节点 run.py 基础上修改 interpolate 逻辑:\n1. 保留 Procrustes 对齐与 log-linear RMS 缩放(scale_damp=1)不变。\n2. 对每个共有类型,取 stage_a 和 stage_b 的该类型细胞;若数量不等,对少的一侧有放回采样使数量相等。\n3. 在表达空间内用贪心最近邻(scipy KDTree,维度=基因数,若基因>50 则先 PCA 到 30 维)配对 a、b 细胞。\n4. 生成插值细胞:expr_i=(1−t)·expr_a_i+t·expr_b_i,coords_i=(1−t)·coords_a_i+t·coords_b_i(z 同样插值)。\n5. 总细胞数 n 沿用 log-linear 公式并夹到 [min_cells, max_cells];从插值池中按类型比例抽取 n 个。\n6. 单输入阶段退路:若 interp_bracket 返回 b=None,保持父节点行为(直接取最近输入)。\n7. vec-score 快速筛选:先在 proxy 上跑 seed 0,若总分 ≤58.5(低于噪声下界)立即回退到父节点逻辑;否则再跑 seed 1 确认。\n关键参数:配对用表达空间(不用坐标,避免对齐残差影响);PCA 维度 30(范围 20–50);无额外超参。",
  "expected_groups": ["local_spatial", "shape_scale", "expression_change"],
  "risks": "1) 类型内 a、b 细胞表达差异大时配对质量差,插值产生不真实细胞——Engineer 应在 stderr 输出每类型配对距离中位数,若 >2×该型内 a 侧中位距离则警告。2) 基因维度高时 KDTree 慢——用 PCA 降维解决,30 min 时限内可行(17616 细胞 × 30 维)。3) 提升可能 <1 分噪声——需两次查分(seed 0,1)确认。",
  "family_id": "T2HI-01",
  "mechanism": "对每个共有类型内的细胞做表达空间最近邻配对,然后按 (1−t, t) 线性插值表达和坐标,生成真正处于中间时间状态的合成细胞,而非只从端点抽取整细胞。",
  "vs_constant_shift": "常数位移对每个类型施加同一向量;本方案为每对配对细胞生成独立的插值状态,位移方向和大小取决于该细胞在表达空间中的配对对象,是逐细胞、非均匀的。",
  "mechanism_evidence": "Engineer 应输出:(1) 插值后坐标的 RMS 是否介于 a、b 的 RMS 之间;(2) 每类型内插值坐标的方差是否小于端点混合的方差(说明邻域更紧凑);(3) 四组分各自相对父节点的变化方向。若插值坐标方差 ≥ 端点混合方差,说明配对无效。",
  "mechanism_off_control": "在 run.py 中设 INTERPOLATE=False 开关:关闭时回退到父节点的 stratified 整细胞抽取逻辑(逐位一致)。预期关闭后四组分与父节点完全相同(66.70/63.90/54.03/53.37),若不同则实现有 bug。",
  "sources": []
}
原始记录位置/home/spark-longxinyang/vec/runs/formal/20261002-204523-search-t2-heart-interp-g24q/nodes/4/researcher.jsonl 4 KB
/home/spark-longxinyang/vec/runs/formal/20261002-204523-search-t2-heart-interp-g24q/nodes/4/researcher.stderr

审查员

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