Isoclock 保姆级解读:从零看懂一套 LA-ICP-MS U-Pb 定年流程

面向第一次接触 U-Pb 定年、或者第一次打开这份源码的人 —— 每一节都先讲人话,再指给你看代码在哪一行

图解版 · 56 张图 · 行号对应重构后的 3676 行 Isoclock2.0.py · 图里所有数字由脚本实算,未手写

面向第一次接触 U-Pb 定年、或者第一次打开这份源码的人。

每一段都尽量做到:先说人话讲原理,再指给你看代码在哪一行,最后说这个参数为什么这么设。


写在最前面

这份文档不是官方手册的重写,也不是 API 文档。它的来历是:有人把 Isoclock2.0.py 从头到尾读了一遍,把里面每一处"为什么"整理出来,写成一条从不懂到懂的路径。

所以先说清楚三件事:

  1. 它不能代替原始手册。软件的操作说明在仓库里的两份 PDF 里,本文只做补充和解释。
  2. 行号对应的是本仓库当前版本(重构后的 3676 行 Isoclock2.0.py)。上游原版行号不同,如果你用的是原版,按函数名搜索即可。
  3. 本文对科学逻辑保持原样。代码里有些写法是故意保留的(哪怕看起来别扭),因为它们会影响已经发表的结果。凡是这种地方,我都会明确标注"原样保留",并说明为什么不能顺手改。

阅读建议:

这份文档怎么用:五类读者,五条入口不必从头读到尾 —— 先找到自己属于哪一类。完全的新手第 1 章 → 第 5 章,顺着读先把"在测什么"和"每一步在干嘛"过一遍只想知道"普通铅"是什么直接跳第 3 章全文最核心的一章,讲到透为止只想知道每步在干嘛跳第 4 章6 步流水线,跟着一个样品的数据走想让审稿人接受结果跳第 5 章三问四招 + 一份可以直接抄的清单想知道这软件哪儿不行跳第 6 章数值缺陷 / 方法局限 / 工程局限三条铁律(先说清楚,免得误会)1 它不能代替官方手册 —— 操作说明在仓库里的两份 PDF 里,本文只做补充和解释。2 行号对应本仓库重构后的 3676 行 Isoclock2.0.py;用上游原版请按函数名搜索。3 科学逻辑保持原样 —— 有些看起来别扭的写法是故意保留的,改了会影响已发表的结果。
图 1 这份文档的五条入口。也可以当作一张"我该从哪读起"的自查表 —— 先定位自己,再决定读多少。


第 1 章 先弄懂在测什么:U-Pb 定年的三条腿

这一章只讲最基础的三件事:我们到底在测什么、一个比值凭什么能变成年龄、以及为什么一次测量能给四个年龄。这三件事是后面所有内容的底座。

第 1 章要搭起来的东西:一个可以被解释的年龄四个小节各负责一根柱子,缺一根都站不住。一个可以被解释的年龄1在测什么锆石与低 U/Pb 矿物的比值差异很大,先分清自己属于哪一类。§1.1 §1.32比值凭什么是年龄两条曲线相除,初始量 U₀消失 —— 所以只需要比值,不需要绝对量。§1.23四个年龄互相校验²⁰⁷Pb/²⁰⁶Pb 对铅丢失不敏感,所以它能用来判断协和度。§1.4一句话:一个比值 + 一个常数 = 一个年龄;不同比值给出不同的年龄,差异本身就是判据。
图 2 第 1 章的骨架:三根柱子撑起同一个结论。一个比值加一个常数就是一个年龄,而不同比值给出的年龄之间的差异,就是后面所有判断的依据。

1.1 三个计时器

自然界的铀和钍会慢慢变成铅,这个"变"的速度(衰变常数)是物理常数,不随温度、压力改变。这就是铀铅定年能用的根本原因。

母体子体衰变常数 λ(/年)半衰期
²³⁸U²⁰⁶Pb1.55125 × 10⁻¹⁰4.468 Ga
²³⁵U²⁰⁷Pb9.8485 × 10⁻¹⁰0.704 Ga
²³²Th²⁰⁸Pb4.9475 × 10⁻¹¹14.01 Ga
三条衰变链:一次激光打点,同时开了三个独立的计时器每个计时器给出一个年龄;它们本该一致 —— 不一致的地方就是这门学问的全部内容。母体衰变常数 λ 与半衰期子体能算出的年龄²³⁸Uλ₂₃₈ = 1.55125×10⁻¹⁰ /年半衰期 4.468 Ga²⁰⁶Pb²⁰⁶Pb/²³⁸U 年龄²³⁵Uλ₂₃₅ = 9.8485×10⁻¹⁰ /年半衰期 0.704 Ga²⁰⁷Pb²⁰⁷Pb/²³⁵U 年龄²³²Thλ₂₃₂ = 4.9475×10⁻¹¹ /年半衰期 14.01 Ga²⁰⁸Pb²⁰⁸Pb/²³²Th 年龄把半衰期画成条:三个计时器的"快慢"差了一个数量级²³⁸U4.468 Ga²³⁵U0.704 Ga²³²Th14.010 Ga²³⁵U 半衰期只有 ²³⁸U 的 1/6.3:它衰变快,老样品里 ²⁰⁷Pb 增长更快。两条链速度不同,才能互相校验。
图 3 三条衰变链就是三个独立的计时器。它们本该给出同一个年龄 —— 不一致的地方,才是后面七章要讲的全部内容。

这三个数字在代码里到处都是,但写法有点"分裂",第一次看很容易被绕晕,提前说清楚:

同一个衰变常数,两种单位 —— 看数字就知道代码此刻用的是什么这不是笔误,是"年龄单位"不同导致的。搞混了,年龄会差 100 万倍。0.155125单位:每百万年(/Ma)用在 SK2model(第 116-118 行)0.000000000155125单位:每年(/yr)用在 Age_Calculate_average(第 470 行)× 10⁶× 10⁻⁶同数量级尺(log₁₀)10^-1210^-910^-610^-310.0000000001551250.155125读者须知看到 0.155125 就知道"年龄以 Ma 为单位",看到 1.55125×10⁻¹⁰ 就知道"年龄以年为单位"。两个值永远同时出现在代码的不同模块里。顺带说²³⁸U/²³⁵U = 137.818 也几乎出现在每个比值换算里:因为它俩是同一元素的同位素,你测不出绝对量,只能测比值。
图 4 同一个衰变常数的两种单位写法。看到哪个数,就知道代码此刻把年龄当成 Ma 还是当年。搞混了,年龄会差 100 万倍。

第四个数字是 ²³⁸U/²³⁵U = 137.818(现代值)。它出现在几乎所有比值换算里:因为 ²³⁵U 和 ²³⁸U 是同一个元素的同位素,你测不出它们的绝对量,只能测它们的比值,而这个比值假设是常数。

1.2 年龄公式:为什么长成 ln(r+1)/λ 这个样子

假设矿物结晶那一刻,里面只有母体 U,一点子体 Pb 都没有(后面会看到,这个假设往往是错的,这就是整篇文章的主题)。过了时间 t 之后:

母体剩余:U = U₀ · e^(−λt)
子体生成:Pb* = U₀ · (1 − e^(−λt))

两式相除,U₀ 消掉,得到比值:

Pb*/U = e^(λt) − 1

反解出 t:

t = ln(Pb*/U + 1) / λ

这就是代码里那个反复出现的模式(第 470 行为例):

log(abs(date_all[2][i]*f206_238+1))/(0.000000000155125)/1000000
#   └─ 校正后 206Pb/238U ──┘   └── λ₂₃₈(每年)──┘  └ 换成 Ma ┘

理解了三件事,这一行就通了:

注意 abs()。这个绝对值函数体现了一种"不抛错"的工程态度:如果某个点的比值算出来是负数(信号太差、背景扣过头),数学上取对数会报错,程序直接崩。加了绝对值,程序能跑完,但结果是没有物理意义的垃圾值。这是这套代码的一个通病:宁可给你一个假数字,也不肯中断。后面第 6 章会回到这一点。

年龄公式为什么长成 ln(r+1)/λ —— 四步推到头1t = 0 时U = U₀ Pb* = 0结晶那一刻的假设:晶格里一点铅都没有2t 之后,母体U = U₀ · e^(−λt)母体按指数衰减,λ 就是图的斜率3t 之后,子体Pb* = U₀ · (1 − e^(−λt))生成量 = 初始量 − 剩下的母体4相除(关键)Pb* / U = e^(λt) − 1U₀ 被消掉 —— 所以定年只需要比值5反解出的年龄t = ln(Pb* / U + 1) / λ指数反函数是对数;+1 是把 −1 移过去把两条曲线画出来,就看懂"U₀ 被消掉"是什么意思了00.20.50.8100.51时间 t(任意单位)占 U₀ 的比例U = U₀·e^(−λt)Pb* = U₀·(1 − e^(−λt))两条曲线都带 U₀。相除 → U₀ 消失 →你只需要知道比值,不需要知道 U 的绝对量代码第 470 行 log(abs(date_all[2][i]*f206_238+1))/(0.000000000155125)/1000000 就是最后那一行:+1、取对数、除 λ、换成 Ma。
图 5 年龄公式的四步推导。关键在于第三步:两条曲线都带 U₀,相除之后 U₀ 消失 —— 所以定年只需要比值,不需要绝对量。

同一条公式换了母体就得到另外三个年龄:

206Pb/238U 年龄: log(r+1) / λ₂₃₈        # 第 470 行
207Pb/235U 年龄: log(r+1) / λ₂₃₅        # 第 472 行
208Pb/232Th 年龄: log(r+1) / λ₂₃₂       # 第 474 行

1.3 为什么单次测量能出四个年龄

一次激光打点,你同时得到 ²⁰⁶Pb、²⁰⁷Pb、²⁰⁸Pb、²³²Th、²³⁸U 五个信号,于是能算三对"母-子"关系和一堆比值。理论上,如果这个点从结晶到现在一直是封闭体系:

三者必须一致。 把它们画在一张图上(横轴 ²⁰⁷Pb/²³⁵U,纵轴 ²⁰⁶Pb/²³⁸U),一致的点全部落在一条曲线上 —— 这条曲线叫协和线(concordia)。

这两根轴不能反。这是 Wetherill 图的正式约定(Isoplot 的 "Normal" 图、IsoplotR 的 type=1 都是这个朝向);反过来把 ²³⁸U/²⁰⁶Pb 放在横轴的那个是 Tera-Wasserburg 图,是另一个东西。轴一反,曲线就从"凹"变成"凸",数据落在线的哪一侧也跟着反 —— 这是最容易画错、也最难自己发现的一处。

不一致的两种情况,含义完全不同:

偏离方向常见原因后果
沿一条指向原点的弦排开(落在协和线下方)放射成因铅丢了(后期变质、流体、退晶质化)叫"不协和",需要做铅丢失模型
沿一条近水平的混合线向右漂(落在协和线右下方)混进了普通铅叫"普通铅污染",需要做普通铅校正

这两件事长得像,但处理方式完全相反。第二章会看到,Isoclock 存在的全部理由,就是第二种情况。

协和线:一条把"两个时钟是否一致"画出来的曲线横轴 ²⁰⁷Pb*/²³⁵U、纵轴 ²⁰⁶Pb*/²³⁸U —— Wetherill 图的正式朝向,与 Isoplot / IsoplotR 的 "Normal" 协和图一致。012345600.10.20.30.4²⁰⁷Pb* / ²³⁵U (横轴)²⁰⁶Pb* / ²³⁸U (纵轴)100015001900协和线 concordia刻度:200 / 500 / 1000 / 1500 / 1900 Ma121铅丢失:数据沿一条两端都交于协和线的"弦"排开,表观 ²⁰⁶Pb/²³⁸U 年龄被压小 —— 教科书说的"在协和线下方"指的是这个。2普通铅混入:数据沿一条近水平的混合线向右漂,同样落到协和线下方。两种机制的位移方向完全不同,这才是判别依据(图按真实坐标绘制)。
图 6 协和线把"两个时钟是否一致"画了出来。铅丢失的数据沿一条指向原点的弦排开,普通铅混入的数据沿一条近水平的混合线向右漂 —— 两条线的方向完全不同,这才是判别依据。

1.4 ²⁰⁷Pb/²⁰⁶Pb 年龄为什么特殊

前面三个年龄里,后一个特别值得单独说,因为它在代码里是一个没有解析解的迭代函数。

Age76Pb()(第 122 行)要解决的问题是:已知测到的 ²⁰⁷Pb/²⁰⁶Pb 比值,反推年龄。

而理论关系是:

²⁰⁷Pb*/²⁰⁶Pb = (1/137.818) · (e^(λ₂₃₅·t) − 1) / (e^(λ₂₃₈·t) − 1)

(对应第 130 行的 Rap。)

这个式子里 t 同时出现在分子分母的指数上,没办法用代数解出来,只能数值求解。代码用的是"割线法"(secant method):

while abs(Rap76 - Rap) > 0.00005 and N < 10:   # 第 133 行
    delta = Rap76 - Rap
    if Rap < Rap76:
        Tmin = Tm          # 猜小了,把下界提上来
    else:
        Tmax = Tm          # 猜大了,把上界压下去
    Rapi = (t=Tmin 时算出的比值)     # 第 138 行
    Raps = (t=Tmax 时算出的比值)     # 第 139 行
    Tm = Tmin + (Tmax - Tmin) * (Rap76 - Rapi) / (Raps - Rapi)
    Age76Pb = Tm
    N = N + 1

逻辑本身没问题。但这里有一个真实的缺陷,我必须在第 6 章单独讲:循环条件是 abs(Rap76 - Rap) > 0.00005,而 Rap 是在进入循环之前算好的、循环里从来没有更新过。所以这个条件一旦第一次为真,就永远为真 —— 循环实际固定跑 10 次就退出。

²⁰⁷Pb/²⁰⁶Pb 年龄为什么没有公式解:割线法的十次逼近t 同时出现在分子和分母的指数上,代数上解不出来,只能一遍遍试。0100020003000400000.10.20.3假设的年龄 t(Ma)算出的 ²⁰⁷Pb*/²⁰⁶Pb R(t)↑ 继续上升,超出纵轴(t > 3.75 Ga)实测值 R*真值 2100 Ma代码返回 2135 Ma割线法怎么走① 先猜区间中点 2278 Ma;② 算出此时 R 是偏大还是偏小;③ 据此把区间的一头收进来;④ 在收缩后的区间上画割线,割线与水平线的交点作为下一次猜测。反复十次。区间从 [0.001, 4556] 开始 —— 这是代码里写死的初始范围,横跨地球年龄。好消息:这个迭代对 1 Ga 以下的样品几乎无误差(实测 1000 Ma 处仅 +0.03 Ma)。坏消息在第 6 章。
图 7 ²⁰⁷Pb/²⁰⁶Pb 年龄没有解析解,只能迭代。水平线是实测值,曲线是"假设年龄 → 理论比值"的关系,每次割线交点让猜测更靠近真值。

这不是致命的(10 次割线法对多数年龄已经够收敛),但它让"收敛判据"变成了一句空话。1.5–2.2 Ga 的老年龄会因此偏高最多约 35 Ma(实测在 2.1 Ga 处是 +34.77 Ma)。这一条已经写进仓库 README 的 Known issues,第 6 章有完整的实测数据。

这个细节值得记住:代码里最危险的不是报错,是看起来在收敛其实没有。 如果作者当初把 Rap 的更新放进循环,这个 bug 就不会存在。

