DRUID 与 ADEPT:一个剥蚀坑里的两个问题

两个工具为什么分开、数据在它们之间如何变化、边界上到底传了什么

一句话:DRUID 负责「测出来」,ADEPT 负责「读出来」。

DRUID 把原始计数率变成随深度变化的年龄(每个深度上的年龄是多少、误差多大); ADEPT 在同一条随深度变化的年龄上认出哪些区间构成真实的、内部自洽的年龄域。

前者是测量问题,后者是判读问题。分开不是设计癖好,是因为它们需要的东西完全不同—— 一个需要标样、干扰校正和分馏校正,另一个需要的是分段判据与地质上的取舍规则。

一、问题的来源在样品里,不在软件里

一次激光剥蚀在锆石上打出一个约 20–40 μm 深的坑。锆石在坑的深度方向上是分层的: 继承核、变质增生边、岩浆环带,年龄可以相差几百个百万年。也就是说, 同一束激光在同一个点上,浅处和深处打到的其实是不同时代的东西。

剥蚀坑穿过不同生长域,信号因此呈阶梯状 左图为一个锆石剖面上向下钻进的剥蚀坑,穿过三个年龄不同的域;右图是同一深度轴上对应的年龄阶梯。 岩浆边 · 最年轻 捕获 / 变质域 继承核 · 最老 剥蚀坑 20–40 μm 深度 ↓ 年龄 → 一域 二域 三域
示意图,非实测数据。剥蚀坑从表面向下穿过不同生长域,信号因此是阶梯状的: 每穿过一个界面,年龄就跳到另一个水平。

常规做法是把整个剥蚀段积分成一个数。这在单年龄的样品上没问题, 在分层的锆石上则会把几个不同的年龄搅在一起——得到的既不是核的年龄,也不是边的年龄, 而是一个没有任何地质含义的混合值。

这不是假想的风险,本批次里就有。测点 S02 整段积分给出 436.5 Ma、协和度 51.7%——协和度这么低的"年龄"是不能用的。但它的浅部深度域 给出 440.4 Ma,是干净的结果。整段平均把这个点的可用信息直接抹掉了。

于是分裂出两个必须分开回答的问题:

① 每一深度上,年龄是多少、误差多大?

这是测量问题。要回答它,需要标样夹逼归一化、204Hg 干扰校正、 普通铅显著性检验、随坑深变化的分馏校正、以及逐窗口的不确定度传播。 这是 DRUID 的工作。

② 这些深度里,哪些区间构成一个真实的年龄域?

这是判读问题。要回答它,需要分段、需要"一个坪至少要多长"、 需要"坪内方差多大算合格"、需要"年龄随深度该朝哪个方向变"。 这是 ADEPT 的工作。

两个问题对应两种错误,而且它们不能互相检查:测量错了(例如标样归一化有偏), 判读得再对也是错的年龄;判读错了(例如把两个混合窗口当成一个坪), 测量再准也得到假的坪。分开做,两边的错误才各自可查。

二、数据一路上的形态变化

下面这条链是一次完整的数据旅程。左侧紫色部分是 DRUID, 右侧绿色部分是 ADEPT,中间那一步是两者唯一的接触面。

数据从原始计数率到坪年龄的七个形态,以及两个工具的分工 紫色区为 DRUID 负责的四步,绿色区为 ADEPT 负责的三步,中间橙色框是唯一的接口。 DRUID 测量:每个深度上的年龄是多少? ① Qtegra CSV原始计数率 cps,239 行 / 点 ② 净信号剥离蚀区间 · 扣气体空白 ③ 校正后的比值干扰 · 普通铅 · 标样夹逼 · F(τ) ④ 逐窗口年龄 ± 1σ — 唯一的接口 Analysis · Time · Age68 · Age68_1s ADEPT 判读:哪些区间构成真实的年龄域? ⑤ PELT 分段在 35 个窗口的序列上找变点 ⑥ 四步过滤范围 · 方差 · 最短坪长 · 方向 ⑦ 坪年龄 + 不确定度 + MSWD可直接写进补充材料的表
数据的形态与负责人。注意第 ④ 步——它是 DRUID 的产出,也是 ADEPT 的全部输入, 两侧的算法复杂度差异很大,但接口上只有一张平铺的表。
阶段数据形态规模(本批次单点)谁做
① 原始7 个质量数的计数率时间序列(cps)239 行 × 7 通道
每 0.32 s 一个 cycle
Qtegra 导出
② 净信号同上,但已剔除未剥蚀段、扣掉气体空白剥蚀段约 38 sDRUID
③ 比值206Pb/238U 与 207Pb/206Pb,已做干扰与普通铅校正,并按主标归一化1 组比值 / 点DRUID
④ 深度剖面逐窗口年龄 ± 1σ35 个窗口 / 点
共 1677 行 / 48 点
DRUID → ADEPT
⑤ 分段窗口序列 → 变点位置段数不限ADEPT
⑥ 过滤每段一行,带四个过滤判据的结果—ADEPT
⑦ 坪坪年龄 + 不确定度 + MSWD51 个坪 / 48 点ADEPT

