Virtual Embryo Challenge更新于 10-03 20:28(北京时间) / 每 5 分钟更新

总览 · ← 返回运行 20261001-105407-g17-researcher-probe

节点 n4

增殖驱动的组成重加权(负结果):类型权重×(1+γ·增殖z),γ=0 复现父本 55.97,γ=±0.25/0.35/1.0 与 5118 细胞均降分。

运行?一次完整的自动搜索或 Agent 会话,有自己的锁定配置和证据包。20261001-105407-g17-researcher-probe
父节点n2
子节点n9
操作?种子:人写的起点;改进:在父节点上改;草稿:从头写;修复:修父节点的报错。改进
状态已打分
分数搜索目标分 55.97(+0.0) · proxy 55.97(+0.0)
审查未审查
用时?从运行开始到结束(或到现在)的挂钟时间。32 分
程序版本5ba0ea2aa1a76a14692dd56d741729afc76ceddf (programs.git)

方法说明?节点程序自带的 METHOD.md:这个程序做了什么、为什么。

来自 programs.git 5ba0ea2aa1:solution/METHOD.md

增殖驱动的组成重加权(负结果):类型权重×(1+γ·增殖z),γ=0 复现父本 55.97,γ=±0.25/0.35/1.0 与 5118 细胞均降分。

做了什么

父本(节点 2,55.97)只重加权组成、不动表达,最弱组是 de_recovery 53.06。本节点按 PLAN 试了两条只依赖输入阶段数据的新杠杆:

  1. 逐类型分化轴位移(PLAN 步骤 1-3,未采用):在 E8.5 每个类型内,用 prior/ 里的细胞周期基因集 (reactome/go/msigdb 的 *cell cycle*|*replication*|*mitotic*|*E2F*|*G2M*,命中面板 2091 个基因)算每细胞增殖 z 分数, 再取类型内 1000 HVG 的前 5 个 PC,选 |corr(PC, s)| 最大者作时间轴,δ_c = 晚端三分之一 − 早端三分之一。 实测:18 个类型无一通过 |corr| >= 0.15(周期信号不在类型内前 5 个主成分里),全部 δ_c = 0,该分支等价于父本,故已从代码中移除。 诊断(未查分):直接用增殖分数三等分(不经 PCA)时,δ 的前 20 个基因是 Rpl/Rps/Tmsb10/Malat1,且 IFT-CM 的 "晚端"下调 Myh6/Ttn/Actc1/Ankrd1 —— 与心肌成熟方向相反。说明这条轴在 E8.5 单快照上主要是核糖体/深度等技术变异,不是发育时间。
  2. 增殖驱动的组成增长(已实现,默认关闭):growth.py 里 type_growth 给每个类型一个增殖 z 分数(对阶段中位数居中、阶段 std 归一, 细胞数 < 30 的类型记 0),growth_sample 把父本的解剖权重(heart ×1.6、edge ×0.25、丢 Neural Tube)乘上 1 + γ·clip(g, -1, 1),其余抽样逻辑(类型顺序、largest_remainder、take、RNG 调用序列)与 heart_reweight 逐调用一致。

查分结果(T1:val proxy,seed 0,单次)

配置榜分de_recoverydirectioncell_statecovariation
γ=0(=父本,逐位相同)55.9753.0658.5656.9054.97
γ=+0.3555.2954.1757.2055.4954.03
γ=-0.2554.9653.0658.0655.1253.18
γ=+1.052.6853.0655.3650.1852.63
N_CELLS=5118(max_cells),γ=055.1253.0658.3855.1653.57

γ 的两个方向都单调劣于 γ=0,说明父本手调的心脏权重已经吃掉了增殖能提供的组成信息(增殖排序本身是合理的: NCC +0.89、aSHF +0.47、Endothelium +0.27 高,Foregut -0.50、Blood -0.68 低)。加大细胞数也降分。 所以提交的默认配置 γ=0、N_CELLS=4000,输出与父本逐位相同(已用 np.array_equal 核对);机制保留在代码里,VEC_GAMMA 可复现上表。

验证过 / 没验证

  • 验证过:γ=0 与父本预测逐位相同;vec-check 在 seed 0 和 seed 3 上均 ok;run.py 只按 len(inputs_by_time(manifest)) 读最后一个输入阶段,proxy/final 同一段代码,无阶段名分支、无硬编码统计量;prior 基因集缺失或命中 < 20 时 γ 自动置 0。
  • 未验证:所有结论只基于 seed 0 单次查分(低于 PLAN 要求的 3-seed 均值),γ=+0.35 的 de_recovery +1.11 可能只是噪声; PLAN 步骤 4 的两阶段伪批量融合分支没有写进提交版本(单快照轴在 proxy 上就没有信号,融合无意义);final 视图上未运行。
  • 运行 ~40 s、峰值内存 ~1.5 GB(type_growth 只在 4000 细胞 × 2091 基因的子块上做稠密计算)。