那行收敛判据为什么等于没写:一个变量没跟着更新循环条件是 abs(R* − R) > 0.00005,而 R 在进循环之前算了一次,循环体内再没动过。代码实际做的事Rap = R(Tm)while |R* − Rap| > 5e−5 and N < 10:更新 Tmin / TmaxTm = 割线交点N += 1Rap 在这个循环里从未被重新计算→ 条件值恒定 → 要么一次都不跑,要么稳稳跑满 10 次本来想做的事while N < 10:更新 Tmin / TmaxTm = 割线交点Rap = R(Tm) ← 关键一行N += 1收敛到 5e−5 就提前退出→ 迭代次数由精度决定,而不是由常数 10 决定但输出会变 → 必须作者确认结果:迭代次数被写死成 10。对多数年龄够用,但在 1.5–2.2 Ga 区间偏差可达 +34.77 Ma。注意判据里还有 delta = R* − Rap 这个赋值 —— 它算完也没被用。
图 8 左边是代码实际做的事,右边是它本来想做的事。差别只有一行:Rap 有没有在循环里重新计算。这一行决定了"收敛判据"是不是一句空话。

第 2 章 分水岭:为什么 Isoclock 不是给锆石写的

这一章回答你问的最关键的那个问题。

同一批数据,两条完全不同的路线分岔点不是矿物名字,而是 ²⁰⁶Pb/²⁰⁴Pb 有多大。同一批 LA-ICP-MS 数据实测 ²⁰⁶Pb/²⁰⁴Pb 有多大?> 1000 典型锆石处理重点:铅丢失,不是普通铅· 普通铅占比 < 1%,扣也行、不扣也行· 主要工作是处理不协和(铅丢失)· ²⁰⁷Pb/²⁰⁶Pb 当"体检指标"用· 界面选择:Standard not Corr. + None< 200 磷灰石 / 黑钨矿处理重点:普通铅是主要矛盾· 普通铅占比可达 10–90%· 必须在计算第一步就扣掉· 四条路选一,还要用 TW 图自证· ²⁰⁴Pb 通道质量决定选哪条路误判的代价:把锆石的流程套到磷灰石上,会把 3% 的误差当成 0.1% 报出去。
图 9 整份文档的分水岭。分岔点不是矿物名字,而是 ²⁰⁶Pb/²⁰⁴Pb —— 这一个数字决定了你的数据里有多少铅根本不是年龄信息。

2.1 先看锆石过的是什么日子

锆石(ZrSiO₄)在 U-Pb 定年里是"天选之子",原因有两个:

锆石为什么是"天选之子":两个原因,一个结果原因一:晶格排斥铅锆石晶格是为 Zr⁴⁺ 和 Si⁴⁺ 准备的。Pb²⁺ 半径 ≈ 1.19 Å,而 Zr⁴⁺ ≈ 0.72 Å(六配位);电价也是 2+ 对 4+ —— 尺寸和电荷都不对。所以结晶时抓到的"初始铅"极少。原因二:富含铀锆石里的 U 常在几十到几千 ppm 的量级。铀多 → 自己衰变长出来的放射成因铅多。两个原因叠加,结果是下面这个数字。²⁰⁶Pb/²⁰⁴Pb 常常 > 1000也就是说:测到的铅里 99.9% 以上是铀衰变自己长出来的于是锆石的常规流程变成这样:① 一般不做普通铅校正② 主要工作是处理铅丢失③ ²⁰⁷Pb/²⁰⁶Pb 当"体检指标"注意:界面里的 Zircon(91500) 按钮指的是"用 91500 当标样",不是"这块样品是锆石、要走特殊流程" —— 这两件事别混。
图 10 两个原因叠加出一个结果。理解这张图,就理解为什么锆石流程里"不扣普通铅"是一个合理选择,而不是偷懒。

第一,它排斥铅。 锆石的晶格是给 Zr⁴⁺ 和 Si⁴⁺ 准备的,Pb²⁺ 半径太大、电荷不对,几乎进不去。所以锆石结晶时抓到的"初始铅"极少。

第二,它富含铀。 锆石里的 U 常是几十到几千 ppm 的量级。

两个因素叠加,结果就是:锆石的 ²⁰⁶Pb/²⁰⁴Pb 常常大于 1000,也就是说,你测到的铅里,99.9% 以上都是铀衰变自己长出来的,外面混进来的那点普通铅可以忽略。

所以锆石的常规处理流程是:

  1. 不做普通铅校正(或者只在极少数情况下做);
  2. 主要工作是处理铅丢失导致的"不协和";
  3. ²⁰⁷Pb/²⁰⁶Pb 比值在锆石里主要当成一个"体检指标",用来看协和度。

如果你拿锆石数据跑 Isoclock,正确的选择是:计算模式选 "Standard not Corr.",样品普通铅校正选 "None"。界面里那个 Zircon(91500) 按钮指的是用 91500 当标样,不是"这块样品是锆石所以要走特殊流程",这两件事别混。

2.2 换一批矿物,情况就反过来了

Isoclock 面向的是磷灰石、榍石、黑钨矿、锡石这一类矿物。它们的共同特征,恰恰和锆石相反:

锆石磷灰石榍石黑钨矿 / 锡石
典型 U 含量几十–几千 ppm< 50 ppm几十–几百 ppm低,变化大
晶格对 Pb 的排斥极强弱中等弱
²⁰⁶Pb/²⁰⁴Pb常 > 1000常 < 200几十–几百常常很低
普通铅占比< 1%可达 10–90%常 10–50%常很高
主要矛盾铅丢失普通铅普通铅普通铅 + 基体
为什么不是"矿物不同",而是"普通铅占比不同"同一台仪器、同样的测量精度:²⁰⁶Pb/²⁰⁴Pb 越低,测到的铅里"不是这块矿物的"就越多。锆石 915001000榍石300磷灰石 MAD-NEW150黑钨矿 / 锡石45205010020050010002000实测 ²⁰⁶Pb/²⁰⁴Pb(对数轴)普通铅占 ²⁰⁶Pb 的比例< 1%10–50%可达 10–90%常很高同一份 ²⁰⁴Pb 通道的测量误差(假设 5%),传到年龄上差多少01234年龄的相对误差(%)← 曲线向左继续急剧上升:R < 42 时误差已超出纵轴上限锆石榍石磷灰石黑钨矿1000100实测 ²⁰⁶Pb/²⁰⁴Pb
图 11 分水岭不在"矿物种类",而在 ²⁰⁶Pb/²⁰⁴Pb 有多大。这个数字决定了你的数据里有多少铅根本不是这块矿物的年龄信息。

2.3 用"误差传递"把这个困难量化

这是理解整件事最有用的一把尺子。

普通铅的 ²⁰⁶Pb/²⁰⁴Pb 大约是 18.7(第 3 章会给出来历)。假设你的样品实测 ²⁰⁶Pb/²⁰⁴Pb = R,那么普通铅在总 ²⁰⁶Pb 里的占比就是:

f_c = 18.7 / R

而你要做的那个减法,是拿"普通铅的量"去减。这份量本身的相对误差会原封不动地传到放射成因铅上,并且被放大:

放射成因 ²⁰⁶Pb 的相对误差 = σ_204 × f_c / (1 − f_c)
                                    └─ 204 通道的相对误差
样品 ²⁰⁶Pb/²⁰⁴Pb普通铅占比 f_c误差传递因子 f_c/(1−f_c)若 204 通道有 5% 相对误差
1000(典型锆石)1.9%0.02→ 年龄误差 0.10%
2009.3%0.10→ 0.52%
10018.7%0.23→ 1.15%
5037.4%0.60→ 2.99%

两行对照着看,结论就出来了:

这就是为什么低 U/Pb 矿物的数据,误差条天生就比锆石宽。不是测得更差,而是年龄信息被普通铅稀释了。

把"普通铅占比"和"误差被放大多少"画在一起普通铅的 ²⁰⁶Pb/²⁰⁴Pb 约 18.7。样品实测值为 R 时,普通铅占 f = 18.7/R,传递因子就是 f/(1−f)。00.20.40.60.81f 或 f/(1−f)← 红线继续上升,已出框f = 18.7/R 普通铅占比f/(1−f) 误差传递因子锆石 ≥1000榍石 300磷灰石 150黑钨矿 48误差传递因子 f/(1−f):R = 48 时是 0.64 —— ²⁰⁴Pb 通道的相对误差有六成会原样传到年龄上读法:两条曲线在 R 大时都贴近 0(锆石:普通铅无所谓),在 R 小时一起翘起来(低 U/Pb 矿物:普通铅是主要矛盾)。这就是"低 U/Pb 矿物的误差条天生更宽"的全部原因 —— 年龄信息被稀释了,不是测得更差。
图 12 两条曲线:普通铅占比 f = 18.7/R,与误差传递因子 f/(1−f)。R 大时两者都贴近 0,R 小时一起翘起来 —— 这就是"低 U/Pb 矿物的误差条天生更宽"的全部原因。

2.4 一句话总结这个差异

在锆石里,普通铅是一个可以事后再处理的"小麻烦";在低 U/Pb 矿物里,它是一个必须在计算第一步就解决的"主要矛盾"。

从"我测的是什么矿物"到"该选哪条校正路径"这张图对应界面上的两组按钮:左边管标样,右边管样品。别把它们搞混。这批样品的 ²⁰⁶Pb/²⁰⁴Pb 大致多少?(用 Mean_Cps.csv 里 206 与 204 的净信号相除即可)> 200:放射成因铅占绝对优势锆石一类。计算模式选 Standard not Corr.,样品校正选 None —— 不做普通铅校正20–200:普通铅已不可忽略但 ²⁰⁴Pb 通道信号尚可< 20:普通铅占大头²⁰⁴Pb 信号弱,可能测不准优先:204Pb 或 Cal 204Pb最直接、假设最少。但先确认仪器导出时做过 Hg 校正(否则 ²⁰⁴Pb 被高估)优先:207Pb 或 208Pb不依赖 ²⁰⁴Pb 通道,避开它信号弱的软肋;代价是要假设普通铅比值无论选哪条:标样侧的 Standard Corr. / Standard not Corr. 取决于标样本身含不含普通铅MAD-NEW 磷灰石含 → Standard Corr.;91500 锆石不含 → Standard not Corr.最后的保险:三条路径各跑一遍(第 5 章敏感性检验),结果一致才写进论文。
图 13 从"我测的是什么矿物"到"该选哪条校正路径"的决策链。界面上那两组按钮分别管标样和样品,这张图把它们的分工摆在了一起。

这句话解释了 Isoclock 为什么长成现在这样:

2.5 还有一件比普通铅更根本的事

这一点必须说,因为它比技术细节重要:

普通铅校正是"数据整理",不是"地质论证"。

如果你测的矿物在结晶之后经历过后期流体改造、重结晶、或者多期生长的混合,那么它的 U-Pb 体系可能已经被重置或者部分重置。这时候,无论你选哪条校正路径、参数调得多精细,你算出来的"年龄"都可能没有对应的地质事件 —— 它只是一个数学上的平均值。

所以拿到一个漂亮年龄时,先问自己三个问题:

算出一个漂亮年龄之后,先过三道闸普通铅校正是"数据整理",不是"地质论证"。算出一个漂亮的年龄关键不是"过不过",而是每一问的答案都要写进论文。审稿人问的其实就是这三问。1这个点和同一块样品其他点一致吗?不一致 → 可能有二期生长、混合或重置三条都留痕2有独立证据证明它是一期形成的吗?结构观察、共生关系、其他定年方法三条都留痕3换一种校正方法,年龄会变多少?第 5.5 节的敏感性检验就是干这个的三条都留痕可以写进结论 —— 但三条假设都要写在明面上
图 14 普通铅校正只是数据整理。这三道闸决定了算出来的那个数能不能被称作"年龄"。第 5.5 节的敏感性检验,做的就是第三问。
  1. 这个点和同一块样品其他点一致吗?
  2. 这个矿物有独立的证据(结构观察、共生关系、其他定年方法)证明它是一期形成的吗?
  3. 如果我把校正方法换一种,年龄会变多少?

第 5 章会给出具体的操作方法。


第 3 章 普通铅:把这件事彻底说透

这一章是全文最核心的部分。

第 3 章:普通铅,三个问题一次说透这一章是全文最核心的部分 —— 慢一点读没关系。①是什么矿物结晶时从周围环境里"抓"进来的铅 —— 它不含这块矿物的年龄信息。²⁰⁴Pb 是它唯一的指纹,可它偏偏特别难测。§3.1 – §3.3②怎么扣四条路径共用同一个思路:只是"比例尺"不同,解的是同一个混合比例 f。S&K 两阶段模型负责给出普通铅本身长什么样。§3.4 – §3.5③参数从哪来用标样年龄反解出普通铅参数;默认值有具体来历;模型铅只是平均猜测。弄清来历,才知道什么时候不该用默认值。§3.6 – §3.8读完这一章,你应该能回答一句话:我的数据里,到底有多少铅不是"我的矿物"的年龄信息?
图 15 第 3 章的三个问题:它是什么、怎么扣、参数从哪来。读完这一章,你应该能一句话回答"我的数据里有多少铅不是我的矿物的"。

3.1 普通铅到底是什么

矿物结晶的那一刻,它的晶格会从周围环境里"抓"一些铅进去。这些铅的来源五花八门:岩浆本身的、围岩带进来的、流体搬运来的。

关键在于:这些铅的量和这块矿物的年龄没有任何关系。 它们是"混进来"的,不是"长出来"的。

但是质谱仪不知道这件事。它只告诉你:"这个样品里有 ²⁰⁶Pb、²⁰⁷Pb、²⁰⁸Pb、²⁰⁴Pb。"

所以你测到的量永远是两部分之和:

实测 Pb = 放射成因 Pb*(来自本矿物 U/Th 衰变,携带年龄信息)
        + 普通 Pb_c  (结晶时混进来的,不携带年龄信息)

定年的全部工作就是:**想办法估出 Pb_c,从实测值里减掉,只留下 Pb*。**

你测到的铅永远是两部分之和质谱仪不会告诉你哪一部分是"长出来的"、哪一部分是"混进来的"。定年的全部工作就是把这个减法做对。锆石 ²⁰⁶Pb/²⁰⁴Pb ≈ 1000放射成因 Pb* —— 带年龄信息1.9%黑钨矿 ²⁰⁶Pb/²⁰⁴Pb ≈ 48放射成因 Pb* —— 带年龄信息39.0% 普通铅磷灰石 ²⁰⁶Pb/²⁰⁴Pb ≈ 100放射成因 Pb* —— 带年龄信息18.7% 普通铅← 条越短,年龄信息被稀释得越厉害实测的铅²⁰⁶Pb、²⁰⁷Pb、²⁰⁸Pb、²⁰⁴Pb减掉普通铅 Pb_c它不携带年龄信息,必须估出来剩下放射成因 Pb*这才是年龄信息的载体难点只有一个:铅是"混进来"的,你不在场,只能靠同位素比值去推断它有多少。第一段(放射成因)和第三段(普通铅)的可测性完全不同 —— 这就是四条路径的由来。
图 16 你测到的铅永远是两部分之和。条越短(²⁰⁶Pb/²⁰⁴Pb 越低),放射成因那一段占比越小,年龄信息被稀释得越厉害。

3.2 ²⁰⁴Pb:普通铅的指纹

铅有四个稳定同位素。其中三个会从 U 和 Th 变出来:

只有 ²⁰⁴Pb 不是任何一条主要衰变链的产物。(²³²Th 链有一个极稀有的分支会产生极微量 ²⁰⁴Pb,可以忽略。)

