2023 7.10~7.16 周报 (RTM研究与正演的Python复现)

news2024/11/21 0:12:56

0 上周回顾

上周简单阅读了论文《Deep-Learning Full-Waveform Inversion Using Seismic Migration Images》, 但是并没读完…因为这篇论文中提到一个技术吸引了注意力: RTM (Reverse-time migration)
于是计划下周去专门熟悉熟悉RTM的机制, 并且试着用Python复现这个操作.
另外, 在复现之前, 我们还需要用Python实现正演相关的操作, 因为RTM的过程也是一种逆向的正演.

1 本周计划

  1. Python复现简单的声波正演.
  2. 利用简单的声波正演手段实现RTM.
  3. Python复现包含复杂的边界吸收条件的弹性波正演.
  4. 试着看: 复杂的弹性波正演可否实现RTM.

2 完成情况

2.1 Python复现简单的声波正演

首先定义库和正演需要的基本参数.

采样间隔(s)x和z轴的间隔(m)震源频率(Hz)时间采样个数震源震动采样个数震源水平位置(m)
0.00110252000121150

然后速度模型是一个 201 × 301 201 \times 301 201×301图像, 通过上述参数不难得知, 这是一个约为 2 2 2km * 3 3 3km 地下区域.
在这里插入图片描述
然后在一共 2000 2000 2000个时域采样点中, 我们在前 121 121 121个采样点中设置了雷克子波样的震源. 即 2 2 2s中的前 0.121 0.121 0.121s内释放震源.
在这里插入图片描述
震源水平位置是 150 150 150, 也就是说在这片工区的中心位置释放震动.

from __future__ import division
import numpy as np
from bruges.filters.wavelets import ricker
import matplotlib.pyplot as plt
from scipy.signal import convolve
import skimage.filters
import matplotlib
import scipy.io
matplotlib.use('TkAgg')

dt = 0.001                          # 采样的时间域间隔
dx = 10                             # 采样的x轴间隔
dz = 10                             # 采样的z轴间隔
f0 = 25
nt = 2000
tmax = nt * dt                      # 最大时间采样点 (实际是0.0999)
t_array = np.arange(0, nt)

wav1 = ricker(duration = 0.120, dt = dt, f = f0)[0]
									# 定义震源波形 (雷克子波)
vmodel = scipy.io.loadmat("./vmodel1182.mat")["vmodel"]
vmodel = vmodel.T
nx, nz = vmodel.shape
isx = 150
isz = 0