三、边界上传来什么

DRUID 交出的那张表

DRUID 的结果工作簿有六个工作表,交给 ADEPT 的只有一张:「剖面窗口」。 它的列名就是按 ADEPT 的输入约定取的,所以不需要改列名、不需要转录。 (另外几张是给人看的:结果 / 标样QC / 深度剖面域 / 不分域年龄 / 运行参数。)

列含义用途
Analysis「序号 样品名」,如 07 S01ADEPT 用它分组,一个值 = 一个测点
Time窗口中心时刻 (s)横轴,等价于坑深
Age68窗口 206Pb/238U 年龄 (Ma)ADEPT 分段的输入
Age68_1s该年龄的 1σ (Ma)ADEPT 算 MSWD 的输入
Tau归一化剥蚀进度(0→1)诊断、画图
R68 / s68比值与其相对不确定度诊断
ThU / U_cps / f206_pctTh/U、238U 信号强度、普通铅占比诊断:判断这个窗口可不可信
n_cycles该窗口里累计了几个 cycle诊断

为什么这些诊断列要一起带过去?因为它们让"这个坪为什么被丢掉"这件事可以事后复核。 只有 Age68 和 Age68_1s 两列也能跑,但一旦某个坪被过滤掉, 你就只能看到"少了一个",看不到"少了的原因是不是 f206 在那一窗突然升高"。

另一条出口,不是给 ADEPT 的。DRUID 跑完还会在 Excel 旁边写一份 .handoff.json: 同一批数据的质控结论 —— 26 条带稳定 key 的检查项(fail / warn / info / pass), 以及"这批数据交给下游时会掉哪些测点、为什么"。ADEPT 完全不读它;它面向的是写脚本批量汇总多个批次, 或者以后接一个自动解释工具的场景。给 ADEPT 的接口始终只有「剖面窗口」那一张表。

ADEPT 这边必须显式给的四个参数

ADEPT 刻意不去猜数据是否已经预处理过。一个 _1s 列无法说明剥蚀窗口在哪里结束, 也无法说明 σ 里包含了哪些项,所以这些必须由调用方声明:

adept("EX2022A_U-Pb结果.xlsx",
      lower_ablation_time    = 0,       # DRUID 已经切好了自己的剥蚀窗口
      upper_ablation_time    = 1e6,     # 上界放到最后一个窗口之后
      smooth                 = "none",  # 数据已做 F(τ) 校正,剖面本就是平的
      calibration_uncertainty = 0)      # σ 已含外部重现性,再乘 3% 就重复计入
参数取值设错的后果
lower/upper_ablation_time0 / 1e6 ADEPT 默认按 29–58 s 切窗口,而 DRUID 的窗口时刻是 29.97–67.47 s。 用默认值会静默截掉尾部 9 个窗口——数据少了一截,不报错,只是年龄变了。
smooth"none" DRUID 已经做过逐深度分馏校正,剖面在域内本来就是平的。 再用 LOESS 平滑,会把真实的域边界一起抹平——边界正是要找的东西。
calibration_uncertainty0 DRUID 的 σ 已含标样外部重现性。再乘 3% 就是把它算两遍: 坪的不确定度会从 1.8 Ma 变成 13.7 Ma,差一个数量级。

为什么不让 ADEPT 直接从 cps 做起

这是最先要排除的方案,因为它看起来最"省事"。

算法上,从 cps 到一个可靠的比值中间有七步:死时间校正、204Hg 干扰、 204Pb 显著性检验、普通铅校正、标样夹逼归一化、F(τ) 分馏校正、jackknife 不确定度。 这七步全在 DRUID 里,而且其中标样夹逼需要整批数据(前后主标), 单个测点根本算不出来——ADEPT 拿到一个孤立的点,F(τ) 无从构造。

物理上,从 cps 逐点推 σ 也不成立:一个 cycle 的 207Pb 只有几十个计数, 真实噪声以闪烁噪声为主而非泊松噪声;而且 cps 本身已经除过积分时间、做过死时间校正, 再套一次泊松公式是把错误的模型用在已经处理过的量上。

所以正确的分工不是"让 ADEPT 做得更少",而是"让 ADEPT 在正确的层级上做事"。 测量层级归 DRUID,判读层级归 ADEPT。

四、用真实数据走一遍

以下全部来自本实验室的批次 EX2022A(48 个样品测点 + 21 个主标 + 14 个监控标样)。

