Depth-Resolved U–Pb Isotope Data reduction

DRUID

随坑深变化的锆石年龄

把 LA-ICP-MS 的原始信号时间序列,直接转化为带不确定度的逐深度年龄剖面。

再展开一点:输入是 Qtegra / iCAP 导出的计数率(cps)时间序列, 输出是带不确定度的年龄、标样质控表以及逐深度的年龄剖面; 其中剖面表可直接交给 R 包 ADEPT 判定坪年龄与 MSWD。 DRUID 测出来,ADEPT 读出来。

依赖 numpy / pandas / matplotlib / openpyxl / xlrd 无 scipy — 统计内核自实现 网页界面 纯标准库,无 npm 无构建 CI 3 系统 × Python 版本 自检 203 项 + 端到端数值回归 质控 28 条检查项,含机读交接文件

5 秒看懂

一眼看到什么

左边是一个测点的逐深度年龄剖面与它被拆出的年龄域,右边是整批的质控判据。 两者都取自示例批次 examples/EX2022A 的真实输出,不是示意图 —— 跑一遍就能复现同样的数字。

示例批次 EX2022A 的一个真实测点:逐深度年龄剖面与它的两个年龄域 横轴是剥蚀时间(对应坑深),纵轴是年龄。每个点是一个 4 秒滑窗,误差棒是 1σ。灰虚线是整段不分域的加权平均,两条彩色实线是两个年龄域的均值,空心点是没进入任何年龄域的窗口。右侧竖直箭头标出整段(不分域)与主域之差。数据取自示例批次的公开结果表,可用仓库里的命令重新算出同样的数字。 S10 · 三个年龄域 35 个 4 秒滑窗 整段 MSWD 3.97 / 上限 1.49 → 整段非常数 440 460 480 500 年龄 (Ma) 0 0.2 0.4 0.6 0.8 1 整段不分域 457.2 D1 472.8 D2 445.9 D3 483.0 Δ +11.3 Ma 剥蚀时间 τ(0 = 积分段起点,1 = 积分段终点) → 坑深方向

图较宽,可左右滑动查看完整的坐标轴

这就是一个测点给出的定量结论(整批 48 个样品测点各出一张,其中 36 个检出多年龄域)。上面这张图是示例批次 examples/EX2022A 的真实输出,不是示意图 —— 仓库里的命令跑一遍就能得到同样的数字。S10 三个年龄域:共 35 个滑窗,3 个域 —— D1 472.8 Ma(8 窗,MSWD 0.90) · D2 445.9 Ma(20 窗,MSWD 0.73) · D3 483.0 Ma(7 窗,MSWD 0.38)。没有一个窗口被剥掉。整段不分域给 457.2 Ma,离主域 445.9 Ma 差 +11.3 Ma(+2.54%)(Δ = 整段 − 主域)—— 这就是"分域"这一步带来的差别。

先看这三个数 判据原文,不是我们另写的

①主标 91500 偏差通过
−0.31% (n=21)
standards.primary_bias · |偏差| < 1.0%(≥2.0% 判 fail)
②监控标样 Ple 偏差通过
+1.85% (n=14)
standards.secondary_bias · |偏差| < 3.0%(≥5.0% 判 fail)
③样品协和度占比通过
94% (中位 101.3%,n=48)
samples.concordance · ≥ 85%(< 70% 判 fail)

三条都过了才往下看年龄。完整的 26 条检查项在第 05 节,也可以用结果旁边的 .handoff.json 给程序读。

标样QC 表(结果.xlsx 的第 2 张表)
标样点数参考年龄加权平均2σMSWD偏差
91500211063.511060.226.690.51−0.31%
Ple14337.16343.402.501.00+1.85%

年龄单位 Ma。偏差 = 加权平均 − 参考值(参考值取 Horstwood et al. 2016 表 S2;参考值来源 / 归一化 / 二次校正三件事都写在「运行参数」表里)。

01从这里开始

六份文档都是单文件自包含(图表全内嵌),离线也能打开。

要动手跑数据,先看这份

上手指南:从零跑通第一批数据