然后我们假设一个二维波场平面, 这个平面在计算机中被若干个距离均匀的点表示, 即交错网络. 然后 u ( x , z , t ) u(x,z,t) u(x,z,t)作为这个平面中任意点的应力值, 如果你把这个平面放在三维空间中 (第三维为振幅), 那么 u ( x , z , t ) ≠ 0 u(x,z,t)≠0 u(x,z,t)=0就表现为平面中的突出与凹陷, 即振幅非零. 而 u ( x , z , t k ) u(x,z,t_k) u(x,z,tk)就意味着波场在变化的过程中第 t k t_k tk时刻的景象, 或者说, 时间从 0 0 0时刻一直进行到 t k t_k tk的时候突然被定格, u ( x , z , t k ) u(x,z,t_k) u(x,z,tk)就是定格时被拍摄下来的快照 (snapshot)
网格的横向与纵向间隔分别被表示为 Δ x \Delta x Δx Δ z \Delta z Δz, 那么如果 x i x_i xi可表征 i Δ x i \Delta x iΔx; z j z_j zj可表征 j Δ z j \Delta z jΔz; t k t_k tk可表征 k Δ t k \Delta t kΔt, 那么 u ( x i , z j , t k ) u(x_i, z_j, t_k) u(xi,zj,tk)也可表示为 u i , j k u_{i,j}^k ui,jk.
现在我们有个二维的波动方程
∂ 2 u ( x i , z j , t k ) ∂ z 2 + ∂ 2 u ( x i , z j , t k ) ∂ x 2 = 1 v 2 ∂ 2 u ( x i , z j , t k ) ∂ t 2 (1) \frac{\partial^{2} u(x_i, z_j, t_k)}{\partial z^{2}}+\frac{\partial^{2} u(x_i, z_j, t_k)}{\partial x^{2}}=\frac{1}{v^{2}} \frac{\partial^{2} u(x_i, z_j, t_k)}{\partial t^{2}} \tag{1} z22u(xi,zj,tk)+x22u(xi,zj,tk)=v21t22u(xi,zj,tk)(1)
然后通过的泰勒的二阶展开的项进行差值计算得到二阶中心差分.
f ( x ) − f ( x − h ) = ∂ f ( x ) ∂ x h − 1 2 ∂ f 2 ( x ) ∂ x 2 h 2 + o ( h 2 ) (2) f(x)-f(x-h)=\frac{\partial f(x)}{\partial x} h-\frac{1}{2} \frac{\partial f^{2}(x)}{\partial x^{2}} h^{2}+o\left(h^{2}\right) \tag{2} f(x)f(xh)=xf(x)h21x2f2(x)h2+o(h2)(2)
f ( x + h ) − f ( x ) = ∂ f ( x ) ∂ x h + 1 2 ∂ f 2 ( x ) ∂ x 2 h 2 + o ( h 2 ) (3) f(x+h)-f(x)=\frac{\partial f(x)}{\partial x} h+\frac{1}{2} \frac{\partial f^{2}(x)}{\partial x^{2}} h^{2}+o\left(h^{2}\right) \tag{3} f(x+h)f(x)=xf(x)h+21x2f2(x)h2+o(h2)(3)
这里差值是3式对2式求差, 这样就获得了一般的二阶中心差分形式, 这些形式表现在对 u u u Δ t \Delta t Δt, Δ x \Delta x Δx Δ z \Delta z Δz的导数可以表示为:
∂ 2 u i , j k ∂ t 2 = u i , j k + 1 − 2 u i , j k + u i , j k − 1 Δ t 2 + O ( Δ t ) (4) \frac{\partial^{2} u^{k}_{i,j}}{\partial t^{2}}=\frac{u^{k+1}_{i,j}-2 u^{k}_{i,j}+u^{k-1}_{i,j}}{\Delta t^{2}} + O(\Delta t)\tag{4} t22ui,jk=Δt2ui,jk+12ui,jk+ui,jk1+O(Δt)(4)
∂ 2 u i , j k ∂ x 2 = u i + 1 , j k − 2 u i , j k + u i − 1 , j k Δ x 2 + O ( Δ x ) (5) \frac{\partial^{2} u^{k}_{i,j}}{\partial x^{2}}=\frac{u^{k}_{i+1,j}-2 u^{k}_{i,j}+u^{k}_{i-1,j}}{\Delta x^{2}} + O(\Delta x)\tag{5} x22ui,jk=Δx2ui+1,jk2ui,jk+ui1,jk+O(Δx)(5)
∂ 2 u i , j k ∂ z 2 = u i , j + 1 k − 2 u i , j k + u i , j − 1 k Δ z 2 + O ( Δ z ) (6) \frac{\partial^{2} u^{k}_{i,j}}{\partial z^{2}}=\frac{u^{k}_{i,j+1}-2 u^{k}_{i,j}+u^{k}_{i,j-1}}{\Delta z^{2}} + O(\Delta z) \tag{6} z22ui,jk=Δz2ui,j+1k2ui,jk+ui,j1k+O(Δz)(6)
4, 5和6式带入到1式中可以得到
1 v 2 u i , j k + 1 − 2 u i , j k + u i , j k − 1 Δ t 2 = u i + 1 , j k − 2 u i , j k + u i − 1 , j k Δ x 2 + u i , j + 1 k − 2 u i , j k + u i , j − 1 k Δ z 2 (7) \frac{1}{v^2} \frac{u^{k+1}_{i,j}-2 u^{k}_{i,j}+u^{k-1}_{i,j}}{\Delta t^{2}} =\frac{u^{k}_{i+1,j}-2 u^{k}_{i,j}+u^{k}_{i-1,j}}{\Delta x^{2}} +\frac{u^{k}_{i,j+1}-2 u^{k}_{i,j}+u^{k}_{i,j-1}}{\Delta z^{2}} \tag{7} v21Δt2ui,jk+12ui,jk+ui,jk1=Δx2ui+1,jk2ui,jk+ui1,jk+Δz2ui,j+1k2ui,jk+ui,j1k(7)
稍微整理下, 我们希望知道下个时刻的波形结构, 因此我们只把这个式子中唯一的 u i , j k + 1 u_{i,j}^{k+1} ui,jk+1整理到左边, 其余全部往右侧移 (因为我们的交错网络每个点的分布是相互均匀的, Δ x = Δ z \Delta x = \Delta z Δx=Δz, 于是得用将 Δ z \Delta z Δz Δ x \Delta x Δx代替) . 得到:
u i , j k + 1 = r 2 ( u i + 1 , j k + u i , j + 1 k − 4 u i , j k + u i − 1 , j k + u i , j − 1 k ) + 2 u i , j k − u i , j k − 1 (8) u^{k+1}_{i,j} = r^2 \left( u^{k}_{i+1,j} + u^{k}_{i,j+1} - 4 u^{k}_{i,j} + u^{k}_{i-1,j} + u^{k}_{i,j-1}\right) + 2 u^{k}_{i,j} - u^{k-1}_{i,j} \tag{8} ui,jk+1=r2(ui+1,jk+ui,j+1k4ui,jk+ui1,jk+ui,j1k)+2ui,jkui,jk1(8)
这里 r = v Δ t Δ x r = \frac{v \Delta t}{\Delta x} r=ΔxvΔt.
同时, 8式括号里面的 i i i j j j是针对整个应力平面 u k + 1 u^{k+1} uk+1的更新一部分, 而这部分刚好可以通过一个卷积 D D D等价. 公式如下
u k + 1 = r 2 ( u k ∗ D ) + 2 u k − u k − 1 , D = ( 0 1 0 1 − 4 1 0 1 0 ) (9) u^{k+1} = r^2 \left( u^{k} * D\right) + 2 u^{k} - u^{k-1}, D= \begin{pmatrix}0& 1& 0\\1& -4& 1\\0& 1& 0\end{pmatrix}\tag{9} uk+1=r2(ukD)+2ukuk1,D= 010141010 (9)
这个公式里面, 我们把针对 i i i j j j的循环囊括了进来, 因此所有操作都是针对矩阵本身进行的. 这个过程可以通过下述代码实现

