总览 · ← 返回运行 20261003-172000-search-t2-heart-extrap-chain-12h
节点 n9
坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261003-172000-search-t2-heart-extrap-chain-12h |
|---|---|
| 父节点 | n3 |
| 子节点 | — |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 改进 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 49.60(-0.4) · proxy_noscale 49.60(-0.4) · 3 次复测均分 49.87 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 39 分 |
| 程序版本 | 35edb10ff1e00e77d71f8265f3a1737e776ae47c (programs.git) |
| 导入自 | 20261003-105537-search-t2-heart-extrap-g24-D#9 |
| 备注 | re-scored at launch (origin 20261003-105537-search-t2-heart-extrap-g24-D node 9, score there 49.60) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git 35edb10ff1:solution/METHOD.md
坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。
方法
- 基座与父节点/copy_last 完全相同:anchor=最后观测阶段(
anchor_entry),n=clip(stage.n, min_cells, max_cells),按型分层无放回抽样(src.task2_spatial.sample.take),行序np.sort保持稳定。代理上 n=24826(≤max_cells),输出全部细胞。 - 表达度量:sklearn PCA(32 维,svd_solver=randomized,random_state=seed)作用于输出的 X。
- 空间图:坐标上的 15-NN(kneighbors n_neighbors=16 去掉自身),与评分器 neighborhood 定义一致;图的边集由坐标点集决定,置换不改变边集,故可用 O(1) 增量更新。
- 贪心 2-opt:随机提议细胞对 (i,j),交换二者坐标(coords[i]↔coords[j],即"行 i 占据的坐标槽"互换);能量变化用闭式 delta = 2(e_i−e_j)·(S_p−S_q) + (a+b)‖e_i−e_j‖²(S=槽的邻居表达和,增量维护;a,b=两槽互为邻居的指示),delta<0 才接受。行序稳定:X 的行序与输出行序和 copy_last 完全一致,置换只改每行携带的坐标(PLAN 第 6 条,修复节点 7 的行序缺陷)。
- 参数:VEC_MAX_SWAPS 默认 0.3n(=7447,绑定约束)、VEC_MAX_PROP 默认 4,000,000(实际 1.31M 提议即达 swap 上限)、VEC_PATIENCE=10000、VEC_PCA_DIM=32、VEC_LOCAL=0(局部提议模式已实现并测试,见下)。
- 机制关闭对照:VEC_MAX_SWAPS=0 → 完全跳过 2-opt,输出逐位等于 copy_last。
机制生效证据(实际改变了什么)
- 交换 7447 次,5152 行坐标改变;Dirichlet 能量 E1/E0 = 0.937(下降 6.3%)。
- X 与对照 array_equal;坐标点集(排序后)array_equal——只有配对关系变。
- 四组分:expression_change、cell_state 保持 50.00(DE 原始值 0、mmd_u/variogram 与对照逐位相同,因为表达完全没动);变化集中在 local_spatial(neighborhood_mmd 0.11445→0.11256)与 shape_scale(occupancy_dice,见下"噪声")。
对照与查分(proxy_noscale,8 次查分)
| 配置 | swaps | E1/E0 | 榜分 | local_spatial | shape_scale | neighborhood_mmd | dice |
|---|---|---|---|---|---|---|---|
| 机制关(=copy_last) | 0 | 1.000 | 50.00 ✓(PLAN 预期) | 50.00 | 50.00 | 0.11445 | 0.8148 |
| 全局 2-opt,500k 提议 | 3196 | 0.971 | 50.23 | 50.56 | 50.37 | 0.11194 | 0.8156 |
| 全局 2-opt,0.3n swaps(提交) | 7447 | 0.937 | 51.51(seed0) | 50.42 | 55.61 | 0.11256 | 0.8221 |
| 同上 seed1 / seed2 | 7447 | 0.934 | 49.88 / 49.74 | 50.45 / 50.61 | 49.06 / 48.35 | 0.11243 / 0.11168 | 0.8131 / 0.8115 |
| 全局 2-opt,12k swaps | 12000 | 0.904 | 49.70 | 51.08 | 47.70 | 0.10961 | 0.8106 |
| 全局 2-opt,25k swaps | 25000 | 0.832 | 49.28 | 51.03 | 46.08 | 0.10983 | 0.8065 |
验证过 / 未验证(诚实声明)
- 验证过:机制本身(能量下降 → neighborhood_mmd 下降)单调且跨 seed 稳健:local_spatial 在全部 3 个 seed、全部 swap 档位都 >50(50.4–51.1),swap 越多 neighborhood 越好(0.1145→0.1096,12k 档后饱和)。行序稳定成立:输出行序、X、坐标点集与 copy_last 逐位一致(array_equal),未复现节点 7 的行序缺陷。机制关闭对照 = 50.00,与 PLAN 预期一致。
- 噪声警告(重要):置换不改变坐标点集本身,但 occupancy_dice / d2_shape 会对细胞下采样(行索引固定、坐标被置换 → 子集几何变化),所以 shape_scale 在 seed 间大幅波动(48.4–55.6),是子抽样噪声而非系统性收益或损失。seed0 的 51.51 含 dice=0.8221 的正向波动;seed 0–2 均值仅 50.38,低于终选护栏要求的 +1。0.3n 档是在 A 半 seed0 上实测最高的档位;12k/25k 档 neighborhood 更好但 dice 系统性偏低(0.8106/0.8065),总分更低。
- 测过但未采用:VEC_LOCAL=1(只提议空间相邻槽的交换):25k swaps 只降能量 0.5%(相邻对 delta 太小),预期对 neighborhood_mmd 无实质改善,未查分。
- 未验证:B 半分数;真实 final 视图(外推榜本地尺子历史上高估官网分,方法卡风险条款适用——本机制不含任何位移方向或尺寸假设,理论上对 final 的行为与代理一致:只做配对重排)。
知识来源
- 评分器 neighborhood 定义(15 最近邻)来自任务书公开的评分规则简报(代码事实),用于选择 kNN 图的 K=15;无保留阶段/基因型的任何信息、无绝对时间或尺寸常数;程序只读 manifest 的相对字段,伪装视图安全(时间平移不影响任何计算:本方法根本不使用时间)。
下一步建议
- shape 组的 seed 间波动源于评分器下采样与行索引的耦合,属尺子噪声;若继续本机制,应看 rank3(3 seed 均值)而非单 seed。要突破 +1 护栏,local_spatial 的稳健收益(~+0.5)不够,需要叠加 expression/cell_state 组的真实改进(如 PLAN 外的型内组成插值),或接受本节点作为"机制验证成功但净收益 <1"的中间结果。
调研员的计划
| 名称 | T2HX-06 坐标置换(稳定行序版):表达kNN引导2-opt重排细胞-坐标配对 |
|---|---|
| 动机 | 父节点3输出逐位等于copy_last(50.00),机制完全未激活(代理无外部前向样本、速度回退反相关已关)。全树唯一超过地板的是节点5(52.74),核心机制为表达kNN引导的坐标2-opt置换(local_spatial +0.98)。节点7从同一父节点复制该机制却得49.60,原因是置换后行序改变导致评分器按索引抽样时occupancy_dice碰到不利子集(0.8066→0.8025,−1.67)。本方案复制节点5已验证的机制,同时修复节点7的行序缺陷,目标稳定获得local_spatial收益而不赔shape_scale。 |
| 做法 | 在父节点3的copy_last基座上新增坐标置换层,步骤: 1. 细胞选取与父节点相同(anchor=末观测阶段,n≤max_cells分层抽样),坐标点集与表达矩阵均不改。 2. 表达空间:对X做PCA取前32主成分(sklearn PCA,whiten=False),作为细胞表达距离度量。 3. 空间图:用坐标建15-NN图(与评分器neighborhood_mmd的邻域定义一致,sklearn NearestNeighbors,n_neighbors=16含自身,取后15)。 4. 目标函数:Dirichlet能量 E=Σ_{(i,j)∈edges}‖pca_i−pca_j‖²。初始E0记录。 5. 贪心2-opt:随机提议配对(i,j),若交换坐标i↔j后E下降则接受;上限VEC_MAX_PROP(默认500000,搜索范围200k–1M);提前终止:连续10000次无接受则停。 6. 行序稳定(关键修复):置换只改坐标赋值(coords_new[i]=coords[j], coords_new[j]=coords[i]),输出行序与输入完全一致,避免评分器按索引抽样时碰到不同细胞子集。验证:输出行序与输入array_equal。 7. 输出:X不变、坐标为重排后值、行序不变。 8. 环境变量:VEC_MAX_SWAPS(最大交换次数,默认=细胞数×0.3)、VEC_MAX_PROP(最大提议数)、VEC_PCA_DIM(默认32)。VEC_MAX_SWAPS=0时不做任何交换,输出逐位等于copy_last(对照)。 9. 快速筛选:先500细胞子集跑通验证E下降且swap>0(<1min),再全量;vec-score查VEC_MAX_SWAPS=0应得50.00,查全量看neighborhood_mmd原始值是否下降(越小越好)。 10. 单输入阶段退路:置换不依赖时间信息,单输入同样适用。 |
| 风险 | 1) occupancy_dice仍可能因坐标重排后点云局部密度变化而微波动(±0.5分内),但行序稳定后不应出现节点7的−1.67;若occupancy_dice skill<0.48,检查是否抽样仍受影响。2) 2-opt在30min内可能未充分收敛(提议数不够),Dirichlet能量下降不足→neighborhood_mmd改善小;Engineer应在小样本上确认能量下降>10%再全量。3) 若细胞数很大(>5000),500k提议可能不够覆盖,可调高至1M但注意时间。4) 行序验证失败(输出行序≠输入行序)说明实现有bug,必须修复后再查分。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:父节点版本 a0271985a8。改动的文件:solution/METHOD.md +31 −21、solution/run.py +115 −211
diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 6640572..0c5d66d 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,32 +1,42 @@-T2HX-01 位移层:外推时优先用视图内"晚于末输入、不晚于目标"的外部样本按伪批量相关匹配细胞型做阻尼位移(保坐标);两输入速度回退在代理上验证有害、默认关;无可用方向时逐位等于 copy_last。+坐标置换(稳定行序版,T2HX-06):copy_last 基座上,在坐标 15-NN 图上用贪心 2-opt 最小化表达 PCA-32 的 Dirichlet 能量,只重排细胞-坐标配对;X、坐标点集、行序逐位不变。 ## 方法 -在父节点(copy_last)之上加一个位移层,管线(全部只读 manifest 的相对时间与区间字段,无绝对阶段/尺寸常数,伪装视图安全):+1. 基座与父节点/copy_last 完全相同:anchor=最后观测阶段(`anchor_entry`),n=clip(stage.n, min_cells, max_cells),按型分层无放回抽样(`src.task2_spatial.sample.take`),行序 `np.sort` 保持稳定。代理上 n=24826(≤max_cells),输出全部细胞。+2. 表达度量:sklearn PCA(32 维,svd_solver=randomized,random_state=seed)作用于输出的 X。+3. 空间图:坐标上的 15-NN(kneighbors n_neighbors=16 去掉自身),与评分器 neighborhood 定义一致;图的边集由坐标点集决定,置换不改变边集,故可用 O(1) 增量更新。+4. 贪心 2-opt:随机提议细胞对 (i,j),交换二者坐标(coords[i]↔coords[j],即"行 i 占据的坐标槽"互换);能量变化用闭式 delta = 2(e_i−e_j)·(S_p−S_q) + (a+b)‖e_i−e_j‖²(S=槽的邻居表达和,增量维护;a,b=两槽互为邻居的指示),delta<0 才接受。**行序稳定**:X 的行序与输出行序和 copy_last 完全一致,置换只改每行携带的坐标(PLAN 第 6 条,修复节点 7 的行序缺陷)。+5. 参数:VEC_MAX_SWAPS 默认 0.3n(=7447,绑定约束)、VEC_MAX_PROP 默认 4,000,000(实际 1.31M 提议即达 swap 上限)、VEC_PATIENCE=10000、VEC_PCA_DIM=32、VEC_LOCAL=0(局部提议模式已实现并测试,见下)。+6. 机制关闭对照:VEC_MAX_SWAPS=0 → 完全跳过 2-opt,输出逐位等于 copy_last。 -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 次查分)+- 交换 7447 次,5152 行坐标改变;Dirichlet 能量 E1/E0 = 0.937(下降 6.3%)。+- X 与对照 array_equal;坐标点集(排序后)array_equal——只有配对关系变。+- 四组分:expression_change、cell_state 保持 50.00(DE 原始值 0、mmd_u/variogram 与对照逐位相同,因为表达完全没动);变化集中在 local_spatial(neighborhood_mmd 0.11445→0.11256)与 shape_scale(occupancy_dice,见下"噪声")。 -- **α=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 正常接收并打分同一文件,格式合规。+## 对照与查分(proxy_noscale,8 次查分)++| 配置 | swaps | E1/E0 | 榜分 | local_spatial | shape_scale | neighborhood_mmd | dice |+|---|---:|---:|---:|---:|---:|---:|---:|+| 机制关(=copy_last) | 0 | 1.000 | **50.00** ✓(PLAN 预期) | 50.00 | 50.00 | 0.11445 | 0.8148 |+| 全局 2-opt,500k 提议 | 3196 | 0.971 | 50.23 | 50.56 | 50.37 | 0.11194 | 0.8156 |+| **全局 2-opt,0.3n swaps(提交)** | 7447 | 0.937 | **51.51**(seed0) | 50.42 | 55.61 | 0.11256 | 0.8221 |+| 同上 seed1 / seed2 | 7447 | 0.934 | 49.88 / 49.74 | 50.45 / 50.61 | 49.06 / 48.35 | 0.11243 / 0.11168 | 0.8131 / 0.8115 |+| 全局 2-opt,12k swaps | 12000 | 0.904 | 49.70 | 51.08 | 47.70 | 0.10961 | 0.8106 |+| 全局 2-opt,25k swaps | 25000 | 0.832 | 49.28 | 51.03 | 46.08 | 0.10983 | 0.8065 |++## 验证过 / 未验证(诚实声明)++- **验证过**:机制本身(能量下降 → neighborhood_mmd 下降)单调且跨 seed 稳健:local_spatial 在全部 3 个 seed、全部 swap 档位都 >50(50.4–51.1),swap 越多 neighborhood 越好(0.1145→0.1096,12k 档后饱和)。行序稳定成立:输出行序、X、坐标点集与 copy_last 逐位一致(array_equal),未复现节点 7 的行序缺陷。机制关闭对照 = 50.00,与 PLAN 预期一致。+- **噪声警告(重要)**:置换不改变坐标点集本身,但 occupancy_dice / d2_shape 会对细胞下采样(行索引固定、坐标被置换 → 子集几何变化),所以 shape_scale 在 seed 间大幅波动(48.4–55.6),是子抽样噪声而非系统性收益或损失。seed0 的 51.51 含 dice=0.8221 的正向波动;**seed 0–2 均值仅 50.38**,低于终选护栏要求的 +1。0.3n 档是在 A 半 seed0 上实测最高的档位;12k/25k 档 neighborhood 更好但 dice 系统性偏低(0.8106/0.8065),总分更低。+- **测过但未采用**:VEC_LOCAL=1(只提议空间相邻槽的交换):25k swaps 只降能量 0.5%(相邻对 delta 太小),预期对 neighborhood_mmd 无实质改善,未查分。+- **未验证**:B 半分数;真实 final 视图(外推榜本地尺子历史上高估官网分,方法卡风险条款适用——本机制不含任何位移方向或尺寸假设,理论上对 final 的行为与代理一致:只做配对重排)。 ## 知识来源 -- GO BP 注释(视图 prior/go/gene_sets_bp.gmt):增殖模块(细胞周期/有丝分裂/DNA 复制)是瞬态程序,其早期轨迹不可靠外推——通用基因功能知识,非任何保留阶段测量。-- 细胞型配对用表达相关而非标签名(外部数据标签词汇可能不同)——PLAN T2HX-01 规定。+- 评分器 neighborhood 定义(15 最近邻)来自任务书公开的评分规则简报(代码事实),用于选择 kNN 图的 K=15;无保留阶段/基因型的任何信息、无绝对时间或尺寸常数;程序只读 manifest 的相对字段,伪装视图安全(时间平移不影响任何计算:本方法根本不使用时间)。 -## 已知弱点 / 未验证+## 下一步建议 -- (a) 路径在真实 final 视图(可能挂 MOSTA E9.5 等前向外部样本)上的效果无法在本地检验;若方向错会低于地板。终选护栏(proxy_noscale 种子均值需超 copy_last 1 分)在本节点上不会通过,正式提交会回落到基线预测。-- 未做:STRING/通路平滑速度、t 统计加权、逐细胞 OT 位移(方法卡已证 OT 伤邻域)。+- shape 组的 seed 间波动源于评分器下采样与行索引的耦合,属尺子噪声;若继续本机制,应看 rank3(3 seed 均值)而非单 seed。要突破 +1 护栏,local_spatial 的稳健收益(~+0.5)不够,需要叠加 expression/cell_state 组的真实改进(如 PLAN 外的型内组成插值),或接受本节点作为"机制验证成功但净收益 <1"的中间结果。diff --git a/solution/run.py b/solution/run.pyindex 196e6b2..d0b3cb4 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,22 +1,22 @@ #!/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-guided coordinate permutation.++Mechanism (family T2HX-06): keep the anchor (last observed) stage's cells,+expression matrix and the coordinate point set bitwise unchanged; only re-pair+cells with coordinates. A greedy 2-opt search on the fixed 15-NN graph of the+coordinate slots minimizes the Dirichlet energy of the cells' expression PCA+embedding, so that spatial neighbours become expression-similar. This targets+neighborhood_mmd (expression-location pairing) without touching any metric+that depends on the point set or the expression values.++Row order is kept identical to the copy_last output: a swap exchanges the+coordinate rows assigned to two cells (coords[i] <-> coords[j]) and never+reorders X or the output rows, so any index-based subsampling by the scorer+sees the same cell subset as the copy_last baseline.++No absolute stage names, times or sizes are used; only the manifest's inputs,+so the program is view-independent. VEC_MAX_SWAPS=0 disables the mechanism+and the output equals copy_last bitwise (mechanism-off control). """ from __future__ import annotations@@ -24,158 +24,90 @@ 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):+MAX_SWAPS_ENV = os.environ.get("VEC_MAX_SWAPS", "") # default: 0.3 * n+MAX_PROP = int(os.environ.get("VEC_MAX_PROP", "4000000"))+PCA_DIM = int(os.environ.get("VEC_PCA_DIM", "32"))+PATIENCE = int(os.environ.get("VEC_PATIENCE", "10000"))+LOCAL = bool(int(os.environ.get("VEC_LOCAL", "0"))) # restrict proposals to spatially adjacent slots+K = 15 # neighbours, matching the scorer's neighborhood definition+++def _dirichlet_energy(E: np.ndarray, nbrs: np.ndarray, nrm2: np.ndarray, S: np.ndarray) -> float:+ # sum_i sum_{u in nbrs[i]} ||e_i - e_u||^2 (asymmetric kNN sum)+ return float(15.0 * nrm2.sum() + np.add.reduceat(nrm2[nbrs.ravel()], np.arange(0, nbrs.size, K)).sum()+ - 2.0 * np.einsum("ij,ij->i", E, S).sum())+++def _permute_coords(coords0: np.ndarray, E: np.ndarray, nbrs: np.ndarray, rev: list[list[int]],+ nbrset: list[set], max_swaps: int, max_prop: int, patience: int,+ rng: np.random.Generator) -> tuple[np.ndarray, dict]:+ n = coords0.shape[0]+ S = E[nbrs].sum(axis=1) # slot -> sum of neighbour expressions (slot p holds row p initially)+ nrm2 = np.einsum("ij,ij->i", E, E)+ e0 = _dirichlet_energy(E, nbrs, nrm2, S)++ pos = np.arange(n) # row -> slot currently holding its coordinates+ occ = np.arange(n) # slot -> row currently occupying it+ swaps = 0+ props = 0+ rejects = 0+ t0 = time.time()+ # For LOCAL mode, candidate partners occupy slots spatially adjacent to the+ # row's current slot, so a swap moves a coordinate only a short distance:+ # the subsampled point cloud geometry (d2_shape / occupancy_dice) stays+ # close to the anchor while expression-position pairing (neighborhood_mmd)+ # is smoothed locally.+ while swaps < max_swaps and props < max_prop and rejects < patience:+ i = int(rng.integers(n))+ p = int(pos[i])+ if LOCAL:+ q = int(nbrs[p][rng.integers(K)])+ j = int(occ[q])+ else:+ j = int(rng.integers(n))+ if i == j: 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+ props += 1+ q = int(pos[j])+ d = E[i] - E[j]+ a = q in nbrset[p]+ b = p in nbrset[q]+ dd = float(d @ d)+ delta = 2.0 * float(d @ (S[p] - S[q])) + (a + b) * dd+ if delta < -1e-12:+ # accept: row i takes slot q, row j takes slot p+ pos[i], pos[j] = q, p+ occ[p], occ[q] = j, i+ dv = -d # e_j - e_i+ for u in rev[p]:+ S[u] += dv+ for u in rev[q]:+ S[u] -= dv+ if a:+ S[p] -= dv+ if b:+ S[q] += dv+ swaps += 1+ rejects = 0+ else:+ rejects += 1+ e1 = _dirichlet_energy(E[occ], nbrs, nrm2[occ], S)+ diag = {"E0": e0, "E1": e1, "ratio": e1 / e0 if e0 > 0 else float("nan"),+ "swaps": swaps, "props": props, "secs": round(time.time() - t0, 1)}+ return coords0[pos], diag def main() -> None:@@ -187,8 +119,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)@@ -199,59 +129,33 @@ def main() -> None: X = stage.X[rows].toarray().astype(np.float32) coords = stage.coords[rows]+ n = 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)+ max_swaps = int(float(MAX_SWAPS_ENV) if MAX_SWAPS_ENV else 0.3 * n)++ if max_swaps > 0 and n > K + 1:+ from sklearn.decomposition import PCA+ from sklearn.neighbors import NearestNeighbors++ dim = int(min(PCA_DIM, X.shape[0] - 1, X.shape[1]))+ pca = PCA(n_components=dim, svd_solver="randomized", random_state=args.seed)+ E = np.ascontiguousarray(pca.fit_transform(X.astype(np.float64)), dtype=np.float64)++ idx = NearestNeighbors(n_neighbors=K + 1).fit(coords).kneighbors(coords, return_distance=False)+ nbrs = np.empty((n, K), dtype=np.int64)+ for i in range(n):+ kept = [int(j) for j in idx[i] if j != i][:K]+ while len(kept) < K:+ kept.append(i)+ nbrs[i] = kept+ rev: list[list[int]] = [[] for _ in range(n)]+ for u in range(n):+ for p in nbrs[u]:+ rev[int(p)].append(u)+ nbrset = [set(int(v) for v in nbrs[i]) for i in range(n)]++ coords, diag = _permute_coords(coords, E, nbrs, rev, nbrset, max_swaps, MAX_PROP, PATIENCE, rng)+ print(f"[diag] permute: {diag}", 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:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 在 copy_last 基座(父节点 3)上把 T2HX-01 位移层整体换成 T2HX-06 坐标置换层:X、坐标点集、行序逐位不动,只在坐标 15-NN 图上做贪心 2-opt(闭式 delta + 增量维护邻居表达和)最小化表达 PCA-32 的 Dirichlet 能量,重排细胞与坐标的配对;提交档 0.3n=7447 次交换、能量降 6.3%,VEC_MAX_SWAPS=0 时逐位等于 copy_last。耗时 2.3s→10.8s,内存 0.42→0.54GB。 |
|---|---|
| 各组分数的变化 | cell_state:完全没动:mmd_u 0.05832→0.05832、variogram 0.05806→0.05806,skill 均 0.500,组 +0.00(符合设计)。 expression_change:完全没动:de_score/de_direction 原始值均 0(无变化保护),skill 0.500→0.500,组 50.00→50.00,+0.00(符合设计:表达矩阵未改)。 local_spatial:机制方向正确但收益在噪声边缘:neighborhood_mmd 0.11261→0.11066,skill 0.500→0.504,得分 +0.11;组 50.00→50.44,+0.44(T2 噪声约 1 分,单项收益小于噪声)。 shape_scale:变坏且超出噪声:occupancy_dice 0.8066→0.8016,skill 0.500→0.440,得分 −0.50;d2_shape 0.04911→0.04938,得分 −0.01;scale_log_ratio 未动(−0.4334)。组 50.00→47.96,−2.04。结构门未触发(邻域 skill 0.504>0.5)。 |
| family_id | T2HX-06 |
| 假设是否成立 | 否 |
| 经验 |
|
| mechanism_active | 是 |
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 3 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 3 次:— |
| 用时 | 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 21,569 · 输出 1,650 · 思考 2,158 |
| 任务(第一行) | 分析节点 n9 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/analyst.jsonl (文件不在) |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 30 |
| 工具调用 | 共 33 次:— |
| 用时 | 26 分 |
| token 数 | 输入 75,511 · 输出 12,331 · 思考 24,589 |
| 任务(第一行) | 按计划修改程序:T2HX-06 坐标置换(稳定行序版):表达kNN引导2-opt重排细胞-坐标配对 |
| 最后的回答(摘录) | Done. Summary: **做了什么**:按 PLAN T2HX-06 在 copy_last 基座上实现"表达kNN引导坐标置换(稳定行序版)"——X、坐标点集、行序逐位不变,仅用贪心 2-opt 在坐标 15-NN 图上最小化 PCA-32 表达 Dirichlet 能量,重排细胞-坐标配对(闭式 delta + 增量维护邻居表达和,全量 24826 细胞约 20s)。 **查分结果**(8/20 次额度):机制关闭对照 = 50.00 ✓(PLAN 预期)。提交配置(0.3n=7447 swaps,E↓6.3%):seed0 = **51.51**(local_spatial 50.42,dice 0.8221 正向波动);seed1/2 = 49.88/49.74。neighborhood_mmd 改善跨 seed、跨档位稳健(local_spatial 全部 >50,0.1145→0.1096),但 shape 组因评分器按行索引下采样与置换耦合而大幅波动(48.4–55.6),3-seed 均值仅 50.38,低于终选 +1 护栏。12k/25k swaps 档 neighborhood 更好但 dice 系统性偏低,总分更低。 **下一步最值得试**:置换机制已验证但净收益 <1;要过护栏需在 expression_change/cell_state 组叠加真实改进(如型内组成插值或先验驱动的秩级方向调整),或与本置换层组合以保住 local_spatial 的稳健 +0.5。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/engineer.jsonl (文件不在) |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 7 分 |
| token 数 | 输入 15,107 · 输出 6,383 · 思考 8,192 |
| 任务(第一行) | 为节点 n9(改进,父节点 n3)写改进计划 PLAN.json |
| 最后的回答(摘录) | _dice碰到不利子集(0.8066→0.8025,−1.67)。本方案复制节点5已验证的机制,同时修复节点7的行序缺陷,目标稳定获得local_spatial收益而不赔shape_scale。",
"approach": "在父节点3的copy_last基座上新增坐标置换层,步骤:\n1. 细胞选取与父节点相同(anchor=末观测阶段,n≤max_cells分层抽样),坐标点集与表达矩阵均不改。\n2. 表达空间:对X做PCA取前32主成分(sklearn PCA,whiten=False),作为细胞表达距离度量。\n3. 空间图:用坐标建15-NN图(与评分器neighborhood_mmd的邻域定义一致,sklearn NearestNeighbors,n_neighbors=16含自身,取后15)。\n4. 目标函数:Dirichlet能量 E=Σ_{(i,j)∈edges}‖pca_i−pca_j‖²。初始E0记录。\n5. 贪心2-opt:随机提议配对(i,j),若交换坐标i↔j后E下降则接受;上限VEC_MAX_PROP(默认500000,搜索范围200k–1M);提前终止:连续10000次无接受则停。\n6. **行序稳定(关键修复)**:置换只改坐标赋值(coords_new[i]=coords[j], coords_new[j]=coords[i]),输出行序与输入完全一致,避免评分器按索引抽样时碰到不同细胞子集。验证:输出行序与输入array_equal。\n7. 输出:X不变、坐标为重排后值、行序不变。\n8. 环境变量:VEC_MAX_SWAPS(最大交换次数,默认=细胞数×0.3)、VEC_MAX_PROP(最大提议数)、VEC_PCA_DIM(默认32)。VEC_MAX_SWAPS=0时不做任何交换,输出逐位等于copy_last(对照)。\n9. 快速筛选:先500细胞子集跑通验证E下降且swap>0(<1min),再全量;vec-score查VEC_MAX_SWAPS=0应得50.00,查全量看neighborhood_mmd原始值是否下降(越小越好)。\n10. 单输入阶段退路:置换不依赖时间信息,单输入同样适用。",
"expected_groups": ["local_spatial", "shape_scale"],
"risks": "1) occupancy_dice仍可能因坐标重排后点云局部密度变化而微波动(±0.5分内),但行序稳定后不应出现节点7的−1.67;若occupancy_dice skill<0.48,检查是否抽样仍受影响。2) 2-opt在30min内可能未充分收敛(提议数不够),Dirichlet能量下降不足→neighborhood_mmd改善小;Engineer应在小样本上确认能量下降>10%再全量。3) 若细胞数很大(>5000),500k提议可能不够覆盖,可调高至1M但注意时间。4) 行序验证失败(输出行序≠输入行序)说明实现有bug,必须修复后再查分。",
"family_id": "T2HX-06",
"mechanism": "在坐标15-NN图上用贪心2-opt最小化表达Dirichlet能量,重排细胞与坐标的配对关系,使表达相似的细胞成为空间邻居,从而改善neighborhood_mmd(表达-位置配对质量)。表达矩阵和坐标点集本身均不变。",
"vs_constant_shift": "不做任何表达位移:每个细胞的表达向量原封不动,每个坐标点也原封不动;只改变哪个细胞对应哪个坐标。常数位移是对每型加一个表达向量,改变表达值本身;本机制不改变任何表达值,只改变表达与空间位置的配对。",
"mechanism_evidence": "1) 交换计数>0且Dirichlet能量下降百分比(E_after/E_before<1);2) neighborhood_mmd原始值下降(0.1126→更低);3) local_spatial组分>50;4) 表达矩阵与坐标点集逐位不变(array_equal验证),只有配对关系改变;5) 四组分中expression_change和cell_state保持50(表达未动),变化集中在local_spatial。",
"mechanism_off_control": "设VEC_MAX_SWAPS=0,程序跳过2-opt循环,输出逐位等于copy_last(X和坐标array_equal验证),vec-score应得50.00、四组全50。与开启置换的输出对比:差异仅在坐标列的排列和local_spatial/shape_scale组分。",
"sources": []
} |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261003-172000-search-t2-heart-extrap-chain-12h/nodes/9/researcher.jsonl (文件不在) |