装环境四条命令(每条都写了"怎么算成功")、数据文件夹怎么摆、结果先看哪三个数、 九类常见报错对应哪一步。不需要任何编程经验。

  • 📄 10 节
  • 📊 7 张示意图
  • ⏱ 约 20 分钟

适合:零基础,只需要会双击文件

想知道为什么这么做

从一块岩石到一个年龄

从"放射性为什么能当时钟"一路讲到"MSWD 该怎么读",不需要任何前置知识。 每一步都说明为什么这么做、做错了会往哪个方向偏,并给出已知的局限。

  • 📄 9 章
  • 📊 10 张图解
  • ⏱ 约 25 分钟

适合:想知道为什么,不需要任何前置知识

已知原理,想看分工

两个工具的联系与数据流

DRUID 与 ADEPT 各负责什么、数据在它们之间如何变化、边界上到底传了什么。 含一张真实测点的剖面图与实测数值对照。

  • 📄 8 节
  • 🔌 接口约定

适合:懂点 Python、知道 U-Pb,只关心分工与列名

结果里出现"跨度很大"的域时,该信还是该疑

一个测点解出相差上千 Ma 的两个域

先把「协和度到底是什么」画在谐和线上(它其实是一条斜率), 再逐个复核 12 个跨度 >500 Ma 的测点:两个面板、五条判据按硬度排序, 每个数都能用仓库里的示例批次复现。

  • 📄 5 节
  • 📊 2 张图
  • 🔍 12 个测点

适合:手上已经跑出结果,不确定极值该不该信的人

想知道「一段一段地读」到底值不值

分域之后,多得到了什么

把一次剥蚀切成几十段分别读,换来的不是「更准的年龄」,而是 「这个年龄的前提成不成立」与「这些段能不能用一条混合轨迹解释」。 同时量化它的代价:把段当独立观测汇总会凭空得到一个漂亮得多的误差棒。 展示用的误差棒已按合成数据的实测倍数放大 —— 工具自报的域级 σ 是偏窄的。

  • 📄 8 节
  • 📊 3 张图
  • 🧮 每个数可复算

适合:想判断该测多少、该切多细的人

要读代码 / 长期维护

仓库与开发约定

分层结构、改动守则、已知地雷与回归基线都在 AGENTS.md 里; 所有数字都能用仓库自带的示例批次复现。

  • 📄 README
  • 🛠 AGENTS.md
  • ⚖ MIT

适合:要读代码 / 长期维护的人

02名字取 Depth-Resolved,不是修辞

与只做整段积分的常规流程相比,最大的区别在于:每个测点被当成一条随时间(即随坑深)变化的信号来处理—— 分馏按深度校正、年龄域按深度划分。

Qtegra CSVcps 时间序列 → DRUID剥蚀区间 · 归一化 · 深度分馏 · 年龄与域 → 结果.xlsx六张表 + .handoff.json → ADEPT坪年龄 + MSWD

下面是同一条流程的细节版:十步流水线,每一步的参数都会写进结果表的「运行参数」页。

DRUID 的十步流水线与它和 ADEPT 的分工 Qtegra 导出的 cps 时间序列,经过剥蚀区间识别与扣空白、汞与普通铅校正加夹逼归一化、F(τ) 深度分馏校正,最后得到年龄、BIC 年龄域与标样质控;结果落成一个 Excel 的六张表,其中「剖面窗口」表交给 ADEPT 判坪年龄与 MSWD。 Qtegra CSV cps 时间序列 DRUID ①—② 剥蚀区间 · 扣空白 ③—⑥ Hg/普通铅校正 · 夹逼归一化 ⑤⑦ F(τ) 深度分馏校正 · σext ⑧—⑩ 年龄 + BIC 年龄域 + 标样 QC 整段年龄 + 逐窗口年龄剖面(含 1σ,jackknife + 外部重现性) 结果.xlsx 六个工作表 「剖面窗口」表 ADEPT 坪年龄 + MSWD 十步流水线,每一步的参数都写进结果表 的「运行参数」页,半年后可回溯。

图较宽,可左右滑动查看完整流程

"分域"这一步到底改变了什么?三种剖面形态

下面三个都是示例批次里的真实测点,每个点是一个 4 秒滑窗,误差棒是该窗口的 1σ。 同样叫"分域",三种形态下的结论完全不同。