def solve_fd2d_withabc(v, w, vmodel, r, dt, dx, dz):
    '''
    Compute wave amplitude at the next k-th time step
    with boundary conditions

    :param v:           Snapshot of amplitude at last step (k-1)
    :param w:           Snapshot of amplitude at previous step (k-2).
    :param vmodel:      Velocity model background
    :param r:           Mixing parameters
    :param dt:          Time sampling interval
    :param dx:          x-axis sampling interval
    :param dz:          z-axis sampling interval
    :return:
    '''

    D = np.array([[0, 1, 0], [1, -4, 1], [0, 1, 0]])
    u_out = 2 * v - 1 * w + (r ** 2) * convolve(v, D, mode='same')
	
	# bottom
    u_out[:, -1] = v[:, -2] + (r[:, -1] - 1) / (r[:, -1] + 1) * (u_out[:, -2] - v[:, -1])
    # right
    u_out[-1, :] = v[-2, :] + (r[-1, :] - 1) / (r[-1, :] + 1) * (u_out[-2, :] - v[-1, :])
    # left
    u_out[0, :] = v[1, :] + (r[0, :] - 1) / (r[0, :] + 1) * (u_out[1, :] - v[0, :])
    
    return u_out

后面三行是边界吸收代码 (低配版), 这个我在别处抄来的. 边界吸收算法可以很复杂, 这里算是超级简单的一种.
然后迭代代码:

u_abc = np.zeros((nx, nz, nt), dtype = float)
r = vmodel*dt/dx           
# 从第一个波场开始进行正演, 更新时间序列中每个节点的波场快照
for k in t_array:                                   # 一个时间采样点一个采样点地尝试
    if k >= 2:                                      # 需从步骤2开始, 因为步骤1和步骤0是初始条件
        v = u_abc[:, :, k - 1]
        w = u_abc[:, :, k - 2]

        u = solve_fd2d_withabc(v, w, vmodel, r, dt, dx, dz)

        if k < len(wav1):                           # 如果源处于活动状态, 则将其幅度添加到波场
                                                    # 源是否活跃取决于雷克子波的影响时间
            u[isx, isz] = u[isx, isz] + wav1[k]

        u_abc[:, :, k] = u

完成迭代后, u_abc就存储不同时刻的波场快照了, 我们可以看到不同时刻的波场快照:
在这里插入图片描述
如果这个时候在图像的第一行设置一个系列检波器, 记录下每个检波器都在 2000 2000 2000个采样时刻下的波动情况, 那么我们就可以获得常规地震工区打炮后获得的地震数据图:

surface_record_raw = u_abc[:,1,:]

在这里插入图片描述
因为我们用的边界吸收条件是比较简易的, 因此这个地震数据存在很多不稳定的反射, 衍射和波形的 “尾流” (应该可以这么说吧…我感觉是挺像尾流的, 就是前向波的末端有抖动).

2.2 利用简单的声波正演手段实现RTM

什么是逆时偏移 (Reverse-time migration: RTM)?
我们这里使用的是叠前逆时偏移, 即对每个单炮记录进行逆时偏移, 然后对于各炮成像结果进行叠加.
单炮记录怎么进行逆时偏移?
它将采样的最后时刻的波场快照 u ( x , z , T ) u(x,z,T) u(x,z,T)消除前向波, 并以之作为起始平面, 按照时间反推, 并且令 u ( x , z = 0 , T ) u(x,z=0,T) u(x,z=0,T)作为震源进行正演材料得到 u ∗ ( x , z , 0 ) u^*(x,z,0) u(x,z,0)时刻的波场快照. 然后呢, 令 u ( x , z = 0 , T − 1 ) u(x,z=0,T-1) u(x,z=0,T1)作为震源进行正演材料得到 u ∗ ( x , z , 1 ) u^*(x,z,1) u(x,z,1)时刻的波场快照, 之后, 令 u ( x , z = 0 , T − 2 ) u(x,z=0,T-2) u(x,z=0,T2)作为震源进行正演材料得到 u ∗ ( x , z , 2 ) u^*(x,z,2) u(x,z,2)时刻的波场快照. 这就是一个Reverse-time的过程.

"令 u ( x , z = 0 , T ) u(x,z=0,T) u(x,z=0,T)作为震源进行正演材料"的含义有点两点:

  1. 震源不再是一个点, 而是横向一条线.
  2. 震源不再是长度为 t < T t<T t<T的雷克子波, 而是持续时间为 T T T的震源

这里, 可以简化理解一个点是: RTM的震源在 T T T时间内形成的二维波形图像其实就是 走时信息静默的 正演单炮山峰波形的上下翻转图.

这里放出代码来辅助理解 (说起来如此繁琐的东西, 用代码非常简单就表示了, 这就是代码的强大):
这里的逆时正演得到的波形图我们简称为上行波, 即 u ∗ u^* u, 为啥是上行波呢? 因为之前正演的波是向下传递的过程中接收器接受生成的, 而RTM震源是利用的接收器信息的逆过程.

vmodel_smooth = skimage.filters.gaussian(vmodel, 30)    # 平滑版的速度模型
r_smooth = vmodel_smooth*dt/dx                          # 得到平滑版的r
u_up = np.zeros((nx, nz, nt), dtype=float)
for k, tk in enumerate(t_array):
    if k >= 2:
        v = u_up[:, :, k - 1]
        w = u_up[:, :, k - 2]

        u = solve_fd2d_withabc(v, w, vmodel_smooth, r_smooth, dt, dx, dz)

        # 上行波是, 整个平面都提供震动源, 而非单一的点震源. 这个波是原本的反射波: [:, -1]->[:, -2]
        u[:, 0] = u[:, 0] + muted_gather[:, -(k - 1)]

        u_up[:, :, k] = u

