yanchang
yanchang
发布于 2026-06-04 / 8 阅读
0
0

如何用 Prominence (突出度) 捕捉地质旋回?

峰值突出度分析 (Prominence Analysis)

红色圆点表示突出度超过设定阈值的显著峰值。

测井数据分析中,寻找地层变化的“拐点”(如层序边界 SB、最大洪泛面 MFS)是核心诉求。从数学上看,这似乎就是一个简单的“求极值”问题。

但在真实的工程场景中,直接调用求导或极值函数往往会遭遇灾难性的失败——真实曲线中充斥着仪器噪声和高频干扰,直接求极值会抓出成百上千个“假拐点”。

本文将以一个自动化测井旋回划分引擎为例,深入拆解 SciPy 中 find_peaks 函数的核心灵魂——Prominence(峰值突出度),看看它是如何通过严密的数学逻辑与动态自适应阈值,在嘈杂的信号中精准锚定真正的地质周期的。

一、 绝对高度的谎言:为什么我们需要 Prominence?

在信号处理中,寻找局部峰值的基本定义很简单:当前点zkz_k 比左右相邻的点都高即可。但这会带来一个致命问题:绝对高度会撒谎。

假设测井曲线整体处于一个剧烈的上升趋势中,一个小小的电噪声毛刺,其绝对高度(zkz_k)可能非常大;但它相对于周边的曲线走向,其实毫无意义。

这就引出了 Prominence(突出度) 的概念。

Prominence 关注的不是zkz_k 本身有多高,而是这个峰相对于其局部背景到底“凸出”了多少

1. Prominence 的严格数学定义

SciPy 计算一个峰值点kk 的 Prominence 可以严格定义为以下三个步骤:

Step 1:划定峰的“独立领地”

向左搜索,直到遇到比当前峰zkz_k 更高的峰,或到达数组边界,得到左侧影响区间LkL_k

向右搜索,同样直到遇到更高的峰或边界,得到右侧影响区间RkR_k

物理意义:这座山峰在被更高的山峰“碾压”之前,能独立控制多广的疆域。

Step 2:寻找领地内的“深渊”

在左右领地内,分别寻找最低点(谷底):

bL=miniLkzib_L=\min_{i\in L_k}z_i
bR=miniRkzib_R=\min_{i\in R_k}z_i

Step 3:确立等高线基准并计算突出度

一个峰要能独立挺立,真正限制它的是两侧谷底中较浅的那一个。因此,我们选取较高的谷底作为该峰的“基准线”bb

b=max(bL,bR)b=\max(b_L,b_R)

最终,峰值突出度PkP_k 的计算公式为:

Pk=zkbP_k=z_k-b

展开为完整形式即:

Pk=zkmax(miniLkzi, miniRkzi)P_k = z_k - \max \left( \min_{i\in L_k}z_i,\ \min_{i\in R_k}z_i \right)

只有当PkτP_k \ge \tauτ\tau 为设定的突出度阈值)时,这个峰才会被算法认定为真正的有效峰。

二、 拒绝“死代码”:动态自适应阈值的设计艺术

在工程代码中,最忌讳的就是写死参数(比如硬编码 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)

这段只有两行的代码,藏着极其周密的数学考量:

  1. np.nanmaxnp.nanmin 的容错性

    真实的测井数据常伴有缺失值(NaN)。使用常规的 np.max 会被 NaN 污染导致全盘崩溃,而 nanmax/nanmin 能够完美免疫空值干扰,计算出有效曲线的真实振幅范围Rz=max(z)min(z)R_z = \max(z)-\min(z)

  2. 百分比动态跟踪 (* 0.05)

    阈值被设定为全曲线振幅范围的 5%。这意味着:曲线起伏剧烈时,阈值水涨船高;曲线平缓时,阈值自动降低。它保证了算法在任何尺度下都能抓取到“排名前列”的显著转折。

  3. 硬下限兜底 (max(..., 0.01))

    如果某段曲线是一潭死水,振幅RzR_z 极小,5% 的计算结果可能趋近于 0。此时 max(..., 0.01) 会强制介入,将最低阈值锁死在 0.01,彻底杜绝将微观仪器底噪误判为地质旋回的可能。

三、 逆向思维:如何用找峰的函数去“找谷”?

scipy.signal.find_peaks 天生只能寻找向上的凸起。但地质学中,代表冲刷面或层序边界的往往是曲线的“波谷”。

如何优雅地找谷?答案是:数学上的不等号反转。

设原始曲线为ziz_i,我们构造一条镜像曲线yi=ziy_i = -z_i

如果kk 是原始曲线的谷值,必定满足:

zk<zk1zk<zk+1z_k < z_{k-1} \quad \text{且} \quad z_k < z_{k+1}

两边同时乘以1-1,不等号方向反转:

zk>zk1zk>zk+1-z_k > -z_{k-1} \quad \text{且} \quad -z_k > -z_{k+1}

也就是:

yk>yk1yk>yk+1y_k > y_{k-1} \quad \text{且} \quad y_k > y_{k+1}

结论:原始曲线的“谷”,就是取负曲线的“峰”。

谷值 Prominence 的推导之美

由于我们是在y=zy = -z 上算峰值,代入前面的突出度公式:

Pk(y)=ykmax(miniLkyi, miniRkyi)P_k^{(y)} = y_k - \max \left( \min_{i\in L_k}y_i,\ \min_{i\in R_k}y_i \right)

由于min(z)=max(z)\min(-z) = -\max(z),且max(A,B)=min(A,B)\max(-A, -B) = -\min(A, B),经过代换化简,我们得到了原始曲线谷值的突出度严格公式:

Pktrough=min(maxiLkzi, maxiRkzi)zkP_k^{trough} = \min \left( \max_{i\in L_k}z_i,\ \max_{i\in R_k}z_i \right) - z_k

公式物理意义: 谷值的突出度,衡量的是谷底(zkz_k)相对于两侧较低的“山肩”(min(max(zL),max(zR))\min(\max(z_L), \max(z_R)))到底往下凹陷了多深。只有下凹足够深的谷,才能通过检测!

四、 从数组矩阵到地质边界的终极映射

通过上述严密的数学体系,代码对平滑后的 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 的\rightarrow 最大洪泛面 (MFS)

  • Fischer 的\rightarrow 层序边界 (SB)

  • INPEFA 的正峰\rightarrow 层序边界 (SB)

  • INPEFA 的波谷\rightarrow 最大洪泛面 (MFS)

结语

NaN 免疫的极差计算,到动态百分比的自适应阈值;从基于不等号反转的找谷技巧,到最终剥离数学与地质业务的灵活配置。这段寻找旋回边界的代码,完美诠释了什么是“健壮的工程算法”。

数学只负责提取特征,而领域知识(Domain Knowledge)赋予了特征以灵魂。


评论