同一个示例批次里的三种剖面形态:均一 / 核边分明 / 复杂变化 三个真实测点,每个点是一个 4 秒滑窗,误差棒是 1σ。灰虚线是整段不分域的加权平均:在 ① 里它就是答案,在 ②③ 里它不对应任何一个窗口。彩色实线是两个或三个年龄域的均值,空心圈是没通过域内相容性检验、不进入任何年龄域的窗口。 ① S43 · 均一 整段就是答案 整段 MSWD 1.14 / 上限 1.49 → 整段常数 420 440 460 480 500 0 0.2 0.4 0.6 0.8 1 ② S34 · 核边分明 一分为二正好 整段 MSWD 5.08 / 上限 1.49 → 整段非常数 450 500 550 年龄 (Ma) 0 0.2 0.4 0.6 0.8 1 D1 469.6 D2 515.2 ③ S18 · 复杂变化 只拆得动一部分 整段 MSWD 3.39 / 上限 1.49 → 整段非常数 425 450 475 500 0 0.2 0.4 0.6 0.8 1 D1 471.8 D2 442.5 D3 474.9 8 个空心点(未进入任何年龄域) 剥蚀时间 τ(0 = 积分段起点,1 = 积分段终点) → 坑深方向

图较宽,可左右滑动查看完整的坐标轴

  • 怎么读整段不分域:把全部窗口当成唯一一个域各年龄域均值(D1 / D2 / D3)没通过域内相容性检验、不进入任何域Δ 一律是「整段 − 主域」
  • ① S43 均一 · 整段 458.7 Ma共 34 个滑窗。整段 MSWD 1.14 < 判据上限 1.49 → 整段就是常数。域表里根本没有这个测点:没有域可拆,灰虚线就是答案。整段积分(另一种算法)给 459.3 Ma,与窗口加权差 0.14%。
  • ② S34 核边分明 · 整段 497.2 Ma共 35 个滑窗。2 个域:D1 469.6 Ma(13 窗,MSWD 1.40) · D2 515.2 Ma(22 窗,MSWD 0.88)。没有一个窗口被剥掉。整段 MSWD 5.08 > 上限 1.49 → 整段非常数,整段 497.2 − 主域 515.2 = -18.03 Ma(-3.50%)。
  • ③ S18 复杂变化 · 整段 458.8 Ma共 35 个滑窗。3 个域:D1 471.8 Ma(7 窗,MSWD 0.15) · D2 442.5 Ma(12 窗,MSWD 0.86) · D3 474.9 Ma(8 窗,MSWD 1.96)。8 个窗口没通过域内相容性检验,被分域步骤剥掉。整段 MSWD 3.39 > 上限 1.49 → 整段非常数,整段 458.8 − 主域 442.5 = +16.30 Ma(+3.68%)。
三个测点都取自示例批次 examples/EX2022A,上面的数字可以自行复现。 S43 整段 MSWD 1.14 低于判据上限 1.49,整段就是常数:域表里根本没有它,所以这里没有彩色实线,灰虚线就是答案。整段积分(另一种算法)给 459.3 Ma,与窗口加权差 0.14%。 S34 分为 2 段平台 469.6 / 515.2 Ma,域均值最大相差 45.6 Ma(9.7%);沿坑深年龄递增:浅部 469.6 → 深部 515.2 Ma,是"核老边新"的常序,一个窗口都没被剥掉。整段不分域给 497.2 Ma,它不等于任何一段。 S18 分为 3 段平台 471.8 / 442.5 / 474.9 Ma,域均值最大相差 32.4 Ma(6.9%);沿坑深年龄不单调(471.8 → 442.5 → 474.9 Ma)—— 不能用一个"核"一个"边"解释,中间 8 个空心点没通过域内相容性检验,被分域步骤剥掉了。整段不分域给 458.8 Ma,它不等于任何一段。 注意 D1 与 D3 的年龄几乎相同(471.8 与 474.9 Ma)却是两个域 ——「域」是深度上连续的一段,不是"年龄相同的所有窗口"。 ① 的灰虚线就是年龄(整段只有一个域,分域无事可做);②、③ 的灰虚线只是在回答"不做分域会得到什么",不是任何一个年龄。