强调!
这里进行上行波模拟的时候, 我基于的是模糊化后的速度模型来进行反向正演的! 这里是有原因的, 因为一般正演可以在现实世界中通过打炮检测来模拟出来 (毕竟震源可控, 只有单个炮点), 但是上行波的模拟很难实现 (震源众多, 波形难以控制). 因此这里采用了高斯平滑的速度模型来模拟人工猜测的低准确度的速度模型

这里的muted_gather是消除前向波, 也就是走时 (Traveltime) 信息的单炮地震图像.

什么是走时信息呢? 走时信息就是震源释放出来的最前面的波, 这个波没有任何反射内容, 就只是震源波的代表. 当然如果完全用前向波代替也不是很恰当, 因为向下传递的波也可以叫做前向波, 但是我们有个限定, 即这个走时信息的波是应当是沿着大地进行横向传播的前向波. 为何? 因为这个波形是每个接收器介绍到的第一个信息, 也就是左图中X[0, :]的每个点收到的第一次波动信息. 我们的单炮地震图像 (右图), 即共炮接受集合 (common shot gathers), 正是左图中X[0, :]的每个点在 T T T采样时间内接受的信号的绘制. 而走时信息就是这个图像的山峰的表面外层, 这个最清晰的部分
请添加图片描述要想消除这个信息不难, 因为由GIF图可知, 只要知道走时信息什么时候穿过来和震源有效波长时间, 然后把每个接收器的这段时间接受的信号设置为0 (静默) 就好了.
在这里插入图片描述

而走时信息之所以叫做"走时 (Traveltime) "信息, 其实就是波走 (Travel) 到一个位置的最短时间(time), 那么用路径除以时间就好.
代码如下:

# 前向波 (走时信息) 静默
muted_gather = surface_record_raw.copy()
x_array = np.arange(0, nx*dx, dx)                   # 基于采样进行标点, 得到地表处每个标点的数组 [0米, dx米, 2dx米, 3dx米, ... , (nx-1)dx米]
v0 = vmodel[:,0]                                    # v0表示地表一层的默认地层速度值 (长度为nx的数组)
# np.cumsum(dx/v0)表示在地表第一层区域内, 波从左到右传播的用时数组 [dx/v0秒, 2dx/v0秒, 3dx/v0秒 ...]
# ...[isx]自然是指的传到震源激发点的用时
# 相减后就得到中间出发向左右两边传递的波在不同位置的时间 [... -2dx/v0秒, -dx/v0秒, 0秒, dx/v0秒, 2dx/v0秒 ...]
traveltimes = abs(np.cumsum(dx/v0) - np.cumsum(dx/v0)[isx])
for traceno in range(len(x_array)):
    muted_gather[traceno, 0:int(traveltimes[traceno]/dt + len(wav1))] = 0   # 最后加上len(wav1)是雷克子波的时间, 因为雷克子波能量完全释放需要时间

请将这个代码放在上行波代码的前面. 最终得到静默走时信息的图像:
在这里插入图片描述
最后, RTM图像的获取需要上行波场和一般正演的波场快照进行时间域上的互相关, 这种互相关是在时间域上互反的, 用代码来解释就是:

migrated_image = np.zeros_like(u_up[:,:,0], dtype = float)
for i in range(nx):
    for j in range(nz):
        # 任何一个点在时间域上互相关: 下行波的t0 * 上行波的t_{end} + 下行波的t1 * 上行波的t_{end-1} + ... + 下行波的t_{end} * 上行波的t_{0}
        migrated_image[i, j] = np.sum(u_up[i, j, :] * u_abc[i, j, ::-1])

可以发现, RTM图像的两个维度都是空间维度上的, 而非共源单炮集合 (common shot gathers) – 也就是单炮地震图像(山峰) – 是时间-空间维度意义上的. 这一点可以认为RTM在空间意义上更接近速度模型.
最终得到RTM图像如下所示 (包括速度模型对比):
在这里插入图片描述
因为我们的炮点位置在地表中点, 因此地下结构中的中间区域信息是比较准确的. 盐体中间部分竟可以只依靠正演得到如此准确的结果, 令人震惊, 上层的层次结构看似混乱, 但是也是有规可循的, 因为层边界似乎信号更集中一些.
我进一步将炮点的位置移动到75和225, 观测它们的RTM图像:
在这里插入图片描述
可以发现, 炮点正下方附近的信息预测都很不错, 轮廓细节与速度模型很接近, 但是距离炮点远点会因为衰减而预测不准确. 但是这些信息应当可以通过部分拼接来取长补短地进行进行融合! 于是我试着融合了一下:
在这里插入图片描述
左图是我直接把所有炮点图对应的RTM图像叠加取平均, 右图是我把炮点下方宽度为60的区域截取下来, 然后把所有RTM截取部分拼在一起 (重叠部分取平均).
效果非常amazing啊, 没想到仅仅是分析正演的波形就可以得到这样的效果.
但是声波信息相对简单, 而且地震波属于弹性波, 因此我们需要更真实的正演信息来进一步验证RTM的可行性.

