总览 · ← 返回运行 20261003-105537-search-t2-heart-extrap-g24-D
节点 n7
copy_last 基座上的表达kNN引导坐标置换(T2HX-06):表达矩阵与坐标点集逐位不变,贪心2-opt在坐标15-NN图上最小化表达Dirichlet能量,仅重排细胞-坐标配对。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261003-105537-search-t2-heart-extrap-g24-D |
|---|---|
| 父节点 | n3 |
| 子节点 | — |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 改进 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 49.60(-0.4) · proxy_noscale 49.60(-0.4) · 3 次复测均分 49.87 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 20 分 |
| 程序版本 | 4e72c04322fa52c6605f2b07636ecb4eb8bdb268 (programs.git) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git 4e72c04322:solution/METHOD.md
copy_last 基座上的表达kNN引导坐标置换(T2HX-06):表达矩阵与坐标点集逐位不变,贪心2-opt在坐标15-NN图上最小化表达Dirichlet能量,仅重排细胞-坐标配对。
方法(复现节点5机制,父节点为节点3)
- 细胞选择与节点3/节点1完全一致:anchor=最后观测阶段(
anchor_entry),n=clip(stage.n, min_cells, max_cells),n≤stage.n 时按型分层take(本视图 24826 ≤ 25179,整份输出)。表达 X 一律不改。 - 置换层(
VEC_PERM_SWAPS,默认 1,000,000 次提案):- 坐标上建 15-NN 图(与评分器 neighborhood 定义一致,含自身),对称化成无向邻接 A;点集不变 → 图固定。
- 表达投影到 PCA 32 维(random_state=0,确定)。
- 目标函数:E = Σ_{(j,k)∈A} ||y_j − y_k||²(Dirichlet 能量),y = 位置上的细胞表达。
- 贪心 2-opt:rng(seed+1000003) 成批生成随机位置对 (p,q),ΔE<0 才交换两位置的细胞;ΔE 只算 p、q 的邻域(度 ~17)。
- 输出:细胞 i 的坐标 = 原坐标槽 inv[i](inv 为 perm 的逆)。X、坐标点集逐位不变,只改配对。
- 单输入阶段退路:置换不依赖两阶段之差,单输入照常执行;nc≤1 时跳过。
- 纯 CPU(EXECUTION.json gpu:false),本视图实测运行 29 s / <2 GB。
机制证据(proxy 视图,seed 0)
- 验证不变量:
array_equal(输出X, 父输出X)=True;坐标点集排序后逐位相等=True;实际换位细胞比例 17.3%(2700/1e6 次交换被接受)。 - 能量:identity(真实 E8.75 配对)E=1.376e8;随机配对 E=3.114e8;表达k-means层级区域匹配 E=1.531e8(比 identity 差);2-opt from identity 200k 次 → 1.363e8(−1%)。identity 本身已接近 2-opt 局部最优。
- 查分(A半,proxy_noscale,共 5 次):
- 父 copy_last 基线:neighborhood_mmd raw 0.11445(=地板),全 8 项 skill 0.5,总分 50.00。
- 随机配对:nbh raw 0.3248(skill 0.26),结构门 0.52 把形状组打到 6.1/12.5,总分 ~37.6 → 配对被破坏重罚,证明 nbh 确实测配对质量。
- 温和2-opt(200k,4.3%换位):nbh raw 0.1139(skill 0.5012,+0.03分);occupancy raw 0.8115(skill 0.4486,−0.43分)→ 总分 ~49.6。occupancy/d2 对同一点集也随细胞顺序波动(评分器按索引抽样,抽到的点子集随配对改变):这是抽样运气,不是信号,跨半/跨种子不可复现。
- 最终版(1M 次,17.3%换位):nbh raw 0.11416(skill 0.5006,+0.015分),occupancy raw 0.8148(=地板),d2 raw 0.04889(≈地板),总分 ≈50.02。
- 机制对照(mechanism_off_control):
VEC_PERM_SWAPS=0输出与父节点逐位相同(X、坐标 array_equal 均 True),预期正式分 50.00(与节点1/3一致)。✓
结论与如实说明
- 该机制在本榜的真实收益上限很小:真实 E8.75 的空间-表达配对已接近 Dirichlet 能量的局部最优,可降空间 ~1–2%,对应 nbh skill +0.001~0.01 量级;随机/层级匹配等更"平滑"的配对全部更差或相近。节点5 的 shape_scale 50→60 无法由点集不变的置换产生真实收益(三个形状指标只看坐标点集),大概率是 occupancy_dice 索引抽样运气,B半 seed0 单次观测,rank3 未验证——本节点未复现该 +10(同一点集下 occupancy 波动 ±0.05 skill 均出现过)。
- 提交版本取 1M 提案:nbh 真实微升(0.11445→0.11416),其余项回到地板,A半总分 50.02。相对父节点是"不劣 + 微小真实正信号",形状项不主动赌抽样运气。
- 未验证:更优提案分布(表达kNN引导的候选对)能否把能量再降 5–10% 及 nbh 是否随之继续下降(时间不够);final 视图(真实 E9.5 目标、末步共有类型可能少)上的表现。
- 护栏预期:按种子均值高出 copy_last 1 分以上的护栏大概率达不到(真实信号 ~+0.02),如实记录。
知识来源
- 15 近邻定义与评分器 neighborhood_mmd 一致(任务书评分简报,公开规则);其余全部为通用算法(PCA、kNN 图、2-opt、Dirichlet 能量),未使用任何保留阶段/基因型的测量信息,无外部常数。
视图无关性
不读绝对时间/阶段名/board/mode;只用锚点阶段自身的表达与坐标、manifest 的 min/max_cells、--seed。伪装视图(时间平移、路径改名、键序打乱)下输出逐位不变。
调研员的计划
| 名称 | copy_last基座上的表达kNN引导坐标置换(复现节点5机制) |
|---|---|
| 动机 | 父节点3输出逐位等于copy_last(50.00),位移机制因代理无外部前向靶且速度方向无信号而完全未生效。全树唯一超越地板的是节点5(52.74):表达kNN引导的坐标置换使shape_scale从50→59.99、local_spatial从50→50.98。该机制不依赖时间方向信号,在代理上可验证,是外推榜唯一已证实有效的改进路径。 |
| 做法 | 在父节点3的copy_last输出基础上加坐标置换层:1) 读取末输入阶段的表达矩阵X和坐标C(均不动);2) 目标函数:对每个细胞,计算其当前坐标处15近邻的平均表达与自身表达的距离(近似neighborhood_mmd);3) 贪心2-opt:随机初始化置换π,迭代尝试交换两细胞的坐标分配,若目标函数下降则接受;4) 迭代次数:先小样本(200细胞、50次swap)验证目标下降,再全量(≤max_cells、200次swap);5) 输出:表达矩阵不变、坐标点集不变,仅细胞-坐标配对改变。关键参数:n_swaps初始200(搜索范围100-500),k_neighbors=15(与评分器一致)。单输入阶段退路:坐标置换不依赖多时间点,单输入即可执行。vec-score快速筛选:先跑200细胞子集看neighborhood_mmd原始值是否下降,再全量提交。 |
| 风险 | 1) 2-opt在30分钟内可能迭代不够充分(节点5耗时未知),Engineer应先小样本确认目标下降再全量;2) 贪心可能陷入局部最优,可试2-3次不同随机初始化取最优;3) 若max_cells很大(>5000),O(n²)近邻计算可能超时,应预计算KD-tree。尽早发现:第一次小样本查分若neighborhood_mmd未下降,检查目标函数实现是否与评分器一致(15近邻、含自身)。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:父节点版本 a0271985a8。改动的文件:solution/EXECUTION.json +1 −0、solution/METHOD.md +34 −27、solution/run.py +87 −211
diff --git a/solution/EXECUTION.json b/solution/EXECUTION.jsonnew file mode 100644index 0000000..6d8012e--- /dev/null+++ b/solution/EXECUTION.json@@ -0,0 +1 @@+{"gpu": false}\ No newline at end of filediff --git a/solution/METHOD.md b/solution/METHOD.mdindex 6640572..b317e6c 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,32 +1,39 @@-T2HX-01 位移层:外推时优先用视图内"晚于末输入、不晚于目标"的外部样本按伪批量相关匹配细胞型做阻尼位移(保坐标);两输入速度回退在代理上验证有害、默认关;无可用方向时逐位等于 copy_last。--## 方法--在父节点(copy_last)之上加一个位移层,管线(全部只读 manifest 的相对时间与区间字段,无绝对阶段/尺寸常数,伪装视图安全):--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,5 次查分)--- **α=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 正常接收并打分同一文件,格式合规。+copy_last 基座上的表达kNN引导坐标置换(T2HX-06):表达矩阵与坐标点集逐位不变,贪心2-opt在坐标15-NN图上最小化表达Dirichlet能量,仅重排细胞-坐标配对。++## 方法(复现节点5机制,父节点为节点3)++1. 细胞选择与节点3/节点1完全一致:anchor=最后观测阶段(`anchor_entry`),n=clip(stage.n, min_cells, max_cells),n≤stage.n 时按型分层 `take`(本视图 24826 ≤ 25179,整份输出)。表达 X 一律不改。+2. 置换层(`VEC_PERM_SWAPS`,默认 1,000,000 次提案):+ - 坐标上建 15-NN 图(与评分器 neighborhood 定义一致,含自身),对称化成无向邻接 A;点集不变 → 图固定。+ - 表达投影到 PCA 32 维(random_state=0,确定)。+ - 目标函数:E = Σ_{(j,k)∈A} ||y_j − y_k||²(Dirichlet 能量),y = 位置上的细胞表达。+ - 贪心 2-opt:rng(seed+1000003) 成批生成随机位置对 (p,q),ΔE<0 才交换两位置的细胞;ΔE 只算 p、q 的邻域(度 ~17)。+ - 输出:细胞 i 的坐标 = 原坐标槽 inv[i](inv 为 perm 的逆)。X、坐标点集逐位不变,只改配对。+3. 单输入阶段退路:置换不依赖两阶段之差,单输入照常执行;nc≤1 时跳过。+4. 纯 CPU(EXECUTION.json gpu:false),本视图实测运行 29 s / <2 GB。++## 机制证据(proxy 视图,seed 0)++- 验证不变量:`array_equal(输出X, 父输出X)`=True;坐标点集排序后逐位相等=True;实际换位细胞比例 17.3%(2700/1e6 次交换被接受)。+- 能量:identity(真实 E8.75 配对)E=1.376e8;随机配对 E=3.114e8;表达k-means层级区域匹配 E=1.531e8(**比 identity 差**);2-opt from identity 200k 次 → 1.363e8(−1%)。identity 本身已接近 2-opt 局部最优。+- 查分(A半,proxy_noscale,共 5 次):+ - 父 copy_last 基线:neighborhood_mmd raw 0.11445(=地板),全 8 项 skill 0.5,总分 50.00。+ - 随机配对:nbh raw 0.3248(skill 0.26),结构门 0.52 把形状组打到 6.1/12.5,总分 ~37.6 → 配对被破坏重罚,证明 nbh 确实测配对质量。+ - 温和2-opt(200k,4.3%换位):nbh raw 0.1139(skill 0.5012,+0.03分);occupancy raw 0.8115(skill 0.4486,−0.43分)→ 总分 ~49.6。occupancy/d2 对**同一点集**也随细胞顺序波动(评分器按索引抽样,抽到的点子集随配对改变):这是抽样运气,不是信号,跨半/跨种子不可复现。+ - **最终版(1M 次,17.3%换位):nbh raw 0.11416(skill 0.5006,+0.015分),occupancy raw 0.8148(=地板),d2 raw 0.04889(≈地板),总分 ≈50.02。**+- 机制对照(mechanism_off_control):`VEC_PERM_SWAPS=0` 输出与父节点逐位相同(X、坐标 array_equal 均 True),预期正式分 50.00(与节点1/3一致)。✓++## 结论与如实说明++- 该机制在本榜的真实收益上限很小:真实 E8.75 的空间-表达配对已接近 Dirichlet 能量的局部最优,可降空间 ~1–2%,对应 nbh skill +0.001~0.01 量级;随机/层级匹配等更"平滑"的配对全部更差或相近。节点5 的 shape_scale 50→60 无法由点集不变的置换产生真实收益(三个形状指标只看坐标点集),大概率是 occupancy_dice 索引抽样运气,B半 seed0 单次观测,rank3 未验证——本节点未复现该 +10(同一点集下 occupancy 波动 ±0.05 skill 均出现过)。+- 提交版本取 1M 提案:nbh 真实微升(0.11445→0.11416),其余项回到地板,A半总分 50.02。相对父节点是"不劣 + 微小真实正信号",形状项不主动赌抽样运气。+- 未验证:更优提案分布(表达kNN引导的候选对)能否把能量再降 5–10% 及 nbh 是否随之继续下降(时间不够);final 视图(真实 E9.5 目标、末步共有类型可能少)上的表现。+- 护栏预期:按种子均值高出 copy_last 1 分以上的护栏大概率达不到(真实信号 ~+0.02),如实记录。 ## 知识来源 -- GO BP 注释(视图 prior/go/gene_sets_bp.gmt):增殖模块(细胞周期/有丝分裂/DNA 复制)是瞬态程序,其早期轨迹不可靠外推——通用基因功能知识,非任何保留阶段测量。-- 细胞型配对用表达相关而非标签名(外部数据标签词汇可能不同)——PLAN T2HX-01 规定。+- 15 近邻定义与评分器 neighborhood_mmd 一致(任务书评分简报,公开规则);其余全部为通用算法(PCA、kNN 图、2-opt、Dirichlet 能量),未使用任何保留阶段/基因型的测量信息,无外部常数。 -## 已知弱点 / 未验证+## 视图无关性 -- (a) 路径在真实 final 视图(可能挂 MOSTA E9.5 等前向外部样本)上的效果无法在本地检验;若方向错会低于地板。终选护栏(proxy_noscale 种子均值需超 copy_last 1 分)在本节点上不会通过,正式提交会回落到基线预测。-- 未做:STRING/通路平滑速度、t 统计加权、逐细胞 OT 位移(方法卡已证 OT 伤邻域)。+不读绝对时间/阶段名/board/mode;只用锚点阶段自身的表达与坐标、manifest 的 min/max_cells、--seed。伪装视图(时间平移、路径改名、键序打乱)下输出逐位不变。diff --git a/solution/run.py b/solution/run.pyindex 196e6b2..a9ee23f 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,22 +1,20 @@ #!/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: copy_last base + expression-kNN-guided coordinate permutation.++Mechanism (family T2HX-06): the expression matrix and the coordinate point set+are kept bit-for-bit identical to the anchor (last observed) stage; only the+cell-to-coordinate assignment is permuted. A greedy 2-opt search swaps the+coordinates of random cell pairs and accepts a swap when it lowers the+Dirichlet energy sum_{(j,k) in kNN} ||y_j - y_k||^2 over the fixed 15-NN+coordinate graph (y = expression in PCA space). This makes expression-similar+cells spatial neighbours, improving local spatial organisation+(neighborhood_mmd) without touching any expression value.++mechanism_off_control: VEC_PERM_SWAPS=0 skips the permutation entirely and the+output equals copy_last bit-for-bit (cell selection identical to the parent).++View independence: no absolute stage names, times, sizes or cell counts are+used; everything is computed from the manifest inputs and --seed. """ from __future__ import annotations@@ -24,158 +22,62 @@ 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():+N_SWAPS = int(os.environ.get("VEC_PERM_SWAPS", "1000000")) # 0 = mechanism off (copy_last)+K_NEIGHBORS = int(os.environ.get("VEC_PERM_K", "15")) # scorer's neighbourhood definition+N_PCA = int(os.environ.get("VEC_PERM_PCA", "32"))+++def permute_assignment(Y: np.ndarray, A_indptr, A_indices, n: int, iters: int, rng) -> np.ndarray:+ """Greedy 2-opt over cell->coordinate-slot assignment.++ perm[j] = cell index placed at coordinate slot j. Minimises+ sum_edges ||Y[perm[j]] - Y[perm[k]]||^2 on the fixed coordinate kNN graph.+ """+ perm = np.arange(n)+ Yv = Y[perm].copy()+ accepted = 0+ done = 0+ while done < iters:+ chunk = min(65536, iters - done)+ pairs = rng.integers(0, n, size=(chunk, 2))+ done += chunk+ for p, q in pairs:+ p = int(p)+ q = int(q)+ if p == q: 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+ yp = Yv[p]+ yq = Yv[q]+ Np = A_indices[A_indptr[p] : A_indptr[p + 1]]+ Nq = A_indices[A_indptr[q] : A_indptr[q + 1]]+ Yp = Yv[Np]+ Yq = Yv[Nq]+ dp = yp - Yp+ dq = yq - Yq+ old = float((dp * dp).sum()) + float((dq * dq).sum())+ ep = yq - Yp+ eq = yp - Yq+ new = float((ep * ep).sum()) + float((eq * eq).sum())+ if new < old:+ perm[p], perm[q] = perm[q], perm[p]+ Yv[p] = yq+ Yv[q] = yp+ accepted += 1+ print(f"[diag] 2-opt: {accepted}/{iters} swaps accepted", file=sys.stderr)+ return perm def main() -> None:@@ -187,8 +89,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)@@ -200,58 +100,34 @@ def main() -> None: X = stage.X[rows].toarray().astype(np.float32) coords = stage.coords[rows] - 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 N_SWAPS > 0 and len(X) > 1:+ t0 = time.time()+ from scipy.sparse import csr_matrix+ from sklearn.decomposition import PCA+ from sklearn.neighbors import NearestNeighbors++ nc = len(X)+ Y = PCA(n_components=min(N_PCA, X.shape[1], nc - 1), random_state=0).fit_transform(+ X.astype(np.float64)+ ).astype(np.float32)+ k = min(K_NEIGHBORS, nc)+ nn = NearestNeighbors(n_neighbors=k).fit(coords)+ _, ind = nn.kneighbors(coords)+ S = csr_matrix(+ (np.ones(nc * k, np.float32), (np.repeat(np.arange(nc), k), ind.ravel())),+ shape=(nc, nc),+ )+ A = ((S + S.T) > 0).astype(np.float32).tocsr()+ perm_rng = np.random.default_rng(args.seed + 1000003)+ perm = permute_assignment(Y, A.indptr, A.indices, nc, N_SWAPS, perm_rng)+ inv = np.empty(nc, dtype=np.int64)+ inv[perm] = np.arange(nc)+ coords = coords[inv] # cell i moves to slot inv[i]; point set unchanged+ moved = float((perm != np.arange(nc)).mean())+ print(+ f"[diag] permutation done: moved={moved:.3f} cells, {time.time()-t0:.1f}s",+ 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的copy_last+位移层整体替换为T2HX-06坐标置换:表达矩阵与坐标点集逐位不变,在坐标15-NN图上做贪心2-opt(PCA32,1M提案,17.3%细胞换位)最小化表达Dirichlet能量,只重排细胞-坐标配对;VEC_PERM_SWAPS=0对照输出逐位等于copy_last。 |
|---|---|
| 各组分数的变化 | cell_state:不变:mmd_u 0.05832、variogram 0.05806均与父节点相同,skill 0.500,得分50.00→50.00。 expression_change:不变:de_score/de_direction原始值均0、skill 0.500,得分50.00→50.00(置换不动表达,符合预期)。 local_spatial:噪声内:neighborhood_mmd原始值0.1126→0.1122,skill 0.500→0.501,得分+0.02,远低于T2约1分的噪声。 shape_scale:变坏但在噪声内:50.00→48.33(-1.67),全部来自occupancy_dice原始值0.8066→0.8025、skill 0.500→0.450、得分-0.42(点集不变但索引顺序改变导致评分器按索引抽到的点子集不同,是抽样运气而非真实形状变化);d2_shape与scale_log_ratio原始值几乎不动。 |
| family_id | T2HX-06 |
| 假设是否成立 | 否 |
| 经验 |
|
| mechanism_active | 是 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 3 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 2 次:bash 1、write 1 |
| 用时 | 不到 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 15,330 · 输出 1,110 · 思考 520 |
| 任务(第一行) | 分析节点 n7 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/analyst.jsonl 10 KB /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/analyst.stderr |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 22 |
| 工具调用 | 共 24 次:bash 19、write 3、read 1、edit 1 |
| 用时 | 17 分 |
| token 数 | 输入 63,621 · 输出 11,053 · 思考 17,942 |
| 任务(第一行) | 按计划修改程序:copy_last基座上的表达kNN引导坐标置换(复现节点5机制) |
| 最后的回答(摘录) | 完成。总结: **做了什么**:按 PLAN 在节点3(copy_last)上实现 T2HX-06 表达kNN引导坐标置换——表达矩阵与坐标点集逐位不变,坐标15-NN图上贪心2-opt最小化表达Dirichlet能量(PCA32),1M提案、17.3%细胞换位;`VEC_PERM_SWAPS=0` 对照输出逐位等于 copy_last ✓;seed 0 复跑逐位确定 ✓;纯CPU,29s。 **查分结果**(A半,6次):父基线 50.00(nbh raw 0.11445=地板);最终版 ≈50.02(nbh raw 0.11416,skill 0.5006,其余项在地板);随机配对 ~37.6(验证 nbh 真测配对);层级区域匹配能量反而比 identity 差。节点5的 shape_scale +10 未被复现——同一点集下形状指标只随索引抽样运气波动,非真实信号。 **下一步最值得试**:(1) 用表达kNN引导的提案分布替代随机对,把能量再降5–10%看 nbh 是否继续下降;(2) 该机制真实收益上限 ~+0.05分,外推榜要突破需换杠杆——nbh 地板=真实配对已近局部最优,DE/cell_state 组需要不依赖速度的先验方向(prior 资源),或接受地板。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/engineer.jsonl 122 KB /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/engineer.stderr |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 1 分 |
| token 数 | 输入 13,540 · 输出 842 · 思考 2,318 |
| 任务(第一行) | 为节点 n7(改进,父节点 n3)写改进计划 PLAN.json |
| 最后的回答(摘录) | ```json
{
"name": "copy_last基座上的表达kNN引导坐标置换(复现节点5机制)",
"motivation": "父节点3输出逐位等于copy_last(50.00),位移机制因代理无外部前向靶且速度方向无信号而完全未生效。全树唯一超越地板的是节点5(52.74):表达kNN引导的坐标置换使shape_scale从50→59.99、local_spatial从50→50.98。该机制不依赖时间方向信号,在代理上可验证,是外推榜唯一已证实有效的改进路径。",
"approach": "在父节点3的copy_last输出基础上加坐标置换层:1) 读取末输入阶段的表达矩阵X和坐标C(均不动);2) 目标函数:对每个细胞,计算其当前坐标处15近邻的平均表达与自身表达的距离(近似neighborhood_mmd);3) 贪心2-opt:随机初始化置换π,迭代尝试交换两细胞的坐标分配,若目标函数下降则接受;4) 迭代次数:先小样本(200细胞、50次swap)验证目标下降,再全量(≤max_cells、200次swap);5) 输出:表达矩阵不变、坐标点集不变,仅细胞-坐标配对改变。关键参数:n_swaps初始200(搜索范围100-500),k_neighbors=15(与评分器一致)。单输入阶段退路:坐标置换不依赖多时间点,单输入即可执行。vec-score快速筛选:先跑200细胞子集看neighborhood_mmd原始值是否下降,再全量提交。",
"expected_groups": ["local_spatial", "shape_scale"],
"risks": "1) 2-opt在30分钟内可能迭代不够充分(节点5耗时未知),Engineer应先小样本确认目标下降再全量;2) 贪心可能陷入局部最优,可试2-3次不同随机初始化取最优;3) 若max_cells很大(>5000),O(n²)近邻计算可能超时,应预计算KD-tree。尽早发现:第一次小样本查分若neighborhood_mmd未下降,检查目标函数实现是否与评分器一致(15近邻、含自身)。",
"family_id": "T2HX-06",
"mechanism": "贪心2-opt坐标置换:保持表达矩阵和坐标点集逐位不变,仅重新分配哪个细胞占据哪个坐标位置,使高表达相似性的细胞在空间上成为近邻,从而降低neighborhood_mmd并通过结构门提升形状组得分。",
"vs_constant_shift": "不改变任何细胞的表达值(无加法/乘法位移),不改变坐标点集(无全局变换);仅做细胞与坐标的组合重分配(排列),是离散组合优化而非连续位移。",
"mechanism_evidence": "1) 置换前后neighborhood_mmd原始值对比(预期从0.1126显著下降);2) shape_scale组因结构门(邻域skill/0.5)提升;3) 表达矩阵和坐标点集array_equal验证不变;4) 实际发生交换的细胞比例(预期>30%的细胞换了位置)。节点5已证实该机制:shape_scale 50→59.99。",
"mechanism_off_control": "设环境变量VEC_PERM_SWAPS=0跳过置换步骤,输出逐位等于copy_last(表达和坐标均不变),预期分数回到50.00。对照验证:array_equal(输出X, 输入X) and array_equal(输出坐标, 输入坐标)。",
"sources": []
}
``` |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/researcher.jsonl 4 KB /home/spark-longxinyang/vec/runs/formal/20261003-105537-search-t2-heart-extrap-g24-D/nodes/7/researcher.stderr |