03三条不轻易改动的设计

每条都来自实测数据的教训。违背它们不会报错,只会悄悄给出错误的年龄。

原则 1

204Pb 普通铅校正,必须先通过显著性检验才做

违反的代价:把噪声当普通铅扣掉,而这份偏差几乎全落在 207Pb/206Pb 上(238U/235U ≈ 137.8,放射成因的 207Pb 本来就比 206Pb 少得多)。

204Pb 只有几~几十 cps,而 204Hg 本底可达数百 cps,两者做差后的残余往往就是噪声。 所以只在 2σ 显著时才扣,不显著就整批不扣。

原则 2

207Pb/206Pb 不做 F(τ) 深度分馏校正

违反的代价:纯属给一个本来就没有系统漂移的量注入噪声。

它是 Pb 同位素比值,几乎不随坑深分馏(随坑深漂移的是 Pb/U 这类元素对比值); 4 s 窗口的 207Pb 净计数中位约 9×10³,计数统计已在 1% 量级 —— 能指望的 Pb-Pb 漂移远小于它。它仍参与标样归一化(F76),只是不做逐深度校正。

原则 3

剥蚀区间用两级阈值,以峰值比例为主

违反的代价:把 washout 拖尾算进积分窗口,系统性稀释全部比值。

只用「本底 + kσ」会把 washout 拖尾算进来——关激光后 10 s 仍有 100~200 cps, 等于把大量零信号混进了积分窗口。

04一个批次两份文件,六张表

一份 Excel 给人看,一份 .handoff.json 给程序读; 从逐点结果到标样质控再到逐深度剖面,全部参数可追溯。

EX2022A_U-Pb结果.xlsx

六张工作表,给人看:结果 / 标样QC / 深度剖面域 / 不分域年龄 / 剖面窗口 / 运行参数。

「剖面窗口」表列名已按 ADEPT 的输入约定取好,可直接喂入。

EX2022A_U-Pb结果.handoff.json

给程序读的质控交接:26 条带稳定 key 的检查项、四级结论、以及本次运行的完整参数。

下游按 key 分支,不要按标题文字匹配——文字会改,key 不会。

结果

每行一个测点:计数率、三种体系年龄与 1σ/2σ、协和度、深度结构

标样QC

主标与监控标样的加权平均年龄、MSWD、与推荐值的偏差

深度剖面域

BIC 检出的年龄域:τ、年龄与 MSWD(与 ADEPT 用同一套算法)

不分域年龄

把整段剥蚀当作一个域的年龄基准,与分域主域、整段积分三种算法并排——差额正好是「分域这一步」的贡献

剖面窗口

逐窗口年龄剖面,列名按 ADEPT 输入约定对齐,可直接喂入

运行参数

本次运行全部参数与被跳过的文件,半年后也能复现

「不分域年龄」表不是一份年龄清单。它把整个剥蚀段当成唯一一个域来算, 示例批次 48 个样品测点里只有 5 个与「整段只有一个年龄」相容; 其余的整段值是几个年龄的加权混合,只能用于横向对比,不能定年。要定年请看「深度剖面域」表。

机读交接文件长什么样

下面是示例批次真实输出的节选(键名与结论原文,未做改写)。

{
  "schema": "druid.handoff/1",
  "verdict": { "level": "warn",
               "counts": { "fail": 0, "warn": 4, "info": 4, "pass": 17 } },
  "checks": [
    { "key": "standards.primary_bias", "level": "pass",
      "criterion": "|偏差| < 1.0%(≥2.0% 判 fail)" },
    { "key": "samples.unresolved_structure", "level": "warn",
      "criterion": "占比 ≤ 50.00%" }
  ],
  "whole_spot": { "n_spots": 48, "n_compatible": 5,
                  "mswd_median": 4.437620781414898 }
}
增字段、增检查项属于兼容扩展,不升 schema;只有改 key 名、改语义、改类型才升。 这样下游可以放心地"多读一个字段",不用怕版本号一动就全线告警。

05先看这三个数

跑完一个批次,别急着解释年龄——先用这三个数判断这一批数据能不能用。 它们是 DRUID 质控层里三条检查项的判据原文,不是我们另写的。