这是一个极其有用的性质。它意味着:

你测到的所有 ²⁰⁴Pb 都是普通铅。

所以 ²⁰⁴Pb 的净信号,就是普通铅总量的"指纹"。

有了这个指纹,再看代码里那些比值就很好懂了:

符号含义代码位置
Q8普通铅的 ²⁰⁶Pb/²⁰⁴Pb第 3177 行
R8普通铅的 ²⁰⁷Pb/²⁰⁴Pb第 3178 行
S8普通铅的 ²⁰⁸Pb/²⁰⁴Pb第 3179 行
Pbc普通铅的 ²⁰⁷Pb/²⁰⁶Pb(= R8/Q8)第 3180 行
P382放射成因的 ²⁰⁷Pb/²⁰⁶Pb第 3175 行

这几个变量名看着不友好,但记住 8 代表 208Pb、4 代表 204Pb、6 代表 206Pb、7 代表 207Pb 就能读懂了。Q8 就是 "²⁰⁶/²⁰⁴",R8 是 "²⁰⁷/²⁰⁴",以此类推。

四个铅同位素里,只有一个不参与衰变 —— 这就是全部的突破口²⁰⁴Pb 不是任何一条主要衰变链的产物,所以它只能来自"混进来的普通铅"。²⁰⁶Pb← ²³⁸U天然丰度 24.1%²⁰⁷Pb← ²³⁵U天然丰度 22.1%²⁰⁸Pb← ²³²Th天然丰度 52.4%²⁰⁴Pb没有母体天然丰度 1.4%正因为如此:你测到的 ²⁰⁴Pb 全部都是普通铅 —— 它的净信号就是普通铅总量的"指纹"。但丰度只有 1.4%,还要跟 ²⁰⁴Hg 抢同一个质量数(见下一张图),所以这个指纹经常模糊得看不清。代码里那五个名字,直接看赋值表达式最可靠变量含义代码赋值位置Q8普通铅的 ²⁰⁶Pb/²⁰⁴Pb18.700 − a × 9.735第 3177 行R8普通铅的 ²⁰⁷Pb/²⁰⁴Pb15.628 − b × (9.735/137.818)第 3178 行S8普通铅的 ²⁰⁸Pb/²⁰⁴Pb38.630 − c × 36.837第 3179 行Pbc普通铅的 ²⁰⁷Pb/²⁰⁶PbR8 / Q8第 3180 行P382放射成因的 ²⁰⁷Pb/²⁰⁶Pb(1/137.818) × (b/a)第 3175 行
图 17 四个铅同位素里只有 ²⁰⁴Pb 不参与衰变,所以测到的 ²⁰⁴Pb 全部是普通铅 —— 这是整个方法的突破口。下半部分是代码里那五个变量的含义对照。

3.3 可是 ²⁰⁴Pb 特别难测

如果 ²⁰⁴Pb 这么有用,为什么还需要别的校正方法?因为它有一个致命弱点:

丰度太低。 自然界的铅里,²⁰⁴Pb 只占 1.4%,而 ²⁰⁶Pb 占 24%、²⁰⁸Pb 占 52%。你要在低 U 矿物本来就微弱的信号里,去精确测量那 1.4%。

再加上一个专门的干扰:

²⁰⁴Hg 的干扰。 汞和铅的原子质量都是 204(²⁰⁴Hg vs ²⁰⁴Pb),单靠质量分辨率分不开。而汞在 Ar 等离子体和样品气溶胶里并不少见。所以测"²⁰⁴Pb"的通道,实际上同时接收了 ²⁰⁴Pb 和 ²⁰⁴Hg。

代码试图处理这个问题:loaddata() 在读文件时额外读了一个 202Hg 通道(第 280 行),就是留着用来反推 ²⁰⁴Hg 的量。

但是这段计算实际上没有生效。 在 _net_signal() 里(第 1569-1571 行)有一段注释:

# 204 通道:原实现直接用原始 204 减背景,未做 Hg 干扰扣除
# (源码里那段 Hg204cal_s 恒为 0、m204_s 从未被引用)
'pb204': np.average(sig['Hg204_s']) - np.average(sig['Hg204_b']),

也就是说,软件直接把 204 通道的原始计数当成普通铅的量,没有扣 Hg。如果仪器导出数据时已经做过 Hg 校正,这样是对的;如果没做,那 ²⁰⁴Pb 就是被高估的,所有基于 204 的校正都会偏。这条也在 README 的 Known issues 里。

你需要做的:确认你的质谱仪导出数据时是否已经做了 Hg 校正(Thermo 软件的 "Hg correction" 之类)。这是你自己的数据你自己的责任。

²⁰⁴Pb 通道的三重困难(以及代码实际只解决了什么)1丰度只有 1.4%²⁰⁴Pb1.4%²⁰⁷Pb22.1%²⁰⁶Pb24.1%²⁰⁸Pb52.4%要在本来就微弱的低 U 矿物信号里,去精确测量这 1.4%。2²⁰⁴Hg 干扰m/z²⁰⁴Hg²⁰⁴Pb两者质量数相同,质量分辨率分不开。3信号弱 → 相对误差大01234²⁰⁶Pb/²⁰⁴ 实测值 →纵轴:²⁰⁶Pb* 相对误差(%)代码做了什么、没做什么做了loaddata() 多读了一个 202Hg 通道(第 280 行),本来就是为了反推 ²⁰⁴Hg。202Hg/²⁰⁴Hg 的天然比值约 0.23,思路是对的。没做源码里那段 Hg 校正的表达式是np.average(x) − np.average(x) —— 恒等于 0,而且结果从未被引用。所以软件完全没做 Hg 校正。你要自己去确认仪器导出时是否已做。
图 18 ²⁰⁴Pb 通道的三重困难。右下角"没做"那一栏是重点:源码里那段 Hg 校正的表达式恒等于 0,所以软件实际上完全没有做 Hg 校正。

3.4 四条校正路径:各用什么当"比例尺"

既然 ²⁰⁴Pb 又弱又脏,就得有替代方案。于是有了四条路。

先看一个统一的思路。假设我们想知道"混合进来的普通铅贡献了多少 ²⁰⁶Pb"。如果我们知道普通铅的三个关键比值(Q8、R8、Pbc 等),那么任何一条能反映"普通铅占比"的观测都能反算出这个比例。

四条路的区别,就是"用什么当比例尺":

四条校正路径共用一个思路:把混合物解回"纯放射成因端元"铅同位素比值是两端元的线性混合。只要知道两个端元,实测值落在哪儿就告诉你混合比例 f。0.20.40.60.8实测 ²⁰⁷Pb/²⁰⁶Pb放射成因端元 0.07253f = 0(一点普通铅都没有)普通铅端元 0.90877f = 1(全是普通铅)样品 Af=0.15样品 Bf=0.45样品 Cf=0.78207Pb 路径就是解这个方程:f206 = (n7/n6 − P382) / (Pbc − P382)  然后 r68 = (n6/nU) × (1 − f206)当 f206 = 0(没有普通铅)时,r68 退化成最朴素的 n6/nU —— 校正的全部作用就是把它拉回纯放射成因端元。区别只在于:四条路径用不同的观测去求同一个 f。有的用 ²⁰⁷/²⁰⁶ 的偏离,有的用 ²⁰⁸Pb,有的直接测 ²⁰⁴Pb。
图 19 四条路径共用一个思路:铅同位素比值是两端元的线性混合,实测值落在哪儿就告诉你混合比例 f。区别只在于用哪个观测量去求同一个 f。

路线一:²⁰⁷Pb 法(界面:207Pb)

比例尺:实测 ²⁰⁷Pb/²⁰⁶Pb 比放射成因值高出了多少。

原理:你要量的是 ²⁰⁶Pb 里有多少是普通铅带来的。虽然你测不出 ²⁰⁴Pb,但你知道普通铅的 ²⁰⁷/²⁰⁶ 是多少(Pbc)。而实测的 ²⁰⁷/²⁰⁶ 是普通铅和放射成因铅的加权混合,混合比例直接可以从"实测值偏离放射成因值多少"里解出来。

代码里的实现(_ratios_std207,第 1600 行):

f206 = (n7 / n6 - P382) / (Pbc - P382)          # 第 1604 行:解出普通铅中 206 的占比
r['r76'] = (n7 - n6 * Pbc * f206) / (n6 * (1.0 - f206))
r['r68'] = (n6 / nU) * (1.0 - f206)

f206 就是"²⁰⁶Pb 里普通铅占的比例"(在代码里叫 f206,其实就是文献里的 f₂₀₆)。看出来了吗 —— 当 f206 = 0(没有普通铅)时,r68 就退化成 n6/nU,也就是最朴素的那个比值。整个校正就是"把混合物解回纯放射成因端元"。

优点:不需要测 ²⁰⁴Pb,信号最强的两个通道(206、207)就够了。 代价:你必须知道普通铅的 ²⁰⁷/²⁰⁶ 是多少(下一节讲这个数字从哪来),而且这个假设错了会直接错到年龄上。

路线二:²⁰⁸Pb 法(界面:208Pb)

比例尺:²⁰⁸Pb 与 Th 的关系。

这一路来自 Zack et al. (2011),原本是为金红石设计的。它不用 ²⁰⁴Pb,而是利用 ²⁰⁸Pb 和普通铅的 ²⁰⁶/²⁰⁸、²⁰⁷/²⁰⁸ 比值。

代码在 _ratios_std208(第 1647 行),核心是这一行:

r['r76'] = (pb207_t - pb208_t * Pbc_78) / (pb206_t - pb208_t * Pbc_68)
r['r68'] = (pb206_t - pb208_t * Pbc_68) / net['nU']

其中 Pbc_78、Pbc_68 就是由 SK2model() 算出来的模式铅 ²⁰⁷/²⁰⁸ 和 ²⁰⁶/²⁰⁸(第 119-120 行)。逻辑是"用 ²⁰⁸Pb 当参照物,把属于普通铅的那部分 206、207 减掉"。

优缺点:适合 Th/U 比较稳定的矿物;如果矿物本身 Th 含量很低或者波动很大,²⁰⁸Pb 反而更不可靠。

路线三:²⁰⁴Pb 法(界面:204Pb)

比例尺:直接测的 ²⁰⁴Pb。

最直接、最少假设的一条路:既然 ²⁰⁴Pb 全部是普通铅,那测到多少 ²⁰⁴Pb 就知道有多少普通铅,再乘以 Q8、R8 就知道该扣掉多少 206、207。

代码在 _ratios_std204(第 1629 行):

m74 = (avg(Pb207_s) - avg(Pb207_b)) / net['pb204']    # 第 1639 行:实测 207/204
m64 = (avg(Pb206_s) - avg(Pb206_b)) / net['pb204']    # 第 1640 行:实测 206/204
r['r76'] = ((Pb207_s - avg(Pb207_b) * ((m74 - R8) / m74)) / n6 * ((m64 - Q8) / m64))
r['r68'] = n6 * ((m64 - Q8) / m64) / nU

优点:物理上最直接,假设最少。 代价:²⁰⁴Pb 信号弱(见 3.3),误差大;而且如果 ²⁰⁴Pb 的净信号为零,这里会直接除零崩溃 —— 这是 README 里的 Known issue 第 2 条。代码没有做任何防护。

路线四:标定 ²⁰⁴Pb 法(界面:Cal 204Pb)

比例尺:用标样实测的 204 通道反过来标定。

这一路来自 Chew et al. (2014),是专为"有普通铅的副矿物标样"设计的。它的巧妙之处在于:不追求 ²⁰⁴Pb 的绝对准确,只用它的相对变化。

在 _ratios_std_cal204(第 1613 行)里,先算一个 n383:

n383 = (r76_std * Pb206_s - avg(Pb206_b) * r76_std - Pb207_s + avg(Pb207_b)) \
       / (Q8 * r76_std - R8)

这一大串的意思是:用标样"应该有的"放射成因 ²⁰⁷/²⁰⁶(P382)去反推,标样每个时间点在 204 通道上"多出来"的那部分信号(含普通铅和 Hg)。然后把这个 n383 按 Q8、R8、S8 分配到 206、207、208 上扣掉。

这一路的好处:它让 204 通道的系统性偏差(比如 Hg 干扰、绝对灵敏度标定不准)在"标样 vs 样品"的比较中被抵消掉一大半。这也是为什么 README 里建议"标样含普通铅时选 Standard Corr. + Cal 204Pb"。

四条路的对照表

界面选项代码方法名比例尺关键假设什么时候用
207Pb_METHOD_207²⁰⁷/²⁰⁶ 的偏离知道普通铅 ²⁰⁷/²⁰⁶204 通道信号差,但 206/207 信号好
208Pb_METHOD_208²⁰⁸Pb 与 Th知道普通铅 ²⁰⁶/²⁰⁸、²⁰⁷/²⁰⁸Th 含量稳定的矿物
204Pb_METHOD_204直接测 ²⁰⁴Pb204 通道干净、H 已校正204 信号足够好
Cal 204Pb_METHOD_CAL204标样标定的 204标样与样品基体相近标样本身含普通铅时的首选

注意界面上的 Standard Corr. / Standard not Corr.(第 3375-3376 行)是管标样的,上面这四条路既管标样也管样品 —— 样品那一组由右侧的 None / 207Pb / 204Pb / 208Pb / Cal 204Pb 按钮(第 3409-3413 行)决定。

四条路:区别只在于"拿什么当比例尺"207Pb_METHOD_207什么时候用²⁰⁴Pb 信号差、但 206/207 信号好比例尺实测 ²⁰⁷Pb/²⁰⁶Pb 比放射成因值高出了多少关键假设必须知道普通铅的 ²⁰⁷/²⁰⁶208Pb_METHOD_208什么时候用Th/U 稳定的矿物比例尺²⁰⁸Pb 与 Th 的关系(Zack 2011,原为金红石设计)关键假设必须知道普通铅的 ²⁰⁶/²⁰⁸、²⁰⁷/²⁰⁸204Pb_METHOD_204什么时候用²⁰⁴ 信号足够好比例尺直接测到的 ²⁰⁴Pb 通道关键假设²⁰⁴Pb 干净、且仪器已做 Hg 校正Cal 204Pb_METHOD_CAL204什么时候用标样本身含普通铅时的首选比例尺用标样实测的 204 通道反过来标定(Chew 2014)关键假设标样与样品基体相近界面上还有一组 Standard Corr. / Standard not Corr.(第 3375-3376 行):那管的是"标样侧",和上面四条各管一边,别混。
图 20 四条路各自用什么当比例尺、背后是什么假设、什么时候该用它。选路的主要依据是²⁰⁴Pb 通道的信号好不好。

3.5 那个"普通铅比值"从哪来:Stacey–Kramers 两阶段模型

现在回到最关键的问题:普通铅的 ²⁰⁶/²⁰⁴、²⁰⁷/²⁰⁴、²⁰⁸/²⁰⁴ 是你不知道的,你怎么可能知道?

答案是一个模型。

地球的铅同位素比值不是恒定的 —— 它在几十亿年里一直在被 U 和 Th 衰变"喂养"而升高。Stacey & Kramers (1975) 提出了一个广被接受的两阶段模型来描述这件事:地球的 U/Pb 比值在 3.7 Ga 前后发生过一次跃变,之后按新的比值演化到今天。

代码里有这个模型的全部常数。看 Cal_age()(第 3170 行):