2.3 Python复现包含复杂的边界吸收条件的弹性波正演.

于是我将一个拥有更高级的边界吸收能力的六阶中心差分的弹性波正演代码移植到了Python上.
源算法文件为.mat文件, 是师兄提供的.
这个算法相对来说就复杂很多了, 不方便介绍, 下面直接提供复现的代码. (一些注释来自@苗妮)

from __future__ import division
import numpy as np
from bruges.filters.wavelets import ricker
import matplotlib.pyplot as plt
from scipy.signal import convolve
import skimage.filters
import matplotlib
import scipy.io
import math
import cv2
matplotlib.use('TkAgg')

vmodel = scipy.io.loadmat("./vmodel1182.mat")["vmodel"]
nz, nx = vmodel.shape
h = 10                                                                           # PML宽度
Nz, Nx = nz + 2 * h, nx + 2 * h                                                  # 套上PML层后的长度
sz, sx = 0, 150                                                                  # 震源位置
dx = 10
dz = 10
nt = 2000                                                                        # 时间采样个数
dt = 0.001
wave_source_ampl = 1                                                             # 震源振幅
nodr = 3                                                                         # 空间差分阶数的一半
f0 = 25                                                                          # 震源频率
sampling_time = dt * nt                                                          # 采样时长 = 采样间隔时间 x 采样个数

##################################
# 计算矩阵C :进行差分计算时的系数 #
##################################
B = np.zeros([nodr, 1])
B[0][0] = 1
A = np.zeros([nodr, nodr])
for i in range(nodr):
    A[i, :] = np.power(np.arange(1, 2 * nodr, 2), 2 * i + 1)
C = np.dot(np.linalg.inv(A), B).reshape(-1)

##################################
        # 计算密度矩阵 #
##################################
rho = 1000 * np.ones([Nz, Nx])                                                   # 密度矩阵包含了套上的PML边
vmodel_pad = np.pad(vmodel, [h, h], 'edge')

##################################
         # 生成震源波 #
##################################
# 标准雷克子波
source_wav = ricker(duration = 0.08, dt=dt, f=f0)[0]
source_wave_duration = len(source_wav)
source_wav = np.concatenate((source_wav, 0 * np.ones(nt - len(source_wav))))

##################################
            # 预览 #
##################################
plt.plot(source_wav)
plt.show()
plt.imshow(vmodel)
plt.show()
plt.imshow(skimage.filters.gaussian(vmodel, smooth_value))
plt.show()

##################################
    # PML层的吸收系数计算 #
##################################
# The PML formula refers to the equations (2) and (3) of Marcinkovich and Olsen, 2003.
v_max = np.max(vmodel_pad)
dp_z = np.zeros([Nz, Nx])
dp_x = np.zeros([Nz, Nx])

# 设置上下两层
dp0_z = 3 * v_max / dz * (8 / 15 - 3 * h / 100 + 1 * h**2 / 1500)
# 计算边缘的吸收因子
dp_z[:h, :] = np.dot(dp0_z * np.power(np.arange(h, 0, -1) / h, 2).reshape(-1, 1), np.ones([1, Nx]))
dp_z[(Nz - h):, :] = dp_z[h-1::-1, :]

# 设置左右两层
dp0_x = 3 * v_max / dx * (8 / 15 - 3 * h / 100 + 1 * h**2 / 1500)
# 计算边缘的吸收因子
dp_x[:, :h] = np.dot(np.ones([Nz, 1]), dp0_x * np.power(np.arange(h, 0, -1) / h, 2).reshape(1, -1))
dp_x[:, (Nx - h):] = dp_x[:, (h-1)::-1]

###################################
    # 依据广义胡克定律求的弹性系数 #
###################################
rho1 = rho.copy()
rho2 = rho.copy()
# Coeffi1 和 Coeffi2 是沿 x 轴和 z 轴方向的PML吸收因子的系数.
# 它们根据PML吸收因子(dp_x 和 dp_z)和时间步长 dt 计算得出. 这些系数在波场更新方程中用于考虑PML的吸收效果.
Coeffi1 = (2 - dt * dp_x) / (2 + dt * dp_x)
Coeffi2 = (2 - dt * dp_z) / (2 + dt * dp_z)
# Coeffi3 和 Coeffi4 是与密度(rho)和空间步长(dx 和 dz)相关的系数, 用于考虑波场更新方程中的空间导数项.
Coeffi3 = 1 / rho1 / dx * (2 * dt / (2 + dt * dp_x))
Coeffi4 = 1 / rho2 / dz * (2 * dt / (2 + dt * dp_z))
# Coeffi5 和 Coeffi6 是与密度(rho)和速度(vp)的平方以及空间步长(dx 和 dz)相关的系数, 用于考虑波场更新方程中的速度和应力项.
Coeffi5 = rho * np.power(vmodel_pad, 2) / dx * (2 * dt / (2 + dt * dp_x))
Coeffi6 = rho * np.power(vmodel_pad, 2) / dz * (2 * dt / (2 + dt * dp_z))

#####################################
            # 迭代前初始化 #
#####################################