①主标 91500 偏差 本批通过
< 1%绝对值上限
本批实测 −0.31% 1060.22 Ma(n=21,MSWD 0.51)

standards.primary_bias |criterion| = |偏差| < 1.0%(≥2.0% 判 fail)

②监控标样 Ple 偏差 本批通过
< 3%绝对值上限
本批实测 +1.85% 343.40 Ma(n=14,MSWD 1.00)

standards.secondary_bias |criterion| = |偏差| < 3.0%(≥5.0% 判 fail)

③协和度占比 本批通过
> 85%样品占比下限
本批实测 93.75%(45 / 48 个样品测点落在 90–110%)

samples.concordance |criterion| = ≥ 85%(< 70% 判 fail)

超过阈值不等于"不能用",而是降级——质控层分四级, 每条检查项自己带着判据,不需要你去查文档对照:

检查passwarnfail为什么这个数
91500 偏差< 1% 1–2%≥ 2% 它是归一化锚点,偏差会整体平移全部样品年龄,不是随机误差
Ple 偏差< 3% 3–5%≥ 5% 监控标样独立于归一化,检验的是"换一个基体还准不准"
协和度占比≥ 85% 70–85%< 70% 占比掉下来通常意味着普通铅校正或剥蚀区间出了问题
三个数都过,才往下看年龄。任何一项不过,先解决仪器与标样的问题, 别急着解释地质含义——那是把分析误差读成了地质事件。 阈值集中在 qc.QCThresholds 一处,换仪器时改那里,改一处即全局生效。

这三个数全过,只说明仪器与标样这一侧没出问题。它们不能告诉你 这份数据该不该拿来定年 —— 那件事见 「什么情况下 DRUID 不适用」。

06与 ADEPT 的接口契约

DRUID 输出的「剖面窗口」表, 可以直接作为 ADEPT 的输入文件。

下表列名就按 ADEPT 的 Format 4 约定取好(Analysis / Time / Age68 / Age68_1s),不用转发、不用改列名。 调用时要显式给四个参数,一个都不能省——ADEPT 刻意不猜数据是否已预处理。

# 「剖面窗口」表 = ADEPT Format 4:Analysis / Time / Age68 / Age68_1s(+诊断列)
adept("EX2022A_U-Pb结果.xlsx",
      lower_ablation_time    = 0,      # DRUID 已切好自己的剥蚀窗口
      upper_ablation_time    = 1e6,    # 上界放到最后一个窗口之后
      smooth                 = "none", # 数据已做 F(τ),再平滑会抹平真实域边界
      calibration_uncertainty = 0)     # σ 已含外部重现性,再乘 3% 是重复计入
别忘了:_1s 是 1σ,不是 2σ。拿错那列,MSWD 差 4 倍。 契约的全部细节见《两个工具的联系与数据流》。

07快速开始

仓库里带着一个匿名示例批次(85 个测点),装好环境就能跑通。

# 跑一个批次:Excel(六张表)+ 一份 .handoff.json(给程序读的质控交接)
python -m druid.cli.reduce_batch --dir examples/EX2022A --out 结果.xlsx

# 秒级自检:不需要 pytest,只需要 numpy / pandas
python tests/run_all.py

# 端到端数值回归(约 1 分钟,核对标样 QC 与年龄分布)
python tests/check_example_batch.py
依赖:numpy · pandas · matplotlib · openpyxl · xlrd,刻意不依赖 scipy。 也可以双击「启动数据处理工具」脚本(Windows .bat / macOS .command)用网页界面,全程不需要命令行。 苹果电脑上怎么走,见 第 08 节。
85
示例测点随仓库发布,可复现文档中的每个数字
203
项自检 + 真实批次的端到端数值回归
3
个系统的持续集成:Ubuntu / Windows / macOS

跨平台

08苹果电脑上怎么用

同位素数据处理的软件,大多最后只能在一台 Windows 机器上跑 —— 要么绑着仪器厂商的采集软件, 要么依赖某个只在 Windows 上编译得出来的库。DRUID 不属于这一类:它只读导出的 CSV / Excel 文本, 从不连仪器;算法层是纯 Python(numpy / pandas / matplotlib / openpyxl / xlrd), 没有一处编译扩展,也没有任何"如果是 Windows 就……"的分支 —— 三个系统算出来的是同一套数字。 真正会绊住人的不是算法,是三样容易混淆的东西:先分清你手上拿到的是哪一个。

