总览 · ← 返回运行 20261003-172000-search-t2-heart-extrap-chain-12h
节点 n12
表达kNN引导坐标置换(T2HX-06,node5复刻):机制已实现并实测,贪心2-opt交换49759对细胞-坐标配对使内部cosine-15NN目标下降,但neighborhood_mmd原始值反升(0.11445→0.1152)、d2_shape变差,总分49.85<地板50,按PLAN停止规则默认关闭(VEC_NSWAPS=0),提交输出逐位等于copy_last。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261003-172000-search-t2-heart-extrap-chain-12h |
|---|---|
| 父节点 | n3 |
| 子节点 | n14 |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 改进 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 50.00(+0.0) · proxy_noscale 50.00(+0.0) |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 33 分 |
| 程序版本 | c6ef840565dd4c89f712e278c2965446610983cd (programs.git) |
| 导入自 | 20261003-105537-search-t2-heart-extrap-g24-D#12 |
| 备注 | re-scored at launch (origin 20261003-105537-search-t2-heart-extrap-g24-D node 12, score there 50.00) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git c6ef840565:solution/METHOD.md
表达kNN引导坐标置换(T2HX-06,node5复刻):机制已实现并实测,贪心2-opt交换49759对细胞-坐标配对使内部cosine-15NN目标下降,但neighborhood_mmd原始值反升(0.11445→0.1152)、d2_shape变差,总分49.85<地板50,按PLAN停止规则默认关闭(VEC_NSWAPS=0),提交输出逐位等于copy_last。
方法
- 基座与父节点3完全相同:anchor=末观测阶段,n 夹到 [min_cells, max_cells],
take按型分层抽样,X 与坐标原样输出。 - 机制层(T2HX-06,
VEC_NSWAPS>0时启用):- 表达 kNN:完整表达矩阵 cosine,k=15(与评分 neighborhood_mmd 的 15-NN 对齐),另取 16 邻做候选;
- 空间 kNN:坐标 euclidean,缓存 pool=60 近邻;
- 候选对:表达 kNN 中不在空间 15-NN 内的 (i,j),去重后按 seed 洗牌;
- 贪心 2-opt:交换 i、j 坐标,Δ = 受影响细胞集(i、j、两者的空间近邻)各自"与空间 15-NN 表达的 cosine 距离和"的变化(候选池 = 静态 pool + 显式加入换位的 i/j),Δ<0 接受并交换 pool,无交换次数上限(修正 node10 的预算约束);迭代 3 轮,每轮重算空间 kNN。
- 输出保证:X 逐位不变,坐标点集(多重集)逐位不变(程序内校验),仅细胞-坐标配对改变。
- 机制关闭对照(PLAN mechanism_off_control):
VEC_NSWAPS=0(当前默认)跳过全部交换循环。
对照与实测(proxy_noscale,3 次查分)
- 对照 VEC_NSWAPS=0:50.00,四组全 50,输出 = copy_last。✓(管线无副作用)
- 全量置换(3 轮,49759 次交换,412s,坐标多重集校验通过):49.85
- neighborhood_mmd 原始 0.11445 → 0.1152(升高,机制方向失败),local_spatial 49.84;
- d2_shape 0.04891 → 0.04944(变差),occupancy_dice 0.8148 → 0.8145(差 0.0003 < 0.002,按 PLAN 视为噪声),shape_scale 49.57;
- 表达两组、cell_state 逐位不变(机制只动配对)。
- PLAN 停止规则:"若 neighborhood_mmd 原始值未降,停止调参、以对照提交" → 触发,默认 VEC_NSWAPS=0。
机制证据与结论(family T2HX-06)
- 机制确实生效:改变了 49759 对细胞-坐标配对(约 2×细胞数的配对尝试中接受 ~1.6 万次/轮),内部目标(Σ cosine 距离到空间 15-NN)单调下降。
- 但内部目标与评分指标不同向:把表达相似的细胞搬成空间邻居会"锐化"预测的邻域平均表达分布,而真值 E9.5 的邻域平均分布并不更锐,MMD 反而变大。这解释了 node5(52.74)的 shape_scale 59.99 更可能来自行序改变导致的评分器子抽样差异(PLAN 风险 1),而非可复现的机制收益——本次实现中 occupancy_dice 变化 < 0.002(噪声),d2_shape 变差。
- node7/9(Dirichlet 能量目标)与本次(cosine-15NN 目标)两种目标函数都实测低于地板:坐标配对置换家族在无真值方向信息的外推榜上没有可靠收益。
验证过 / 没验证
- 验证过:对照=50.00 逐位等于 copy_last;全量置换 49.85 及四组分解;坐标多重集不变性(程序内 assert 级校验);确定性(固定 seed 的 rng 洗牌,无全局随机态)。
- 没验证:同型内限制的置换变体(时间不够);其他 k / 目标函数——但停止规则已触发,且两种目标(本节点与 node7/9)都失败,判断该家族不值得继续。
- 运行时 ~7s(默认对照路径);机制路径 ~8 分钟,仍在 30 分钟限制内。
知识来源
无外部生物知识;15-NN 对齐来自评分规则简报的公开描述(neighborhood_mmd 定义)。
下一步建议
置换家族两次独立实现(Dirichlet、cosine-15NN)均低于地板,建议放弃 T2HX-06;外推榜唯一未试尽的方向是 expression_change 组:父节点已证明速度外推无信号,剩下的是用 prior(Reactome/CollecTRI)构造符号级方向假设并只做秩一致的保守调整,或直接接受 copy_last 保底。
调研员的计划
| 名称 | 表达kNN引导坐标置换(修正node7/9/10失败原因的node5复刻) |
|---|---|
| 动机 | 全树唯一超过地板的是node5(52.74,shape_scale 59.99,local_spatial 50.98),机制为表达kNN引导贪心2-opt坐标-细胞配对置换。节点7/9改用坐标15-NN图上Dirichlet能量最小化(不同搜索空间与目标),shape_scale反降至48.33/47.96;节点10加了VEC_SWAP预算上限后退回50.00。父节点3四组均=50.00(地板),本方案针对node5成功而7/9/10失败的具体实现差异做修正复刻。 |
| 做法 | 1) 数据加载与copy_last输出同父节点3的run.py(view_io接口,anchor=末观测阶段,n≤max_cells,坐标原样)。2) 表达kNN图:在完整表达矩阵上以cosine距离取k=15近邻(与评分指标neighborhood_mmd的15-NN对齐)。3) 空间kNN图:坐标上euclidean k=15。4) 候选对生成:对每个细胞i,取其表达kNN中不在其空间kNN内的细胞j(表达相似但空间远),构成候选交换对。5) 贪心2-opt:对每个候选对(i,j),计算交换坐标后目标函数变化Δ=Σ(交换后各细胞与其空间15-NN的表达cosine距离之和)−(交换前),Δ<0则接受交换并更新空间邻居索引。6) 迭代2–3轮或至无改善对。不设交换次数上限(修正node10的预算约束问题)。7) 输出:表达矩阵逐位不变,坐标点集(多重集)不变,仅行序(细胞-坐标配对)改变。8) 单输入阶段退路:本方法不依赖第二时间点,单输入即可运行。9) 快速筛选:先跑VEC_NSWAPS=0对照确认=50.00,再跑全量置换,读score_parts中neighborhood_mmd与occupancy_dice原始值。若neighborhood_mmd原始值未降(<0.1126),停止调参、以对照提交。 |
| 风险 | 1) shape_scale改善可能部分来自评分器按索引抽样时不同行序导致的子集差异(node7分析已指出),跨种子可能不稳健——Engineer应在第一次查分后对比occupancy_dice原始值,若变化<0.002则视为噪声。2) 贪心搜索若候选对过多(>2M),30分钟内可能跑不完——用numpy向量化计算Δ,限制每轮最多扫描3n个候选对。3) 若表达kNN与目标阶段空间组织不匹配,置换可能轻微恶化neighborhood_mmd——以VEC_NSWAPS=0对照为保底,低于50.00则提交对照。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:父节点版本 a0271985a8。改动的文件:solution/METHOD.md +30 −21、solution/run.py +143 −213
diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 6640572..8546109 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,32 +1,41 @@-T2HX-01 位移层:外推时优先用视图内"晚于末输入、不晚于目标"的外部样本按伪批量相关匹配细胞型做阻尼位移(保坐标);两输入速度回退在代理上验证有害、默认关;无可用方向时逐位等于 copy_last。+表达kNN引导坐标置换(T2HX-06,node5复刻):机制已实现并实测,贪心2-opt交换49759对细胞-坐标配对使内部cosine-15NN目标下降,但neighborhood_mmd原始值反升(0.11445→0.1152)、d2_shape变差,总分49.85<地板50,按PLAN停止规则默认关闭(VEC_NSWAPS=0),提交输出逐位等于copy_last。 ## 方法 -在父节点(copy_last)之上加一个位移层,管线(全部只读 manifest 的相对时间与区间字段,无绝对阶段/尺寸常数,伪装视图安全):+1. 基座与父节点3完全相同:anchor=末观测阶段,n 夹到 [min_cells, max_cells],`take` 按型分层抽样,X 与坐标原样输出。+2. 机制层(T2HX-06,`VEC_NSWAPS>0` 时启用):+ - 表达 kNN:完整表达矩阵 cosine,k=15(与评分 neighborhood_mmd 的 15-NN 对齐),另取 16 邻做候选;+ - 空间 kNN:坐标 euclidean,缓存 pool=60 近邻;+ - 候选对:表达 kNN 中不在空间 15-NN 内的 (i,j),去重后按 seed 洗牌;+ - 贪心 2-opt:交换 i、j 坐标,Δ = 受影响细胞集(i、j、两者的空间近邻)各自"与空间 15-NN 表达的 cosine 距离和"的变化(候选池 = 静态 pool + 显式加入换位的 i/j),Δ<0 接受并交换 pool,无交换次数上限(修正 node10 的预算约束);迭代 3 轮,每轮重算空间 kNN。+ - 输出保证:X 逐位不变,坐标点集(多重集)逐位不变(程序内校验),仅细胞-坐标配对改变。+3. 机制关闭对照(PLAN mechanism_off_control):`VEC_NSWAPS=0`(**当前默认**)跳过全部交换循环。 -1. 细胞选择与坐标和父节点完全相同:anchor=最后观测阶段,n≤max_cells 整份输出,超过则按型分层抽样;坐标一律不动(保 neighborhood_mmd 与结构门)。-2. 位移方向按优先级:- - (a) **外部前向靶**:`manifest.external` 中存在区间 (e_lo, e_hi) 满足 e_lo > 末输入时间且 e_hi ≤ 目标时间的样本时,加载并对齐到面板(<50 共有基因则放弃;X 若像原始计数则重算 log1p(CP10k));anchor 各细胞型与外部型在共有基因伪批量上算 Pearson r,r≥0.5 才配对;每细胞 shift = α·[β·型级差 + (1−β)·(匹配型内最近邻外部细胞 − 本细胞)],α=0.5、β=1(默认)。- - (b) 两输入速度回退:型级伪批量差 × α_vel × dt_ratio。**默认 α_vel=0(关)**,理由见下。- - (c) 都没有 → 输出逐位等于 copy_last。-3. shift 逐基因截断在 ±2·std(基因 in anchor),X+shift 后截 0。异常时 (a) 路径 try/except 回退,绝不崩。+## 对照与实测(proxy_noscale,3 次查分) -## 机制对照与验证(proxy_noscale,5 次查分)+- 对照 VEC_NSWAPS=0:**50.00**,四组全 50,输出 = copy_last。✓(管线无副作用)+- 全量置换(3 轮,49759 次交换,412s,坐标多重集校验通过):**49.85**+ - neighborhood_mmd 原始 0.11445 → **0.1152(升高,机制方向失败)**,local_spatial 49.84;+ - d2_shape 0.04891 → 0.04944(变差),occupancy_dice 0.8148 → 0.8145(差 0.0003 < 0.002,按 PLAN 视为噪声),shape_scale 49.57;+ - 表达两组、cell_state 逐位不变(机制只动配对)。+- PLAN 停止规则:"若 neighborhood_mmd 原始值未降,停止调参、以对照提交" → 触发,默认 VEC_NSWAPS=0。 -- **α=0 对照(mechanism_off_control)**:默认配置在代理上的输出与父节点逐位相同(X、坐标 array_equal 验证),vec-score 实测 **50.00**,四组全 50。✓-- **速度回退 (b) 的方向质量**(这是代理上唯一可测的方向):- - 型级伪批量速度 α_eff=0.45/0.9/1.5:**49.0 / 48.7 / 48.3**。de_score 原始值 −0.056(低于 chance),de_direction ≈ −0.003~−0.007:E8.25→E8.75 的型级速度与真值 E8.75→E9.5 变化**不相关甚至反相关**,且 DE 两项对 α 近似不变(秩/符号指标),所以任何 α 都拿不到 DE 收益,只赔 variogram(skill 0.36–0.44)。与方法卡"α 网格单调低于地板"一致。- - 去掉增殖模块(GO BP 名字含 cell cycle/mitotic/DNA replication 等的 44 个面板基因置零,通用基因功能注释,来自视图 prior/go)α_eff=0.45:**49.05**。de_direction 转微正(+0.015,skill 0.504)但 de_score 更差(−0.139),variogram 仍赔 0.8 分。-- 结论:代理上速度外推方向无信号,(b) 默认关(α_vel=0);代理行为 = copy_last = 50.00。-- **外部路径 (a) 冒烟测试**(把末输入时间假装提前以触发):加载 qiu E8.75、27883 基因对齐、33 个 anchor 型中 1 个(心脏谱系)r≥0.5 配对成功、shift 逐型非零——机械上可运行;代理视图内没有真正"晚于末输入"的外部样本,故该路径的科学效果**未能在本地验证**(这正是方法卡指出的外推盲区:本地尺子无法检验方向,且外推榜本地分历史上高估官网分)。-- vec-check 对本 board 返回 "not allowed for this client"(客户端限制,非格式问题);vec-score 正常接收并打分同一文件,格式合规。+## 机制证据与结论(family T2HX-06)++- 机制确实生效:改变了 49759 对细胞-坐标配对(约 2×细胞数的配对尝试中接受 ~1.6 万次/轮),内部目标(Σ cosine 距离到空间 15-NN)单调下降。+- 但内部目标与评分指标不同向:把表达相似的细胞搬成空间邻居会"锐化"预测的邻域平均表达分布,而真值 E9.5 的邻域平均分布并不更锐,MMD 反而变大。这解释了 node5(52.74)的 shape_scale 59.99 更可能来自行序改变导致的评分器子抽样差异(PLAN 风险 1),而非可复现的机制收益——本次实现中 occupancy_dice 变化 < 0.002(噪声),d2_shape 变差。+- node7/9(Dirichlet 能量目标)与本次(cosine-15NN 目标)两种目标函数都实测低于地板:坐标配对置换家族在无真值方向信息的外推榜上没有可靠收益。++## 验证过 / 没验证++- 验证过:对照=50.00 逐位等于 copy_last;全量置换 49.85 及四组分解;坐标多重集不变性(程序内 assert 级校验);确定性(固定 seed 的 rng 洗牌,无全局随机态)。+- 没验证:同型内限制的置换变体(时间不够);其他 k / 目标函数——但停止规则已触发,且两种目标(本节点与 node7/9)都失败,判断该家族不值得继续。+- 运行时 ~7s(默认对照路径);机制路径 ~8 分钟,仍在 30 分钟限制内。 ## 知识来源 -- GO BP 注释(视图 prior/go/gene_sets_bp.gmt):增殖模块(细胞周期/有丝分裂/DNA 复制)是瞬态程序,其早期轨迹不可靠外推——通用基因功能知识,非任何保留阶段测量。-- 细胞型配对用表达相关而非标签名(外部数据标签词汇可能不同)——PLAN T2HX-01 规定。+无外部生物知识;15-NN 对齐来自评分规则简报的公开描述(neighborhood_mmd 定义)。 -## 已知弱点 / 未验证+## 下一步建议 -- (a) 路径在真实 final 视图(可能挂 MOSTA E9.5 等前向外部样本)上的效果无法在本地检验;若方向错会低于地板。终选护栏(proxy_noscale 种子均值需超 copy_last 1 分)在本节点上不会通过,正式提交会回落到基线预测。-- 未做:STRING/通路平滑速度、t 统计加权、逐细胞 OT 位移(方法卡已证 OT 伤邻域)。+置换家族两次独立实现(Dirichlet、cosine-15NN)均低于地板,建议放弃 T2HX-06;外推榜唯一未试尽的方向是 expression_change 组:父节点已证明速度外推无信号,剩下的是用 prior(Reactome/CollecTRI)构造符号级方向假设并只做秩一致的保守调整,或直接接受 copy_last 保底。diff --git a/solution/run.py b/solution/run.pyindex 196e6b2..e610b14 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,22 +1,17 @@ #!/usr/bin/env python3-"""T2 extrapolation: expression-matched pseudobulk displacement on the anchor stage.--Mechanism (family T2HX-01): shift each anchor-stage cell's expression toward a-forward target while keeping coordinates untouched (preserves local spatial-pairing). Target priority, computed only from view data and relative times:-- (a) an external sample whose whole stage interval lies strictly after the- last input and at/before the target: per-cell type of the anchor is- matched to external types by pseudobulk Pearson correlation on shared- panel genes (r >= 0.5); shift blends the matched type-level difference- (weight beta) with a nearest-neighbour cell-level difference (1 - beta).- (b) else with >= 2 inputs: per-type pseudobulk velocity between the last two- inputs, extrapolated over (target - last) time; type-level shift.- (c) else: copy_last exactly (alpha also forces this when set to 0).--Shifts are per-gene capped at cap * std(gene in anchor), values clipped at 0.-No absolute stage names, times or sizes are hardcoded; only relative time-differences from the manifest are used, so the program is view-independent.+"""T2 extrapolation: expression-kNN-guided greedy 2-opt coordinate permutation.++Mechanism (family T2HX-06, replication of node 5): on the anchor (last observed)+stage output, find pairs of cells that are similar in expression (cosine kNN,+k=15, aligned with the neighborhood_mmd metric's 15-NN definition) but not+already spatial neighbours, and greedily swap their coordinates when the swap+reduces the total cosine distance between every cell's expression and its+spatial 15-NN expressions. The expression matrix is bitwise unchanged and the+coordinate point multiset is unchanged; only the cell-coordinate pairing+changes.++VEC_NSWAPS=0 disables all swaps (mechanism-off control: output bitwise equals+copy_last). No absolute stage names, times or sizes are used; view-independent. """ from __future__ import annotations@@ -24,158 +19,25 @@ from __future__ import annotations import argparse import os import sys+import time import numpy as np from src.task2_spatial.sample import take from src.task2_spatial.view_io import ( anchor_entry,- extrap_step, load_manifest, panel_genes, read_stage,- target_time, write_t2, ) -ALPHA = float(os.environ.get("VEC_ALPHA", "0.5")) # external-forward path-ALPHA_VEL = float(os.environ.get("VEC_ALPHA_VEL", "0.0")) # two-input velocity fallback (proxy-validated: harmful, kept off)-BETA = float(os.environ.get("VEC_BETA", "1.0"))-CAP = float(os.environ.get("VEC_CAP", "2.0"))-TIME_SCALE = bool(int(os.environ.get("VEC_TS", "1")))-CC_ZERO = bool(int(os.environ.get("VEC_CC", "0")))-MATCH_R = 0.5-MIN_SHARED = 50--_CC_KEYWORDS = ("cell cycle", "mitotic", "meiotic", "dna replication", "chromosome segregation", "cytokinesis")---def _cell_cycle_genes(view: str) -> set[str]:- """Genes in GO BP terms whose name indicates proliferation programs (generic- gene-function annotation from the view's prior/; transient modules whose- early trajectory is not trusted for forward extrapolation)."""- out: set[str] = set()- try:- path = os.path.join(view, "prior", "go", "gene_sets_bp.gmt")- with open(path) as fh:- for line in fh:- parts = line.rstrip("\n").split("\t")- if len(parts) < 3:- continue- name = parts[1].lower()- if any(k in name for k in _CC_KEYWORDS):- out.update(parts[2:])- except OSError:- pass- return out---def _pb(X: np.ndarray, labels: np.ndarray) -> tuple[list[str], np.ndarray]:- types = sorted(set(labels.tolist()))- out = np.zeros((len(types), X.shape[1]), dtype=np.float64)- for k, t in enumerate(types):- m = labels == t- if m.any():- out[k] = X[m].mean(axis=0)- return types, out---def _external_forward_entry(manifest: dict, last_time: float, tt: float) -> dict | None:- for e in manifest.get("external") or []:- try:- lo = float(e.get("e_lo", -np.inf))- hi = float(e.get("e_hi", lo))- except (TypeError, ValueError):- continue- if lo > last_time + 1e-6 and hi <= tt + 1e-6:- return e- return None---def _load_external(view: str, entry: dict, genes: list[str]):- import anndata as ad- from scipy import sparse-- ad_obj = ad.read_h5ad(os.path.join(view, entry["path"]))- ext_genes = [str(g) for g in ad_obj.var_names]- pos = {g: i for i, g in enumerate(ext_genes)}- shared = [g for g in genes if g in pos]- if len(shared) < MIN_SHARED:- return None, None- cols = np.array([pos[g] for g in shared])- X = ad_obj.X- X = X.toarray() if sparse.issparse(X) else np.asarray(X)- X = np.nan_to_num(np.asarray(X, dtype=np.float64))- if "log1p" not in ad_obj.uns and X.max() > 30.0: # looks like raw counts- tot = X.sum(axis=1, keepdims=True)- tot[tot <= 0] = 1.0- X = np.log1p(X / tot * 10000.0)- X = X[:, cols]- labels = (- np.asarray(ad_obj.obs["celltype"]).astype(str)- if "celltype" in ad_obj.obs- else np.array(["ext"] * X.shape[0])- )- return shared, (X, labels)---def _direction_external(view, manifest, genes, X_a, lab_a, pb_a, last_time, tt):- entry = _external_forward_entry(manifest, last_time, tt)- if entry is None:- return None- try:- shared, loaded = _load_external(view, entry, genes)- if loaded is None:- return None- X_e, lab_e = loaded- gidx = np.array([genes.index(g) for g in shared])- types_e, pb_e = _pb(X_e, lab_e)- # match anchor types to external types by pseudobulk Pearson r on shared genes- A = pb_a[:, gidx]- B = pb_e- A = A - A.mean(axis=1, keepdims=True)- B = B - B.mean(axis=1, keepdims=True)- na = np.linalg.norm(A, axis=1, keepdims=True)- nb = np.linalg.norm(B, axis=1, keepdims=True)- na[na == 0] = 1.0- nb[nb == 0] = 1.0- C = (A / na) @ (B / nb).T- best = C.argmax(axis=1)- ok = C[np.arange(len(best)), best] >= MATCH_R- print(f"[diag] external target {entry.get('id')}: matched {ok.sum()}/{len(ok)} anchor types", file=sys.stderr)- if not ok.any():- return None- # type-level difference on full panel (external genes only)- diff_t = np.zeros_like(pb_a)- for k in range(len(ok)):- if ok[k]:- diff_t[k, gidx] = pb_e[best[k]] - pb_a[k, gidx]- # cell-level: nearest external neighbour within matched type- from sklearn.neighbors import NearestNeighbors-- shift = np.zeros_like(X_a)- d_sub = X_a[:, gidx]- types_a = sorted(set(lab_a.tolist()))- for k in range(len(ok)):- if not ok[k]:- continue- m = lab_a == types_a[k]- if not m.any():- continue- tgt_m = lab_e == types_e[best[k]]- if not tgt_m.any():- continue- nn = NearestNeighbors(n_neighbors=1).fit(X_e[tgt_m][:, : len(shared)])- _, ind = nn.kneighbors(d_sub[m])- cell_diff = np.zeros((m.sum(), X_a.shape[1]))- cell_diff[:, gidx] = X_e[tgt_m][ind[:, 0]] - d_sub[m]- type_diff = np.zeros((m.sum(), X_a.shape[1]))- type_diff[:, gidx] = diff_t[k, gidx]- shift[m] = BETA * type_diff + (1.0 - BETA) * cell_diff- return shift- except Exception as exc: # never crash on the optional external path- print(f"[diag] external path failed: {exc}", file=sys.stderr)- return None+KNN_K = 15 # spatial neighbourhood size (matches metric definition)+POOL = 60 # cached spatial neighbours per cell for delta evaluation+EXPR_K = 16 # expression kNN incl. self+ROUNDS = int(os.environ.get("VEC_ROUNDS", "3"))+NSWAPS = int(os.environ.get("VEC_NSWAPS", "0")) # proxy test: permutation harmful (49.85 < 50), default off per PLAN stop-rule+EPS = 1e-9 def main() -> None:@@ -187,8 +49,6 @@ def main() -> None: manifest = load_manifest(args.data) genes = panel_genes(args.data, manifest)- prev, last, dt_ratio = extrap_step(manifest)- tt = target_time(manifest) stage = read_stage(args.data, anchor_entry(manifest), genes) n = int(np.clip(stage.n, manifest["min_cells"], manifest["max_cells"])) rng = np.random.default_rng(args.seed)@@ -198,60 +58,130 @@ def main() -> None: rows = np.sort(rng.choice(stage.n, size=n, replace=True)) X = stage.X[rows].toarray().astype(np.float32)- coords = stage.coords[rows]+ coords = np.ascontiguousarray(stage.coords[rows], dtype=np.float64)[:, :3]+ n_cells = X.shape[0] - if ALPHA > 0.0 or ALPHA_VEL > 0.0:- lab = stage.labels[rows]- types, pb_a = _pb(X.astype(np.float64), lab)- shift = None- src = "none"- if ALPHA > 0.0:- shift = _direction_external(- args.data, manifest, genes, X.astype(np.float64), lab, pb_a, float(last["time"]), tt- )- if shift is not None:- shift *= ALPHA- src = "external"- if shift is None and prev is not None and ALPHA_VEL > 0.0:- src = "velocity"- pstage = read_stage(args.data, prev, genes)- Xp = pstage.X.toarray().astype(np.float64)- _, pb_p = _pb(Xp, pstage.labels)- tmap_p = {t: i for i, t in enumerate(_pb(Xp, pstage.labels)[0])}- glob = pb_a.mean(axis=0) - Xp.mean(axis=0)- v = np.tile(glob, (len(types), 1))- for k, t in enumerate(types):- if t in tmap_p:- v[k] = pb_a[k] - pb_p[tmap_p[t]]- scale = ALPHA_VEL * (dt_ratio if (TIME_SCALE and dt_ratio) else 1.0)- per_type = {t: scale * v[k] for k, t in enumerate(types)}- shift = np.zeros_like(X, dtype=np.float64)- for t in types:- m = lab == t- if m.any():- shift[m] = per_type[t]- del Xp- if shift is None:- src = "copy_last(no usable forward direction)"- if shift is not None:- if CC_ZERO:- cc = _cell_cycle_genes(args.data)- idx = [i for i, g in enumerate(genes) if g in cc]- if idx:- shift[:, idx] = 0.0- print(f"[diag] cc-zero: {len(idx)} genes zeroed", file=sys.stderr)- sd = X.astype(np.float64).std(axis=0)- capv = CAP * np.maximum(sd, 1e-3)- shift = np.clip(shift, -capv, capv)- X = np.clip(X.astype(np.float64) + shift, 0.0, None).astype(np.float32)- norms = np.linalg.norm(shift, axis=1)- print(f"[diag] src={src} shift norm mean/p50/p95: {norms.mean():.3f} "- f"{np.percentile(norms,50):.3f} {np.percentile(norms,95):.3f}", file=sys.stderr)- gmag = np.abs(shift).mean(axis=0)- top = np.argsort(-gmag)[:20]- print("[diag] top shifted genes: " + ", ".join(f"{genes[i]}({gmag[i]:.2f})" for i in top), file=sys.stderr)- else:- print(f"[diag] src={src}: no shift applied", file=sys.stderr)+ if NSWAPS > 0 and n_cells > KNN_K + 2:+ t0 = time.time()+ from sklearn.neighbors import NearestNeighbors++ nn_e = NearestNeighbors(n_neighbors=min(EXPR_K, n_cells), metric="cosine").fit(X)+ _, eidx = nn_e.kneighbors(X)++ Xn = X.astype(np.float64)+ nrm = np.linalg.norm(Xn, axis=1, keepdims=True)+ nrm[nrm == 0] = 1.0+ Xn = Xn / nrm+ G = Xn @ Xn.T # cosine similarity+ np.fill_diagonal(G, -1.0)+ del Xn++ P = coords.copy()+ pool = np.zeros((n_cells, POOL), dtype=np.int64)+ nswaps = 0+ k = min(KNN_K, n_cells - 1)++ for rd in range(ROUNDS):+ nn_s = NearestNeighbors(n_neighbors=min(POOL, n_cells)).fit(P)+ _, sidx = nn_s.kneighbors(P)+ pool[:, : sidx.shape[1]] = sidx+ snbr_set = [set(r[:KNN_K].tolist()) for r in sidx]++ # candidate pairs: expression-similar but not already spatial neighbours+ seen = set()+ cands = []+ for i in range(n_cells):+ row_e = eidx[i]+ row_s = snbr_set[i]+ for jj in row_e:+ j = int(jj)+ if j == i or j in row_s:+ continue+ key = (i, j) if i < j else (j, i)+ if key not in seen:+ seen.add(key)+ cands.append(key)+ cands = np.array(cands, dtype=np.int64)+ rng.shuffle(cands)+ print(f"[diag] round {rd}: {len(cands)} candidate pairs, setup {time.time()-t0:.1f}s", file=sys.stderr)++ ar_n = np.arange(n_cells, dtype=np.int64)+ acc_this_round = 0+ for pair in cands:+ if nswaps >= NSWAPS:+ break+ i = int(pair[0])+ j = int(pair[1])+ pi = P[i].copy()+ pj = P[j].copy()+ si = pool[i].copy()+ sj = pool[j].copy()+ A = np.union1d(si, sj)+ A = A[(A != i) & (A != j)]+ m = A.shape[0]++ # ---- cost for i and j themselves (candidates = partner's pool) ----+ def _self_cost(pos, Cp, Gx):+ Cp = Cp[(Cp != i) & (Cp != j)]+ d = np.sqrt(((P[Cp] - pos) ** 2).sum(1))+ s = Gx[Cp]+ d = np.concatenate([[0.0], d]) # self, distance 0+ s = np.concatenate([[1.0], s]) # self, similarity 1+ kk = min(k, d.shape[0])+ sel = np.argpartition(d, kk - 1)[:kk]+ return float(kk - s[sel].sum())++ pre_i = _self_cost(pi, si, G[i])+ pre_j = _self_cost(pj, sj, G[j])+ post_i = _self_cost(pj, sj, G[i])+ post_j = _self_cost(pi, si, G[j])++ # ---- cost for affected bystanders A (positions unchanged) ----+ PA = P[A]+ CA = pool[A] # m x POOL+ mask = (CA == i) | (CA == j) | (CA == A[:, None])+ Dp = np.sqrt(((P[CA] - PA[:, None, :]) ** 2).sum(2))+ Dp[mask] = np.inf+ Sp = G[A[:, None], CA]+ Sp[mask] = -1.0+ dAi = np.sqrt(((PA - pi) ** 2).sum(1))+ dAj = np.sqrt(((PA - pj) ** 2).sum(1))+ sAi = G[A, i][:, None]+ sAj = G[A, j][:, None]+ d0 = np.zeros((m, 1))+ s1 = np.ones((m, 1))++ def _bystander(di_col, dj_col):+ D = np.hstack([Dp, di_col, dj_col, d0])+ S = np.hstack([Sp, sAi, sAj, s1])+ kk = min(k, D.shape[1])+ sel = np.argpartition(D, kk - 1, axis=1)[:, :kk]+ return float((kk - S[np.arange(m)[:, None], sel].sum(1)).sum())++ pre_A = _bystander(dAi[:, None], dAj[:, None])+ post_A = _bystander(dAj[:, None], dAi[:, None])++ delta = (post_i + post_j + post_A) - (pre_i + pre_j + pre_A)+ if delta < -EPS:+ P[i] = pj+ P[j] = pi+ pool[i] = sj+ pool[j] = si+ nswaps += 1+ acc_this_round += 1++ print(f"[diag] round {rd}: accepted {acc_this_round}, total {nswaps}, {time.time()-t0:.1f}s", file=sys.stderr)+ if acc_this_round == 0:+ break++ coords = np.ascontiguousarray(P, dtype=np.float64)+ src_coords = np.ascontiguousarray(stage.coords[rows], dtype=np.float64)[:, :3]+ for c in range(3):+ if not np.array_equal(np.sort(coords[:, c]), np.sort(src_coords[:, c])):+ print("[diag] WARNING: coordinate multiset changed!", file=sys.stderr)+ print(f"[diag] permutation done: {nswaps} swaps, {time.time()-t0:.1f}s", file=sys.stderr)+ else:+ print("[diag] control (NSWAPS=0): output = copy_last", file=sys.stderr) write_t2(args.out, X, coords, genes, seed=args.seed)
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k007 | Interval staging and held-out-window filtering of external data | notes/official/来件/virtualembryo.ai/rules.md |
| k016 | Degenerate-solution checks for population predictions | notes/handover/02_知识学习路线.md |
| k024 | World-model evaluation dimensions for state-transition predictors | notes/competition/07_biomedical_world_models.md |
分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 把父节点3的 T2HX-01 位移层整体删掉,改为实现 T2HX-06:表达 cosine-15NN 引导的贪心 2-opt 细胞-坐标配对置换(X 与坐标点集不变,仅改行序配对)。本地实测全量置换(49759 次交换)得 49.85、neighborhood_mmd 原始值 0.11445→0.1152 上升,触发 PLAN 停止规则,故默认 VEC_NSWAPS=0 关闭机制,提交输出逐位等于 copy_last——变化量表八项原始值与父节点完全相同(de_score/de_direction 0、mmd_u 0.05832、variogram 0.05806、d2_shape 0.04911、occupancy_dice 0.8066、scale_log_ratio -0.4334、neighborhood_mmd 0.1126),榜分 50.00 = 地板。 |
|---|---|
| 各组分数的变化 | cell_state:噪声内(严格为 0):mmd_u 0.05832、variogram 0.05806,skill 均 0.500、各 6.25 分,与父节点相同。 expression_change:噪声内(严格为 0):de_score 与 de_direction 原始值 0、skill 0.500、各 6.25 分,与父节点逐位相同。 local_spatial:噪声内(严格为 0):neighborhood_mmd 原始值 0.1126、12.50 分,与父节点相同。机制开启态本地实测反而恶化(0.11445→0.1152,组 49.84),故被关闭。 shape_scale:噪声内(严格为 0):d2_shape 0.04911、occupancy_dice 0.8066、scale_log_ratio -0.4334,各 4.17 分,与父节点相同。Engineer 报告的机制开启态 shape_scale 49.57(d2_shape 0.04891→0.04944 变差、occupancy_dice 变化 0.0003)未进入提交。 |
| family_id | T2HX-06 |
| 假设是否成立 | 否 |
| 经验 |
|
| mechanism_active | 否 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 4 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 3 次:— |
| 用时 | 不到 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 18,054 · 输出 1,506 · 思考 992 |
| 任务(第一行) | 分析节点 n12 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/12/analyst.jsonl (文件不在) |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 17 |
| 工具调用 | 共 19 次:— |
| 用时 | 25 分 |
| token 数 | 输入 53,618 · 输出 8,877 · 思考 11,619 |
| 任务(第一行) | 按计划修改程序:表达kNN引导坐标置换(修正node7/9/10失败原因的node5复刻) |
| 最后的回答(摘录) | **做了什么**:按 PLAN 实现 T2HX-06 表达kNN引导坐标置换(node5 复刻,修正 node7/9/10 的失败点:cosine-15NN 目标、无交换预算上限、Δ评估显式处理换位细胞),并修复了一个坐标视图复制 bug。 **查分结果**(3/20 次):对照 VEC_NSWAPS=0 = **50.00**(逐位等于 copy_last,已验证);全量置换(49759 次交换)= **49.85**,neighborhood_mmd 原始值反升(0.11445→0.1152),d2_shape 变差,occupancy_dice 变化 <0.002(噪声)。PLAN 停止规则触发,默认关闭机制,提交输出 = copy_last = 50.00。 **下一步建议**:置换家族两种独立目标(Dirichlet 于 node7/9、cosine-15NN 于本节点)均低于地板,node5 的 52.74 大概率是行序子抽样运气,建议放弃 T2HX-06;剩余未试尽方向是用 prior(Reactome/CollecTRI)做符号级保守表达调整攻 expression_change 组。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/12/engineer.jsonl (文件不在) |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 6 分 |
| token 数 | 输入 15,839 · 输出 9,446 · 思考 8,192 |
| 任务(第一行) | 为节点 n12(改进,父节点 n3)写改进计划 PLAN.json |
| 最后的回答(摘录) | different objective (Dirichlet energy on coordinate-NN graph); node 10 failed by adding artificial swap budget constraints. I'll propose a faithful replication with corrections.
```json
{
"name": "表达kNN引导坐标置换(修正node7/9/10失败原因的node5复刻)",
"motivation": "全树唯一超过地板的是node5(52.74,shape_scale 59.99,local_spatial 50.98),机制为表达kNN引导贪心2-opt坐标-细胞配对置换。节点7/9改用坐标15-NN图上Dirichlet能量最小化(不同搜索空间与目标),shape_scale反降至48.33/47.96;节点10加了VEC_SWAP预算上限后退回50.00。父节点3四组均=50.00(地板),本方案针对node5成功而7/9/10失败的具体实现差异做修正复刻。",
"approach": "1) 数据加载与copy_last输出同父节点3的run.py(view_io接口,anchor=末观测阶段,n≤max_cells,坐标原样)。2) 表达kNN图:在完整表达矩阵上以cosine距离取k=15近邻(与评分指标neighborhood_mmd的15-NN对齐)。3) 空间kNN图:坐标上euclidean k=15。4) 候选对生成:对每个细胞i,取其表达kNN中不在其空间kNN内的细胞j(表达相似但空间远),构成候选交换对。5) 贪心2-opt:对每个候选对(i,j),计算交换坐标后目标函数变化Δ=Σ(交换后各细胞与其空间15-NN的表达cosine距离之和)−(交换前),Δ<0则接受交换并更新空间邻居索引。6) 迭代2–3轮或至无改善对。不设交换次数上限(修正node10的预算约束问题)。7) 输出:表达矩阵逐位不变,坐标点集(多重集)不变,仅行序(细胞-坐标配对)改变。8) 单输入阶段退路:本方法不依赖第二时间点,单输入即可运行。9) 快速筛选:先跑VEC_NSWAPS=0对照确认=50.00,再跑全量置换,读score_parts中neighborhood_mmd与occupancy_dice原始值。若neighborhood_mmd原始值未降(<0.1126),停止调参、以对照提交。",
"expected_groups": ["local_spatial", "shape_scale"],
"risks": "1) shape_scale改善可能部分来自评分器按索引抽样时不同行序导致的子集差异(node7分析已指出),跨种子可能不稳健——Engineer应在第一次查分后对比occupancy_dice原始值,若变化<0.002则视为噪声。2) 贪心搜索若候选对过多(>2M),30分钟内可能跑不完——用numpy向量化计算Δ,限制每轮最多扫描3n个候选对。3) 若表达kNN与目标阶段空间组织不匹配,置换可能轻微恶化neighborhood_mmd——以VEC_NSWAPS=0对照为保底,低于50.00则提交对照。",
"family_id": "T2HX-06",
"mechanism": "在表达空间找相似但空间上远的细胞对,贪心交换其坐标使表达相似细胞成为空间邻居,从而改善表达-位置配对(降低neighborhood_mmd),只重排配对、不改表达矩阵与坐标点集。",
"vs_constant_shift": "常数位移对每个细胞型加同一个表达向量,不改变细胞间相对关系也不动坐标;本方法不改表达值,只重排细胞与坐标的配对,直接优化表达-空间一致性,是组合/配对层面的操作而非表达幅度平移。",
"mechanism_evidence": "1) neighborhood_mmd原始值从0.1126下降(目标≤0.110);2) 实际交换细胞数>0且交换后表达kNN与空间kNN的重叠率上升(诊断输出);3) score_parts中local_spatial组得分>12.50;4) 若shape_scale也改善,读occupancy_dice原始值变化方向。",
"mechanism_off_control": "设环境变量VEC_NSWAPS=0,跳过全部交换循环,输出应与copy_last逐位相同(array_equal验证),vec-score应返回50.00、四组均50。若对照≠50.00说明管线有副作用,需先修复。",
"sources": []
}
``` |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/12/researcher.jsonl (文件不在) |