下一步最值得试

  1. 表达侧必须换信号源:单快照的增殖轴在 E8.5 上是技术轴。final 视图有 E8.5+E9.5 两个阶段, 同名类型的两阶段伪批量差 δ2_c 是唯一有真值方向的位移信号(配 T1-02 式逐基因 t 统计收缩,k≈4), 在 final 上直接可算;proxy 上无法验证,只能靠 final 的一次查分。
  2. 组成侧改成网格搜索父本的三个手调数(heart 权重、edge 权重、是否丢 Neural Tube), 这是唯一被证明有效的杠杆(copy_last 49.77 → 55.97),比任何表达位移的期望收益都高;用 3-seed 均值防噪声。
  3. covariation 54.97 与 mmd 是第二弱项:可试在抽出的真实细胞上做同类型内的轻微 jitter(保留协方差结构的量级), 而不是加独立噪声。

调研员的计划

名称类型内分化轴位移 + t 统计收缩(叠加父本心脏重加权)
动机父节点 2(55.97)最弱的组是 de_recovery 53.06,而节点 1 copy_last 的 de_recovery 恰为 50.00 地板,即父本靠纯组成重加权只把 de_recovery 抬了 +3.06,covariation 54.97 也只 +3.43;父本 run.py 只调用 heart_reweight 抽样、完全不改表达,所以任何逐基因的表达位移都还是空白。要提 de_recovery 必须给类型均值一个正确符号的位移,但方向库里唯一能在单输入阶段(proxy)真正算出位移信号的是 T1-06(单快照伪时间方向),T1-02/04/07/08 在 proxy 上都退化成 copy_last、无法查分验证。因此本节点做 T1-06 的轻量版 + T1-02 式经验贝叶斯收缩,组成部分完全不动(沿用 heart_reweight),实现单变量对照。
做法【结构】保留父本管线:load_manifest -> panel_genes -> inputs_by_time -> read_stage(最后一个输入阶段) -> heart_reweight 抽 n=target_n_cells(manifest,4000) 个真实细胞 -> 在这些细胞上叠加逐类型位移 -> write_prediction。N_CELLS、heart_reweight、抽样顺序全部不改,唯一新变量是位移。

【步骤 1:逐类型分化轴(在完整输入阶段上算,不是在 4000 抽样上算)】对最后输入阶段的每个细胞类型 c(labels_of),若 n_c>=100:(a) 取该类型内方差最大的 1000 个基因,log1p 表达做列中心化,TruncatedSVD/np.linalg.svd 取前 5 个 PC(n_c x 1000,float32,秒级);(b) 算增殖打分 s_i = 该类型细胞在 prior 基因集(data/external/prior/reactome/gene_sets.gmt 与 go/gene_sets_bp.gmt 中名字含 cell cycle / DNA replication / mitotic 的集,10-500 基因)内基因的 z 分数均值;(c) 在 5 个 PC 中选 |corr(PC_k, s)| 最大者 k*,并要求 |corr|>=0.15,否则该类型位移置 0(宁缺勿错);把轴定向为 s 递减方向(增殖高=早端)。

【步骤 2:基因空间位移】沿 k* 轴把该类型细胞分成三个等份,δ_c = mean_log(高分位组) − mean_log(低分位组),在全部面板基因上计算(只用两组均值,不做全基因 SVD)。随后三道清洗:(i) 把细胞周期基因集内的 δ_g 强制置 0——周期变化不是发育时间,否则 de_recovery 会被假 DE 污染;(ii) 逐基因可靠性收缩(关键,直接打 de_recovery 的精度):se_g = sqrt(2*v_g/max(n_third,1)),v_g 为该类型内基因 g 的方差,t_g = δ_g/se_g,δ_g <- δ_g * t_g^2/(t_g^2 + k),初值 k=4(等价要求 |t|>~2 才基本保留),搜索范围 k∈{2,4,8};(iii) 幅度截断:|δ_g| 夹到该类型 |δ| 的 99 分位。n_c<100 的类型 δ_c=0。

