DRUID 与 ADEPT:一个剥蚀坑里的两个问题
两个工具为什么分开、数据在它们之间如何变化、边界上到底传了什么
一句话:DRUID 负责「测出来」,ADEPT 负责「读出来」。
DRUID 把原始计数率变成随深度变化的年龄(每个深度上的年龄是多少、误差多大); ADEPT 在同一条随深度变化的年龄上认出哪些区间构成真实的、内部自洽的年龄域。
前者是测量问题,后者是判读问题。分开不是设计癖好,是因为它们需要的东西完全不同—— 一个需要标样、干扰校正和分馏校正,另一个需要的是分段判据与地质上的取舍规则。
一、问题的来源在样品里,不在软件里
一次激光剥蚀在锆石上打出一个约 20–40 μm 深的坑。锆石在坑的深度方向上是分层的: 继承核、变质增生边、岩浆环带,年龄可以相差几百个百万年。也就是说, 同一束激光在同一个点上,浅处和深处打到的其实是不同时代的东西。
常规做法是把整个剥蚀段积分成一个数。这在单年龄的样品上没问题, 在分层的锆石上则会把几个不同的年龄搅在一起——得到的既不是核的年龄,也不是边的年龄, 而是一个没有任何地质含义的混合值。
这不是假想的风险,本批次里就有。测点 S02 整段积分给出
436.5 Ma、协和度 51.7%——协和度这么低的"年龄"是不能用的。但它的浅部深度域
给出 440.4 Ma,是干净的结果。整段平均把这个点的可用信息直接抹掉了。
于是分裂出两个必须分开回答的问题:
① 每一深度上,年龄是多少、误差多大?
这是测量问题。要回答它,需要标样夹逼归一化、204Hg 干扰校正、 普通铅显著性检验、随坑深变化的分馏校正、以及逐窗口的不确定度传播。 这是 DRUID 的工作。
② 这些深度里,哪些区间构成一个真实的年龄域?
这是判读问题。要回答它,需要分段、需要"一个坪至少要多长"、 需要"坪内方差多大算合格"、需要"年龄随深度该朝哪个方向变"。 这是 ADEPT 的工作。
两个问题对应两种错误,而且它们不能互相检查:测量错了(例如标样归一化有偏), 判读得再对也是错的年龄;判读错了(例如把两个混合窗口当成一个坪), 测量再准也得到假的坪。分开做,两边的错误才各自可查。
二、数据一路上的形态变化
下面这条链是一次完整的数据旅程。左侧紫色部分是 DRUID, 右侧绿色部分是 ADEPT,中间那一步是两者唯一的接触面。
| 阶段 | 数据形态 | 规模(本批次单点) | 谁做 |
|---|---|---|---|
| ① 原始 | 7 个质量数的计数率时间序列(cps) | 239 行 × 7 通道 每 0.32 s 一个 cycle | Qtegra 导出 |
| ② 净信号 | 同上,但已剔除未剥蚀段、扣掉气体空白 | 剥蚀段约 38 s | DRUID |
| ③ 比值 | 206Pb/238U 与 207Pb/206Pb,已做干扰与普通铅校正,并按主标归一化 | 1 组比值 / 点 | DRUID |
| ④ 深度剖面 | 逐窗口年龄 ± 1σ | 35 个窗口 / 点 共 1677 行 / 48 点 | DRUID → ADEPT |
| ⑤ 分段 | 窗口序列 → 变点位置 | 段数不限 | ADEPT |
| ⑥ 过滤 | 每段一行,带四个过滤判据的结果 | — | ADEPT |
| ⑦ 坪 | 坪年龄 + 不确定度 + MSWD | 51 个坪 / 48 点 | ADEPT |
三、边界上传来什么
DRUID 交出的那张表
DRUID 的结果工作簿有六个工作表,交给 ADEPT 的只有一张:「剖面窗口」。 它的列名就是按 ADEPT 的输入约定取的,所以不需要改列名、不需要转录。 (另外几张是给人看的:结果 / 标样QC / 深度剖面域 / 不分域年龄 / 运行参数。)
| 列 | 含义 | 用途 |
|---|---|---|
| Analysis | 「序号 样品名」,如 07 S01 | ADEPT 用它分组,一个值 = 一个测点 |
| Time | 窗口中心时刻 (s) | 横轴,等价于坑深 |
| Age68 | 窗口 206Pb/238U 年龄 (Ma) | ADEPT 分段的输入 |
| Age68_1s | 该年龄的 1σ (Ma) | ADEPT 算 MSWD 的输入 |
| Tau | 归一化剥蚀进度(0→1) | 诊断、画图 |
| R68 / s68 | 比值与其相对不确定度 | 诊断 |
| ThU / U_cps / f206_pct | Th/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_time | 0 / 1e6 | ADEPT 默认按 29–58 s 切窗口,而 DRUID 的窗口时刻是 29.97–67.47 s。 用默认值会静默截掉尾部 9 个窗口——数据少了一截,不报错,只是年龄变了。 |
| smooth | "none" | DRUID 已经做过逐深度分馏校正,剖面在域内本来就是平的。 再用 LOESS 平滑,会把真实的域边界一起抹平——边界正是要找的东西。 |
| calibration_uncertainty | 0 | 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 个监控标样)。
同一个测点,两个工具独立算出来的结果:
| 区间 | 年龄 | 1σ | 内部自洽性 | |
|---|---|---|---|---|
| DRUID 域 D1 | 前 14 个窗口 | 453.84 Ma | 1.79 Ma | — |
| ADEPT 坪 1 | 前 13 个窗口 | 453.06 Ma | 1.83 Ma | MSWD 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——否则会得出
"两个工具算出的不确定度差一倍"这种并不存在的结论。
七、谁负责什么
| 环节 | DRUID | ADEPT | 备注 |
|---|---|---|---|
| 读原始 CSV / 序列文件 | ✓ | — | 识别 7 个质量数与剥蚀区间 |
| 死时间校正 | ✓ | — | 默认关闭,需实测标定 |
| 204Hg 干扰、普通铅显著性检验 | ✓ | — | 这是本流程最容易被忽略的一步 |
| 标样夹逼归一化(含仪器漂移) | ✓ | — | 需要整批数据 |
| F(τ) 逐深度分馏校正 | ✓ | — | 只对 Pb/U,不对 Pb/Pb |
| 逐点不确定度(内部 + 外部) | ✓ | — | jackknife + 标样散度正交相加 |
| 深度分段 | 诊断用 | ✓ | DRUID 判"均一/多域",ADEPT 定坪 |
| 坪的取舍判据 | — | ✓ | 最短坪长、方差上限、单调方向 |
| MSWD 与内部自洽性检验 | — | ✓ | 需要每点 σ 才能算 |
| 完整不确定度预算 | — | ✓ | 随机 / 系统 / 衰变常数分列 |
| 参数敏感性扫描 | — | ✓ | adept_sensitivity() |
| 成图 | ✓ | ✓ | 用途不同:DRUID 出剖面图,ADEPT 出坪图 |
八、什么时候只需要其中一个
- 只拿到原始 CSV → 必须先跑 DRUID。ADEPT 可选:如果只关心整段年龄, DRUID 的结果表就够了;如果要坪年龄和 MSWD,再接 ADEPT。
- 只拿到别人给的年龄剖面(没有 cps,例如合作者发来的 Excel)→ 只需 ADEPT。
只要表里有
Analysis/Time/Age68/Age68_1s, 格式就是兼容的;没有 σ 列也能跑,只是不报 MSWD。 - 数据已发表、只想复核坪 → 只需 ADEPT。把当时的年龄序列喂回去, 看 MSWD 与坪的划分是否复现。
- 要出整段的元素含量、Th/U 趋势 → 只需 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。