1677DRUID 交出的窗口行数
35中位窗口数 / 测点
51ADEPT 认出的坪
1.25MSWD 中位数
S01 的逐窗口年龄剖面与 ADEPT 认出的坪
一个真实测点。蓝点是 DRUID 交出来的 35 个窗口年龄(误差棒 1σ), 红虚线是整段平均,绿带是 ADEPT 在同样这些点上认出的两个坪。 中间约 490 Ma 的那一段没有被确认为坪——它是混合/过渡带,不该被当作一个年龄报出去。 这正是 ADEPT 存在的意义:光有散点,你得自己判断哪一段能用。

同一个测点,两个工具独立算出来的结果:

区间年龄1σ内部自洽性
DRUID 域 D1前 14 个窗口453.84 Ma1.79 Ma—
ADEPT 坪 1前 13 个窗口453.06 Ma1.83 MaMSWD 1.09

两者差了 0.78 Ma,而这些数各自带约 1.8 Ma 的误差——差异远小于误差, 在统计上完全一致。这不是巧合:两边用的是同一批窗口、同一套比值, 但分段算法、过滤判据、以及坪年龄的求法是各自独立写的 (DRUID 用 BIC 二分 + MSWD 剥离混合窗口,ADEPT 用 PELT + 四步过滤 + 反比方差加权)。 两条独立路径给出同一个答案,这本身就是一次交叉验证。

整批 51 个坪的 MSWD 分布更能说明问题:

统计量值怎么读
中位数1.25围绕 1,健康
下四分位 / 上四分位0.62 / 1.71分布不偏、无系统性偏移

MSWD 是"点的散布是否与给出的误差相符"的检验。如果 DRUID 的逐点 σ 系统性地偏小, 这里的中位数会明显大于 1(可能到 5、10 甚至更高)。中位 1.25 说明 DRUID 报出的 不确定度是诚实的——这是一个只有把两个工具接起来才可能得到的结论, 单看任何一个工具都得不出来。

五、"两边都能分域"这件事怎么理解

这里必须说清楚,否则确实会让人觉得奇怪:DRUID 自己也会分割深度剖面 (depth/domains.py,BIC 二分),ADEPT 也做分段(PELT + 四步过滤)。 这不是隐藏的重叠,是真实存在的重叠。它成立于三个理由:

1. 判据不同,问的问题也不同

DRUID 的分域是诊断:目的是判定"这个测点到底是均一还是多域", 并顺便为 F(τ) 找出该用哪些窗口、把混合窗口剥掉。它的输出是一个标签 (均一 / 多域(3))和一张粗略的域明细表, 服务于"这个点能不能用"这个判断。

ADEPT 的分段是产出:目的是给出可直接写进补充材料的坪年龄表。 它的四步过滤(年龄范围、坪内方差、最短坪长、年龄随深度的单调方向) 是地质上"什么算一个坪"的判据,再配上参数敏感性扫描——这些 DRUID 不做,也不打算做。

2. 两者的结论应当一致,不一致本身就是信号

在同一个测点上,如果 DRUID 说"多域(3)"而 ADEPT 只给出 1 个坪, 说明有 2 个域被 ADEPT 的判据挡掉了——可能是坪太短,也可能是方差超限。 这不是矛盾,而是一个值得看一眼的提示:要么参数选得太严,要么那两个域本来就不该报。 上面那幅图里,中间约 490 Ma 的段落就属于这种情况。

3. 但不要拿两套数字写文章

同一个测点从两个工具各得到一个年龄,看起来可以互相印证, 但写进文章时必须只选一套算法。混用会让审稿人无法判断你的数字是怎么来的。

建议以 ADEPT 的输出为准:它是 Wang et al. (2026) 那篇方法文章的实现, 审稿人认这套做法,而且它给出了完整的不确定度预算(随机 / 系统 / 衰变常数三项分开) 和可直接引用的 MSWD。DRUID 的域表只作诊断用。

4. 有一批窗口只出现在一张表里

DRUID 在分域之后会把边界窗口剥掉(refine_domains(),目的是保住域内的纯度)。 实测全批次 180 / 1677 = 10.7% 的窗口不落在任何域里,剥得最狠的几个测点一次丢掉 17~24 个窗口。

关键在于:这些窗口仍然留在 剖面窗口 表里(那张表是 1677 行 = 全部窗口, 不是"只留域内的")。所以它们照旧会喂给 ADEPT,而 ADEPT 也可能在里头判出一个合格坪—— 实测有一个测点,DRUID 丢掉的那 18 个窗口整体是 463.45 ± 2.26 Ma(1σ)/ MSWD 4.41, 不成坪;但 ADEPT 在同一段里找到了一个 448 ± 3 Ma / n=11 / MSWD 0.67 的坪。