【步骤 3:施加】对抽样出的每个细胞 i:x_i <- clip(x_i + β * δ_{c(i)}, 0, None)(log1p 空间)。β 初值网格 {0, 0.25, 0.5},可加 0.75;β=0 必须逐位复现父本 55.97,用作管线自检。因为每个类型加的是常向量,…
风险1) 轴向定错符号或选到细胞周期轴:会让 de_recovery/direction 反而下降。早期发现:不查分就能自检——打印每个类型的 |corr(PC,s)|、被选中的 PC 序号、以及 δ_c 的前 20 个上调基因是否富集细胞周期集(若富集说明步骤 2(i) 没生效或轴仍是周期轴);|corr|<0.15 的类型必须 δ=0。2) β 过大导致越过目标阶段(overshoot):表现为 de_recovery 随 β 单调下降到 β=0 之下,粗网格 + 99 分位截断可控,不要细调 β(proxy 上调参会过拟合噪声)。3) 提升幅度可能落在噪声内:所有比较一律用 3 seed 均值和 +2 分门槛,单次结果不采纳;β=0 必须复现 55.97,否则说明抽样/写出路径被改动,先修管线再评分。4) 运行时与内存:逐类型 SVD 若在全基因上做会炸内存(父本峰值 1.21 GB),必须只在 1000 个 HVG 上做 SVD、在全基因上只算两组均值;细胞数多的类型先子采样到 3000 再算轴。若单类型 PCA 仍慢,退化方案是全局一次 PCA + 类型内投影。5) prior 基因集文件缺失或基因名对不上面板:读不到 gmt 时,细胞周期集退化为空 -> 此时必须禁用整个位移分支(β 强制 0)而不是跳过清洗,避免生成被周期驱动的假 DE;开工前 5 分钟先确认 prior 路径与命中率(T1 面板 Reactome 覆盖约 27%、GO 约 73%)。6) final 两阶段融合分支无法在 proxy 上验证:只要求它语法可达、cos<=0 时退回 δ1_c,且分支键是输入阶段数;不要为了 final 在 proxy 上调任何参数。

代码改动?这个节点的程序和父节点程序的逐行差别:绿色是新增,红色是删除。

对比:父节点版本 39e48cc98a。改动的文件:solution/METHOD.md +47 −0、solution/README.md +15 −3、solution/growth.py +137 −0、solution/run.py +31 −8

