第一次接触"褶积合成地震记录"这个概念时,我也是一头雾水。简单来说,这就像是用数学方法模拟地震波在地下传播的过程。想象一下,你在地下不同深度放置了几面镜子(地质界面),然后在地面敲击一下(震源),声波会在这些镜子之间来回反射,最终传回地面的就是地震记录。
在实际的地球物理勘探中,我们无法直接看到地下结构,但可以通过分析这些反射回来的地震波来推断地下情况。而合成地震记录就是通过计算机模拟这个过程,帮助我们理解和解释真实的地震数据。
用Python实现这个过程主要有三个关键步骤:
在开始之前,我们需要确保安装了以下Python库:
python复制pip install numpy matplotlib scipy
我们以一个简单的双层地质模型为例:
python复制# 第一层介质参数
ρ1 = 2000 # 密度,单位kg/m³
v1 = 1500 # 纵波速度,单位m/s
h = 100 # 厚度,单位m
# 第二层介质参数
ρ2 = 2100
v2 = 2200
这个模型表示:地下100米处有一个界面,上层介质的波速为1500m/s,下层为2200m/s。这种速度变化会产生地震波的反射。
反射系数R的计算公式是:
python复制R = (ρ2*v2 - ρ1*v1)/(ρ2*v2 + ρ1*v1)
这个公式的物理意义很简单:它反映了地震波遇到不同介质时有多少能量被反射回来。数值在-1到1之间,正数表示反射波相位不变,负数表示相位反转。
地震波从地面传到界面再返回地面的时间称为双程旅行时:
python复制t0 = 2*h/v1 # 单位秒
我们需要把反射系数放在正确的时间位置上:
python复制# 时间轴设置
dt = 0.0001 # 时间采样间隔
t_max = 0.3 # 记录总时长
# 初始化反射系数序列
r = np.zeros((round(t_max/dt), 1))
rd = round(t0/dt) # 计算反射系数在序列中的位置
r[rd] = R # 在对应位置放置反射系数
r = np.array(r).flatten() # 转换为一维数组
雷克子波是地震勘探中常用的一种数学模型,它很好地模拟了实际震源产生的地震波形。其数学表达式为:
python复制def ricker_wave(f, t):
return (1-2*(np.pi*f*t)**2)*np.exp(-(np.pi*f*t)**2)
其中f是主频,t是时间序列。
python复制f = 50 # 子波主频50Hz
ts_max = 0.1 # 子波时间长度
ts = np.linspace(-ts_max/2, ts_max/2, round(ts_max/dt)+1) # 时间轴
ricker = ricker_wave(f, ts) # 生成子波
python复制plt.figure(figsize=(10,4))
plt.plot(ts, ricker)
plt.title('50Hz Ricker Wavelet')
plt.xlabel('Time (s)')
plt.ylabel('Amplitude')
plt.grid()
plt.show()
你会看到一个对称的波形,中心是主瓣,两侧是旁瓣,这与实际地震波形非常相似。
褶积运算可以理解为"滑动加权平均"的过程。在地震勘探中,它表示地震子波在地下反射后叠加形成的记录。
python复制trace_conv = np.convolve(ricker, r, mode='full')
为了使合成记录更易解释,我们需要进行零相位校正:
python复制nts = len(ricker)
trace = trace_conv[round(nts/2):round(nts/2)+len(r)]
这一步相当于把褶积结果"往回移动"半个子波长度,确保反射系数与子波主峰对齐。
python复制plt.figure(figsize=(12, 6))
# 反射系数序列
plt.subplot(1,3,1)
plt.plot([0, R], [t0, t0], linewidth=2)
plt.ylim(0.3, 0)
plt.title('Reflectivity Series r(t)')
plt.xlabel('Amplitude')
plt.ylabel('Time (s)')
# 雷克子波
plt.subplot(1,3,2)
plt.plot(ricker, ts)
plt.ylim(0.02, -0.02)
plt.title('Ricker Wavelet w(t)')
plt.xlabel('Amplitude')
# 合成地震记录
plt.subplot(1,3,3)
plt.plot(trace, np.linspace(0, t_max, len(trace)))
plt.ylim(0.3, 0)
plt.title('Synthetic Seismic Trace s(t)')
plt.xlabel('Amplitude')
plt.tight_layout()
plt.show()
在最终的合成地震记录中,你会看到一个明显的波形变化,对应着地下100米处的界面。这个波形的极性和幅度反映了界面的反射特性:
在实际项目中应用这个方法时,有几个关键点需要注意:
采样率选择:dt不能太大,否则会丢失高频信息。一般要满足采样率至少是最高频率的2倍以上。
子波主频选择:f的选择要与实际地震数据匹配。主频越高,分辨率越高,但穿透深度会减小。
相位校正:零相位化处理非常重要,否则会导致解释错误。我曾在一个项目中因为忽略了这一步,导致解释的界面位置偏差了十几米。
多层模型扩展:本文展示的是双层模型,实际应用中可能需要处理几十甚至上百个界面。这时可以用循环来构建反射系数序列。
python复制# 多层模型示例
interfaces = [100, 200, 350] # 界面深度(m)
vs = [1500, 1800, 2200, 2500] # 各层速度
rhos = [2000, 2100, 2300, 2400] # 各层密度
r = np.zeros((round(t_max/dt), 1))
for i in range(len(interfaces)):
t0 = 2*interfaces[i]/vs[i]
R = (rhos[i+1]*vs[i+1]-rhos[i]*vs[i])/(rhos[i+1]*vs[i+1]+rhos[i]*vs[i])
rd = round(t0/dt)
r[rd] = R
掌握了基础方法后,你可以尝试以下扩展:
python复制noise_level = 0.1 # 噪声水平
trace_noisy = trace + noise_level*np.random.randn(len(trace))
频带限制:实际地震系统有带宽限制,可以通过滤波来模拟。
非零偏移距:本文是垂直入射情况,可以扩展到非零偏移距模型。
吸收衰减:考虑地层对地震波的吸收衰减效应。
各向异性:在复杂地层中,波速可能随方向变化。
我在一个油田项目中就使用了多层模型加上噪声模拟,结果与实测数据吻合度达到85%,为后续解释工作提供了很好的参考。