两边都"有道理",这不是谁错了,而是取向不同:DRUID 判据不符就剥边界、保域内纯度; ADEPT 是 PELT 分段 + 四步过滤、保分段完整性。代价是同一个样品可能在两张表里 各有一个"有效年龄"——这也是上面第 3 条"只选一套算法"的另一个理由。 另外,DRUID 的拒绝从不给理由,ADEPT 会给(最常见的是 Filter 2 的方差阈值)。

六、四个容易做错的地方

① _1s 是 1σ,不是 2σ

DRUID 的结果表里 s68_1sig 与 s68_2sig 是并列的两列。 拿 2σ 那一列去做 ADEPT 的输入,MSWD 会缩小到四分之一—— 一个本该被判定为"不自洽"的坪,会看起来完全没问题。这是最危险的一种错误:不报错,只是结论反了。

② 已校正的数据不要再平滑

DRUID 的 F(τ) 校正就是为了让域内的剖面变平。 再套一次 LOESS,会把真实的域边界一起抹平——边界恰恰是要找的东西。

③ 外部重现性不要计两次

DRUID 的 σ 构成是 sqrt(内部 jackknife² + (外部重现性 × 比值)²), 已经包含了标样散度。ADEPT 的 calibration_uncertainty 默认再乘 3%, 所以接 DRUID 的数据时必须置 0。不置零的后果是量级级别的:1.8 Ma → 13.7 Ma。

④ 读表的时候,σ 的倍数和上面是反过来的一件事

上面①说的是喂进去的方向。反过来,读出来的时候也要当心: ADEPT 的 Final total uncertainty (Ma) 是 1σ, 而 DRUID 深度剖面域 表的 s2_Ma 是 2σ。 τ 精确对齐的 8 对上,两边比值实测 2.0000 ± 0.0003(25 对含部分重叠的中位 2.011)。 所以两张表不能并排直接读,先把 DRUID 的数除以 2——否则会得出 "两个工具算出的不确定度差一倍"这种并不存在的结论。

七、谁负责什么

环节DRUIDADEPT备注
读原始 CSV / 序列文件✓—识别 7 个质量数与剥蚀区间
死时间校正✓—默认关闭,需实测标定
204Hg 干扰、普通铅显著性检验✓—这是本流程最容易被忽略的一步
标样夹逼归一化(含仪器漂移)✓—需要整批数据
F(τ) 逐深度分馏校正✓—只对 Pb/U,不对 Pb/Pb
逐点不确定度(内部 + 外部)✓—jackknife + 标样散度正交相加
深度分段诊断用✓DRUID 判"均一/多域",ADEPT 定坪
坪的取舍判据—✓最短坪长、方差上限、单调方向
MSWD 与内部自洽性检验—✓需要每点 σ 才能算
完整不确定度预算—✓随机 / 系统 / 衰变常数分列
参数敏感性扫描—✓adept_sensitivity()
成图✓✓用途不同:DRUID 出剖面图,ADEPT 出坪图

八、什么时候只需要其中一个

也就是说 ADEPT 并不依赖 DRUID。DRUID 是这条链在自己实验室里的前端, 而不是 ADEPT 的前置条件——这一点让两个工具各自独立可用, 也正是它们能互为验证的原因。

附:一次完整调用

# ① DRUID:一个批次 → 结果表 + 逐窗口剖面
python -m druid.cli.reduce_batch ^
    --dir "data\EX2022A" ^
    --out "结果\EX2022A_U-Pb结果.xlsx"

# ② ADEPT:读同一份工作簿里的「剖面窗口」表
Rscript -e "library(ADEPT)
r <- adept('结果/EX2022A_U-Pb结果.xlsx',
            output_path = NA, make_plots = FALSE,
            lower_ablation_time = 0, upper_ablation_time = 1e6,
            smooth = 'none', calibration_uncertainty = 0)
print(r$summary[, c('Analysis','Final age (Ma)','MSWD')])"

ADEPT 会读工作簿里的每一张表,只处理含 Age68 的那张, 其余的表(结果 / 标样QC / 深度剖面域 / 不分域年龄 / 运行参数)会给出警告后跳过。 所以直接把 DRUID 的结果工作簿整个丢给它就行,不需要先拆出那张表。

ADEPT 自己写出来的 xlsx 是坏 XML,别用程序去读它。 它在 output_path 写出的 <批次>_ADEPT.xlsx, 用 openpyxl / pandas 打开会直接报 ParseError: not well-formed (invalid token)。根因是 深度剖面域 那张表的中文列名(年龄_Ma / MSWD_概率) 被 base-R 的写手写成了非法控制字节(每个文件固定 18 个)。 示例批次跑出来的产物也一样错,所以这是长期存在的问题,不是某一次跑坏了。

Excel 自己容错能打开,但表头是乱码;严格的解析器一律拒绝。 绕法很简单:报告里的数字全部从 ADEPT 另存的 CSV 读(full.csv 是段级、 summary 是坪级),不碰那个 xlsx。