diff --git a/solution/METHOD.md b/solution/METHOD.mdnew file mode 100644index 0000000..c711d1c--- /dev/null+++ b/solution/METHOD.md@@ -0,0 +1,47 @@+增殖驱动的组成重加权(负结果):类型权重×(1+γ·增殖z),γ=0 复现父本 55.97,γ=±0.25/0.35/1.0 与 5118 细胞均降分。++## 做了什么++父本(节点 2,55.97)只重加权组成、不动表达,最弱组是 de_recovery 53.06。本节点按 PLAN 试了两条只依赖输入阶段数据的新杠杆:++1. **逐类型分化轴位移(PLAN 步骤 1-3,未采用)**:在 E8.5 每个类型内,用 `prior/` 里的细胞周期基因集+   (reactome/go/msigdb 的 `*cell cycle*|*replication*|*mitotic*|*E2F*|*G2M*`,命中面板 2091 个基因)算每细胞增殖 z 分数,+   再取类型内 1000 HVG 的前 5 个 PC,选 |corr(PC, s)| 最大者作时间轴,δ_c = 晚端三分之一 − 早端三分之一。+   **实测:18 个类型无一通过 |corr| >= 0.15**(周期信号不在类型内前 5 个主成分里),全部 δ_c = 0,该分支等价于父本,故已从代码中移除。+   诊断(未查分):直接用增殖分数三等分(不经 PCA)时,δ 的前 20 个基因是 Rpl/Rps/Tmsb10/Malat1,且 IFT-CM 的+   "晚端"下调 Myh6/Ttn/Actc1/Ankrd1 —— 与心肌成熟方向相反。说明这条轴在 E8.5 单快照上主要是核糖体/深度等技术变异,不是发育时间。+2. **增殖驱动的组成增长(已实现,默认关闭)**:`growth.py` 里 `type_growth` 给每个类型一个增殖 z 分数(对阶段中位数居中、阶段 std 归一,+   细胞数 < 30 的类型记 0),`growth_sample` 把父本的解剖权重(heart ×1.6、edge ×0.25、丢 Neural Tube)乘上+   `1 + γ·clip(g, -1, 1)`,其余抽样逻辑(类型顺序、`largest_remainder`、`take`、RNG 调用序列)与 `heart_reweight` 逐调用一致。++## 查分结果(T1:val proxy,seed 0,单次)++| 配置 | 榜分 | de_recovery | direction | cell_state | covariation |+|---|---|---|---|---|---|+| γ=0(=父本,逐位相同) | **55.97** | 53.06 | 58.56 | 56.90 | 54.97 |+| γ=+0.35 | 55.29 | 54.17 | 57.20 | 55.49 | 54.03 |+| γ=-0.25 | 54.96 | 53.06 | 58.06 | 55.12 | 53.18 |+| γ=+1.0 | 52.68 | 53.06 | 55.36 | 50.18 | 52.63 |+| N_CELLS=5118(max_cells),γ=0 | 55.12 | 53.06 | 58.38 | 55.16 | 53.57 |++γ 的两个方向都单调劣于 γ=0,说明父本手调的心脏权重已经吃掉了增殖能提供的组成信息(增殖排序本身是合理的:+NCC +0.89、aSHF +0.47、Endothelium +0.27 高,Foregut -0.50、Blood -0.68 低)。加大细胞数也降分。+所以**提交的默认配置 γ=0、N_CELLS=4000,输出与父本逐位相同**(已用 `np.array_equal` 核对);机制保留在代码里,`VEC_GAMMA` 可复现上表。++## 验证过 / 没验证++- 验证过:`γ=0` 与父本预测逐位相同;`vec-check` 在 seed 0 和 seed 3 上均 ok;`run.py` 只按 `len(inputs_by_time(manifest))`+  读最后一个输入阶段,proxy/final 同一段代码,无阶段名分支、无硬编码统计量;prior 基因集缺失或命中 < 20 时 γ 自动置 0。+- 未验证:所有结论只基于 seed 0 单次查分(低于 PLAN 要求的 3-seed 均值),γ=+0.35 的 de_recovery +1.11 可能只是噪声;+  PLAN 步骤 4 的两阶段伪批量融合分支没有写进提交版本(单快照轴在 proxy 上就没有信号,融合无意义);final 视图上未运行。+- 运行 ~40 s、峰值内存 ~1.5 GB(`type_growth` 只在 4000 细胞 × 2091 基因的子块上做稠密计算)。++## 下一步最值得试++1. **表达侧必须换信号源**:单快照的增殖轴在 E8.5 上是技术轴。final 视图有 E8.5+E9.5 两个阶段,+   同名类型的两阶段伪批量差 δ2_c 是唯一有真值方向的位移信号(配 T1-02 式逐基因 t 统计收缩,k≈4),+   在 final 上直接可算;proxy 上无法验证,只能靠 final 的一次查分。+2. **组成侧改成网格搜索父本的三个手调数**(heart 权重、edge 权重、是否丢 Neural Tube),+   这是唯一被证明有效的杠杆(copy_last 49.77 → 55.97),比任何表达位移的期望收益都高;用 3-seed 均值防噪声。+3. covariation 54.97 与 mmd 是第二弱项:可试在抽出的真实细胞上做同类型内的轻微 jitter(保留协方差结构的量级),+   而不是加独立噪声。diff --git a/solution/README.md b/solution/README.mdindex e66c7c3..9d4b241 100644--- a/solution/README.md+++ b/solution/README.md@@ -1,4 +1,16 @@-# heart_jcf_peri+# node 4 (improve, parent = heart_jcf_peri 55.97) -run2(20260927-172643-grok-t1)的 T1 胜者:对最新输入阶段按细胞类型重加权再抽 4000 个真实细胞,不改表达。丢 Neural Tube;心脏类(CM、内皮/心内膜、SHF/PHM、JCF、心包、心外膜原基)×1.6;表面外胚层、EXEM、轴旁中胚层 ×0.25;其余 ×1。-proxy 预期 55.97(seed 0,与 run2 的预测逐位相同)。final 读 E9.5,类型集合已含 E9.5 的名字(V-CM、Endocardium、BEC、aPHM、pPHM、Proepicardium)。+Composition is the parent's: latest input stage resampled by cell type with `heart_reweight`'s+anatomical weights (heart x1.6, dissection edges x0.25, Neural Tube dropped), 4000 real cells,+expression untouched. Added here: `growth.py` scales those weights by each type's own+proliferation level, measured at run time from the view's `prior/` cell cycle gene sets+(`VEC_GAMMA`, default 0.0 = parent bit for bit).++Measured on T1:val proxy, seed 0: gamma 0.0 -> 55.97, +0.35 -> 55.29, -0.25 -> 54.96,++1.0 -> 52.68; N_CELLS 5118 -> 55.12. Both growth directions are worse, so the shipped+default keeps the parent's composition. See METHOD.md for the single-snapshot+differentiation-axis attempt that was dropped (no type reached |corr(PC, cell cycle)| >= 0.15,+and the score-based axis was ribosome/depth-driven with the wrong sign on CM maturation genes).++Runs on proxy (E8.5 -> E9.5) and final (E8.5, E9.5 -> E10.5) with the same code: it reads only+`inputs_by_time(manifest)[-1]` and branches on nothing else.diff --git a/solution/growth.py b/solution/growth.pynew file mode 100644index 0000000..c77c2f6--- /dev/null+++ b/solution/growth.py@@ -0,0 +1,137 @@+"""Proliferation-driven composition growth for a T1 temporal prediction.++Each cell type of the latest observed stage gets a growth factor from its own+proliferation level: the mean z score of that type's cells over the cell cycle+gene sets shipped in the view's ``prior/`` directory, centred on the stage+median and scaled by the stage's spread. The parent's hand-set anatomical+weights are multiplied by ``1 + gamma * clip(growth, -1, 1)``, so a type that+cycles harder than the stage median gains relative mass over the interval and+a slower one loses it. Every number is measured on the input stage at run+time; no stage name and no held-out statistic appears in this file.++``gamma = 0`` (or a view without usable cell cycle priors) reproduces+``src.task1_temporal.reweight.heart_reweight`` exactly.+"""++from __future__ import annotations++import re+from pathlib import Path++import numpy as np+from scipy import sparse++MIN_CELLS = 30+SUBSAMPLE = 4000+MIN_CC_GENES = 20+_CC_NAME = re.compile(r"cell cycle|dna replication|mitotic|e2f|g2m|s phase|m phase", re.I)+++def _read_gmt(path: Path) -> list[tuple[str, list[str]]]:+    sets = []+    with path.open("r", encoding="utf-8") as fh:+        for line in fh:+            parts = line.rstrip("\n").split("\t")+            if len(parts) < 3:+                continue+            label = parts[1] if parts[0].startswith(("R-", "GO:")) else parts[0]+            genes = [g for g in parts[2:] if g] or [g for g in parts[1:] if g]+            sets.append((label, genes))+    return sets+++def cell_cycle_genes(view: str | Path, manifest: dict, genes: list[str]) -> np.ndarray:+    """Boolean mask over the panel: genes belonging to a proliferation set in ``prior/``."""+    root = Path(view)+    mask = np.zeros(len(genes), dtype=bool)+    index = {g: i for i, g in enumerate(genes)}+    for entry in manifest.get("prior") or []:+        path = root / entry["path"]+        if path.is_dir():+            candidates = sorted(path.glob("*.gmt"))+        elif path.suffix == ".gmt" and path.exists():+            candidates = [path]+        else:+            continue+        for gmt in candidates:+            for label, members in _read_gmt(gmt):+                if not _CC_NAME.search(label):+                    continue+                if len(members) < 10 or len(members) > 600:+                    continue+                for g in members:+                    j = index.get(g)+                    if j is not None:+                        mask[j] = True+    return mask+++def type_growth(+    X,+    labels: np.ndarray,+    cc_mask: np.ndarray,+    rng: np.random.Generator | None = None,+) -> dict[str, float]:+    """Per-type proliferation z score, centred on the stage median (0 = average)."""+    rng = rng if rng is not None else np.random.default_rng(0)+    n = X.shape[0]+    rows = np.arange(n) if n <= SUBSAMPLE else np.sort(rng.choice(n, size=SUBSAMPLE, replace=False))+    D = np.asarray(X[rows].todense() if sparse.issparse(X) else X[rows], dtype=np.float32)+    cc_idx = np.flatnonzero(cc_mask)+    if cc_idx.size < MIN_CC_GENES:+        return {}+    B = D[:, cc_idx]+    mu = B.mean(axis=0, keepdims=True)+    sd = B.std(axis=0, keepdims=True)+    keep = sd[0] > 1e-6+    if int(keep.sum()) < MIN_CC_GENES:+        return {}+    score = ((B[:, keep] - mu[:, keep]) / sd[:, keep]).mean(axis=1)+    lab = np.asarray(labels)[rows]+    centre = float(np.median(score))+    scale = float(np.std(score)) or 1.0+    out: dict[str, float] = {}+    for t in np.unique(lab):+        m = lab == t+        out[str(t)] = 0.0 if int(m.sum()) < MIN_CELLS else float((score[m].mean() - centre) / scale)+    return out+++def growth_sample(+    X,+    labels: np.ndarray,+    n_cells: int,+    growth: dict[str, float],+    gamma: float,+    seed: int = 0,+):+    """Resample ``n_cells`` rows with anatomical weights scaled by proliferation.++    Mirrors ``src.task1_temporal.reweight.heart_reweight`` call for call, so+    ``gamma = 0`` (or an empty ``growth``) reproduces it exactly.+    """+    from src.task1_temporal.reweight import (+        DROP_TYPES,+        EDGE_WEIGHT,+        HEART_WEIGHT,+        largest_remainder,+        take,+        type_weights,+    )++    rng = np.random.default_rng(seed)+    types = [str(t) for t in np.unique(labels) if str(t) not in DROP_TYPES]+    counts = np.array([(labels == t).sum() for t in types], dtype=np.float64)+    w = type_weights(types, HEART_WEIGHT, EDGE_WEIGHT)+    if gamma != 0.0 and growth:+        g = np.array([np.clip(growth.get(t, 0.0), -1.0, 1.0) for t in types], dtype=np.float64)+        w = np.maximum(w * (1.0 + gamma * g), 1e-3)+    alloc = largest_remainder(counts * w, n_cells)+    blocks = []+    for t, k in zip(types, alloc):+        if k <= 0:+            continue+        blocks.append(take(X, np.flatnonzero(labels == t), int(k), rng))+    out = sparse.vstack(blocks, format="csr").astype(np.float32)+    out.eliminate_zeros()+    return outdiff --git a/solution/run.py b/solution/run.pyindex a752b21..5161138 100644--- a/solution/run.py+++ b/solution/run.py@@ -1,17 +1,24 @@ #!/usr/bin/env python3-"""heart_jcf_peri: run2's T1 winner. Reweight the latest input stage by cell type.+"""heart_jcf_peri + proliferation-driven composition growth. -Neural Tube dropped; heart types (CM, endothelium/endocardium, SHF/PHM, JCF,-pericardium, proepicardium) x1.6; surface ectoderm, EXEM, paraxial mesoderm-x0.25; everything else x1. 4000 real cells, no expression shift. Proxy reads-E8.5, final reads E9.5; the type set already names both stages' labels.+Composition: the latest input stage is resampled by cell type with the parent's+anatomical weights (heart x1.6, dissection edges x0.25, neural tube dropped),+and those weights are additionally scaled by each type's own proliferation+level measured on the input stage: a type whose cells cycle harder than the+stage median gains relative mass over the interval, a slower one loses it.+The per-type growth factor is computed from the data at run time (cell cycle+gene sets come from the view's ``prior/``), never hard-coded, so the same code+runs on the proxy view (E8.5 -> E9.5) and the final view (E9.5 -> E10.5).+Expression is not shifted: cells are real cells of the last observed stage.++``gamma = 0`` reproduces the parent (heart_jcf_peri) bit for bit. """  from __future__ import annotations  import argparse+import os -from src.task1_temporal.reweight import heart_reweight from src.task1_temporal.view_io import (     inputs_by_time,     labels_of,@@ -22,7 +29,12 @@ from src.task1_temporal.view_io import (     write_prediction, ) +from growth import cell_cycle_genes, growth_sample, type_growth+ N_CELLS = 4000+# measured on proxy seed 0: 0.0 -> 55.97, +0.35 -> 55.29, -0.25 -> 54.96, +1.0 -> 52.68+GAMMA = float(os.environ.get("VEC_GAMMA", "0.0"))+VERBOSE = bool(os.environ.get("VEC_VERBOSE", ""))   def main() -> None:@@ -34,9 +46,20 @@ def main() -> None:      manifest = load_manifest(args.data)     genes = panel_genes(args.data, manifest)-    last = read_stage(args.data, inputs_by_time(manifest)[-1], genes)+    stages = inputs_by_time(manifest)+    last = read_stage(args.data, stages[-1], genes)+    labels = labels_of(last)++    cc_mask = cell_cycle_genes(args.data, manifest, genes)+    growth = type_growth(last.X, labels, cc_mask, rng=__import__("numpy").random.default_rng(args.seed))+    if VERBOSE:+        print(f"cell cycle genes: {int(cc_mask.sum())}")+        for t, g in sorted(growth.items(), key=lambda kv: -kv[1]):+            print(f"  {t}: g={g:+.3f} -> w_mult={max(1.0 + GAMMA * max(min(g, 1.0), -1.0), 1e-3):.3f}")++    gamma = GAMMA if int(cc_mask.sum()) >= 20 and growth else 0.0     n = target_n_cells(manifest, N_CELLS)-    X = heart_reweight(last.X, labels_of(last), n_cells=n, seed=args.seed)+    X = growth_sample(last.X, labels, n_cells=n, growth=growth, gamma=gamma, seed=args.seed)     write_prediction(X, genes, args.out, seed=args.seed)  