# 设置外部空间: 所有波场值均为空, 防止越界
NZ = Nz + 2 * nodr
NX = Nx + 2 * nodr
# 套上PML层之后的有效索引区域
Znodes = np.arange(nodr, NZ - nodr, 1)
Xnodes = np.arange(nodr, NX - nodr, 1)
# 原图有效索引区域
znodes = np.arange(nodr + h, nodr + h + nz)
xnodes = np.arange(nodr + h, nodr + h + nx)
# 套上PML层和外部空间的震源位置
sz_pad = nodr + h + sz
sx_pad = nodr + h + sx
# 初始化应力矩阵和速度有向分量有关矩阵
Ut = np.zeros([NZ, NX])
Uz = np.zeros([NZ, NX])
Ux = np.zeros([NZ, NX])
Vz = np.zeros([NZ, NX])
Vx = np.zeros([NZ, NX])
U = -1 * np.ones([nz, nx, nt])
Psum = -1 * np.ones([Nz,Nx])

def index_2dim(matrix, height_index_array, width_index_array):
    return matrix[height_index_array[:, np.newaxis], width_index_array]

print("开始进行时间迭代...")
for cur_time in range(nt):
    Ux[sz_pad, sx_pad] = Ux[sz_pad, sx_pad] + wave_source_ampl * source_wav[cur_time] / 2
    																			 # 震源点的外力补充情况
    Uz[sz_pad, sx_pad] = Uz[sz_pad, sx_pad] + wave_source_ampl * source_wav[cur_time] / 2
    Ut = Ux + Uz																 # Ut为Ux和Uz两个分力矩阵的结合
    U[:, :, cur_time] = index_2dim(Ut, znodes, xnodes)

    if (cur_time + 1) % 200 == 0:
        print("{}/{}".format(cur_time + 1, nt))

    Psum[:,:] = 0                                                                # 中间变量矩阵
    for i in range(1, nodr + 1):
        Psum = Psum + C[i-1] * (index_2dim(Ut, Znodes, Xnodes + i) - index_2dim(Ut, Znodes, Xnodes + 1 - i))
    Vx[nodr:NZ - nodr, nodr:NX - nodr] = Coeffi1 * index_2dim(Vx, Znodes, Xnodes) - Coeffi3 * Psum

    Psum[:, :] = 0
    for i in range(1, nodr + 1):
        Psum = Psum + C[i-1] * (index_2dim(Ut, Znodes + i, Xnodes) - index_2dim(Ut, Znodes + 1 - i, Xnodes))
    Vz[nodr:NZ - nodr, nodr:NX - nodr] = Coeffi2 * index_2dim(Vz, Znodes, Xnodes) - Coeffi4 * Psum

    Psum[:, :] = 0
    for i in range(1, nodr + 1):
        Psum = Psum + C[i-1] * (index_2dim(Vx, Znodes, Xnodes - 1 + i) - index_2dim(Vx, Znodes, Xnodes - i))
    Ux[nodr:NZ - nodr, nodr:NX - nodr] = Coeffi1 * index_2dim(Ux, Znodes, Xnodes) - Coeffi5 * Psum

    Psum[:, :] = 0
    for i in range(1, nodr + 1):
        Psum = Psum + C[i-1] * (index_2dim(Vz, Znodes - 1 + i, Xnodes) - index_2dim(Vz, Znodes - i, Xnodes))
    Uz[nodr:NZ - nodr, nodr:NX - nodr] = Coeffi2 * index_2dim(Uz, Znodes, Xnodes) - Coeffi6 * Psum
print("迭代完成!")

common_shot_gather = U[1, :, :].T

# 走时信息静默
muted_gather = common_shot_gather.copy()