你手上是WindowsmacOS为什么
源码文件夹 双击 启动数据处理工具.bat 双击 启动数据处理工具.command 两个脚本做同一件事,只是外壳不同。Mac 上走这条,需要先装 Python 与依赖
单文件程序 DRUID.exe 双击即用,不必装 Python ✗ 用不了 它是把 Python 与全部依赖一起冻结出来的 Windows 可执行文件,不是"跨系统的安装包"
Mac 上的单文件程序 — 得在 Mac 本机重新冻结一次 冻结工具(PyInstaller)只能为"它当时所在的那个系统"出货,不能跨系统生成

Mac 上最短的一条路:源码 + .command

# 1) 装环境。在含 druid/ 的那一层(包根目录)执行 —— 包与五个依赖一次装好
python3 -m pip install -e .
#    怎么算成功:末行出现  Successfully installed ...

# 2) 双击「启动数据处理工具.command」。它会自己找解释器、检查依赖,
#    再启动本地网页界面。首次被系统拦下 → 见下面第 1 条。

# 3) 窗口里会打印一个 http://127.0.0.1:端口 的地址 —— 用浏览器打开它。
#    之后全程在网页上点:选文件夹 → 看检查报告 → 开始处理 → 下载结果。
#    要退出,直接关掉那个终端窗口。
[1]

「来自身份不明的开发者」不是脚本坏了

那是 Gatekeeper 在拦没有付费签名的脚本。二选一:右键点它 → 打开 → 再点一次「打开」; 或者在终端里执行 xattr -d com.apple.quarantine 启动数据处理工具.command。 只需做一次,之后照常双击。

[2]

中文字体不用你配 —— 但这件事本来会静默出错

画图库找不到中文字体时不报错、不警告,只是把汉字全画成方块,进程照样返回成功。 所以字体候选里已经列上 Mac 自带的苹方(PingFang SC),Mac 上开箱就是中文图; Linux 上则可能要自己补一次 fonts-noto-cjk。

[3]

跨平台的是计算,不是"一台机器服务全组"

网页界面只监听本机回环地址,Mac 与 Windows 一致:它是给你自己用的界面, 不是局域网里的服务。要多人共用就各自装一份 —— 装好之后运行不需要联网,也不需要任何授权。

"能在 Mac 上跑"不是推断出来的。持续集成里挂着一条 macos-latest 的作业, 在 Apple Silicon 真机上装依赖、跑全部自检,每次改动都会跑一遍 —— 它盯的正是这一节里最容易静默失败的两件事:中文字体真的画出汉字、启动器真的能被执行 (行尾必须是 LF,可执行位不能丢)。逐步说明与常见问题在仓库根目录的 使用说明.txt,三个系统都覆盖到了。

边界

09什么情况下 DRUID 不适用

结果是要写进文章的,边界越清楚越好用。下面四条不是"以后会修"的 待办,而是使用者必须自己判断的事。

[!]

标样与样品计数率相差一个数量级时,二次校正只治标、不治本

主标约 1×10⁵ cps、样品约 1×10⁶ cps 时,单一线性因子消不掉高计数率下的非线性残差。 正式发表前应缩小信号强度差距(小束斑/低能量)或做死时间校正。

[!]

--deadtime-ns 默认为 0,是刻意的

死时间需由标样计数率扫描实验标定,不凭经验填;默认开启会让历史上所有未校正结果不可比。 要用请显式传参。

[!]

窗口重叠让 MSWD 系统性偏小

win=4 s / step=1 s 的滑窗引入相关性,年龄域判据因此偏宽松——方向是漏杀混合窗口, 不会错杀;下游 ADEPT 的坪判定会补上这一层。

[!]

「不分域年龄」表里 43 / 48 个测点判「整段非常数」

不是 bug,是真实结果:整段把倾斜剖面与混合域一起算了进来。这张表按设计只用于横向对比, 能定年的只有那 5 个整段常数的点。