总览 · ← 返回运行 20261001-233756-search-t1-abc-r0-B-population
节点 n5
copy_last(最新官方阶段)+ 单快照 Palantir 伪时间趋势基因的小幅上移(ε=0.02·σ,仅正向、无裁剪),方向由 prior/Reactome 增殖根或前一官方阶段 delta 对齐决定;对齐不可靠(如 X3)则回退 copy_last。
| 运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。 | 20261001-233756-search-t1-abc-r0-B-population |
|---|---|
| 父节点 | n2 |
| 子节点 | n10 |
| 操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。 | 改进 |
| 状态 | 已打分 |
| 分数 | 搜索目标分 50.83(+0.8) · proxy 51.25(+1.2) · proxy2 51.25(+1.2) · X3 50.00(+0.0) · 3 次复测均分 50.60 |
| 审查 | 未审查 |
| 用时?从运行开始到结束(或到现在)的挂钟时间。 | 28 分 |
| 程序版本 | 4047019f3ff385df322c79b59104726928166e7e (programs.git) |
方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。
来自 programs.git 4047019f3f:solution/METHOD.md
copy_last(最新官方阶段)+ 单快照 Palantir 伪时间趋势基因的小幅上移(ε=0.02·σ,仅正向、无裁剪),方向由 prior/Reactome 增殖根或前一官方阶段 delta 对齐决定;对齐不可靠(如 X3)则回退 copy_last。
方法
在父节点 2 骨架(基群体=最新官方输入阶段,外部阶段永不直接输出,sample_rows 无放回抽样,legacy α 位移保持关闭)上新增 pseudotime.py:
- 从基阶段确定性亚抽样 ≤5000 细胞(rng seed+777000,不影响抽样 rng,ε=0 时输出与父节点逐位一致)。
- 增殖打分:
prior/reactome/gene_sets.gmt的 "Cell Cycle" 基因集(R-MMU-1640170,586/面板基因),逐基因 z 分数取均值。根细胞 = 增殖 top50 中最接近其质心者(k041/CellRank2 惯例:根由增殖/祖细胞特征定,绝不用目标数据)。 - 伪时间:Palantir(
JAX_PLATFORMS=cpu,2000 HVG,diffusion 10 维,multiscale,500 waypoints,seed=args.seed);失败回退 scanpy diffmap+DPT;再失败→无位移。 - 定向:(a) 若存在更早的官方输入阶段,取伪批量 delta=(base−prev),按 spearman(ρ_g, delta_g)(σ>0.05 的基因)对齐,要求 align≥0.15,否则翻转;(b) 否则用增殖门:spearman(prolif, pt)≤−0.05(不满足则翻转,翻转后仍不满足→回退);(c) 两门都不可靠→无位移(copy_last)。
- 趋势基因:亚抽样上逐基因 spearman ρ(表达, 伪时间),取 |ρ|≥0.1 的 top1000。
- 位移:只上移 ρ>0 的基因,x+d_g,d_g=ε·σ_g(σ_g=基因在亚抽样上的 std,ε 默认 0.02,env
VEC_EPS)。不裁剪(正向位移天然非负)。ρ、σ 全部现场估计,无任何硬编码统计量。
关键发现(为什么是"只上移、无裁剪、小 ε")
- 裁剪毁 covariation:双向位移 + clip(x+d,0) 会把负位移基因的大量小非零值抹成 0,X3 上 ε=0.02 就使 covariation 50→44.2(ε=0.1→33.6)。
- 负值被打分器拒绝:不裁剪的双向位移产生负表达,
vec-score直接 score_failed("metrics expect log-normalized nonnegative data")。 - 任何均匀位移对 pred-target covariation 有 ~ε² 惩罚:即使无裁剪,proxy ε=0.1 时 covariation 42.2,ε=0.05 时 48.4,ε=0.02 时 51.9;而 de/direction/cell_state 增益 ~ε 线性 → 存在小 ε 最优。
- X3 的伪时间轴定向不可靠:X3(心脏-only,3 类型)增殖门 |rho|≈0.0002;与真实 E8.75→E9.0 delta 的 align 仅 ±0.067;且 delta 对齐方向在 A 半上 de_direction=−0.165(与 E9.0→E9.5 目标方向反)。故 ALIGN_MIN 提高到 0.15 后 X3 自动回退 copy_last(输出与父节点逐位一致,50.00 保底)。
- proxy E8.5(全胚、强谱系梯度)增殖门 rho=−0.167,通过;方向被证实正确:de_direction=+0.045,四个组全部 ≥ 父节点。
关键参数
ε=0.02(扫描 0.02/0.03/0.05/0.1 → proxy 51.41/51.29/51.05/49.98);RHO_MIN=0.1;K_GENES=1000;ALIGN_MIN=0.15;AXIS_MIN_RHO=0.05;N_SUB=5000。α(legacy 逐类型位移)默认 0,保留未用。
验证过什么(vec-score,A 半)
| 预测 | proxy | proxy2 | X3 |
|---|---|---|---|
| 父节点(copy_last) | 50.04 | 50.40 | 50.00 |
| 本节点 ε=0.02 只上移无裁剪 | 51.41 | 51.41 | 50.00(=copy_last,逐位一致) |
| ε=0.03 | 51.29 | - | - |
| ε=0.05 | 51.05 | - | - |
| ε=0.1 | 49.98 | - | - |
| ε=0.02/0.05/0.1 双向+裁剪(仅 X3 定向生效) | - | - | 44.2/39.4/33.6 组分 covariation |
ε=0.02 在 proxy 上四组全升:cell_state 50.78、covariation 51.88、de_recovery 51.96、direction 51.26(父 49.93/50.11/50.00/50.11)。proxy2 预测与 proxy 逐位相同(同一基阶段),查分同为 51.41。三视图 vec-check 通过;X3 运行时 ~5s、proxy ~20s、内存与父节点同级。
没验证什么 / 风险
- 增益 +1.4(proxy/proxy2)低于 2 分噪声线,但四组同向且 ε 曲线有内部一致的最优点;B 半可能回落。
- final 视图(E8.5+E9.5→E10.5)未验证:定向将走 delta 对齐路径(E8.5→E9.5 官方伪批量,预期 align 远高于 X3 的 0.067),该路径在 X3 上被证明会选错方向(X3 特有:align 太弱),在 final 上 align 若 ≥0.15 生效、否则回退增殖门/copy_last——无崩溃风险,但方向正确性无本地证据。
- 种子间方差未测(本地只跑 seed 0);伪时间/根选择对 seed 的敏感性未测(Palantir waypoints 有 seed,deterministic)。
- Palantir 在 proxy 上出现 "Sparse solver failed, falling back to dense inverse" 警告,结果仍确定。
生物学知识来源
- Reactome "Cell Cycle"(R-MMU-1640170):来自 view 内
prior/reactome/gene_sets.gmt,用于增殖打分定根(通用机制知识:祖细胞增殖高于分化细胞)。 - Palantir(DOI 10.1038/s41587-019-0068-4)与 CellRank 2(DOI 10.1038/s41592-024-02303-9)的根选择惯例:k041 离线记录;未使用任何保留阶段/禁窗测量数据。
调研员的计划
| 名称 | 单快照伪时间定向微位移(prior 选根,ε≤0.05) |
|---|---|
| 动机 | 父节点 2(50.03)在三把尺子上均为 copy_last 地板:四组分 cell_state 49.93 / covariation 50.11 / de_recovery 50.00 / direction 50.11,de_recovery 与 direction 最弱且预测完全没有时间方向。X3 实测逐类型数据驱动位移 α=0.25/0.5/1.0 → 44.15/42.43/40.33,covariation 50→28.6 单调崩塌(k018 亦载 α=1 低于 copy_last),故不能再做数据驱动的大位移;但方向组分的损失来自「不动」,沿有生物学依据的发育轴做极小位移可在不破坏共变的前提下提供方向与 DE 趋势信号。k041 表明 Palantir/scanpy 伪时间在单快照可用,与 proxy/proxy2/X3 的代码路径一致。 |
| 做法 | 在父节点 run.py 骨架上(基群体=最新官方阶段、sample_rows 抽样、外部阶段永不输出;α 位移分支整体删除或保持不触发)加一个伪时间微位移模块,任何一步失败即回退 copy_last:1) 伪时间:对基阶段(最多亚抽样 5000 细胞)设 JAX_PLATFORMS=cpu,Palantir run_diffusion_maps(n_components=10)+determine_multiscale_space+run_palantir(num_waypoints=500);失败则 scanpy diffmap+DPT 回退;再失败则纯 copy_last。2) 根:用 prior/ 中 Reactome 细胞周期基因集算增殖打分,根=打分最高的祖细胞(k041:以增殖/祖细胞特征定根,绝不用目标数据);健全性检查伪时间十分位的增殖均值应单调递减,否则翻转轴,翻转后仍异常则回退。3) 趋势基因:对全部基因算表达~伪时间的 Spearman ρ(用亚抽样的 5000 细胞估计,位移应用到全体),取 |ρ|≥0.1 且按 |ρ| 前 1000 个为位移基因集。4) 位移:x_new = clip(x + ε·sign(ρ_g)·σ_g, 0),σ_g 为基因 g 在基阶段的表达标准差,ε 环境变量 VEC_EPS 初值 0.01,扫描 {0.005,0.01,0.02,0.05};σ 与 ρ 均内部估计,不硬编码任何阶段的统计量。5) 查分流程(≤20 次):先只跑 X3 全扫 4 个 ε(4 次),看总分与四组分;任一 ε 使 covariation 较 50 下降 >2 即弃该 ε;最佳 ε 在 X3 复跑 1 次确认 >2 分噪声;随后最佳 ε 跑 proxy、proxy2(2 次,单阶段/双输入下基阶段同为官方最新阶段,代码同一);有余力补最佳 ε 复跑 2 次。proxy2 的外部 Qiu E9.0 不参与;X3 无官方阶段时以最新外部阶段为基(同父节点),伪时间仍在单快照内成立。 |
| 风险 | 1) 根或轴方向反了 → 位移背离目标,全组降分;首轮 X3 运行即可暴露,立即回退或翻转。2) ε 过大重蹈 α 位移破坏 covariation 的覆辙 → 以 X3 组分为闸,任何 ε 下 covariation 掉 >2 即弃;扫描上限仅 0.05(等效幅度远低于已证有害的 α=0.25)。3) 真实增益可能 <2 分噪声 → 最佳 ε 至少复跑 2 次,未稳定超 50.03+2 就如实报告无增益。4) Palantir/jax 导入失败或耗时超限 → JAX_PLATFORMS=cpu + DPT 回退 + 5000 细胞亚抽样;父节点基线仅 1.1s/1.26GB,预算充足。 |
代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。
对比:父节点版本 bd089b836e。改动的文件:solution/METHOD.md +32 −20、solution/pseudotime.py +239 −0、solution/run.py +45 −21
diff --git a/solution/METHOD.md b/solution/METHOD.mdindex 5735adc..7d42620 100644--- a/solution/METHOD.md+++ b/solution/METHOD.md@@ -1,37 +1,49 @@-以最新官方输入阶段做 copy_last(外部阶段永不直接当输出),类型配对伪批量位移默认收缩系数 α=0(实测任何 α>0 均降分),保留 shift 机制供后续节点调参。+copy_last(最新官方阶段)+ 单快照 Palantir 伪时间趋势基因的小幅上移(ε=0.02·σ,仅正向、无裁剪),方向由 prior/Reactome 增殖根或前一官方阶段 delta 对齐决定;对齐不可靠(如 X3)则回退 copy_last。 ## 方法 -- 基群体 = 最新**官方**输入阶段(proxy: E8.5;proxy2: E8.5 而非外部 Qiu E9.0;final: E9.5;X3 无官方输入时用最新外部阶段 E9.0)。这是对父节点的关键修正:父节点在 proxy2 上把只有心脏谱系、2174 个细胞、基因不全的 Qiu E9.0 直接当预测输出,proxy2 仅 27.43。CONTRACT 本身要求"不要把外部细胞直接当预测输出"。-- 无放回抽样到 target_n_cells(`sample_rows`),保留经验细胞分布与共变结构。-- 位移机制(默认关闭):对基阶段与其前一阶段共有的细胞类型(两边各 ≥30 个细胞)计算伪批量差值,乘 α 后加到该类型细胞行上,clip≥0。α 由环境变量 `VEC_ALPHA` 覆盖,默认 0.0,此时完全跳过(等价 copy_last)。-- 单阶段(proxy)自动退化为 copy_last,不会崩。+在父节点 2 骨架(基群体=最新官方输入阶段,外部阶段永不直接输出,sample_rows 无放回抽样,legacy α 位移保持关闭)上新增 `pseudotime.py`:++1. 从基阶段确定性亚抽样 ≤5000 细胞(rng seed+777000,不影响抽样 rng,ε=0 时输出与父节点逐位一致)。+2. 增殖打分:`prior/reactome/gene_sets.gmt` 的 "Cell Cycle" 基因集(R-MMU-1640170,586/面板基因),逐基因 z 分数取均值。根细胞 = 增殖 top50 中最接近其质心者(k041/CellRank2 惯例:根由增殖/祖细胞特征定,绝不用目标数据)。+3. 伪时间:Palantir(`JAX_PLATFORMS=cpu`,2000 HVG,diffusion 10 维,multiscale,500 waypoints,seed=args.seed);失败回退 scanpy diffmap+DPT;再失败→无位移。+4. 定向:(a) 若存在更早的**官方**输入阶段,取伪批量 delta=(base−prev),按 spearman(ρ_g, delta_g)(σ>0.05 的基因)对齐,要求 align≥0.15,否则翻转;(b) 否则用增殖门:spearman(prolif, pt)≤−0.05(不满足则翻转,翻转后仍不满足→回退);(c) 两门都不可靠→无位移(copy_last)。+5. 趋势基因:亚抽样上逐基因 spearman ρ(表达, 伪时间),取 |ρ|≥0.1 的 top1000。+6. 位移:**只上移** ρ>0 的基因,x+d_g,d_g=ε·σ_g(σ_g=基因在亚抽样上的 std,ε 默认 0.02,env `VEC_EPS`)。不裁剪(正向位移天然非负)。ρ、σ 全部现场估计,无任何硬编码统计量。++## 关键发现(为什么是"只上移、无裁剪、小 ε")++- **裁剪毁 covariation**:双向位移 + clip(x+d,0) 会把负位移基因的大量小非零值抹成 0,X3 上 ε=0.02 就使 covariation 50→44.2(ε=0.1→33.6)。+- **负值被打分器拒绝**:不裁剪的双向位移产生负表达,`vec-score` 直接 score_failed("metrics expect log-normalized nonnegative data")。+- **任何均匀位移对 pred-target covariation 有 ~ε² 惩罚**:即使无裁剪,proxy ε=0.1 时 covariation 42.2,ε=0.05 时 48.4,ε=0.02 时 51.9;而 de/direction/cell_state 增益 ~ε 线性 → 存在小 ε 最优。+- **X3 的伪时间轴定向不可靠**:X3(心脏-only,3 类型)增殖门 |rho|≈0.0002;与真实 E8.75→E9.0 delta 的 align 仅 ±0.067;且 delta 对齐方向在 A 半上 de_direction=−0.165(与 E9.0→E9.5 目标方向反)。故 ALIGN_MIN 提高到 0.15 后 X3 自动回退 copy_last(输出与父节点逐位一致,50.00 保底)。+- proxy E8.5(全胚、强谱系梯度)增殖门 rho=−0.167,通过;方向被证实正确:de_direction=+0.045,四个组全部 ≥ 父节点。 ## 关键参数 -- α=0.0(默认)。MIN_TYPE_CELLS=30(α=0 时无作用)。+ε=0.02(扫描 0.02/0.03/0.05/0.1 → proxy 51.41/51.29/51.05/49.98);RHO_MIN=0.1;K_GENES=1000;ALIGN_MIN=0.15;AXIS_MIN_RHO=0.05;N_SUB=5000。α(legacy 逐类型位移)默认 0,保留未用。 ## 验证过什么(vec-score,A 半) | 预测 | proxy | proxy2 | X3 | |---|---|---|---|-| 父节点(seed shift) | 50.04 | 27.43 | 40.53 |-| 本节点 α=0 | 50.40 | 50.40 | 50.00 |-| 本节点 α=0.25(仅 X3 生效) | - | - | 44.15 |-| 本节点 α=0.5(仅 X3 生效) | - | - | 42.43 |-| 本节点 α=1.0(仅 X3 生效) | - | - | 40.33 |+| 父节点(copy_last) | 50.04 | 50.40 | 50.00 |+| 本节点 ε=0.02 只上移无裁剪 | **51.41** | **51.41** | 50.00(=copy_last,逐位一致) |+| ε=0.03 | 51.29 | - | - |+| ε=0.05 | 51.05 | - | - |+| ε=0.1 | 49.98 | - | - |+| ε=0.02/0.05/0.1 双向+裁剪(仅 X3 定向生效) | - | - | 44.2/39.4/33.6 组分 covariation | -- X3 上 α 单调有害:α↑ → covariation(50→28.6→21.9→15.3)、de_recovery、cell_state 全降,与方法卡"常数位移在 T1 上 48.6 低于 copy_last"一致。故取 α=0。-- proxy/proxy2 在本程序下路径相同(都用官方 E8.5,proxy2 无更早阶段),预测与已查分文件一致。-- 三个视图均通过 vec-check;输出在 α=0 下与已查分的 preds 完全一致(同 seed 同抽样)。+ε=0.02 在 proxy 上四组全升:cell_state 50.78、covariation 51.88、de_recovery 51.96、direction 51.26(父 49.93/50.11/50.00/50.11)。proxy2 预测与 proxy 逐位相同(同一基阶段),查分同为 51.41。三视图 vec-check 通过;X3 运行时 ~5s、proxy ~20s、内存与父节点同级。 -## 没验证什么+## 没验证什么 / 风险 -- 逐基因经验贝叶斯收缩(PLAN 第 2 步):α 全局收缩已单调劣于 copy_last,逐基因收缩只在 α>0 时有意义,未测。-- 分层重抽样(PLAN 第 3 步):抽样已是按行的无放回/有放回混合,未做按类型分层,未测其增益。-- final 视图(E8.5+E9.5→E10.5)无法在本地查分;α=0 时行为 = copy_last(E9.5)。-- 用 E8.75→E9.0 的类型比例趋势外推 X3 组成:未测(预计增益 < 噪声 2 分)。+- 增益 +1.4(proxy/proxy2)低于 2 分噪声线,但四组同向且 ε 曲线有内部一致的最优点;B 半可能回落。+- **final 视图(E8.5+E9.5→E10.5)未验证**:定向将走 delta 对齐路径(E8.5→E9.5 官方伪批量,预期 align 远高于 X3 的 0.067),该路径在 X3 上被证明会选错方向(X3 特有:align 太弱),在 final 上 align 若 ≥0.15 生效、否则回退增殖门/copy_last——无崩溃风险,但方向正确性无本地证据。+- 种子间方差未测(本地只跑 seed 0);伪时间/根选择对 seed 的敏感性未测(Palantir waypoints 有 seed,deterministic)。+- Palantir 在 proxy 上出现 "Sparse solver failed, falling back to dense inverse" 警告,结果仍确定。 ## 生物学知识来源 -无外部文献知识;仅使用视图内数据与 CONTRACT 中关于外部输入阶段局限的说明。+- Reactome "Cell Cycle"(R-MMU-1640170):来自 view 内 `prior/reactome/gene_sets.gmt`,用于增殖打分定根(通用机制知识:祖细胞增殖高于分化细胞)。+- Palantir(DOI 10.1038/s41587-019-0068-4)与 CellRank 2(DOI 10.1038/s41592-024-02303-9)的根选择惯例:k041 离线记录;未使用任何保留阶段/禁窗测量数据。diff --git a/solution/pseudotime.py b/solution/pseudotime.pynew file mode 100644index 0000000..d555b34--- /dev/null+++ b/solution/pseudotime.py@@ -0,0 +1,239 @@+"""Single-snapshot pseudotime orientation + tiny directional displacement.++Pipeline (any failure -> no displacement -> copy_last):+1. subsample <= N_SUB cells from the base stage (deterministic, seeded);+2. proliferation score from the prior/ Reactome "Cell Cycle" gene set;+ root = most central among top-proliferation cells (k041 / CellRank2:+ roots from proliferation/progenitor features, never target data);+3. pseudotime: Palantir (CPU jax), fallback scanpy diffmap+DPT;+4. orientation:+ a. if an earlier OFFICIAL input stage exists, orient the axis so that the+ per-gene pseudotime trend rho correlates positively with the observed+ pseudobulk delta (base - earlier); require align >= ALIGN_MIN;+ b. else use the proliferation gate (rho(prolif, pt) must be negative+ after optional flip; require |rho| >= AXIS_MIN_RHO);+ c. inconclusive -> abort.+5. trend genes: |rho| >= RHO_MIN, top K_GENES by |rho|;+6. displacement d_g = eps * sign(rho_g) * sigma_g, sigma_g = in-run std.+"""++from __future__ import annotations++import os++os.environ.setdefault("JAX_PLATFORMS", "cpu")+os.environ.setdefault("JAX_PLATFORM_NAMES", "cpu")++import numpy as np+from scipy import sparse+from scipy.stats import rankdata, spearmanr++N_SUB = 5000+RHO_MIN = 0.1+K_GENES = 1000+AXIS_MIN_RHO = 0.05+ALIGN_MIN = 0.15+++def _subsample_rows(n: int, n_sub: int, seed: int) -> np.ndarray:+ if n <= n_sub:+ return np.arange(n)+ return np.sort(np.random.default_rng(seed + 777_000).choice(n, size=n_sub, replace=False))+++def _proliferation_genes(view: str, genes: list[str]) -> np.ndarray | None:+ """Column indices of Reactome 'Cell Cycle' genes present in the panel."""+ gmt = os.path.join(view, "prior", "reactome", "gene_sets.gmt")+ if not os.path.exists(gmt):+ return None+ gene_ix = {g: i for i, g in enumerate(genes)}+ best = None+ with open(gmt) as fh:+ for line in fh:+ parts = line.rstrip("\n").split("\t")+ if len(parts) < 3:+ continue+ name = parts[1]+ if name.lower() == "cell cycle":+ best = parts[2:]+ break+ if best is None and "cell cycle" in name.lower():+ best = parts[2:]+ if not best:+ return None+ ix = np.array(sorted(gene_ix[g] for g in best if g in gene_ix), dtype=np.int64)+ return ix if len(ix) >= 20 else None+++def _prolif_score(Xs: np.ndarray, cc_ix: np.ndarray) -> np.ndarray:+ sub = Xs[:, cc_ix].astype(np.float64)+ mu = sub.mean(axis=0)+ sd = sub.std(axis=0)+ keep = sd > 1e-8+ sub = sub[:, keep]+ return ((sub - sub.mean(axis=0)) / sub.std(axis=0)).mean(axis=1)+++def _hvg_indices(Xs: np.ndarray, k: int = 2000) -> np.ndarray:+ var = Xs.astype(np.float64).var(axis=0)+ return np.argsort(-var)[:k]+++def _palantir_pseudotime(Xs: np.ndarray, root_i: int, seed: int) -> np.ndarray | None:+ import logging++ import pandas as pd+ import palantir++ logging.getLogger("palantir").setLevel(logging.ERROR)+ n = Xs.shape[0]+ hvg = _hvg_indices(Xs, 2000)+ ids = [f"c{i}" for i in range(n)]+ df = pd.DataFrame(Xs[:, hvg].astype(np.float64), index=ids)+ dm = palantir.utils.run_diffusion_maps(df, n_components=10, seed=0)+ ms = palantir.utils.determine_multiscale_space(dm)+ if ms is None:+ return None+ pr = palantir.core.run_palantir(ms, early_cell=ids[root_i], num_waypoints=min(500, n), seed=seed)+ pt_ser = pr.pseudotime+ loc = pd.Index(ids).get_indexer(pt_ser.index)+ out = np.empty(n, dtype=np.float64)+ out[loc] = np.asarray(pt_ser, dtype=np.float64).ravel()+ return out+++def _dpt_pseudotime(Xs: np.ndarray, root_i: int) -> np.ndarray | None:+ import anndata as ad+ import scanpy as sc++ a = ad.AnnData(X=Xs.astype(np.float32))+ sc.pp.highly_variable_genes(a, n_top_genes=min(2000, a.n_vars))+ a = a[:, a.var["highly_variable"]].copy()+ sc.pp.pca(a, n_comps=50)+ sc.pp.neighbors(a)+ sc.tl.diffmap(a, n_comps=10)+ a.uns["iroot"] = int(root_i)+ sc.tl.dpt(a)+ pt = np.asarray(a.obs["dpt_pseudotime"], dtype=np.float64)+ return None if not np.isfinite(pt).all() else pt+++def _root_cell(Xs: np.ndarray, prolif: np.ndarray, top_k: int = 50) -> int:+ k = min(top_k, Xs.shape[0])+ top = np.argsort(-prolif)[:k]+ hvg = _hvg_indices(Xs, 500)+ Z = Xs[np.ix_(top, hvg)].astype(np.float64)+ center = Z.mean(axis=0)+ return int(top[int(np.argmin(((Z - center) ** 2).sum(axis=1)))])+++def _spearman_genes(Xs: np.ndarray, pt: np.ndarray, chunk: int = 4000):+ n, g = Xs.shape+ r_pt = rankdata(pt).astype(np.float64)+ r_pt -= r_pt.mean()+ denom_pt = np.sqrt((r_pt**2).sum())+ rho = np.zeros(g, dtype=np.float64)+ sigma = np.zeros(g, dtype=np.float64)+ for s in range(0, g, chunk):+ e = min(g, s + chunk)+ Xc = Xs[:, s:e].astype(np.float64)+ sigma[s:e] = Xc.std(axis=0)+ R = rankdata(Xc, axis=0)+ R -= R.mean(axis=0, keepdims=True)+ num = R.T @ r_pt+ den = np.sqrt((R**2).sum(axis=0)) * denom_pt+ with np.errstate(invalid="ignore", divide="ignore"):+ rho[s:e] = np.where(den > 1e-12, num / den, 0.0)+ return rho, sigma+++def orient_and_displace(+ X_base: sparse.csr_matrix,+ X_prev: sparse.csr_matrix | None,+ genes: list[str],+ view: str,+ eps: float,+ seed: int,+ n_sub: int = N_SUB,+) -> tuple[np.ndarray, np.ndarray] | None:+ """Return (sorted_gene_indices, displacement) or None (-> copy_last)."""+ rows = _subsample_rows(X_base.shape[0], n_sub, seed)+ Xs = np.asarray(X_base[rows].todense(), dtype=np.float32)++ cc_ix = _proliferation_genes(view, genes)+ if cc_ix is None:+ return None+ prolif = _prolif_score(Xs, cc_ix)+ root_i = _root_cell(Xs, prolif)++ pt = None+ try:+ pt = _palantir_pseudotime(Xs, root_i, seed)+ except Exception:+ pt = None+ if pt is None:+ try:+ pt = _dpt_pseudotime(Xs, root_i)+ except Exception:+ pt = None+ if pt is None or not np.isfinite(pt).all() or pt.std() < 1e-9:+ return None++ rho, sigma = _spearman_genes(Xs, pt)++ # orientation+ oriented = False+ if X_prev is not None:+ delta = np.asarray(X_base.mean(axis=0)).ravel() - np.asarray(X_prev.mean(axis=0)).ravel()+ keep = sigma > 0.05+ if keep.sum() >= 100:+ align = spearmanr(rho[keep], delta[keep]).statistic+ if align <= -ALIGN_MIN:+ rho = -rho+ align = -align+ oriented = align >= ALIGN_MIN+ if not oriented:+ axis_rho = spearmanr(prolif, pt).statistic+ if axis_rho > -AXIS_MIN_RHO:+ pt = pt.max() - pt+ rho = -rho+ axis_rho = spearmanr(prolif, pt).statistic+ oriented = axis_rho <= -AXIS_MIN_RHO+ if not oriented:+ return None++ ab = np.abs(rho)+ cand = np.flatnonzero(ab >= RHO_MIN)+ if cand.size == 0:+ return None+ cand = np.sort(cand[np.argsort(-ab[cand])][:K_GENES])+ if os.environ.get("VEC_UPONLY", "1") == "1":+ # clip(x + d, 0) for d < 0 destroys small nonzero values and wrecked+ # covariation on X3 (50 -> 44 at eps=0.02). Up-shifts need no clip:+ # x + d (d>0) keeps nonnegativity and leaves the correlation matrix+ # and all pairwise cell distances exactly intact.+ cand = cand[rho[cand] > 0]+ if cand.size == 0:+ return None+ d = eps * np.sign(rho[cand]) * sigma[cand]+ d = np.where(np.isfinite(d), d, 0.0)+ return cand, d+++def apply_displacement(X: sparse.csr_matrix, sel: np.ndarray, d: np.ndarray) -> sparse.csr_matrix:+ """clip(X[:, sel] + d, 0); other columns untouched. Sparsity preserved."""+ g = X.shape[1]+ mask = np.zeros(g, dtype=bool)+ mask[sel] = True+ uns = ~mask+ Xc = X.tocsc()+ B = np.asarray(Xc[:, sel].todense(), dtype=np.float32) + d.astype(np.float32)+ if os.environ.get("VEC_CLIP", "0") == "1":+ B = np.clip(B, 0, None)+ if B.min() < 0: # safety: scorer requires nonnegative+ B = np.clip(B, 0, None)+ S = sparse.hstack([Xc[:, uns].tocsr(), sparse.csr_matrix(B)], format="csr")+ order = np.concatenate([np.flatnonzero(uns), sel])+ inv = np.empty(g, dtype=np.int64)+ inv[order] = np.arange(g)+ return S[:, inv].tocsr()diff --git a/solution/run.py b/solution/run.pyindex ff1b865..1675464 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,28 +1,25 @@ #!/usr/bin/env python3-"""copy_last on the latest OFFICIAL stage + alpha-shrunk per-type pseudobulk shift.--Two changes over the seed pseudobulk_shift:--1. The base population is always the latest OFFICIAL input stage. An external- input (proxy2: Qiu E9.0, heart-only, partial panel) is never copied as the- prediction, per the contract ("do not output external cells directly");- it is only used to estimate a delta when its cell-type labels overlap the- base stage's labels. When no official input exists (X3-style test views),- the latest input of any source is the base.--2. The per-type pseudobulk delta (latest - previous step, types present in- both) is multiplied by a shrinkage factor ALPHA before being added, and- only types with a decent cell count in both stages are shifted. Rows are- clipped at 0 after the shift.--With a single usable stage (T1 proxy) this is exactly copy_last.+"""copy_last on the latest OFFICIAL stage + tiny pseudotime-oriented displacement.++Base population (unchanged from node 2): the latest OFFICIAL input stage; an+external input (proxy2: Qiu E9.0) is never copied as the prediction. With no+official input (X3-style views) the latest input of any source is the base.++New in node 5: a single-snapshot pseudotime micro-displacement (see+pseudotime.py). A developmental axis is estimated inside the base stage with+Palantir (fallback: scanpy DPT), rooted at the most central high-proliferation+cell (Reactome Cell Cycle score from prior/; never any target-stage data).+Genes whose expression trends with pseudotime (|spearman| >= 0.1, top 1000)+are shifted by eps * sign(rho) * sigma_g (sigma_g = in-run std), eps tiny+(default 0.01, env VEC_EPS). Any failure in the pipeline -> plain copy_last.+The legacy per-type alpha pseudobulk shift stays disabled (alpha > 0 was+measured monotonically harmful on X3). """ from __future__ import annotations import argparse--import numpy as np+import os from src.task1_temporal.view_io import ( inputs_by_time,@@ -36,12 +33,15 @@ from src.task1_temporal.view_io import ( write_prediction, ) -ALPHA = float(__import__("os").environ.get("VEC_ALPHA", "0.0"))+EPS = float(os.environ.get("VEC_EPS", "0.02"))+ALPHA = float(os.environ.get("VEC_ALPHA", "0.0")) MIN_TYPE_CELLS = 30 # types with fewer cells in either stage are copied def type_deltas_shrunk(prev_X, prev_labels, last_X, last_labels): """mean(last|c) - mean(prev|c) per shared type, x ALPHA, count-gated."""+ import numpy as np+ out = {} prev_counts = {} for t in np.unique(prev_labels):@@ -59,6 +59,7 @@ def type_deltas_shrunk(prev_X, prev_labels, last_X, last_labels): def shift_rows(X, labels, deltas):+ import numpy as np from scipy import sparse X = X.tocsr() if sparse.issparse(X) else sparse.csr_matrix(X)@@ -79,6 +80,9 @@ def shift_rows(X, labels, deltas): def main() -> None:+ import numpy as np+ from scipy import sparse+ parser = argparse.ArgumentParser() parser.add_argument("--data", required=True) parser.add_argument("--out", required=True)@@ -97,7 +101,7 @@ def main() -> None: X = last.X[rows] last_labels = labels_of(last) - # previous step: latest input strictly before the base with overlapping labels+ # legacy per-type shift (disabled unless VEC_ALPHA > 0) prev_entries = [e for e in all_inputs if e["time"] < base_entry["time"]] if prev_entries and ALPHA > 0: prev = read_stage(args.data, prev_entries[-1], genes)@@ -106,6 +110,26 @@ def main() -> None: if deltas: X = shift_rows(X, last_labels[rows], deltas) + # pseudotime micro-displacement (EPS > 0); any failure -> copy_last+ if EPS > 0:+ try:+ import pseudotime++ Xb = last.X.tocsr() if sparse.issparse(last.X) else sparse.csr_matrix(last.X)+ X_prev = None+ prev_official = [e for e in prev_entries if not is_external(e)]+ if prev_official:+ pa = read_stage(args.data, prev_official[-1], genes)+ X_prev = pa.X.tocsr() if sparse.issparse(pa.X) else sparse.csr_matrix(pa.X)+ res = pseudotime.orient_and_displace(Xb, X_prev, genes, args.data, EPS, args.seed)+ del X_prev+ if res is not None:+ sel, d = res+ Xs = X.tocsr() if sparse.issparse(X) else sparse.csr_matrix(X)+ X = pseudotime.apply_displacement(Xs, sel, d)+ except Exception:+ pass+ write_prediction(X, genes, args.out, seed=args.seed)
调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。
用到的知识库条目
| 编号 | 标题 | 出处 |
|---|---|---|
| k018 | Damped per-type shift: shrinkage alpha on the observed delta | notes/plan/cards/T1.md |
| k041 | Within-stage pseudotime and graph toolkit offline: scanpy DPT/PAGA/Leiden, Palantir, CellRank 2 | 10.1186/s13059-019-1663-x (PAGA); 10.1038/s41587-019-0068-4 (Palantir); 10.1038/s41592-024-02303-9 (CellRank 2) |
| k031 | Offline OT toolkit in the sandbox: moscot TemporalProblem, wot OTModel, POT, geomloss | 10.1038/s41586-024-08453-2 (moscot); 10.1016/j.cell.2019.01.006 (Waddington-OT) |
计划里引用的来源
- DOI 10.1038/s41587-019-0068-4 — Palantir:扩散映射多尺度伪时间,单快照内给出连续发育轴,是本方案方向信号的来源(文献库 k041 离线可用记录)
- DOI 10.1038/s41592-024-02303-9 — CellRank 2:根细胞应由增殖/祖细胞特征而非目标数据确定的做法依据(k041 离线记录)
分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。
| 改了什么 | 在父节点 copy_last 骨架上新增 pseudotime.py:基阶段亚抽样 5000 细胞跑 Palantir(CPU,回退 scanpy DPT)伪时间,Reactome Cell Cycle 增殖打分定根,趋势基因 |ρ|≥0.1 top1000 只上移 d=ε·σ_g(ε=0.02,不裁剪);有更早官方阶段时用伪批量 delta 对齐定向(align≥0.15),否则增殖门(≤−0.05),均不可靠则回退 copy_last(X3 走此路径)。 |
|---|---|
| 各组分数的变化 | cell_state:+0.31(49.93→50.23),在噪声内 covariation:+1.02(50.11→51.13),在噪声内;组件级:proxy/proxy2 各 +1.21(50.04→51.25),X3 +0.00(回退 copy_last,与父节点一致) de_recovery:+1.33(50.00→51.33),在噪声内(<2 分) direction:+0.70(50.11→50.81),在噪声内 |
| 假设是否成立 | unclear |
| 经验 |
|
| 下一步建议 |
|
对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。
只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。
分析员
| 角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。 | 分析员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数?大模型一共回复了几轮(每轮可以调用多个工具)。 | 4 |
| 工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。 | 共 3 次:bash 2、write 1 |
| 用时 | 不到 1 分 |
| token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。 | 输入 11,693 · 输出 1,263 · 思考 556 |
| 任务(第一行) | 分析节点 n5 的打分结果,写 ANALYSIS.json |
| 最后的回答(摘录) | ANALYSIS.json written |
| 原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。 | /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/analyst.jsonl 10 KB /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/analyst.stderr |
工程师
| 角色 | 工程师 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 38 |
| 工具调用 | 共 44 次:bash 32、edit 7、write 4、read 1 |
| 用时 | 25 分 |
| token 数 | 输入 113,031 · 输出 18,836 · 思考 23,321 |
| 任务(第一行) | 按计划修改程序:单快照伪时间定向微位移(prior 选根,ε≤0.05) |
| 最后的回答(摘录) | **做了什么**:在父节点 copy_last 骨架上加单快照 Palantir 伪时间定向微位移模块——Reactome Cell Cycle 增殖打分定根(prior/),趋势基因 |ρ|≥0.1 top1000,只上移(d=ε·σ_g)、不裁剪;有更早官方阶段时用伪批量 delta 对齐定向(align≥0.15 门槛),否则增殖门(≤−0.05),都不可靠则回退 copy_last。 **查分结果**(A 半):proxy ε=0.02 → **51.41**(四组全升:cell_state 50.78 / covariation 51.88 / de 51.96 / direction 51.26);proxy2 → **51.41**;X3 定向门不过 → 回退 copy_last,输出与父节点逐位一致(50.00)。ε 曲线 0.02/0.03/0.05/0.1 → 51.41/51.29/51.05/49.98。关键教训:clip 双向位移毁 covariation(X3 ε=0.02 即 50→44);负值被打分器拒绝;X3 心脏-only 数据伪时间轴与真实时间 delta 对齐仅 0.067 且方向选错(de_direction=−0.165)。三视图 vec-check 通过,proxy 运行 ~20s。 **下一步最值得试**:(1) final 视图会走 delta 对齐路径,X3 证明弱对齐会选错方向——应加"对齐强度分级"(align<0.3 时把 ε 再缩一半);(2) 只上移丢掉了 ρ<0 基因的下调信号,可试负向基因用乘性收缩 x·c_g(c<1,保零保非负保相关)恢复完整方向信号;(3) ε 最优点附近(0.015–0.025)多种子复测确认 +1.4 增益超噪声。 |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/engineer.jsonl 175 KB /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/engineer.stderr |
调研员
| 角色 | 调研员 alibaba-token-plan-cn/qwen3.8-max |
|---|---|
| 调用次数 | 1 次 |
| 轮数 | 1 |
| 工具调用 | 共 0 次:— |
| 用时 | 1 分 |
| token 数 | 输入 9,727 · 输出 1,145 · 思考 3,235 |
| 任务(第一行) | 为节点 n5(改进,父节点 n2)写改进计划 PLAN.json |
| 最后的回答(摘录) | ```json
{"name": "单快照伪时间定向微位移(prior 选根,ε≤0.05)",
"motivation": "父节点 2(50.03)在三把尺子上均为 copy_last 地板:四组分 cell_state 49.93 / covariation 50.11 / de_recovery 50.00 / direction 50.11,de_recovery 与 direction 最弱且预测完全没有时间方向。X3 实测逐类型数据驱动位移 α=0.25/0.5/1.0 → 44.15/42.43/40.33,covariation 50→28.6 单调崩塌(k018 亦载 α=1 低于 copy_last),故不能再做数据驱动的大位移;但方向组分的损失来自「不动」,沿有生物学依据的发育轴做极小位移可在不破坏共变的前提下提供方向与 DE 趋势信号。k041 表明 Palantir/scanpy 伪时间在单快照可用,与 proxy/proxy2/X3 的代码路径一致。",
"approach": "在父节点 run.py 骨架上(基群体=最新官方阶段、sample_rows 抽样、外部阶段永不输出;α 位移分支整体删除或保持不触发)加一个伪时间微位移模块,任何一步失败即回退 copy_last:1) 伪时间:对基阶段(最多亚抽样 5000 细胞)设 JAX_PLATFORMS=cpu,Palantir run_diffusion_maps(n_components=10)+determine_multiscale_space+run_palantir(num_waypoints=500);失败则 scanpy diffmap+DPT 回退;再失败则纯 copy_last。2) 根:用 prior/ 中 Reactome 细胞周期基因集算增殖打分,根=打分最高的祖细胞(k041:以增殖/祖细胞特征定根,绝不用目标数据);健全性检查伪时间十分位的增殖均值应单调递减,否则翻转轴,翻转后仍异常则回退。3) 趋势基因:对全部基因算表达~伪时间的 Spearman ρ(用亚抽样的 5000 细胞估计,位移应用到全体),取 |ρ|≥0.1 且按 |ρ| 前 1000 个为位移基因集。4) 位移:x_new = clip(x + ε·sign(ρ_g)·σ_g, 0),σ_g 为基因 g 在基阶段的表达标准差,ε 环境变量 VEC_EPS 初值 0.01,扫描 {0.005,0.01,0.02,0.05};σ 与 ρ 均内部估计,不硬编码任何阶段的统计量。5) 查分流程(≤20 次):先只跑 X3 全扫 4 个 ε(4 次),看总分与四组分;任一 ε 使 covariation 较 50 下降 >2 即弃该 ε;最佳 ε 在 X3 复跑 1 次确认 >2 分噪声;随后最佳 ε 跑 proxy、proxy2(2 次,单阶段/双输入下基阶段同为官方最新阶段,代码同一);有余力补最佳 ε 复跑 2 次。proxy2 的外部 Qiu E9.0 不参与;X3 无官方阶段时以最新外部阶段为基(同父节点),伪时间仍在单快照内成立。",
"expected_groups": ["direction", "de_recovery"],
"risks": "1) 根或轴方向反了 → 位移背离目标,全组降分;首轮 X3 运行即可暴露,立即回退或翻转。2) ε 过大重蹈 α 位移破坏 covariation 的覆辙 → 以 X3 组分为闸,任何 ε 下 covariation 掉 >2 即弃;扫描上限仅 0.05(等效幅度远低于已证有害的 α=0.25)。3) 真实增益可能 <2 分噪声 → 最佳 ε 至少复跑 2 次,未稳定超 50.03+2 就如实报告无增益。4) Palantir/jax 导入失败或耗时超限 → JAX_PLATFORMS=cpu + DPT 回退 + 5000 细胞亚抽样;父节点基线仅 1.1s/1.26GB,预算充足。",
"sources": ["DOI 10.1038/s41587-019-0068-4 — Palantir:扩散映射多尺度伪时间,单快照内给出连续发育轴,是本方案方向信号的来源(文献库 k041 离线可用记录)", "DOI 10.1038/s41592-024-02303-9 — CellRank 2:根细胞应由增殖/祖细胞特征而非目标数据确定的做法依据(k041 离线记录)"]}
``` |
| 原始记录位置 | /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/researcher.jsonl 4 KB /home/spark-longxinyang/vec/runs/formal/20261001-233756-search-t1-abc-r0-B-population/nodes/5/researcher.stderr |