def Cal_age(age):
    a = exp(0.000000000155125 * age * 1000000) - 1      # 206Pb*/238U
    b = exp(0.00000000098485  * age * 1000000) - 1      # 207Pb*/235U
    c = exp(0.000000000049475 * age * 1000000) - 1      # 208Pb*/232Th
    P382 = 1/137.818 * (b/a)                             # 放射成因 207Pb/206Pb
    Q8 = 18.700 - a * 9.735                              # 普通铅 206Pb/204Pb
    R8 = 15.628 - b * (9.735/137.818)                    # 普通铅 207Pb/204Pb
    S8 = 38.630 - c * 36.837                             # 普通铅 208Pb/204Pb
    Pbc = R8 / Q8                                        # 普通铅 207Pb/206Pb

逐个常数解释:

常数含义来历
18.700今天地球铅的 ²⁰⁶Pb/²⁰⁴PbStacey & Kramers (1975) 现代值
15.628今天地球铅的 ²⁰⁷Pb/²⁰⁴Pb同上
38.630今天地球铅的 ²⁰⁸Pb/²⁰⁴Pb同上
9.735μ,即 ²³⁸U/²⁰⁴Pb(第二阶段)同上,约 9.74
36.837μ × κ,即 ²³²Th/²⁰⁴Pb(κ ≈ 3.78 为 ²³²Th/²³⁸U)SK2model 里写作 36.84
11.152 / 12.998 / 31.233.7 Ga 时地球铅的三个比值见 SK2model(第 116-118 行)
3.7两阶段模型的分界时间(Ga)同上

读法:Q8 = 18.700 − a × 9.735 的意思是

"今天地球铅的 ²⁰⁶/²⁰⁴ 是 18.700。把从时间 T 到现在这段里长出来的放射成因 ²⁰⁶Pb(a × μ)减掉,就得到 T 时刻地球铅的 ²⁰⁶/²⁰⁴。"

R8、S8 同理。然后 Pbc = R8/Q8 就给出了"T 时刻模式铅的 ²⁰⁷/²⁰⁶"。

SK2model() 是同一个模型的另一种写法,从 3.7 Ga 的初始值往后加:

Pbc_64 = 11.152 + 9.74 * (exp(0.155125*3.7) - exp(0.155125*age/1000))     # 第 116 行
Pbc_74 = 12.998 + (9.74/137.818) * (exp(0.98485*3.7) - exp(0.98485*age/1000))
Pbc_84 = 31.23 + 36.84 * (exp(0.049475*3.7) - exp(0.049475*age/1000))

两者数学上等价。实测对照(同一 T 下两个函数各自算出的 ²⁰⁶Pb/²⁰⁴Pb):

TCal_age 的 Q8SK2model 的 Pbc_64差异
1 Ma18.698518.70180.003
100 Ma18.547818.55100.003
1000 Ma17.066417.06890.002
3700 Ma11.152611.15200.001

两条曲线全程重合,残余不超过 0.003(约 0.02%,符号在 3 Ga 附近翻转一次),来自 μ 与 κ 在两处取了不同的舍入位数。它们说的是同一件事。

Stacey–Kramers 两阶段模型:普通铅的比值不是常数,它随时间变地球的铅被 U 和 Th 一路"喂养"着长大。所以"普通铅长什么样"取决于它是哪个年代的。00.51233.74.579121518时间(Ga,从现在往回数)²⁰⁶Pb/²⁰⁴Pb3.7 Ga:两阶段的分界今天 18.700483 Ma 17.94100 Ma 18.553.7 Ga 11.152第二阶段(代码只用这一段)第一阶段代码里两套写法,说的是同一件事Cal_age(age)从今天的 18.700 往回减:Q8 = 18.700 − a×9.735SK2model(age)从 3.7 Ga 的 11.152 往前加:Pbc_64 = 11.152 + …
图 21 Stacey–Kramers 两阶段模型:普通铅的比值不是常数,它随地球的 U/Pb 演化而变。所以"普通铅长什么样"取决于它是哪个年代的。

(顺带一提:Cal_age 用 36.837、SK2model 用 36.84,Cal_age 用 9.735、SK2model 用 9.74 —— 这是从原始文献不同舍入位数抄来的,差异小于 0.01%,可以忽略,但它确实说明这些常数是手工录入的,将来换模型或换引用时必须逐处核对。)

两个函数到底是不是一回事:把差异放大 1000 倍去看01233.71113151719²⁰⁶Pb/²⁰⁴PbCal_age 的 Q8(粗线)SK2model 的 Pbc_64(虚线)两条线完全重合 —— 粗线把虚线盖住了01233.7-0.00100.0020.004时间(Ga,往回数)差值0今天:+0.00333.7 Ga:-0.0006全程不超过 0.003,占 18.7 的 0.02%;符号在 3 Ga 附近翻转一次这点残差来自舍入:Cal_age 里 μ 取 9.735、κ 取 36.837,SK2model 里取 9.74 和 36.84。绝对值可以忽略;但它说明这些常数是手工录入的 —— 换模型或换文献时必须逐处核对。
图 22 把两个函数的差值放大来看:从今天的 +0.0033 缓慢变到 3.7 Ga 的 −0.0006,全程不超过 0.02%,符号在 3 Ga 附近翻转一次。残差来自 μ 与 κ 的舍入位数不同 —— 这说明这些常数是手工录入的。

3.6 为什么用"标样年龄"来算这些参数

这是初学者最容易卡住的一点。

看 skmethod()(第 3188 行,界面选 "S & K" 时触发):

Standard_age = tk.simpledialog.askfloat(..., initialvalue='485')   # 问你标样年龄
Standard_name = tk.simpledialog.askstring(..., initialvalue='MAD')
R8, Q8, S8, P382, a, b, c, Pbc = Cal_age(Standard_age)              # 用标样年龄算参数

为什么是标样年龄,不是样品年龄?

因为标样里的普通铅,是标样结晶的那个时刻混进去的。MAD-NEW 磷灰石标样结晶于约 483 Ma,它当时抓到的地球铅就是 483 Ma 前的地球铅 —— 其 ²⁰⁶/²⁰⁴ 是 18.700 − (e^(λ·0.483Ga) − 1) × 9.735 ≈ 17.94,而不是今天的 18.700。

所以:标样的普通铅参数要按标样年龄算。

而样品的普通铅参数是另一套,由 PbCorrSamples()(第 2883 行)在界面上单独问你 —— 要么直接输入同位素比值,要么输入"样品的估计年龄"让程序用 S&K 模型算。

这就回答了"为什么那么设置":普通铅的 ²⁰⁷/²⁰⁶ 不是常数,它随时间变化。你必须告诉软件"这块矿物的普通铅大概是哪个年代的",或者"它长什么样"。

为什么"普通铅比值"要用标样的年龄去算标样里的普通铅是标样结晶那一刻混进去的 —— 它"冻住"在当年的地球铅组成上,不是今天的。00.51233.71113151719²⁰⁶Pb/²⁰⁴Pb483 Ma²⁰⁶/²⁰⁴ = 17.94²⁰⁷/²⁰⁶ = 0.8686100 Ma²⁰⁶/²⁰⁴ = 18.55²⁰⁷/²⁰⁶ = 0.8422两种做法差多少(把 age 代进 Cal_age 实算)按标样年龄 483 Ma(MAD-NEW)²⁰⁶Pb/²⁰⁴Pb = 17.9426²⁰⁷Pb/²⁰⁴Pb = 15.5850²⁰⁷Pb/²⁰⁶Pb = 0.868603按界面默认 100 Ma²⁰⁶Pb/²⁰⁴Pb = 18.5478²⁰⁷Pb/²⁰⁴Pb = 15.6207²⁰⁷Pb/²⁰⁶Pb = 0.842185
图 23 为什么普通铅参数要按标样年龄算:标样里的普通铅"冻住"在它结晶那一刻的地球铅组成上。483 Ma 与 100 Ma 对应的数值差得不少。

3.7 那些默认值是怎么来的(这一段能解答很多疑惑)

PbCorrSamples() 里每个分支都给了默认值。逐个验证一下,你会发现它们不是随便写的:

207Pb 法(第 2900、2905-2906 行)

initialvalue = '100'          # 样品估计年龄,默认 100 Ma
initialvalue = '0.048015'     # 放射成因 207Pb/206Pb
initialvalue = '0.842185'     # 初始(普通)207Pb/206Pb

把 age = 100 Ma 代进第 2902-2903 行:

所以那两个"神仙数字"就是 100 Ma 时的 S&K 模式铅值。 手动输入 0.048015 和 0.842185,等价于告诉程序"我的样品普通铅是 100 Ma 的模式铅"。

(0.842185 与精确值逐位吻合;0.048015 与精确值 0.048036 差 0.04%,这是原作者手工取整留下的痕迹。这类"看起来随意、其实有出处"的默认值,照抄之前一定要先反算一遍它对应什么假设。)

204Pb 法(第 2926-2930 行)

initialvalue = '15.6207'   # 初始 207Pb/204Pb
initialvalue = '18.5478'   # 初始 206Pb/204Pb

同样代 age = 100 Ma:

完全对上。

208Pb 法(第 2914-2919 行)

initialvalue = '0.48240'   # 初始 206Pb/208Pb
initialvalue = '0.40628'   # 初始 207Pb/208Pb

(其中 38.447 = 38.630 − (e^(0.0049475)−1) × 36.837,也是 100 Ma 的值。)

三个分支的默认值全部指向同一个年龄:100 Ma。 理解了这一点,你就知道这些数字该怎么改了 —— 它们应该对应你样品的真实估计年龄,而不是照抄默认值。

界面里那些"神仙默认值",反算之后全部指向同一个年龄把默认值当成精确值反解,得到的是同一个数:100 Ma。这不是巧合,是原作者按 100 Ma 的模式铅算好的。207Pb 法放射成因 ²⁰⁷Pb/²⁰⁶Pb = 0.048015初始 ²⁰⁷Pb/²⁰⁶Pb = 0.842185(精确值应为 0.048036,差 0.04%,是手工取整的痕迹)204Pb 法初始 ²⁰⁷Pb/²⁰⁴Pb = 15.6207初始 ²⁰⁶Pb/²⁰⁴Pb = 18.5478(与 Cal_age(100) 逐位吻合:15.6207 / 18.5478)208Pb 法初始 ²⁰⁶Pb/²⁰⁸Pb = 0.48240初始 ²⁰⁷Pb/²⁰⁸Pb = 0.40628(由 18.54781 / 38.447 与 15.62069 / 38.447 得到)age = 100 Ma三个分支的默认值说的是同一个假设样品普通铅 = 100 Ma 的模式铅结论:这些数字不是"通用真理",而是一个假设。照抄之前,先把它反算成年龄 —— 再换成你自己样品的估计年龄。
图 24 把界面里的默认值当成精确值反算,三个分支得到同一个答案:100 Ma 的模式铅。这不是巧合,是原作者算好写进去的。

3.8 必须承认的事:模型铅只是"平均猜测"

上面整套东西有一个隐含前提:你的样品的普通铅,长得像那个年代的地球平均铅。

这个前提经常不成立:

模型铅给的是"统计意义上最可能的值",不是"你这块样品的真实值"。

所以有一条原则要记住:

能实测的,就不要假设。

具体来说:

  1. 如果你的 ²⁰⁴Pb 信号足够好,优先用 204 或 Cal 204Pb 路线,因为它是"实测"的;
  2. 如果研究区有已知的普通铅同位素组成(比如同一矿区的方铅矿数据),直接输入那些比值,比用 S&K 模型强得多;
  3. 最重要的一条:用 Tera-Wasserburg 图(第 5 章)让数据自己说出普通铅组成,而不是由你规定。这是唯一能让审稿人真正放心的做法。
模型铅只是"统计上最可能的值",不是你这块样品的真值S&K 给的是"那个年代的地球平均铅"。下面三种情况下,这个前提会明显不成立。地壳重熔的岩浆普通铅明显富 ²⁰⁷Pb、²⁰⁸Pb地壳的 U/Pb、Th/Pb 更高周围有硫化物方铅矿等混入的铅极端富放射成因矿区尺度常见经历多期流体普通铅本身就是一个混合物没有单一端元可假设能实测的,就不要假设。因为普通铅校正是"数据整理",不是"地质论证" —— 假设错了,年龄就错,而且看不出来。按可信度从高到低,三个选择①²⁰⁴Pb 信号足够好 → 用 204Pb 或 Cal 204Pb因为它是"实测"的,不是假设的②研究区有已知的普通铅组成(如同一矿区方铅矿数据)→ 直接输入那些比值比套 S&K 模型强得多③用 Tera-Wasserburg 图让数据自己说出普通铅组成(第 5 章)唯一能让审稿人真正放心的做法
图 25 模型铅只是"统计上最可能的值"。三种情况下这个假设会明显不成立,以及按可信度排序的三个替代选择。

第 4 章 从原始文件到年龄:每一步在干什么

这一章把整条流水线拆成 6 步,跟着数据走一遍:从仪器导出的原始文件,到最终输出表里的每一列。

第 4 章导览:6 步流水线加一次输出,与节号一一对应跟着一个样品的数据走一遍 —— 每一步都能在磁盘上找到对应文件。输入:三种仪器的原始文件第 0 步0导出 8 通道§4.2第 1 步1读文件loaddata()§4.3第 2 步2划分积分窗§4.4第 3 步3背景扣除dataprocess()§4.5第 4 步4分馏校正SSB§4.6第 5 步5算年龄Age76Pb()§4.7输出6结果表xls / csv§4.8输出:xls 结果表 + Mean_Cps.csv排查问题的抓手:先看磁盘上的中间结果,再回头找是哪一步的输入不对。第 4.1 节那张流程图给的是数据流;这张图给的是"哪一节管哪个环节"。
图 26 第 4 章的节次索引:哪个小节管哪个环节、每步产出什么文件。排查问题时,先看磁盘上的中间结果,再回头找是哪一步的输入不对。

4.1 全局视野

界面上的九个步骤(README 里的 (1)–(9))对应的就是这条链。下面逐步展开。

从原始文件到年龄:一条链上的八个环节界面上那九个步骤(README 的 (1)–(9))对应的就是这条链。左边是"数据",右边是"解释"。原始质谱文件Thermo / Agilent / Element 三种格式第 (1)–(4) 步loaddata()读出固定的 8 个通道,多一个少一个都报错第 (5) 步积分窗口背景段 + 信号段,存在 Time_setting.npy第 (6) 步dataprocess()背景扣除 + 2σ 过滤 + 算六个比值第 (8) 步result_all.csv / Mean_Cps.csv23 列比值 + 16 个通道均值第 (8) 步Age_Calculate_average()整批标样求一个因子 → A*.xlsAge_Calculate()相邻标样分段求因子 → L*.xls
图 27 从原始文件到年龄的完整链条。左边是"数据",右边是"解释" —— 最后一步分叉成 Average 与 Liner 两条出口。

4.2 第 0 步:仪器必须导出哪 8 个通道

loaddata()(第 273 行)返回固定的 8 个通道:

序号通道用途
0Time时间轴,用于把秒换算成数组下标
1202Hg监控 Hg 干扰(注意:这段计算目前没生效,见 3.3)
2204Pb普通铅指纹(实际是"204 通道",含 Hg)
3206Pb主要年龄信息
4207Pb主要年龄信息
5208PbTh-Pb 体系
6232Th208Pb/232Th
7238U分母

少任何一个都会报错。这是 README 里"读这个再开始"四条注意事项的第 4 条。