调研来源?调研员查到并用到的知识条目和文献检索结果(只列标题和编号)。

用到的知识库条目

编号标题出处
k014Scorer noise and invariance on our proxy boardsnotes/pitfalls/04_scorer_invariance.md
k020Correcting sampling-scope (dissection) bias in compositionnotes/guides/modeling_and_evaluation_guide.html
k007Interval staging and held-out-window filtering of external datanotes/来件/virtualembryo.ai/rules.md

分析结果?分析员写的 ANALYSIS.json:改了什么、各组分数怎么变、假设是否成立、经验和下一步建议。

改了什么新增 solution/growth.py:按 prior/ 细胞周期基因集算每类型增殖 z 分数,把父本解剖权重乘 (1+γ·clip(g,-1,1)),其余抽样/RNG 序列与 heart_reweight 逐调用一致;提交默认 γ=0、N_CELLS=4000,输出与父节点逐位相同(变化量表四组全为 +0.00,与'逐位相同'一致)。PLAN 步骤 1-3 的单快照分化轴位移分支因无信号已删除,未进入提交版本。
各组分数的变化board:55.97 -> 55.97,+0.00;代价是耗时 1.0s -> 2.4s、内存 1.21GB -> 1.30GB(多算的 type_growth 在 γ=0 时纯属开销)
cell_state:56.90 -> 56.90,+0.00,无变化
covariation:54.97 -> 54.97,+0.00,无变化
de_recovery:53.06 -> 53.06,+0.00,提交配置与父本逐位相同,无任何变化
direction:58.56 -> 58.56,+0.00,无变化
假设是否成立否
经验
  1. T1 proxy 单输入阶段(E8.5)内做类型内 PCA 选增殖轴:18 个类型无一达到 |corr(PC, 细胞周期分)| >= 0.15,即单快照的发育时间信号不在类型内前 5 个主成分里;PLAN 的'宁缺勿错'阈值直接把整条分支变成恒等映射,等价于父本。
  2. 改用增殖分数直接三等分(跳过 PCA)时,δ 的 top 基因是 Rpl/Rps/Tmsb10/Malat1,且 IFT-CM 的'晚端'下调 Myh6/Ttn/Actc1(与心肌成熟反向)——说明 E8.5 单快照上细胞周期 z 分数主导的是核糖体含量/测序深度技术轴,不能当伪时间用。
  3. 组成侧再叠加增殖驱动增长(γ)在两个方向都单调劣于 γ=0:+0.35 -> 55.29、-0.25 -> 54.96、+1.0 -> 52.68(均为 seed 0 单次)。父本手调的心脏 ×1.6/边缘 ×0.25 已经吃掉了增殖能提供的组成信息,再乘一个数据导出的因子只是扰动。
  4. N_CELLS 从 4000 提到 max_cells=5118 也降分(55.12,seed 0 单次):target_n_cells(manifest,4000) 不是可自由放大的旋钮,4000 附近是局部最优。
  5. 查分预算纪律的代价:Engineer 用掉 5/20 次查分但全部是 seed 0 单次,低于 PLAN 自己定的 3-seed 均值门槛;因此 γ=+0.35 的 de_recovery +1.11 完全在 T1 噪声(约 2 分)内,不能作为'表达位移有希望'的证据。Engineer 自述与'已用 3 seed 比较'的 PLAN 要求冲突,以单次事实为准。
  6. 提交逐位复现父本(np.array_equal 校验通过)是有价值的自检:它证明新增的 growth_sample 没有改动类型顺序/largest_remainder/take/RNG 调用序列,可安全作为后续节点的基础设施。
