总览 · ← 返回运行 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)。
流程:
vio.interp_bracket(manifest)取目标两侧最近输入 (a, b) 与t=(target−a)/(b−a);不被括号(外推/单输入)则退路为分层复制 anchor 阶段到 [min,max],不崩。- 坐标对齐:
frame.align_pair(..., "procrustes")(xy Kabsch 允许反射,z 符号按共有类型质心相关定向,z 不旋转),两侧再scale_to_rms到 log-线性目标 RMS(scale_damp=1)。 - logit 组成插值(机制核心之一):共有类型(两侧各 ≥5 细胞)用
q=sigmoid((1−t)·logit(p_a)+t·logit(p_b));单侧类型按该侧权重×(1−t)或×t;归一化后按总细胞数N=interp_count(log-线性,夹到 [min,max])分配每型配额(带上限+余额重分配)。 - 类型内最近邻伪批量插值(机制核心之二):共有类型按配额从两侧各用 k-NN 贪心区域生长(
KNN_K=25、约N_CLUSTERS=10个空间连续簇)抽细胞;对每个抽出细胞,在同类型对侧的表达 PCA(前PCA_DIM=50维)空间找最近邻,做凸组合x=(1−t)·x_self + t·x_nn(产生连续过渡的合成细胞,非整细胞混抽)。单侧类型直接取真实细胞。 - 合并 →
_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 的取舍。
调研员的计划
| 名称 | 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()
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k007 | Interval staging and held-out-window filtering of external data | notes/official/来件/virtualembryo.ai/rules.md |
| k023 | Time-split validation with a held-out intermediate or next time point | notes/handover/02_知识学习路线.md |
| k028 | LLM-driven code evolution for analysis algorithms | 10.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_id | T2HI-01 |
| 假设是否成立 | 是 |
| 经验 |
|
| mechanism_active | 是 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、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 |