为什么必须齐全:因为不同校正路径要用不同的通道组合。哪怕你打算用 207Pb 法(不用 204),程序在设计上也要求 204 通道存在 —— 这是软件的强制项,不是科学上的必需。

仪器必须导出这八个通道 —— 少一个程序就罢工注意:哪怕你打算走 207Pb 法(根本不用 204),程序在设计上仍强制要求 204 通道存在。202204206207208232238质量数 m/z(横轴不是等比例,只示意位置)²⁰²Hg监 Hg²⁰⁴Pb普通铅指纹²⁰⁶Pb + ²⁰⁷Pb主要年龄信息²⁰⁸PbTh-Pb 体系²³²Th208/232²³⁸U分母²⁰⁴ 通道是个特例:它同时接收 ²⁰⁴Pb 和 ²⁰⁴Hg(质量数相同,分不开),而代码并没有做 Hg 校正。所以你测到的 204 到底是"铅"还是"铅 + 汞",取决于仪器导出时有没有做过校正 —— 这是你的责任,不是软件的责任。还有一个时间轴Time 通道(序号 0)是第 8 个通道:它把"秒"换算成数组下标。激光剥蚀是时间过程,没有它就没法划积分窗口。这八个通道的名字在代码里是固定的:Time / 202Hg / 204Pb / 206Pb / 207Pb / 208Pb / 232Th / 238U(第 273 行)。
图 28 八个通道各自的位置与用途。²⁰⁴ 通道要特别注意:它同时接收 ²⁰⁴Pb 和 ²⁰⁴Hg。

4.3 第 1 步:读文件(loaddata)

三种仪器的导出格式不一样,代码用三个分支处理(第 275 行起的 mode = ele.get()):

界面选项mode处理方式
Element0跳过 13 行,列名硬编码为 Time, 202Hg, 204Pb, ...
Agilent1先试跳 3 行,失败再试跳 2 行;列名按"纯数字"匹配(204Pb 匹配 204)
Thermo2跳过 7 行,列名按原名匹配

这一段经过一次重构,从"把整个文件读成字符串矩阵再逐列转换"改成"先读表头定位列,再用 np.loadtxt(usecols=...) 只读需要的那 8 列并直接按 float 解析"。对 3250 行的 Agilent 文件,从 39 ms 降到 9.5 ms(4.1 倍)。

这不是科学改动,是纯工程改动:输入输出仍然逐位相同(24 个数组、0 处不一致)。

新手最容易踩的坑:输入目录里不能有别的东西。 程序会把目录里所有文件都当数据文件处理,多一个无关的 CSV 就报错。

读文件:三种仪器格式,三个分支,外加一次 4 倍提速第 275 行起按界面选项分流。这一段是纯工程改动 —— 输入输出逐位相同,只是快了很多。Elementmode = 0跳过 13 行列名硬编码为Time, 202Hg, 204Pb, …Agilentmode = 1先试跳 3 行,失败再试 2 行列名按"纯数字"匹配即 204Pb 匹配 204Thermomode = 2跳过 7 行列名按原名匹配(网页端目前只支持这一种)提速:同样的 Agilent 文件(3250 行),读入快 4.1 倍改法:不再"把整个文件读成字符串矩阵再逐列转换",而是先读表头定位列,再用 np.loadtxt(usecols=…) 只读需要的 8 列。改之前39.0 ms改之后9.5 ms新手最容易踩的坑:输入目录里不能有别的东西。程序会把目录里所有文件都当数据文件处理,多一个无关 CSV 就报错。
图 29 三种仪器格式的三个分支,以及这次重构带来的 4.1 倍提速。这一段是纯工程改动 —— 输入输出逐位相同。

4.4 第 2 步:划分积分窗口

激光剥蚀是一个时间过程:先开激光打样品(信号段),然后关激光等背景(背景段),或者反过来。

界面上选中一个样品会画出信号曲线(showplt,第 1761 行),然后你框选两段:

为什么要扣背景:因为质谱仪测到的是"样品信号 + 环境底色"。你要的是样品那部分。

积分窗口存在 Time_setting.npy 里,dataprocess() 开头(第 1409-1417 行)有一个自检:

if numn[name][3] - numn[name][2] < 0 or numn[name][4] - numn[name][2] < 0:
    wrong_samples.append(name)          # 窗口越界 → 记入错误列表
elif numn[name][3] + 1 > numn[name][4] - 1:
    numn[name][3] = numn[name][4]       # 首尾太近 → 强行对齐
    numn[name][4] = numn[name][3]

这是一个"带病运行"的设计:窗口画错了,程序不中断,而是把一个明显不合理的窗口(起点=终点)硬塞进去算,最后给你一个垃圾结果。所以一定要看 wrong_samples 相关的日志信息。

积分窗口:激光剥蚀是一个时间过程,你得告诉程序哪一段是信号界面上选中样品会画出信号曲线(showplt,第 1761 行),然后你框选背景段和信号段。015304560759010010001e4时间(秒)cps(对数轴)背景段4–26 s信号段32–86 s²⁰⁶Pb背景(载气 + 等离子体)注意看信号段的形状:它不是平的,会随剥蚀坑变深而缓慢上升 —— 这就是 downhole 分馏的来源。窗口画错了会怎样:dataprocess() 开头(第 1409–1417 行)有个自检,但它不中断程序。如果起点终点太近,它会把窗口强行改成"起点 = 终点",然后给你一个垃圾结果,只在日志里记一笔 wrong_samples。窗口不好会污染平均值,是下游所有误差的来源之一。选窗口时优先看 ²⁰⁶Pb 与 ²³⁸U 两条曲线的平台段。窗口存在 Time_setting.npy 里(用的是 pickle),换一批数据要重新选。
图 30 激光剥蚀的信号曲线与积分窗口。注意信号段并不平 —— 它随剥蚀坑变深缓慢上升,这就是 downhole 分馏的来源。

4.5 第 3 步:背景扣除与比值(dataprocess + reduce_sample)

背景扣除

_net_signal()(第 1561 行):

'n6': Pb206_s - np.average(Pb206_b),     # 净 206Pb 信号
'n7': Pb207_s - np.average(Pb207_b),
'n8': Pb208_s - np.average(Pb208_b),
'n2': Th232_s - np.average(Th232_b),
'nU': U238_s - np.average(U238_b),
'pb204': np.average(Hg204_s) - np.average(Hg204_b),   # 204 通道

注意一个细节:前四个是"逐点数组减一个标量"(信号段每个时间点减去背景段均值),而 pb204 是"先各自求平均再相减"(得到一个标量)。这个不一致是从原始代码继承下来的,后面会带来麻烦(第 4.7 节的 r84)。

六个比值

_ratios_sample()(第 1588 行,不校正的基准版本):

比值表达式输出列
r76n7 / n6²⁰⁷Pb/²⁰⁶Pb
r68n6 / nU²⁰⁶Pb/²³⁸U
r82n8 / n2²⁰⁸Pb/²³²Th
r86n8 / n6²⁰⁸Pb/²⁰⁶Pb
r26Th232_s / Pb206_s²³²Th/²⁰⁶Pb(注意用的是原始信号,不是净信号)
r84n8 / pb204²⁰⁸Pb/²⁰⁴Pb(分母是标量)

r26 和 r84 这两处的"不走寻常路"是原样保留的。改造时必须照抄,否则结果会变。

2σ 离群过滤

激光剥蚀的信号常有几个时间点被包裹体、裂纹、或者等离子体抖动污染。代码用最朴素的方法剔除(_filter_2s,第 1525 行):

return np.where(np.abs(x - np.average(x)) < 2.0 * np.std(x), x, nan)

把偏离均值超过 2 倍标准差的时间点替换成 nan(注意:不是删掉,是标记)。

这里有一个只有做过数值对照才会知道的细节:np.nanmean / np.nanstd 在 NumPy 里是把 nan 替换成 0 之后在整个原长数组上求和,而不是"压缩掉无效元素再求"。两种做法的浮点求和顺序不同,末位会漂。代码忠实复刻了 NumPy 的行为(第 1537 行、第 1533-1535 行的注释),所以不要"顺手优化"成 x[~isnan].mean() —— 那会让结果与原来不一致。

背景扣除的两个细节,和一个"看起来优化了其实会改数"的坑_net_signal() 第 1561 行:信号段每个点减去背景段均值。01020304050600400080001e4信号段内的逐秒点净信号(cps)均值±2σ→ nan→ nan2σ 过滤(_filter_2s,第 1525 行):把偏离均值超过 2σ 的点替换成 nan —— 注意是标记,不是删掉。千万别"顺手优化"成 x[~isnan].mean()NumPy 的 np.nanmean / np.nanstd 是把 nan 替换成 0 之后在整个原长数组上求和,而不是压缩掉无效元素再求。两种写法的浮点求和顺序不同,末位会漂。重构时这一点必须照抄,否则结果与原版不一致。数组式(逐点减标量)n6 = Pb206_s − average(Pb206_b)标量式(先各自平均再相减)pb204 = average(Hg204_s) − average(Hg204_b) ← 注意这不一致
图 31 2σ 过滤把超出范围的点标记成 nan(不是删掉)。右下角那个"标量式"是个容易忽略的不一致:pb204 是先各自求平均再相减,其余四个通道是逐点减标量。

输出两个文件

4.6 第 4 步:分馏与基体效应校正(SSB)

为什么需要这一步

激光剥蚀出来的物质,轻的同位素比重同位素更容易被带走(分馏)。而且随着剥蚀坑越打越深,坑底的成分会变,分馏也会变(这叫 downhole fractionation)。

结果就是:你测到的 ²⁰⁶Pb/²³⁸U 不等于样品真实的 ²⁰⁶Pb/²³⁸U,它有一个系统性的偏移。

怎么办:Sample-Standard-Bracketing

思路很朴素:

我有一块年龄已知的标样。用同样的仪器、同样的激光条件去打它,测出来的比值和它的真实比值之间的差距,就代表了"这台仪器现在的偏差"。把这个偏差按比例应用到样品上。

代码里就是一行(factor,第 384 行):

def factor(stand_v, i):
    return stand_v / date_all[i][standard].mean()

然后(第 441-448 行):

result_cal_207Pb_206Pb = date_all[0] * f207_206      # 注意:这是数组整体相乘
result_cal_206Pb_238U  = date_all[2] * f206_238
result_cal_207Pb_235U  = date_all[4] * f207_235
result_cal_208Pb_232Th = date_all[6] * f208_232

这就是核心:样品实测值 × f = 校正后真值。

Average 与 Liner 的区别

Average(Age_Calculate_average)Liner(Age_Calculate)
分馏因子来源整批标样求一个平均值相邻两个标样之间分段求值
代码第 418-430 行第 1024 行的 factor(SEP, ...)
能否校正时间漂移不能能(分段近似)
适用仪器很稳定、批次短批次长、仪器有漂移

Liner 的实现思路(第 961-992 行):把标样的位置序列切成段,每一段标样"夹住"一组样品,这组样品就用这段标样的平均因子。

注意:Liner 是分段,不是插值,更不是回归。它把两个标样之间的所有样品当成同一段处理,中间不做时间趋势外推。

分馏校正:用已知年龄的标样去"称"出仪器的偏差factor = 标样理论比值 ÷ 标样实测比值。然后:样品实测值 × f = 校正后的真值。一批数据的顺序(S = 标样,s = 样品)SsssSssssSssSAverage 做法:所有标样求一个平均因子整批一个 f≈ 平均Liner 做法:相邻标样之间分段,每段一个因子第 1 段一个 f第 2 段一个 f第 3 段一个 fLiner 只是"分段",不是插值、也不是回归:两个标样之间隔 10 个样品,这 10 个样品用的是同一个因子,中间的时间趋势被完全忽略。Average代码:第 418–430 行分馏因子:整批标样一个平均值能否校正时间漂移:不能Liner代码:第 1024 行 factor(SEP, …)分馏因子:分段各一个能否校正时间漂移:能(分段近似)代码里其实写了 4 种漂移回归模型(regression(),第 152 行,用 scipy.curve_fit),但从未被调用 —— 属于"写了没接线"。
图 32 Sample-Standard-Bracketing 的实际形态。Average 用整批标样求一个因子,Liner 分段各求一个 —— 所以 Liner 能近似校正时间漂移,Average 不能。

代码里其实写了 4 种回归模型(regression(),第 152 行:线性、对数、开方、幂函数,用 scipy.curve_fit 拟合),但这个函数从未被调用 —— 全局变量 regression_method 只在 main() 里被赋值(第 2998 行 regression_method = 1),界面上没有任何控件能改它。这是一个"写了但没接线"的功能,不是死代码,第 6 章会讨论怎么把它接上。

4.7 第 5 步:算年龄

四个年龄

(Age_Calculate_average,第 470-476 行)

输出列公式代码
²⁰⁶Pb/²³⁸U 年龄ln(r68_corr + 1) / λ₂₃₈第 470 行
²⁰⁷Pb/²³⁵U 年龄ln(r75_corr + 1) / λ₂₃₅第 472 行
²⁰⁸Pb/²³²Th 年龄ln(r82_corr + 1) / λ₂₃₂第 474 行
²⁰⁷Pb/²⁰⁶Pb 年龄迭代求解(Age76Pb)第 476 行

误差传播与那个 ρ

这一步值得仔细看,因为它决定了你报告的误差有没有意义。

第 455-461 行:

w = sqrt((2s_r68 / r68)² + excess_V²) * 100       # 206/238 的相对误差(%)
s = sqrt((2s_r75 / r75)² + excess_V²) * 100       # 207/235 的相对误差(%)
o = sqrt((2s_r76 / r76)² + 0.0001) * 100          # 207/206 的相对误差(%)
if abs((w² + s² − o²) / (2ws)) < w/s:
    rho = abs((w² + s² − o²) / (2ws))
else:
    rho = w/s

三件事:

第一,excess_V 是"额外误差"。 它是界面上的一个输入框(第 3533 行),默认值 3(第 3516 行 var5.set(3)),单位是百分比。它代表"测量本身之外的不确定性" —— 标样的长期重现性、基体差异、仪器日间波动等等。

这是整个软件里最需要你自己判断的一个数字。 默认 3% 是一个偏保守的通用值。如果你这批数据的标样重现性只有 1%,那么用 3% 会高估误差;如果标样重现性有 5%,用 3% 就是低估,审稿人一眼就能看出来。

第二,²⁰⁷Pb/²⁰⁶Pb 那一项用的是硬编码的 0.0001(等于 1% 的平方),不是 excess_V²。这是原始代码里的不一致,属于"原样保留"。实际影响:不管你把 excess_V 设成多少,207/206 的额外误差永远按 1% 算。

第三,rho 是 ²⁰⁶/²³⁸ 与 ²⁰⁷/²³⁵ 两个误差的相关系数。 为什么需要它?因为:

²⁰⁷Pb/²⁰⁶Pb ≈ (²⁰⁷Pb/²³⁵U) / (²⁰⁶Pb/²³⁸U) × (常数)

三个比值不是独立的,它们的误差满足 o² ≈ w² + s² − 2ρws。反解就得到代码里那个式子。这个 ρ 必须输出给画协和图的软件(IsoplotR 等),否则在图上画误差椭圆时会把两个不独立的误差当成独立的,椭圆画错,年龄也跟着错。

输出表里第 40 列就是 rho(第 879 行),这个设计是对的。

