测井数据分析中,寻找地层变化的“拐点”(如层序边界 SB、最大洪泛面 MFS)是核心诉求。从数学上看,这似乎就是一个简单的“求极值”问题。
但在真实的工程场景中,直接调用求导或极值函数往往会遭遇灾难性的失败——真实曲线中充斥着仪器噪声和高频干扰,直接求极值会抓出成百上千个“假拐点”。
本文将以一个自动化测井旋回划分引擎为例,深入拆解 SciPy 中 find_peaks 函数的核心灵魂——Prominence(峰值突出度),看看它是如何通过严密的数学逻辑与动态自适应阈值,在嘈杂的信号中精准锚定真正的地质周期的。
一、 绝对高度的谎言:为什么我们需要 Prominence?
在信号处理中,寻找局部峰值的基本定义很简单:当前点 比左右相邻的点都高即可。但这会带来一个致命问题:绝对高度会撒谎。
假设测井曲线整体处于一个剧烈的上升趋势中,一个小小的电噪声毛刺,其绝对高度()可能非常大;但它相对于周边的曲线走向,其实毫无意义。
这就引出了 Prominence(突出度) 的概念。
Prominence 关注的不是 本身有多高,而是这个峰相对于其局部背景到底“凸出”了多少。
1. Prominence 的严格数学定义
SciPy 计算一个峰值点 的 Prominence 可以严格定义为以下三个步骤:
Step 1:划定峰的“独立领地”
向左搜索,直到遇到比当前峰 更高的峰,或到达数组边界,得到左侧影响区间;
向右搜索,同样直到遇到更高的峰或边界,得到右侧影响区间。
物理意义:这座山峰在被更高的山峰“碾压”之前,能独立控制多广的疆域。
Step 2:寻找领地内的“深渊”
在左右领地内,分别寻找最低点(谷底):
Step 3:确立等高线基准并计算突出度
一个峰要能独立挺立,真正限制它的是两侧谷底中较浅的那一个。因此,我们选取较高的谷底作为该峰的“基准线”:
最终,峰值突出度 的计算公式为:
展开为完整形式即:
只有当( 为设定的突出度阈值)时,这个峰才会被算法认定为真正的有效峰。
二、 拒绝“死代码”:动态自适应阈值的设计艺术
在工程代码中,最忌讳的就是写死参数(比如硬编码 prominence=5)。换一口井、换一条曲线,信号幅度可能天差地别,死阈值会导致算法瞬间失效。
优秀的工程代码必须具备自适应能力。在本项目中,阈值是这样动态生成的:
Python
f_prom = max((np.nanmax(fischer_smooth) - np.nanmin(fischer_smooth)) * 0.05, 0.01)
i_prom = max((np.nanmax(inpefa_smooth) - np.nanmin(inpefa_smooth)) * 0.05, 0.01)
这段只有两行的代码,藏着极其周密的数学考量:
np.nanmax与np.nanmin的容错性真实的测井数据常伴有缺失值(
NaN)。使用常规的np.max会被NaN污染导致全盘崩溃,而nanmax/nanmin能够完美免疫空值干扰,计算出有效曲线的真实振幅范围。百分比动态跟踪 (
* 0.05)阈值被设定为全曲线振幅范围的 5%。这意味着:曲线起伏剧烈时,阈值水涨船高;曲线平缓时,阈值自动降低。它保证了算法在任何尺度下都能抓取到“排名前列”的显著转折。
硬下限兜底 (
max(..., 0.01))如果某段曲线是一潭死水,振幅 极小,5% 的计算结果可能趋近于 0。此时
max(..., 0.01)会强制介入,将最低阈值锁死在 0.01,彻底杜绝将微观仪器底噪误判为地质旋回的可能。
三、 逆向思维:如何用找峰的函数去“找谷”?
scipy.signal.find_peaks 天生只能寻找向上的凸起。但地质学中,代表冲刷面或层序边界的往往是曲线的“波谷”。
如何优雅地找谷?答案是:数学上的不等号反转。
设原始曲线为,我们构造一条镜像曲线。
如果 是原始曲线的谷值,必定满足:
两边同时乘以,不等号方向反转:
也就是:
结论:原始曲线的“谷”,就是取负曲线的“峰”。
谷值 Prominence 的推导之美
由于我们是在 上算峰值,代入前面的突出度公式:
由于,且,经过代换化简,我们得到了原始曲线谷值的突出度严格公式:
公式物理意义: 谷值的突出度,衡量的是谷底()相对于两侧较低的“山肩”()到底往下凹陷了多深。只有下凹足够深的谷,才能通过检测!
四、 从数组矩阵到地质边界的终极映射
通过上述严密的数学体系,代码对平滑后的 Fischer 曲线和 INPEFA 曲线发起了四路并发检测,得到了四个坚如磐石的候选索引集合:
Python
# Fischer 曲线的峰与谷
f_peaks, _ = find_peaks(fischer_smooth, prominence=f_prom)
f_troughs, _ = find_peaks(-fischer_smooth, prominence=f_prom)
# INPEFA 曲线的峰与谷
i_peaks, _ = find_peaks(inpefa_smooth, prominence=i_prom)
i_troughs, _ = find_peaks(-inpefa_smooth, prominence=i_prom)
数学到此为止,接下来交棒给地质学。
这些数组下标(k)究竟代表什么地质事件?这取决于工区的沉积背景和标准井校准。系统通过开放配置参数,将死板的数组转化为灵活的业务规则:
Python
# 配置文件中的地质规则映射
FISCHER_PEAK_IS_MFS = True
INPEFA_PEAK_IS_SB = True
# 动态组装候选集
f_mfs_cands = f_peaks if FISCHER_PEAK_IS_MFS else f_troughs
f_sb_cands = f_troughs if FISCHER_PEAK_IS_MFS else f_peaks
i_sb_cands = i_peaks if INPEFA_PEAK_IS_SB else i_troughs
i_mfs_cands = i_troughs if INPEFA_PEAK_IS_SB else i_peaks
在当前的默认配置下:
Fischer 的峰 最大洪泛面 (MFS)
Fischer 的谷 层序边界 (SB)
INPEFA 的正峰 层序边界 (SB)
INPEFA 的波谷 最大洪泛面 (MFS)
结语
从 NaN 免疫的极差计算,到动态百分比的自适应阈值;从基于不等号反转的找谷技巧,到最终剥离数学与地质业务的灵活配置。这段寻找旋回边界的代码,完美诠释了什么是“健壮的工程算法”。
数学只负责提取特征,而领域知识(Domain Knowledge)赋予了特征以灵魂。