下一步建议
  1. 组成侧网格搜索父本三个手调数(HEART_WEIGHT ∈ {1.3,1.6,2.0}、EDGE_WEIGHT ∈ {0.1,0.25,0.5}、是否丢 Neural Tube),这是唯一被证明有效的杠杆(copy_last 49.77 -> 55.97);每配置 3 seed 取均值、门槛总分与 de_recovery 均 >= +2,针对全组尤其 covariation/cell_state。
  2. 表达位移只在 final 视图做:同名类型的两阶段伪批量差 δ2_c = mean(last|c) - mean(prev|c) 配 T1-02 式 t 统计收缩(se_g=sqrt(2v_g/n),δ·t²/(t²+k),k≈4)+ 细胞周期基因强制置 0 + |δ| 99 分位截断;proxy 上无信号可验,别在 proxy 调 β/k。
  3. 针对 covariation 54.97(第二弱组):在抽出的真实细胞上做同类型内的轻微 jitter(同类型内配对置换部分基因值或按类型内协方差采样),保持类型内协方差量级,避免加独立高斯噪声破坏协方差结构;用 3 seed 均值判定。
  4. 删掉或惰性化 γ=0 时的 type_growth 计算:当前每次运行都白算 2091 基因 × 4000 细胞的稠密 z 分数,耗时 1.0s -> 2.4s、内存 +0.09GB 而分数不变,在时间/内存受限的节点上是纯损耗。