误差怎么合成,以及那个 ρ 为什么必须输出第 455–461 行:三行代码决定你报出去的误差有没有意义。w = sqrt((2s_r68 / r68)² + excess_V²) × 100s = sqrt((2s_r75 / r75)² + excess_V²) × 100o = sqrt((2s_r76 / r76)² + 0.0001) × 100rho = |(w² + s² − o²) / (2ws)|(当它小于 w/s 时;否则取 rho = w/s)w → ²⁰⁶Pb/²³⁸U 的相对误差(%)三个比值不独立²⁰⁷Pb/²⁰⁶Pb ≈ (²⁰⁷Pb/²³⁵U) / (²⁰⁶Pb/²³⁸U) × 常数所以它们的误差满足o² ≈ w² + s² − 2ρws反解就得到代码里那个 ρ。不输出 ρ,IsoplotR 会把两个不独立的误差当独立的处理。有一处不一致被原样保留了:²⁰⁷Pb/²⁰⁶Pb 那一项用的是硬编码的 0.0001(= 1% 的平方),不是 excess_V²。实际影响:不管你界面里把 excess_V 设成多少,²⁰⁷/²⁰⁶ 的额外误差永远按 1% 算。excess_V 是整个软件里最需要你自己判断的一个数字界面输入框在第 3533 行,默认值 3(第 3516 行 var5.set(3)),单位是百分比。计数统计误差信号越强、积分越久越小(内部误差)标样重现性同一实验室的长期 2SD(外部误差)基体差异标样与样品的剥蚀行为差异(外部误差)仪器日间漂移不同批次之间的系统差(外部误差)
图 33 三行代码决定你报出去的误差有没有意义。右下角那个 ρ 必须输出,否则在协同图上画误差椭圆时会把两个不独立的误差当成独立的。

4.8 输出文件清单

文件内容什么时候产生
result_all.csv每样品 23 列比值与计数第 (8) 步 Fractionation Correction
Mean_Cps.csv每样品 16 个通道均值同上
cal_age_result_A*.xls年龄表(Average)第 (9) 步 Export (Average)
cal_age_result_L*.xls年龄表(Liner)第 (9) 步 Export (Liner)
信号图每个样品选中时实时生成第 (7) 步

年龄表(xls)的列很密,值得说明(Age_Calculate_average 第 806-888 行定义):

列号内容
0-1文件名、样品名
2-15六个比值 + 各自的 2s
17-18²⁰⁶Pb/²³⁸U 年龄 + 2s
19-20²⁰⁷Pb/²³⁵U 年龄 + 2s
21-22²⁰⁸Pb/²³²Th 年龄 + 2s
23-24²⁰⁷Pb/²⁰⁶Pb 年龄 + 2s
25-29校正后年龄(按所选普通铅路径)
31-34Tera-Wasserburg 图用:²³⁸U/²⁰⁶Pb + 2s、²⁰⁷Pb/²⁰⁶Pb + 2s
36-40作图用:²⁰⁷Pb/²³⁵U、²⁰⁶Pb/²³⁸U、各自的 2s、ρ
42-45²⁰⁸Pb/²³²Th、²⁰⁸Pb/²⁰⁶Pb + 2s
46-49微量元素 U、Th、Pb(ppm)

31-34 这四列是这套软件最有价值的输出之一,第 5 章会讲怎么用。

年龄表里那 46 列,分成几块看就不乱了Age_Calculate_average 第 806–888 行定义。最值钱的是 31–34 和 36–40 这十列。2–1517–2425–2931–3436–4042–4546–4901020304050列号0–1标识文件 / 样品名2–15六个比值 + 各自的 2s校正后的比值17–24四个未校正年龄 + 2s206/238、207/235、208/232、207/20625–29校正后年龄按所选普通铅路径31–34Tera-Wasserburg 用²³⁸U/²⁰⁶Pb 与 ²⁰⁷Pb/²⁰⁶Pb 各带 2s36–40普通协和图用²⁰⁷Pb/²³⁵U、²⁰⁶Pb/²³⁸U、2s、ρ42–45Th-Pb 体系²⁰⁸Pb/²³²Th、²⁰⁸Pb/²⁰⁶Pb + 2s46–49微量元素U、Th、Pb(ppm)31–34 是这套软件最有价值的输出之一:它把"普通铅是多少"从假设变成拟合结果。变量名难记没关系,真正要记住的是:Isoclock 的输出是"中间产品",最后一步要去 IsoplotR 做。
图 34 年龄表的 46 列分成八块来看。最值钱的是 31–34(TW 图用)与 36–40(普通协和图用)。

关于微量元素换算

第 352-379 行有一个 coefficient() 函数,用 NIST 玻璃标样的已知含量把 cps 换算成 ppm:

标样U (ppm)Th (ppm)Pb (ppm)
NIST610461.5457.2426
NIST61237.3837.7938.57
NIST6140.8320.7482.32

用的是 系数 = 标样已知含量 / 标样实测 cps 均值,然后样品 cps × 系数。

注意:这里假设"微量元素的外标"和"U-Pb 的标样"是两回事 —— 微量元素走 NIST 玻璃,U-Pb 走矿物标样。如果两者混用,换算出来的 U、Th、Pb 含量只能当半定量看。

U 含量这一列(第 18 列)还有一个用处:校验数据合理性。如果你测的是磷灰石,U 含量算出来是 5000 ppm,那大概率哪里出问题了。


第 5 章 怎么让别人接受你的结果

这一章是实用的。审稿人和同行不会因为你用了某个软件就相信你,他们会问三个问题。

第 5 章导览:审稿人问三句,你准备四招这一章是实用的 —— 它讲的是怎么把结果"讲清楚"。1"你怎么知道普通铅扣对了?"2"你怎么知道仪器分馏校正对了?"3"你报的误差是内部误差还是真实可重复性?"对策一把标样表现亮出来§5.2 n / 均值 / 2SD 全写出来对策二用 Tera-Wasserburg 图自证§5.3 最重要的一招对策三把外部误差算对§5.4 区分内部误差与重现性对策四做敏感性检验§5.5 换一条路再跑一遍对策四对三问都适用第 5.6 节给出一份可以直接抄进论文的清单;第 5.7 节提醒:别为了好看的年龄去挑方法。
图 35 审稿人的三个问题与四种对策的对应关系。注意对策二(TW 图)之所以最重要,是因为它同时回答了"扣得对不对"和"扣的过程可不可复现"。

5.1 审稿人会问的三个问题

  1. "你怎么知道普通铅扣对了?"
  2. "你怎么知道仪器分馏校正对了?"
  3. "你报的误差是内部误差还是真实可重复性?"

三个问题各有对策。

审稿人会问的三个问题,以及对应的三招他们不会因为你用了某个软件就相信你。这三个问题几乎一定会来。"你怎么知道普通铅扣对了?"对策 用 Tera-Wasserburg 图自证:让数据自己拟合下截距,并且报出 MSWD。第 5.3 节"你怎么知道分馏校正对了?"对策 报出这批数据里标样的重复性(n、均值、2SD),并说明它和推荐值的关系。第 5.2 节"你报的误差是内部误差还是真实可重复性?"对策 明确写出年龄不确定度含外部误差 X%,并给出依据。第 5.4 节最有力的一招是 TW 图:它把"普通铅是多少"从假设变成拟合结果。而且软件已经把需要的列输出好了。
图 36 审稿人的三个问题与各自的解法。最有力的一招是 Tera-Wasserburg 图 —— 它把"普通铅是多少"从假设变成拟合结果。

5.2 对策一:把你的标样表现亮出来

你的结果的可信度上限,就是你这批数据里标样的表现。

因为 SSB 的逻辑是"用标样校准样品",如果标样自己在这批数据里就飘了 5%,那你不可能比 5% 更准。

好消息是:代码本来就已经算了这些量(第 423、428 行):

std_68_2s = date_all[2][standard].std() / date_all[2][standard].mean()
std_75_2s = date_all[4][standard].std() / date_all[4][standard].mean()

但算完就丢掉了,没有输出到任何文件、也没有打印。这是软件的一个实际缺陷(第 6 章列为改进项)。

你可以怎么补救:

你的结果的可信度上限,就是这批数据里标样的表现SSB 的逻辑是"用标样校准样品"。标样自己飘了 5%,你不可能比 5% 更准。460470480490500²⁰⁶Pb/²³⁸U 年龄(Ma)推荐值 483 Ma本批均值 484.3 Ma±2SD 带(n = 10)← 每个空心圆是一次标样测量从这张图能读出两件事外部误差该取多少本批 2SD = ±6.3 Ma,即 ±0.65%。界面里的 excess_V 不该小于这个量级。而软件算完就丢掉了std_68_2s、std_75_2s(第 423、428 行)没有输出到任何文件、也没有打印。补救办法:打开 xls,找标样的行,看 ²⁰⁶Pb/²³⁸U 年龄那一列,自己算一遍波动。
图 37 你的结果的可信度上限就是这批标样在数据里的表现。而软件把 std_68_2s 算完就丢掉了,所以这一步需要你手动看一眼 xls。

5.3 对策二:用 Tera-Wasserburg 图自证(最重要的一招)

这是最能说服审稿人的一步,而软件已经把需要的数据准备好了。

什么是 Tera-Wasserburg 图

一种特殊的等时线图:

把所有数据点画上去,如果它们来自同一个年龄、同一批普通铅,它们会大致落在一条直线上。这条直线:

为什么它能解决问题

因为它把"普通铅是多少"从假设变成了测量结果。

前面第 3 章说过:S&K 模型给的普通铅组成只是"平均猜测"。而在 TW 图上,你用的是"数据自己拟合出来的截距",不需要假设普通铅是什么。

这就直接回答了审稿人的第一个问题:不是"我相信模型铅",而是"我让 20 个数据点自己告诉我普通铅长什么样,而且它们线性度很好(MSWD 小),说明这个假设是自洽的"。

Tera-Wasserburg 图:让数据自己说出"普通铅是多少"同一个结晶年龄、混入不同量普通铅的一组数据,在这张图上一定排成一条直线。05101520253000.20.40.60.8²³⁸U / ²⁰⁶Pb (注意是倒过来的)²⁰⁷Pb / ²⁰⁶Pb5001000200030004000协和线刻度单位 Ma下截距 = 普通铅的 ²⁰⁷Pb/²⁰⁶Pb = 0.852上截距 = 年龄 = 250 Ma每个椭圆是一次分析普通铅越多,点越靠左上为什么这一招最能说服审稿人不是"我相信 S&K 模型铅",而是"我让 20 个数据点自己告诉我普通铅长什么样,而且它们线性度很好(MSWD 小),说明这个假设是自洽的"。判据:下截距应与你选的 ²⁰⁷/²⁰⁶ 差不离;MSWD 在 1.5 以内都算可以。
图 38 Tera-Wasserburg 图。同一个结晶年龄、混入不同量普通铅的数据一定排成一条直线:下截距给出普通铅组成,上截距与协和线的交点给出年龄。

怎么操作

  1. 从 cal_age_result_*.xls 里取 31-34 列(²³⁸U/²⁰⁶Pb + 2s、²⁰⁷Pb/²⁰⁶Pb + 2s);
  2. 也可以取 36-40 列(²⁰⁷Pb/²³⁵U、²⁰⁶Pb/²³⁸U、ρ)画普通协和图;
  3. 把这些数据贴进 IsoplotR(在线版免费:isoplotr.london-geochron.com)或者 IsoplotR 的 R 包;
  4. 选择 "Tera-Wasserburg" 模式,让它拟合下截距和上截距。

判据:

这一招还有一个额外好处:如果下截距显著不等于模型值,你可以直接把 TW 图拟合出的截距当作普通铅组成输入回 Isoclock 重算一遍。这就形成了一个闭环。

5.4 对策三:把外部误差算对

回顾 4.7 节:误差 = 内部误差 ⊕ 外部误差(excess_V)。

审稿人对只报内部误差的结果通常是不接受的,因为那会让误差条小得离谱(往往 ±0.5% 都不到),而实际上不同实验室测同一块标样能差 2–5%。

内部误差 ⊕ 外部误差:为什么只报 2s 会被拒误差 = 内部误差 ⊕ 外部误差(就是界面里的 excess_V)。两者传播方式不同,作用也不同。内部误差来自这一次测量的计数统计。信号越强、积分时间越长,它越小。它是"这个点的精度"(precision)。只报它,误差条会小到 ±0.5% 以内。外部误差来自两次测量之间的差异。含标样不确定性、基体差异、仪器漂移。它是"这个点的准确度"(accuracy)。不同实验室测同一块标样能差 2–5%。组合方式:相对误差 = sqrt( (2s/r)² + excess_V² ) —— 两项是"平方和再开方",不是相加。所以 excess_V 只要不是 0,它往往就主导了最终误差条的长度。excess_V 怎么定(界面上那个输入框)有长期的标样重现性数据用那个 2SD 值标样有推荐不确定度(如 91500 的 ²⁰⁶Pb/²³⁸U 约 ±1%)直接用它两者都有取大的,或按误差传播合成都没有保守取 2–5%,并在文中说明论文里一定要写明白:"年龄不确定度含外部误差 X%(依据为 …)"。含含糊糊只报一个 2s,最容易被抓。
图 39 内部误差与外部误差的区别,以及界面上那个 excess_V 该怎么定。两者是"平方和再开方",不是相加。

怎么定 excess_V:

情况建议
有长期的标样重现性数据(同一实验室、同一仪器)用那个 2SD 值
标样有推荐不确定度(如 91500 的 ²⁰⁶Pb/²³⁸U 推荐值有 ±1% 左右)用它
两者都有取大的,或者按误差传播合成
都没有保守取 2–5%,并在文中说明

并且一定要在论文里写明白:"年龄不确定度含外部误差 X%(依据为 …)"。含含糊糊地只报一个 2s,最容易被抓。

5.5 对策四:做敏感性检验(换一条路再跑一遍)

这是最省力、也最有力的稳健性检验。

具体做法:同一批数据,用不同的普通铅校正路径各跑一遍:

跑法界面设置
A(主结果)样品 Cal 204Pb,标样 Cal 204Pb(Std),Standard Corr.
B样品 207Pb,标样 207Pb(Std),Standard Corr.
C样品 208Pb,标样 208Pb(Std),Standard Corr.
D(对照)样品 None,Standard not Corr. —— 看不校正会偏多少

然后对比:

这个检验不花什么时间(软件跑得很快),但它在审稿意见里非常有说服力。 建议直接做成附表。

最省力的稳健性检验:换一条校正路径,再跑一遍同一批数据,四种设置各跑一次。软件跑得很快,但这一步在审稿意见里非常有说服力。230235240245250255260加权平均年龄(Ma)A 样品 Cal 204Pb251.4 ± 3.0主结果B 样品 207Pb249.8 ± 3.2C 样品 208Pb253.1 ± 3.4D 样品 None238.6 ± 2.8对照A 的结果怎么读这张图A、B、C 在误差范围内一致非常好。说明结果不依赖校正方法的选择,可以直接写进论文,附一条"三种路径一致"。差很多(比如超误差 30 Ma)警示信号:普通铅组成假设不稳,或数据有问题。不要挑好看的报,回头查 TW 图是不是散开了。
图 40 敏感性检验:同一批数据用四种设置各跑一遍。A/B/C 一致说明结果不依赖校正方法的选择;D 与 A 的差值就是"不校正会偏多少"。

5.6 一份"可以直接抄"的附录清单

论文方法部分或附录里,建议把下面这些写清楚。少写一条,就可能被追问一轮。