x_array = np.arange(0, nx*dx, dx
v0 = vmodel[0,:] 
traveltimes = abs(np.cumsum(dx/v0) - np.cumsum(dx/v0)[sx])
for traceno in range(len(x_array)):
    muted_gather[0:int(traveltimes[traceno]/dt + source_wave_duration), traceno] = 0
    
# 对时间域进行下采样
# common_shot_gather = cv2.resize(common_shot_gather, dsize=(301, 400), interpolation=cv2.INTER_CUBIC) 

# 展示共炮集合图
fgr2, axs2 = plt.subplots(1,2, figsize = (16,6))
im1=axs2[0].imshow(common_shot_gather, cmap = plt.cm.seismic, vmin=-0.4, vmax=0.44, aspect='auto')
im2=axs2[1].imshow(muted_gather, cmap = plt.cm.seismic, aspect='auto')
axs2[0].set_title('Seismic wave structure (muting the forward wave)')
axs2[1].set_title('Seismic wave structure')
fgr2.colorbar(im1, ax = axs2[0])
fgr2.colorbar(im2, ax = axs2[1])
plt.show()

这个代码中, 我将走时静默的代码一并加入了进来, 左图为共源炮点图 (即单炮地震图像), 右图是它走时信息静默后的图像.

在这里插入图片描述

之前的单炮图像和走时信息静默图像, 可以发现, 采用更高级的边界吸收条件后, 波变"干净"了很多. 这是因为一些边界信息吸收不完全的地方和低阶差分对波拟合的误差都被很好地消除了.

2.4 复杂的弹性波正演可否实现RTM?

不知道为什么, 我尝试基于弹性波的正演过程还原RTM中上行波后进行互相关…但是效果并不这么好. 于是我有点怀疑代码的问题, 或者是我哪里理解有问题, 于是思考了几天.
最后我想出了一个思路, 不妨基于声波的二阶中心差分来构造上行波, 虽然上行波正演的过程基于的震源是弹性波的结构, 但是会不会有意想不到的效果呢? 当然, 推动我这个思路的缘由在于: 实际地质探测的过程中, 或者历史上的已有地震探测记录中, 可能根本没有上行波的地震资料, 因此没必要纠结上行波在正演的过程中是否足够真实, 毕竟RTM是一种手段, 并不需要多"像"真实情况, 因此这个波形可以脱离弹性波的范畴 (I guess…and I hope so).
结果如下:
在这里插入图片描述
效果非常amazing啊! 能得到完美的轮廓, 而且相比于声波波形的单炮RTM (炮点位置在中点) 那张图, 这里明显更"干净", 扰动少了很多. 于是, 照猫画虎, 我们试着模仿2.2中的RTM图像的叠加方法, 来看下弹性波RTM的叠加效果图:
在这里插入图片描述
效果还挺不错的. 相比于声波, 我们通过更加准确的弹性波, 更高阶的差分手段, 以及更高级的边界吸收条件为基础, 生成了更加 “干净” 的叠前RTM图像. 从结果上来说, RTM可以通过弹性波来实现, 而且效果非常好! (当然很大程度得以中心差分阶数和边界吸收条件).

3 存在的主要问题

简单总结这一周干的事情的意义:
目前来说, 我们可以在已知

  1. 正演的所有时间序列的波场信息 (弹性波)
  2. 初始速度模型

的情况下, 可以生成上面这种和速度模型高度接近的波形模拟图像.

  • 问题1: (应该好克服)
    我们需要提供一个初始速度模型, 这个倒是符合传统的FWI的观点, 但是有悖于DL-FWI的观念.
    但我们的思维也不见得如此死板, 如果说能以提供一个初始速度模型为代价, 来从而得到如此精确的速度模型波形模拟图像, 试想倘若用它来指导网络训练, 那么最终获得的速度模型将会多么精确?
    这个是可以谈的.

  • 问题2: (有点不确定)
    但是我最担忧的还是 " 正演的所有时间序列的波场信息 " 这个资料是否是工区现场能够提供的信息, 因为这个必须作为和上行波进行互相关才能得到RTM图像. 如果可以提供这个信息或者可以通过已知的单炮的山峰图逆向生成得到, 那么这个问题就可以推广到实际的预测. 否则RTM可能还是只是空中楼阁.
    在这里插入图片描述

最后再提一嘴RTM在空间意义上的优势:
叠前多炮数据本身一张炮面图像的纵向维度是时间而非 速度模型 采用的深度, 这一点加大了端到端的神经网络拟合的难度 (要拟合不同时空域的关系), 而且也会被质疑解释性.
但是RTM图像本身和速度模型的维度完全一致, 并且边缘结构上存在大量相似信息. 若以之作为网络的输入的辅助资料, 将极大提高了网络拟合的效率和准确性, 降低了拟合边缘的难度 (边缘结构样貌的拟合是目前DL-FWI网络拟合的最大难点).

4 下一步工作

继续完成上周提到的论文阅读, 看看有关论文是如何利用RTM来完成DL-FWI的任务.
这一点的继续了解, 希望能解决问题2中的担忧.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/755522.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据处理 | Matlab实现Lichtenberg算法的机器学习数据选择

文章目录 效果一览基本介绍源码设计参考资料效果一览 基本介绍 Matlab实现Lichtenberg算法的机器学习数据选择 Lichtenberg算法适用于回归和分类数据集,并根据数量和最大覆盖范围选择最佳算法。Lichtenberg算法(Lichtenberg algorithm,LA)是由Pereira等人于2021年提出的一种…

Python爬虫——urllib_下载

urlretrieve(url&#xff0c; filename)函数 url 代表的是下载的路径 filename文件的名字 下载网页: url_page "http://www.baidu.com" urllib.request.urlretrieve(url_page, baidu.html)下载图片: url_img "https://img0.baidu.com/it/u2751401762,34216…

VUE研究

1.v2与v3的区别 vue3对源码的管理根据模块进行拆分&#xff0c;在不同目录中对不同的模块进行分别维护&#xff1b; vue3是基于typescript语言进行开发的&#xff0c;这样可以进行更好的类型检查&#xff1b; vue3体积减小&#xff0c;去除了不常使用的API&#xff0c;Tree sha…

DevOps B站学习版(二)

学习地址&#xff1a; 01.DevOps的诞生_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1Pt4y1H7Zq/?p1&vd_source1f09c23f556b3d6a9b7706f8db12fa54%E3%80%81 正文开始 找到这个地方&#xff0c;修改 可以写成基于标签拉取和构建工程&#xff0c;下面也选择Tag即可…

Python 自学 day03 容器tuple(元组)的定义与使用,序列,字典,集合,多返回值传递,不定长参数函数

1. tuple 元组 1.1 元组的定义 定义&#xff1a;元组同列表一样&#xff0c;都是可以封装多个、不同类型的元素在内。但最大的不同点在于: 元组一旦定义完成﹐就不可修改。 1.2 元组的创建方法 t1 (1,111,1111,11,1111,222) #元组的定义方法 t2 (22,) …

Postman+Newman+Git+Jenkins+Slack 接口自动化和监控

目录 前言&#xff1a; 一、Newman 介绍&#xff1a; 1、简介 2、安装 3、检查 4、运行 二、Newman 命令行介绍&#xff1a; newman run [options] 测试结果配置 ------------------------------------分 割 线----------------------------------------------------…

2023年最新水果编曲软件FL Studio Producer Edition 21.0.3 Build 3517中文完美至尊解锁免费下载安装激活详细教程

fl studio21.0.3.3517中文解锁特别版是一款功能强大的编曲软件&#xff0c;也就是众所熟知的水果软件。它可以编曲、剪辑、录音、混音&#xff0c;让您的计算机成为全功能录音室。除此之外&#xff0c;这款软件功能非常强大&#xff0c;为用户提供了许多音频处理工具&#xff0…

《红蓝攻防构建实战化网络安全防御体系》读书笔记

作者&#xff1a;奇安信安服团队 ◆ 1.3 红队 各个团队在演练中的角色与分工情况如下。目标系统运营单位&#xff1a;负责红队整体的指挥、组织和协调。安全运营团队&#xff1a;负责整体防护和攻击监控工作。攻防专家&#xff1a;负责对安全监控中发现的可疑攻击进行分析和研…

LiveNVR监控流媒体Onvif/RTSP功能-安全控制HTTP接口鉴权开启禁止游客访问开启后401 Unauthorized如何播放调用接口

LiveNVR安全控制HTTP接口鉴权开启禁止游客访问开启后401 Unauthorized如何播放调用接口&#xff1f; 1、安全控制1.1、接口鉴权1.2、禁止游客访问 2、401 Unauthorized2.1、携带token调用接口2.1.1、获取鉴权token2.1.2、调用其它接口2.1.2.1、携带 CookieToken2.1.2.2、携带 U…

VUE- quill-editor 编辑器使用及自定义toobar详解

vue使用编辑器&#xff0c;这里讲解编辑器quil-editor 官网&#xff1a;https://quilljs.com/docs/modules/toolbar 1&#xff1a;安装quill-eidtor npm i quill1.3.6 --save 2&#xff1a;创建一个页面&#xff0c;再template里写入 <template><div class"…

@ConditionalOnMissingBean 不生效

还要一点需要注意的是 有顶级接口类型写接口类型,像下面这个也控制不住加载多个相同类型的Bean,因为父类最先加载,子类之间不能算作同一种类型Bean

Git详细安装教程

对于Git这块&#xff0c;我也算是个小白&#xff0c;最近在学习Git&#xff0c;所以趁此机会详细讲解一下Git的安装教程以及安装过程中遇到的问题&#xff0c;也欢迎大家对其补充&#xff0c;共同进步&#xff01; 1、下载Git Git的下载地址&#xff08;windows系统&#xff…

【STM32CubeIDE】 stm32f103的内部Flash读写,double数值读写

单片机stm32f103c8t6&#xff0c;程序存储器64Kb&#xff1a; 对其最后一页&#xff0c;第63页进行读写操作&#xff0c;空间1Kb。 写入一个32位的数据0x12345678到Flash首地址为0x0800FC00.则在Flash中存储情况如下&#xff1a; 即&#xff0c;低位地址存储数据的低位&#xf…

Python实现简易计算器

文章目录 一、需求分析1.1 功能分析1.2 性能分析 二、技术原理三、详细设计3.1 导入tkinter库3.2 定义全局变量3.3 定义添加函数3.4 定义结果函数3.5 定义清空函数3.6 创建主窗口并指定其大小和位置3.7 创建输入框3.8 创建数字和运算符按钮3.9 创建等于号和清除按钮 四、功能实…

解决打开excel时报错 “不能使用对象链接和嵌入”

问题截图 打开excel文件或者插入对象时&#xff0c;直接弹出不能使用对象链接和嵌入报错信息。 解决方法 按 winr 组合快捷键&#xff0c;打开运行&#xff0c;输入 dcomcnfg.exe 按回车确定 此时进入到组件服务管理界面&#xff0c;依次选择 组件服务-计算机-我的电脑-DOCM…

Vue+Node(Egg框架)实现文件上传保存至本地和下载文件

文章目录 1、前端代码2、后端代码 本文Node搭建的服务器是基于egg框架搭建&#xff0c;如果使用其他服务器端框架&#xff0c;可参考部分代码&#xff0c;不保证所有框架都能实现 实现效果&#xff1a; 1、前端代码 前端部分vue利用的element-ui文件上传组件&#xff0c;相关…

C++服务器框架开发11——编译调试1/cmake学习

该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见&#xff1a;[C高级教程]从零开始开发服务器框架(sylar) 上一篇&#xff1a;C服务器框架开发10——日志系统1~9代码 C服务器框架开发11——编译调试1/cmake学习 目前进度ubuntu下的cmake学习简单样例同…

【论文笔记】SINE: SINgle Image Editing with Text-to-Image Diffusion Models

声明 不定期更新自己精度论文&#xff0c;通俗易懂&#xff0c;初级小白也可以理解 涉及范围&#xff1a;深度学习方向&#xff0c;包括 CV、NLP 论文标题&#xff1a;SINE: SINgle Image Editing with Text-to-Image Diffusion Models 论文链接&#xff1a;https://www.seman…

ESXI 安装win10详细步骤

在esix安装win10安装过程遇到了坑&#xff0c;发现必须对具体选项进行设置后才可&#xff0c;做下记录&#xff1a; 1、CPU设置 &#xff12;、硬盘 3、网络适配器 4、驱动器 5、虚拟机选项

LangChain大型语言模型(LLM)应用开发(四):QA over Documents

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…