对话摘要?每个角色和大模型对话的统计:轮数、工具调用、用时、token 数和最后的回答摘录;原始记录只给路径。

只给统计和最后回答的摘录;完整对话请到原始记录位置里列出的文件看。

分析员

角色?调研员写计划、工程师改代码、分析员解读分数、审查员检查作弊。分析员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数?大模型一共回复了几轮(每轮可以调用多个工具)。5
工具调用?大模型调用读文件、执行命令等工具的次数,按工具名统计。共 4 次:—
用时不到 1 分
token 数?输入 / 输出(含思考)的 token 数,来自接口返回的用量。输入 12,344 · 输出 1,528 · 思考 450
任务(第一行)分析节点 n4 的打分结果,写 ANALYSIS.json
最后的回答(摘录)
ANALYSIS.json written
原始记录位置?完整对话记录在 Spark 上的文件路径;看板不复制原始内容。/home/spark-longxinyang/vec/g17_wt/agent/runs/20261001-105407-g17-researcher-probe/nodes/4/analyst.jsonl (文件不在)

工程师

角色工程师 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数35
工具调用共 40 次:—
用时26 分
token 数输入 63,222 · 输出 16,735 · 思考 12,528
任务(第一行)按计划修改程序:类型内分化轴位移 + t 统计收缩(叠加父本心脏重加权)
最后的回答(摘录)
**做了什么**:按 PLAN 试了两条只依赖输入阶段数据的新杠杆。(1) 单快照分化轴位移:E8.5 内 18 个类型无一达到 |corr(PC, 细胞周期分)| ≥ 0.15,δ 全为 0;改用增殖分数直接三等分时,δ 的 top 基因是 Rpl/Rps/Tmsb10/Malat1,IFT-CM "晚端"还下调 Myh6/Ttn/Actc1(与成熟方向相反)——这条轴是技术变异,已弃用并从代码移除。(2) 增殖驱动的组成增长(`solution/growth.py`,保留):父本解剖权重 × (1+γ·clip(增殖z,−1,1)),抽样与 RNG 调用序列与 `heart_reweight` 逐调用一致。