论文方法部分 / 附录必须写清楚的 16 条少写一条,就可能被追问一轮。数据采集3 条· 仪器型号、激光系统、 剥蚀策略(光斑、频率、 能量密度)· 采集的气体、载气流量· 前冲洗 / 后冲洗时间标样3 条· 标样名称· 标样所用年龄及来源文献· 本批数据的重复性 (n、均值、2SD)数据处理7 条· 软件名称与版本· 计算模式(Corr. / not Corr.)· 标样普通铅校正路径· 样品普通铅校正路径· 普通铅参数的来源· excess_V 的取值与依据· 是否做了 Hg 校正结果校验3 条· TW 图的下截距与 MSWD· 不同校正路径的结果对比· 标样与参考值的偏差这 16 条里最容易漏的两条 —— "普通铅参数是怎么来的"和"是否做了 Hg 校正" —— 恰好都在普通铅那一环。
图 41 一份可以直接抄的附录清单,四大类共 16 条。最容易漏的两条恰好都在普通铅那一环 —— 因为它们是假设,而假设不会自己出现在输出表里。
【数据采集】
- 仪器型号、激光系统、剥蚀策略(光斑、频率、能量密度)
- 采集的气体、载气流量
- 每次分析的前冲洗/后冲洗时间

【标样】
- 标样名称(如 MAD-NEW)
- 标样所用年龄及其来源文献(如 483 Ma, 某文献)
- 本批数据中标样的重复性(n、均值、2SD)

【数据处理】
- 软件名称与版本(Isoclock v2.0)
- 计算模式:Standard Corr. / Standard not Corr.
- 标样普通铅校正路径:Cal 204Pb(Std) / 207Pb(Std) / ...
- 样品普通铅校正路径:Cal 204Pb / 207Pb / ...
- 普通铅参数的来源:S&K 模型(并给出所用年龄)或实测输入值
- excess_V 的取值与依据
- 是否做了 Hg 校正(仪器自动 / 未做)

【结果校验】
- Tera-Wasserburg 图的下截距与 MSWD
- 不同校正路径的结果对比(附表)
- 标样与参考值的偏差

5.7 一句提醒

不要为了得到"好看的年龄"而去挑校正方法。 这不仅是学术道德问题,也是实际风险问题 —— 挑出来的结果在后续被别的方法交叉验证时很容易被发现。

换句话说:第 5.5 节的敏感性检验,应该在你决定最终报哪个数之前做,而不是之后。

敏感性检验该在什么时候做同一件事,顺序不同,性质就不同。对的顺序① 先跑完一批② 四条路径都跑③ 看年龄差多少④ 再决定报哪个错的顺序① 先跑完一批② 看哪个数好看③ 只报那一个④ 被交叉验证发现敏感性检验不是"补一个附表" —— 它决定了你最终报出去的那个数。
图 42 敏感性检验不是"补一个附表",它决定了你最终报出去的那个数。顺序反了,性质就从"检验"变成了"挑选"。

第 6 章 局限与可以改进的方向

这一章我尽量说全,并且按"改了会不会影响已有结果"来分类。这是诚实的部分 —— 一个软件的真实能力边界,比它的功能列表更重要。

第 6 章导览:三种局限,按"改了会不会影响已有结果"分类一个软件的真实能力边界,比它的功能列表更重要。数值缺陷 §6.1改了会改变输出三条,都已写入 README的 Known issues。其中危害最大的那条(Age76Pb 收敛判据)修复最简单:一行。但它会动数值,必须先由作者确认。方法局限 §6.2不是 bug,是边界六条(局限 4 – 局限 9)。同龄假设、SSB 基体假设、Liner 只分段不插值、没有等时线与 MSWD、²⁰⁸Pb/²³²Th 通常不可靠、abs() 掩盖失效。工程局限 §6.3不影响数值,影响使用八条,分三组:· 已修复 / 已改进 3 条· 影响使用体验  4 条· 兼容与安全风险 1 条大部分改起来不难,但需要有人愿意改。
图 43 第 6 章的三种局限,分类依据只有一条:改了会不会影响已有结果。这个分类决定了"谁能改、谁必须由作者确认、谁改不了"。

6.1 三条需要留意的数值行为(已写入 README 的 Notes for users)

缺陷 1:Age76Pb 的收敛判据失效

位置:第 122-145 行,特别是第 133 行。

Rap = (exp(0.00098485 * Tm) - 1) / (exp(0.000155125 * Tm) - 1) / 137.818   # 第 130 行,循环外

while abs(Rap76 - Rap) > 0.00005 and N < 10:    # 第 133 行
    ...
    Tm = Tmin + (Tmax - Tmin) * (Rap76 - Rapi) / (Raps - Rapi)
    Age76Pb = Tm
    N = N + 1                                    # Rap 从未更新

Rap 在进入循环前算了一次,循环体内没有任何地方重新计算它。所以 abs(Rap76 - Rap) 的值在整个循环里是恒定的 —— 条件要么一开始就是假(循环不执行),要么永远为真(循环跑满 10 次)。

后果:迭代次数被写死成 10。实测(把这段源码逐字复制出来,与二分法高精度解对比,输入为各年龄对应的理论 ²⁰⁷Pb/²⁰⁶Pb):

目标年龄Age76Pb 返回精确解偏差
100 Ma100.00100.00−0.00
500 Ma500.00500.00+0.00
1000 Ma1000.031000.00+0.03
1500 Ma1502.261500.00+2.26(+0.15%)
2100 Ma2134.772100.00+34.77(+1.66%)
2500 Ma2499.312500.00−0.69

这组数字与 README 里"约 +35 Ma(1.7%)@ 2.1 Ga"完全吻合。值得注意的是,误差不是单调的:1.5 Ga 以下几乎为零,2.1 Ga 附近最差,2.5 Ga 又回落到零 —— 这正是"迭代次数被写死"的典型症状(不同年龄需要不同的迭代次数才能收敛,10 次恰好对某些区间够、对另一些不够)。

缺陷 1 实测:Age76Pb 的偏差不是单调的,2.1 Ga 处最差把第 122–145 行的源码逐字复制出来,与二分法高精度解逐点对比。050010001500200025003000-10010203040真实年龄(Ma)Age76Pb 偏差(Ma)2.1 Ga:+34.77 Ma(+1.66%)1.5 Ga:+2.262.5 Ga:−0.691.5 Ga 以下几乎为零这种形状本身就是诊断证据不同年龄需要不同的迭代次数才能收敛。10 次恰好对某些区间够、对另一些不够 —— 所以偏差会时大时小,而不是单调增长。影响范围其实很有限只影响 ²⁰⁷Pb/²⁰⁶Pb 年龄这一列(输出 23–24 列)。²⁰⁶Pb/²³⁸U、²⁰⁷Pb/²³⁵U 有解析解,不受影响。所以 < 1 Ga 的样品实际可以忽略这条。
图 44 Age76Pb 偏差的实测曲线。偏差不是单调的 —— 这正是"迭代次数被写死成 10 次"的典型症状:不同年龄需要不同的迭代次数才能收敛。

影响范围:只影响 ²⁰⁷Pb/²⁰⁶Pb 年龄这一列(输出第 23-24 列)。²⁰⁶Pb/²³⁸U、²⁰⁷Pb/²³⁵U 年龄有解析解,不受影响。所以对年轻样品(< 1 Ga)来说,这条缺陷实际可以忽略;只在老样品里才需要警惕。

README 建议 1.5–2.2 Ga 的年龄谨慎使用。

修复难度:极低。把 Rap 的更新移进循环即可。但这会改变数值输出,所以需要作者确认。

更稳妥的替代:直接改用 SciPy 的 brentq 求根,几十年内不会再有收敛问题。

缺陷 2:²⁰⁴Pb 路径的除零

位置:_ratios_std204,第 1639-1640 行。

m74 = (...) / net['pb204']
m64 = (...) / net['pb204']

如果某个时间点的净 ²⁰⁴ 信号为 0(或整段 204 通道的背景恰好等于信号),这里直接 ZeroDivisionError,整个批次的处理中断。

后果:不是算错,是跑不完。所以危害等级反而比缺陷 1 低(至少它不会骗你)。

修复难度:低。加一个保护即可,但要想清楚"该怎么处理"(跳过该点?该点标记为无效?)—— 这需要作者决定语义。

缺陷 3:²⁰⁴Hg 校正恒为 0

位置:_net_signal,第 1569-1571 行。

# 204 通道:原实现直接用原始 204 减背景,未做 Hg 干扰扣除
# (源码里那段 Hg204cal_s 恒为 0、m204_s 从未被引用)
'pb204': np.average(sig['Hg204_s']) - np.average(sig['Hg204_b']),

原始代码里有一段试图计算 Hg 对 204 的贡献,但表达式是 np.average(x) - np.average(x) —— 恒等于 0,而且它的结果从未被引用。所以软件完全没有做 Hg 校正。

后果:如果仪器导出数据时已经做了 Hg 校正,无害;如果没有,那么:

危害等级:中高,因为它安静地给出错误结果,不报错、不警告。

你能做的:去确认仪器导出时到底有没有做 Hg 校正。这是数据层面的问题,软件层面改不了。

修复难度:中。需要 ²⁰⁴Hg/²⁰²Hg 的天然比值(约 0.23)和仪器的质量歧视校正因子,后者需要专门的 Hg 标定。

三条已确认缺陷:危害等级与修复难度并不成正比都已经写进仓库 README 的 Known issues。排在最前面的不是最严重的那个。1Age76Pb 收敛判据失效第 122–145 行(特别是第 133 行)迭代次数被写死成 10 次,1.5–2.2 Ga 偏差可达 +34.77 Ma安静地给出偏大的年龄危害:中高极低:一行改动2²⁰⁴Pb 路径的除零第 1639–1640 行net["pb204"] 为 0 时 ZeroDivisionError,整批处理中断不是算错,是跑不完危害:低低:加保护,但要定语义3²⁰⁴Hg 校正恒为 0第 1569–1571 行源码那段表达式是 average(x) − average(x),恒等于 0若仪器未做 Hg 校正:²⁰⁴Pb 被高估 → 年龄偏年轻危害:中高中:需要 Hg 标定与天然比值
图 45 三条已确认缺陷的对照。注意:危害最大的那条修复最简单(一行),而最容易发现的那条(除零崩溃)危害反而最小。

6.2 方法层面的局限

这些不是 bug,是这套方法本身的边界。

六条方法局限,分别卡在流程的哪一环不是"软件不好用",而是这套方法本身的边界。A 读数据原始信号背景扣除比值这一段没有已知的方法局限:纯计算,已有测试覆盖。B 校正普通铅校正分馏校正局限 4 · 标样与样品"同龄假设"普通铅参数是一个全局常数,对所有样品一视同仁。局限 5 · SSB 假设基体相同局限 6 · Liner 只是分段,不插值、不回归。C 出年龄算年龄输出与统计局限 8 · ²⁰⁸Pb/²³²Th 年龄通常不可靠Th 的信号强度与稳定性都不如 U,初始假设更难满足。局限 9 · abs() 让病态点不报错比值小于 0.0460455 时返回 0,不是崩溃而是给个数。局限 7 · 没有等时线、也没有 MSWD这一步在软件之外 —— 必须导出到 IsoplotR 做。
图 46 六条方法局限分别卡在流程的哪一环。凡是落在"算年龄"那一环的,都会安静地给出一个数字而不是报错 —— 这也是它们最难被发现的原因。

局限 4:标样与样品"同龄假设"的传递

Cal_age(Standard_age) 用标样年龄定出 Q8/R8/S8,供标样校正使用 —— 这是对的。但样品侧的普通铅参数(common_Pb、common_Pb207_204 等)来自用户输入的一个年龄或一组比值,是一个全局常数,对所有样品一视同仁。

问题:如果这批样品跨越了较宽的年龄区间,或者普通铅来源不同,用一个常数去校正所有人,会把系统误差引入到每个点上。

改进方向:允许按样品组(而不是整批)指定普通铅参数;或者做迭代 —— 先用初值算年龄,再用算出的年龄反推普通铅,反复两三次。(这是很多文献里的标准做法,代码里目前没有。)

局限 5:SSB 假设"标样与样品分馏行为相同"

外标法的前提是标样和样品的基体足够接近。MAD-NEW 磷灰石标样配磷灰石样品是合理的;但用 NIST 玻璃做标样去测黑钨矿,两者的剥蚀行为、气溶胶传输效率、电离效率都不同,分馏因子不能直接套用。

这是方法层面的固有局限,不是软件问题。你能做的是:选基体尽量接近的标样,并在论文里说明。

局限 6:Liner 只是分段,不插值、不回归

前面说过,Age_Calculate 把标样分成若干段,每段一个因子。如果两个标样之间隔了 10 个样品,这 10 个样品用的是同一个因子,中间的时间趋势被完全忽略。

regression()(第 152 行)实现了 4 种漂移模型,但没有接到界面上,从未执行。

改进方向:把 regression() 接线(工作量小),或者用更标准的时间插值(对每个样品,用它前后两个标样的因子做线性内插)。后者是当前文献里的普遍做法。

局限 7:没有等时线拟合、没有 MSWD

Isoclock 只做"逐点校正 + 逐点年龄",不做统计拟合:

这意味着你必须把数据导出到 IsoplotR(或其他软件)做第二步。这不是缺陷,但使用者必须知道:Isoclock 的输出是"中间产品",不是最终结果。

好消息是它输出的列(31-40)恰好是为这个目的设计的,导出很方便。

局限 8:²⁰⁸Pb/²³²Th 年龄通常不可靠

软件会输出这个年龄(第 21-22 列)。但要提醒:Th 和 U 的地球化学行为差异大,²³²Th 的信号强度和稳定性通常不如 ²³⁸U,而且初始 Th/Pb 假设更难满足。

建议:除非你研究的是独居石这类富 Th 矿物,否则把 ²⁰⁸Pb/²³²Th 年龄当作参考,不要作为主要结论。

局限 9:abs() 掩盖了失效

前面第 1.2 节提过:所有年龄计算都套了 abs()。这避免了负数取对数导致崩溃,但也意味着当数据病态时,你得到的不是报错,而是一个看似正常的数字。

判断方法:算完年龄后,回头检查输入的比值。如果某点校正后的 ²⁰⁶Pb/²³⁸U 是负数,或者年龄是 0(Age76Pb 在比值小于 0.0460455 时返回 0,第 128、144 行),那这个点就是无效的,必须剔除。

6.3 工程层面的局限

问题影响改进方向
输入/输出目录不能相同必须开两个目录加一个检查,或者允许同目录
输入目录里有无关文件就报错容易踩坑按扩展名过滤
每批数据必须重启程序效率低状态管理重构
输出表靠"列的位置"索引改动列顺序就全错引入列名 schema
状态文件用 pickle(Time_setting.npy 等)安全与兼容性风险改用 JSON
转换脚本曾用 eval() 解析文件名任意代码执行风险已修复(改为 int(float(...)))
异常静默失败打包成 exe 后尤其严重已改进(日志带 traceback、Tk 回调异常已接管)
缺自动化测试改动容易引入回归本 fork 已补(网页端 67 项测试 + 桌面端对照验证)
八条工程局限:按"会不会影响已有结果"分三组第一组省钱最多 —— 它们让"改动引入回归"变得看得见。已修复 / 已改进3 条· 转换脚本的 eval() → 已改为 int(float(...))· 异常静默失败 → 日志带 traceback· 缺自动化测试 → 网页端 67 项测试 +   桌面端对照验证影响使用体验4 条· 输入与输出目录不能相同· 输入目录有无关文件即报错· 每批数据必须重启程序· 输出表靠"列的位置"索引 → 改动列顺序就会全错兼容与安全风险1 条· 状态文件用 pickle (Time_setting.npy 等)· 安全性与兼容性都有风险· 改进方向:改用 JSON合计 8 条:3 条已解决、4 条是使用体验、1 条是技术债。
图 47 八条工程局限分成三组。第一组(已修复/已改进)虽然不影响数值,却是后续所有改动能不能安全进行的前提。

6.4 如果只能改三件事

按"性价比"排序(性价比 = 影响 ÷ 改动量):

  1. 修 Age76Pb 的收敛判据。 一行改动,解决一个影响 1.7% 的系统偏差。但会改变输出,需要作者确认。
  2. 把标样重复性输出出来。 现在 std_68_2s、std_75_2s 算完就丢。多写两行 print 或加两列到 xls,用户立刻就能看到自己数据的可信度上限。
  3. 加一个"可疑值标记"机制。 现在 abs() 和兜底逻辑会把病态点变成正常数字。加一列 flag,标出"比值负数""年龄为 0""204 信号为零"的点。

再往后的优先级:Hg 校正(需要仪器配合)、regression() 接线、按样品组指定普通铅、JSON 状态文件、列名 schema。

如果只能改三件事:按"影响 ÷ 改动量"排序258258改动量 → (越小越省事)影响 → (越大越值得)性价比高:先做这些往后排① 修 Age76Pb 收敛判据一行改动,消除 1.7% 偏差② 把标样重复性输出出来std_68_2s 算完就丢③ 加"可疑值标记"机制标出负数、零年龄、零信号Hg 校正需要仪器配合与标定regression() 接线4 种漂移模型未接线按样品组指定普通铅或改成迭代式求解JSON 状态文件替换 pickle 状态文件列名 schema输出表靠列位置索引注意第 ① 条的附带条件:修它会改变数值输出。凡是会改变输出的改动,都必须由原作者确认 —— 因为已经发表的结果要对得上。凡是"不影响已有结果"的改动(②③④),可以先做;凡是"会改数"的改动(①),先沟通。
图 48 如果只能改三件事。"影响 ÷ 改动量"最高的是前三个,但第 ① 条会改变数值输出,必须先由作者确认。

6.5 一句话总结局限

Isoclock 能可靠地完成"从原始信号到校正后比值与年龄"这一段,并且它的四条普通铅校正路径是有文献支撑的正规做法。

它不能完成的是统计与论证那一段 —— 等时线、MSWD、加权平均、以及"这个年龄有没有地质意义"的判断。

后者必须由你在 IsoplotR 里、结合地质观察来完成。 这不是软件的缺陷,是分工。

一句话总结:这不是缺陷,是分工Isoclock 负责的一段从原始信号到"校正后比值与年龄"。四条普通铅校正路径都有文献支撑,这一段是可靠、可复现、可被审稿人接受的。你和 IsoplotR 负责的一段等时线拟合、MSWD、加权平均,以及"这个年龄有没有地质意义"。Isoclock 只把数据整理到门口。分工线不是缺陷线所以:把 Isoclock 的输出直接当结论,会漏掉统计那一段;因为它没做那一段,就说它不行,也不公平。
图 49 把"软件能力"和"研究者的工作"分开看。这条线是分工线,不是缺陷线 —— 但使用者必须知道它在哪里,否则会把中间产品当最终结论。

第 7 章 代码地图:新手该从哪读起

Isoclock2.0.py 有 3676 行,但真正需要理解的科学代码不到 300 行。剩下的是界面代码。

第 7 章导览:3676 行里,真正要读的不到 300 行剩下的绝大部分是界面代码 —— 这份地图告诉你从哪读起。科学代码 < 300 行(约 8%)界面与其它 ≈ 3300 行(约 92%)主界面、三个界面页、对话框、状态管理、文件读写……≈ 300 行:六个核心函数 + 四条普通铅校正路径 + 一个 ²⁰⁷Pb/²⁰⁶Pb 迭代求解第 7 章的三个小节:§7.1 核心函数索引§7.2 三条推荐阅读路径§7.3 读代码的三个注意结论:你不需要读懂 3676 行。读懂那不到 300 行,就看懂了整个方法。
图 50 3676 行里真正要读的部分有多大。这不是"代码写得不好",而是界面代码本来就占大头 —— 关键是有人告诉你哪 300 行值得读。

7.1 核心函数索引

函数行号职责建议阅读顺序
SK2model40S&K 模式铅(3.7 Ga 起算写法)6
Age76Pb48²⁰⁷Pb/²⁰⁶Pb 年龄(迭代)7
regression78漂移回归模型(未接线)—
loaddata199读三种仪器文件1
Cal_age3096S&K 参数(现代值起算写法)→ Q8/R8/S8/P382/Pbc5
Age_Calculate_average244平均分馏因子 + 算年龄 + 出 xls8
Age_Calculate852分段分馏因子(Liner)9
dataprocess1331主处理流程2
_filter_2s14512σ 离群标记3
_mean_2sem1456均值与 2 倍标准误4
_net_signal1487背景扣除3
_ratios_std2071526207Pb 校正10
_ratios_std_cal2041539Cal 204Pb 校正11
_ratios_std2041555204Pb 校正12
_ratios_std2081573208Pb 校正13
reduce_sample1607还原成 23 列输出行14
PbCorrSamples2809询问样品普通铅参数15
instructure0/1/21781 / 2064 / 2348三个界面页—
main2885主界面与全局变量—
核心函数的调用关系与建议阅读顺序圆括号里是第 7.1 节表格给的阅读顺序编号;"—" 表示不在主线上。A 读数据loaddata (1)dataprocess (2)reduce_sample (14)B 工具函数(被 dataprocess 调用)_net_signal (3)_filter_2s (3)_mean_2sem (4)调用C 四条普通铅校正路径_ratios_std207 (10)_ratios_std_cal204 (11)_ratios_std204 (12)_ratios_std208 (13)校正后比值D 科学内核(被 C 与 E 调用)Cal_age (5)SK2model (6)Age76Pb (7)提供模式铅参数与 ²⁰⁷Pb/²⁰⁶Pb 年龄E 算年龄与出表factor (310)Age_Calculate_average (8)Age_Calculate (9)出年龄不在主线上的两个:regression (78) 的四种漂移模型从未接线;PbCorrSamples (2809) 只负责询问普通铅参数。界面部分 instructure0/1/2 (1781/2064/2348)与 main (2885) 可以先跳过 —— 它们跟方法无关。
图 51 第 7.1 节那张表的图形版:五个层次、建议阅读顺序、以及两个不在主线上的函数。沿着 A → C → E 走一遍,整个方法就通了。

7.2 三条推荐阅读路径

路径 A:想理解科学逻辑(推荐先走这条)

SK2model (114)  →  Cal_age (3170)  →  Age76Pb (122)
       ↓
  _net_signal (1561)  →  _ratios_std_cal204 (1613)
       ↓
  factor (384)  →  Age_Calculate_average 第 418-484 行

这条路上的每个函数都不到 40 行,加起来不到 200 行,但包含了整个方法的核心。

路径 B:想理解数据处理

loaddata (273)  →  dataprocess (1405)  →  _load_channels (1375)
       ↓
  _mean_cps_row (1393)  →  reduce_sample (1681)

重点是看数据从"文件里的数字"变成"比值"的每一步。

路径 C:想改界面

main (2959)  →  instructure0 (1855)  →  showplt (1761)

界面代码占了全文件 80% 以上,都是 Tk 的布局与回调,读的时候可以把里面所有的 Frame、Entry、Button 先跳过,只找 command= 后面接的函数名。

代码地图:3676 行里真正需要读懂的不到 300 行剩下的是界面代码。按这三条路径读,很快就能上手。路径 A 想理解科学逻辑(推荐先走这条)SK2model第 114 行Cal_age第 3170 行Age76Pb第 122 行_net_signal第 1561 行_ratios_std_cal204第 1613 行factor第 384 行每个函数都不到 40 行,加起来不到 200 行,但包含了整个方法的核心。路径 B 想理解数据处理loaddata第 273 行dataprocess第 1405 行_load_channels第 1375 行_mean_cps_row第 1393 行reduce_sample第 1681 行重点看数据从"文件里的数字"变成"比值"的每一步。路径 C 想改界面main第 2959 行instructure0第 1855 行showplt第 1761 行界面代码占全文件 80% 以上,读的时候可以把所有 Frame /Entry / Button 先跳过。读这份代码的三个注意事项·全局变量满天飞standard、Q8、excess_V、PbCorrS 都通过 global 传递;先搜它在哪里被赋值。·很多"故意保留的不一致"两处数学等价但写法不同时,先看注释 —— 浮点路径不同会改末位。·留意 abs() 和 except这是两个"消灭错误信息"的机制。看到就要警惕。
图 52 三条阅读路径。科学逻辑那条路走完只要读不到 200 行,但包含了整个方法的骨架。

7.3 读这份代码的三个注意事项

  1. 全局变量满天飞。 像 standard、Standard_age、Q8、excess_V、PbCorrS 这些都在函数之间通过 global 传递。读的时候先用搜索功能找到它在哪里被赋值。
  2. 很多写法是"故意保留的不一致"。 看到两处数学等价但写法不同的代码,不要急着统一 —— 代码注释里通常写明了为什么(浮点路径不同会改变末位)。
  3. 留意 abs() 和 except。 这是两个"消灭错误信息"的机制。看到它们,就要提高警惕。
读这份代码时,三处最容易踩空的地方这一段没有新知识,只有三个提醒。① 全局变量满天飞standard、Standard_age、Q8、excess_V、PbCorrS 这些都在函数之间通过 global 传递。读之前先用搜索功能找到它在哪里被赋值。② 故意保留的不一致看到两处数学等价、写法却不同的代码 —— 别急着统一。注释里通常写了为什么:浮点路径不同,末位会变,已发表的结果跟着变。③ 留意 abs() 和 except这是两个"消灭错误信息"的机制。abs() 让负数比值不报错,except 让异常不出声。看到它们,就要提高警惕。
图 53 读代码时最容易被绊住的三处。它们都不影响理解方法,但会让人误以为自己看错了 —— 所以先说在前面。

第 8 章 术语速查表

术语英文一句话解释
协和concordant²⁰⁶Pb/²³⁸U 和 ²⁰⁷Pb/²³⁵U 给出的年龄一致
不协和discordant两者不一致,通常是放射成因铅丢失
协和线concordia所有协和点连成的曲线,是年龄的"标尺"
普通铅common Pb (Pb_c)矿物结晶时混进来的铅,不含年龄信息
放射成因铅radiogenic Pb (Pb*)由本矿物内 U/Th 衰变产生的铅,含年龄信息
初始铅initial Pb同普通铅,强调"结晶时刻"
模式铅model Pb用地球演化模型算出的理论普通铅组成
μmu²³⁸U/²⁰⁴Pb 比值,S&K 模型取约 9.74
κkappa²³²Th/²³⁸U 比值,S&K 模型取约 3.78
分馏fractionation仪器造成同位素比值偏离真值的系统偏差
剥蚀坑分馏downhole fractionation激光越打越深导致的分馏变化
SSBSample-Standard-Bracketing用已知年龄标样校准样品的方法
外标external standard与样品分开测量的校准物质
Tera-WasserburgTW 图横轴 ²³⁸U/²⁰⁶Pb、纵轴 ²⁰⁷Pb/²⁰⁶Pb 的等时线图
下截距lower interceptTW 图上给出普通铅 ²⁰⁷/²⁰⁶ 的截距
2s2 sigma2 倍标准差,约 95% 置信水平
内部误差internal error单次测量的计数统计误差
外部误差external error重复测量之间的重现性误差
MSWDmean square weighted deviation衡量数据点与拟合线一致程度的指标,1 附近最好
ρ (rho)error correlation²⁰⁶Pb/²³⁸U 与 ²⁰⁷Pb/²³⁵U 误差的相关系数
cpscounts per second计数率,质谱仪原始输出单位
一张图串起全部术语左边这一支决定"减法扣得对不对",右边这一支决定"比值准不准"。它们最后汇到同一个年龄上。一次激光打点测到的铅永远是两部分之和普通铅 Pb_c —— 不含年龄信息放射成因铅 Pb* —— 携带年龄信息²⁰⁴Pb 指纹唯一不参与衰变的铅同位素分馏与 SSB用已知年龄标样称出仪器偏差模式铅 S&Kμ ≈ 9.74、κ ≈ 3.78,随时间演化协和线 concordia两个时钟一致的点连成的线四条校正路径207 / 208 / 204 / Cal 204不协和 discordia铅丢失留下的弦Tera-Wasserburg 图把假设变成拟合结果2s / ρ / MSWD误差、相关性、拟合优度校正后的比值 → 四个年龄²⁰⁶Pb/²³⁸U ²⁰⁷Pb/²³⁵U ²⁰⁷Pb/²⁰⁶Pb ²⁰⁸Pb/²³²Th
图 54 全部术语一张图。左支决定"减法扣得对不对",右支决定"比值准不准",最后汇到同一条出口:四个年龄。

附录 参考文献

本文中提到的所有方法出处:

参考文献与代码的对应关系想追根溯源,就按这张图找 —— 每一处方法都能追到原文。Chew, Petrus & Kamber 2014Chemical Geology 363, 185–199_ratios_std_cal204Cal 204Pb 法Stacey & Kramers 1975Earth and Planetary Science Letters 26SK2model 与 Cal_age全部模式铅常数Zack et al. 2011Contributions to Mineralogy and Petrology 162_ratios_std208208Pb 法Tera & Wasserburg 1972Earth and Planetary Science Letters 14第 5.3 节的 TW 图等时线图的原始出处标样推荐年龄(AY-4 = 154、MAD-NEW ≈ 483、91500 = 1062、wc-1 = 254.4 Ma)以各标样原始文献为准,代码里的数值只是默认建议值 —— 用之前请核对你那一批的实际标样。
图 55 每一处方法都能追到原文。图里四个对应关系是本文所有科学论断的依据,也是被质疑时最直接的回应材料。

→ 对应代码里的 Cal 204Pb 法(_ratios_std_cal204)。

→ 对应代码里的 SK2model 与 Cal_age 中的全部模式铅常数。

→ 对应代码里的 208Pb 法(_ratios_std208)。

→ Tera-Wasserburg 图的原始出处。


最后

这份文档的写法,是尽量把"为什么"讲清楚,而不是只讲"怎么点按钮"。

因为 U-Pb 定年里最容易出问题的从来不是操作 —— 是操作背后的那一步假设。软件把假设做成了默认值,默认值用起来毫无摩擦,于是大家就忘了它们存在。

记住三件事就够了1普通铅的参数不是常数它是你告诉软件的一个假设 —— 假设写错了,年龄就错了,而且不会报错。2模型铅只是"平均猜测"能用实测就用实测,能用等时线就用等时线。3报年龄时,顺便报你的假设以及"换一种假设,这个年龄会怎么变"。做到第三条,你的结果就比大多数已发表的结果更站得住。
图 56 整份文档最后剩下的三句话。如果只记住三件事,就记这三件 —— 它们都关于"假设",而不是关于"操作"。

记住三件事就够了:

  1. 普通铅的参数不是常数,是你告诉软件的一个假设;
  2. 模型铅只是平均猜测,能用实测就用实测,能用等时线就用等时线;
  3. 报年龄的时候,顺便报一下你对普通铅做了哪些假设、以及换一种假设年龄会怎么变。

做到第三条,你的结果就比大多数已发表的结果更站得住。

← 项目首页