**查分**(proxy,seed 0,单次,用掉 5/20):γ=0 → **55.97**(与父本预测 `np.array_equal` 为真);γ=+0.35 → 55.29(de_recovery 54.17,其余全降);γ=−0.25 → 54.96;γ=+1.0 → 52.68;N_CELLS=5118 → 55.12。两个方向都单调劣于 γ=0,手调心脏权重已吃掉增殖信息,故提交默认 γ=0、4000 细胞,`vec-check` 在 seed 0/3 均 ok,运行 2.3 s。所有结论只基于单 seed,低于 PLAN 的 3-seed 门槛。

**下一步最值得试**:① 组成侧网格搜索父本三个手调数(heart/edge 权重、是否丢 Neural Tube)——唯一被证明有效的杠杆(49.77→55.97),用 3-seed 均值;② 表达位移只能在 final 的两阶段上做(同名类型 E8.5→E9.5 伪批量差 + t 统计收缩),proxy 无法验证;③ covariation 侧试同类型内轻微 jitter 而非独立噪声。
原始记录位置/home/spark-longxinyang/vec/g17_wt/agent/runs/20261001-105407-g17-researcher-probe/nodes/4/engineer.jsonl (文件不在)

调研员

角色调研员 alibaba-token-plan-cn/qwen3.8-max
调用次数1 次
轮数7
工具调用共 11 次:—
用时3 分
token 数输入 22,622 · 输出 3,026 · 思考 5,189
任务(第一行)为节点 n4(改进,父节点 n2)写改进计划 PLAN.json
最后的回答(摘录)
PLAN.json written
原始记录位置/home/spark-longxinyang/vec/g17_wt/agent/runs/20261001-105407-g17-researcher-probe/nodes/4/researcher.jsonl (文件不在)