Forward modeling (1): Python implementation of two-dimensional acoustic wave forward modeling program (central difference)

Table of contents

1. Principle: 

1) Two-dimensional acoustic wave equation:

​edit

2) Convergence conditions (not very clear)

3) Lake wavelet

4) Two-dimensional space attenuation function

 5) Boundary absorption conditions (not very clear..)

 2. Programming implementation

1) Parameter setting:

2) Reker wavelet and two-dimensional space attenuation function

3) Boundary absorption condition

4) Wave equation, iterative formula:

5) The whole code is as follows:

3. Realization of two-dimensional wave equation based on matlab 


Numerical solution of wave equation is one of the core technologies of wave equation forward modeling, reverse time migration and full waveform inversion. In this paper, the second-order finite difference is used to discretize the wave equation, and then the numerical solution of the wave equation is realized, and its propagation process in the medium is simulated.

NumPy is usually used together with SciPy (Scientific Python) and Matplotlib (drawing library). This combination is widely used to replace MatLab. It is a powerful scientific computing environment that helps us learn data science or machine learning through Python.

SciPy includes modules for optimization, linear algebra, integration, interpolation, special functions, fast Fourier transforms, signal and image processing, ordinary differential equation solving, and other calculations commonly used in science and engineering.

Matplotlib is a visual interface for the Python programming language and its numerical mathematics extension package NumPy. It provides an application programming interface (API) for drawing embedded in applications using common GUI toolkits such as Tkinter, wxPython, Qt or GTK+. 

The code part of this article only uses the NumPy and Matplotlib packages.

1. Principle: 

1) Two-dimensional acoustic wave equation:

Where u is the sound pressure, f is the sound pressure at the source center, x/z is the sampling point in the x/z direction, t is the time, and v is the velocity.

Using Taylor's formula to expand:

insert image description here


Adding the two formulas gives:

insert image description here


Then there are:

Approximate second-order difference operator :

insert image description here

Discretize the second derivative using the second central difference operator:

insert image description here

Substitute the above formula into the acoustic wave equation to get the second-order central difference scheme:

insert image description here

insert image description here

 The schematic diagram of its space and time difference format is shown in the figure below:

insert image description here                                          insert image description here

2) Convergence conditions (not very clear)

insert image description here

3) Lake wavelet

The Lake wavelet is one of the seismic wavelets. The seismic wave excited by the seismic source, propagated underground and received by people on the ground or in the well is usually a short pulse vibration, which is called a vibration wavelet, as shown in the figure below.

The formula is: f(t)=(1-2*(pi*f*t)^2)*exp(-(pi*f*t)^2), where t is the time and f is the main frequency.

4) Two-dimensional space attenuation function

The source center is 1, no attenuation, the farther away from the center, the greater the attenuation.

h_x_z =  np.zeros((Nx+1,Nz+1))  #np.exp(-0.25 * ((x - Nx//2)**2 + (z - Nz//2)**2))  二维空间衰减函数
h_x_z[Nx//2,Nz//2] = 1   # 在Nx//2,Nz//2处激发
h_x_z =  np.exp(-alpha ** 2 * ((x - Nx//2)**2 + (z - Nz//2)**2))  # 二维空间衰减函数
 

The figure below shows the curve of h_x_z[150], the entire two-dimensional space attenuation coefficient h_x_z, with [150,150] as the center (shock source), attenuates around.

 5) Boundary absorption conditions (not very clear..)

Function: There is no boundary when the sound wave propagates, so there is no boundary reflection problem. However, due to the limited observation range during forward simulation, there must be a boundary. The boundary absorption condition is to absorb energy as much as possible and minimize boundary reflection. (My understanding, welcome to discuss) The following are two boundary absorption conditions. The PML condition is more commonly used now.

Clayton-Engquist mono-wave absorbing boundary condition: It was first discovered and promoted by Clayton et al., and its differential expression is:

insert image description here

Among them: n is the outer normal direction of the boundary; s is the tangent direction of the boundary.
Discretize the above formula to get the upper, lower, left, and right boundary difference format as follows:
insert image description here
insert image description here
where:  insert image description here   N and M are the grid numbers of the boundary.

Reynolds boundary conditions: For the two-dimensional acoustic wave equation, the differential operator of the two-dimensional acoustic wave equation can be used to obtain:

insert image description here
By discretizing the above formula, the calculation formula of the upper, lower, left, and right boundaries can be obtained:

insert image description here

insert image description here

 2. Programming implementation

1) Parameter setting:

  1. The length in the x/z direction is 1500m, the space step in the x/z direction is 5m, and the number of sampling points in each direction is 301;
  2. The simulation time is 1s, the time step is 0.001s, and the number of time samples is 1000;
  3. The source frequency is 25Hz;
  4. Space attenuation factor 0.5;
  5. The wave speed is fixed, 3000m/s at any position
  6. The source location is at the center; the initial sound pressure is zero.
# 区域大小
Nx = 301
Nz = 301
# 空间间隔
dx = h
dy = h

# 时间采样数
Nt = 1000
# 时间步
dt = 1 / Nt

# 速度模型
v = np.ones((Nx+1,Nz+1)) * 3000
u = np.zeros((Nt+1,Nx+1,Nz+1))
h = 5

# 子波主频
fm = 25 
# 空间衰减因子
alpha = 0.5

# 迭代公式中的r
A = v **2 * dt ** 2 / h ** 2 
C = v * dt / h

2) Reker wavelet and two-dimensional space attenuation function

t = np.arange(0, Nt+1)
t0 = 0  # 延迟时间,相当于在t=t0时激发 ,震幅在t0时最大,相位也在此
s_t = (1 - 2 * (np.pi * fm * dt * (t - t0)) ** 2) * np.exp( - (np.pi * fm * dt * (t - t0)) ** 2)

x = np.arange(0,Nx+1)
z = np.arange(0,Nz+1)
x,z = np.meshgrid(x,z)

h_x_z =  np.zeros((Nx+1,Nz+1))  #np.exp(-0.25 * ((x - Nx//2)**2 + (z - Nz//2)**2))  二维空间衰减函数
h_x_z[Nx//2,Nz//2] = 1   # 在Nx//2,Nz//2处激发
h_x_z =  np.exp(-alpha ** 2 * ((x - Nx//2)**2 + (z - Nz//2)**2))  # 二维空间衰减函数

x0 = Nx // 2
z0 = Nz // 2
u0 = lambda r, s: 0.25*np.exp(-((r-x0)**2+(s-z0)**2))
JJ = np.arange(1,Nz)
II = np.arange(1,Nx)
II,JJ = np.meshgrid(II,JJ)

3) Boundary absorption condition

# 边界条件
ii = np.arange(Nx+1)
jj = np.arange(Nz+1)
# Clayton-Engquist-majda 二阶吸收边界条件
u[t+1,  0, jj] = (2 - 2 * C[ 0, jj] - C[ 0, jj] ** 2) * u[t,  0, jj] \
                        + 2 * C[ 1, jj] * (1 + C[ 1, jj]) * u[t,  1, jj] \
                        - C[ 2, jj] ** 2 * u[t,  2, jj] \
                        + (2 * C[ 0, jj] - 1) * u[t - 1,  0, jj] \
                        - 2 * C[ 1, jj] * u[t - 1,  1, jj]


# 下部
u[t+1, -1, jj] = (2 - 2 * C[ -1, jj] - C[ -1, jj] ** 2) * u[t,  -1, jj] \
                        + 2 * C[ -2, jj] * (1 + C[ -2, jj]) * u[t,  -2, jj] \
                        - C[ -3, jj] ** 2 * u[t,  -3, jj] \
                        + (2 * C[ -1, jj] - 1) * u[t - 1,  -1, jj] \
                        - 2 * C[ -2, jj] * u[t - 1,  -2, jj]

# 左部
u[t+1, ii,  0] = (2 - 2 * C[ii,  0] - C[ii,  0] ** 2) * u[t,  ii, 0] \
                        + 2 * C[ii,  1] * (1 + C[ii,  1]) * u[t,  ii, 1] \
                        - C[ii,  2] ** 2 * u[t,  ii, 2] \
                        + (2 * C[ii,  0] - 1) * u[t - 1,  ii, 0] \
                        - 2 * C[ii,  1] * u[t - 1,  ii, 1]
# 右部
u[t+1, ii, -1] = (2 - 2 * C[ii,  -1] - C[ii,  -1] ** 2) * u[t,  ii, -1] \
                        + 2 * C[ii,  -2] * (1 + C[ii,  -2]) * u[t,  ii, -2] \
                        - C[ii,  -3] ** 2 * u[t,  ii, -3] \
                        + (2 * C[ii,  -1] - 1) * u[t - 1,  ii, -1] \
                        - 2 * C[ii,  -2] * u[t - 1,  ii, -2]
#Reynolds 边界条件
u[t+1,ii, 0] = u[t,ii, 0] + u[t,ii, 1] - u[t-1,ii, 1] + C[ii, 1]*u[t,ii, 1] - C[ii, 0]*u[t,ii, 0] -C[ii, 2]*u[t-1,ii, 2] +C[ii, 1]*u[t-1,ii, 1]

u[t+1,ii,-1] = u[t,ii,-1] + u[t,ii,-2] - u[t-1,ii,-2] + C[ii,-2]*u[t,ii,-2] - C[ii,-1]*u[t,ii,-1] -C[ii,-3]*u[t-1,ii,-3] +C[ii,-2]*u[t-1,ii,-2]

u[t+1, 0,jj] = u[t, 0,jj] + u[t, 1,jj] - u[t-1, 1,jj] + C[ 1,jj]*u[t, 1,jj] - C[ 0,jj]*u[t, 0,jj] -C[ 2,jj]*u[t-1, 2,jj] +C[ 1,jj]*u[t-1, 1,jj]

u[t+1,-1,jj] = u[t,-1,jj] + u[t,-2,jj] - u[t-1,-2,jj] + C[-2,jj]*u[t,-2,jj] - C[-1,jj]*u[t,-1,jj] -C[-3,jj]*u[t-1,-3,jj] +C[-1,jj]*u[t-1,-2,jj]

4) Wave equation, iterative formula:

# 迭代公式
u[t+1,II,JJ] = s_t[t]*h_x_z[II,JJ]+A[II,JJ]*(u[t,II,JJ+1]+u[t,II,JJ-1]+u[t,II+1,JJ]+u[t,II-1,JJ])+(2-4*A[II,JJ])*u[t,II,JJ]-u[t-1,II,JJ]

5) The whole code is as follows:

import numpy as np
import imageio
import os
import pandas as pd
from matplotlib import pyplot as plt
# 解决中文问题
plt.rcParams['font.sans-serif'] = ['SimHei']
# 解决负号显示问题
plt.rcParams['axes.unicode_minus'] = False


Nx = 301
Nz = 301
Nt = 1000
v = np.ones((Nx+1,Nz+1)) * 3000
h = 5
fm = 25
alpha = 0.5
dt = 1 / Nt
dx = h
dy = h
A = v **2 * dt ** 2 / h ** 2 
C = v * dt / h

r = np.max(v)*dt/h
assert r < 0.707,f'r should < 0.707, but is {r}'
u = np.zeros((Nt+1,Nx+1,Nz+1))


t = np.arange(0, Nt+1)
t0 = 0  # 延迟时间,相当于在t=t0时激发 ,震幅在t0时最大,相位也在此
s_t = (1 - 2 * (np.pi * fm * dt * (t - t0)) ** 2) * np.exp( - (np.pi * fm * dt * (t - t0)) ** 2)

plt.plot(s_t)
plt.show()

x = np.arange(0,Nx+1)
z = np.arange(0,Nz+1)
x,z = np.meshgrid(x,z)
# print(((z - Nz//2)**2).shape)


h_x_z =  np.zeros((Nx+1,Nz+1))  #np.exp(-0.25 * ((x - Nx//2)**2 + (z - Nz//2)**2))  二维空间衰减函数
h_x_z[Nx//2,Nz//2] = 1   # 在Nx//2,Nz//2处激发
h_x_z =  np.exp(-alpha ** 2 * ((x - Nx//2)**2 + (z - Nz//2)**2))  # 二维空间衰减函数


JJ = np.arange(1,Nz)
II = np.arange(1,Nx)
II,JJ = np.meshgrid(II,JJ)

mode = 'c_e'
img_path = './2_order'
if not os.path.exists(img_path):
    os.makedirs(img_path)

for t in range(2,Nt):
    print('\rstep {} / {}'.format(t ,Nt), end="")
    
    # 边界条件
    ii = np.arange(Nx+1)
    jj = np.arange(Nz+1)
    if mode == 'c_e':
        # Clayton-Engquist-majda 二阶吸收边界条件
        u[t+1,  0, jj] = (2 - 2 * C[ 0, jj] - C[ 0, jj] ** 2) * u[t,  0, jj] \
                                + 2 * C[ 1, jj] * (1 + C[ 1, jj]) * u[t,  1, jj] \
                                - C[ 2, jj] ** 2 * u[t,  2, jj] \
                                + (2 * C[ 0, jj] - 1) * u[t - 1,  0, jj] \
                                - 2 * C[ 1, jj] * u[t - 1,  1, jj]


        # 下部
        u[t+1, -1, jj] = (2 - 2 * C[ -1, jj] - C[ -1, jj] ** 2) * u[t,  -1, jj] \
                                + 2 * C[ -2, jj] * (1 + C[ -2, jj]) * u[t,  -2, jj] \
                                - C[ -3, jj] ** 2 * u[t,  -3, jj] \
                                + (2 * C[ -1, jj] - 1) * u[t - 1,  -1, jj] \
                                - 2 * C[ -2, jj] * u[t - 1,  -2, jj]

        # 左部
        u[t+1, ii,  0] = (2 - 2 * C[ii,  0] - C[ii,  0] ** 2) * u[t,  ii, 0] \
                                + 2 * C[ii,  1] * (1 + C[ii,  1]) * u[t,  ii, 1] \
                                - C[ii,  2] ** 2 * u[t,  ii, 2] \
                                + (2 * C[ii,  0] - 1) * u[t - 1,  ii, 0] \
                                - 2 * C[ii,  1] * u[t - 1,  ii, 1]
        # 右部
        u[t+1, ii, -1] = (2 - 2 * C[ii,  -1] - C[ii,  -1] ** 2) * u[t,  ii, -1] \
                                + 2 * C[ii,  -2] * (1 + C[ii,  -2]) * u[t,  ii, -2] \
                                - C[ii,  -3] ** 2 * u[t,  ii, -3] \
                                + (2 * C[ii,  -1] - 1) * u[t - 1,  ii, -1] \
                                - 2 * C[ii,  -2] * u[t - 1,  ii, -2]
    if mode == 're':
        #Reynolds 边界条件
        u[t+1,ii, 0] = u[t,ii, 0] + u[t,ii, 1] - u[t-1,ii, 1] + C[ii, 1]*u[t,ii, 1] - C[ii, 0]*u[t,ii, 0] -C[ii, 2]*u[t-1,ii, 2] +C[ii, 1]*u[t-1,ii, 1]

        u[t+1,ii,-1] = u[t,ii,-1] + u[t,ii,-2] - u[t-1,ii,-2] + C[ii,-2]*u[t,ii,-2] - C[ii,-1]*u[t,ii,-1] -C[ii,-3]*u[t-1,ii,-3] +C[ii,-2]*u[t-1,ii,-2]

        u[t+1, 0,jj] = u[t, 0,jj] + u[t, 1,jj] - u[t-1, 1,jj] + C[ 1,jj]*u[t, 1,jj] - C[ 0,jj]*u[t, 0,jj] -C[ 2,jj]*u[t-1, 2,jj] +C[ 1,jj]*u[t-1, 1,jj]

        u[t+1,-1,jj] = u[t,-1,jj] + u[t,-2,jj] - u[t-1,-2,jj] + C[-2,jj]*u[t,-2,jj] - C[-1,jj]*u[t,-1,jj] -C[-3,jj]*u[t-1,-3,jj] +C[-1,jj]*u[t-1,-2,jj]


    # 迭代公式
    u[t+1,II,JJ] = s_t[t]*h_x_z[II,JJ]*v[II, JJ]**2*dt**2+A[II,JJ]*(u[t,II,JJ+1]+u[t,II,JJ-1]+u[t,II+1,JJ]+u[t,II-1,JJ])+(2-4*A[II,JJ])*u[t,II,JJ]-u[t-1,II,JJ]


    
    plt.imshow(u[t], cmap='gray_r') # 'seismic' gray_r
    # plt.axis('off')  # 隐藏坐标轴
    plt.colorbar()
    if t % 10 == 0:
        plt.savefig(os.path.join(img_path, str(t) + '.png'),
                    bbox_inches="tight", # 去除坐标轴占用空间
                    pad_inches=0.0) # 去除所有白边
        plt.pause(0.05)
        
    plt.cla()
    plt.clf()  # 清除所有轴,但是窗口打开,这样它可以被重复使用
plt.close()

# 保存gif
filenames=[]
for files in os.listdir(img_path ):
    if files.endswith('jpg') or files.endswith('jpeg') or files.endswith('png'):
        file=os.path.join(img_path ,files)
        filenames.append(file)

images = []
for filename in filenames:
    images.append(imageio.imread(filename))

imageio.mimsave(os.path.join(img_path,  'wave.gif'), images,duration=0.0001)

reference:

Numerical solution of the wave equation (1)_Solution of the acoustic wave equation_MaT--2018's Blog-CSDN Blog

3. Realization of two-dimensional wave equation based on matlab

close all
clear
clc

% 此程序是有限差分法实现声波方程数值模拟


%% 参数设置
delta_t = 0.001; % s
delta_s = 10; % 空间差分:delta_s = delta_x = delta_y (m)
nx = 800;
ny = 800;
nt = 1000;
fmain = 12.5;
%loop:按照10000s为一次大循环;slice代表每隔1000s做一个切片
loop_num = 3;
slice = 1000;
slice_index = 1;
%% 初始化
%震源
t = 1:nt;
t0 = 50;
s_t = (1-2*(pi*fmain*delta_t*(t-t0)).^2).*exp(-(pi*fmain*delta_t*(t-t0)).^2);%源
%一个loop代表向后计算10000s,目的是减少内存消耗
%间隔1000s保存一张切片
num_slice = nt*loop_num/slice;
U_loop(1:ny,1:nx,1:num_slice) = 0;
U_next_loop(1:ny,1:nx,1:2) = 0;
%初始化数组变量
w(1:ny,1:nx) = 0;
U(1:ny,1:nx,1:nt) = 0;
w(400,400) = 1;
V(1:ny,1:nx) = 2000;
A = V.^2*delta_t^2/delta_s^2;
B = V*delta_t/delta_s;

%% 开始计算
JJ = 2:ny-1;
II = 2:nx-1;
start_time = clock;
for loop = 1:loop_num
    fprintf('Loop=%d\n',loop)
    for i_t = 2:nt-1
        if(loop>1)
            s_t(i_t) = 0;
        end
        %上边界
        U(1,II,i_t+1) = (2-2*B(1,II)-A(1,II)).*U(1,II,i_t)+2*B(1,II).*(1+B(1,II)).*U(2,II,i_t)...
            -A(1,II).*U(3,II,i_t)+(2*B(1,II)-1).*U(1,II,i_t-1)-2*B(1,II).*U(2,II,i_t-1);
        %下边界
        U(ny,II,i_t+1) = (2-2*B(ny,II)-A(ny,II)).*U(ny,II,i_t)+2*B(ny,II).*(1+B(ny,II)).*U(ny-1,II,i_t)...
            -A(ny,II).*U(ny-2,II,i_t)+(2*B(ny,II)-1).*U(ny,II,i_t-1)-2*B(1,II).*U(ny-1,II,i_t-1);
        %左边界
        U(JJ,1,i_t+1) = (2-2*B(JJ,1)-A(JJ,1)).*U(JJ,1,i_t)+2*B(JJ,1).*(1+B(JJ,1)).*U(JJ,1+1,i_t)...
                            -A(JJ,1).*U(JJ,1+2,i_t)+(2*B(JJ,1)-1).*U(JJ,1,i_t-1)-2*B(JJ,1).*U(JJ,1+1,i_t-1);
        %右边界
        U(JJ,nx,i_t+1) = (2-2*B(JJ,nx)-A(JJ,nx)).*U(JJ,nx,i_t)+2*B(JJ,nx).*(1+B(JJ,nx)).*U(JJ,nx-1,i_t)...
                            -A(JJ,nx).*U(JJ,nx-2,i_t)+(2*B(JJ,nx)-1).*U(JJ,nx,i_t-1)-2*B(JJ,nx).*U(JJ,nx-1,i_t-1);
        %递推公式
        U(JJ,II,i_t+1) = s_t(i_t).*w(JJ,II)+A(JJ,II).*(U(JJ,II+1,i_t)+U(JJ,II-1,i_t)+U(JJ+1,II,i_t)+U(JJ-1,II,i_t))+...
                            (2-4*A(JJ,II)).*U(JJ,II,i_t)-U(JJ,II,i_t-1);
        if(mod(i_t,100)==0)
            run_time = etime(clock,start_time);
            fprintf('step=%d,total=%d,累计耗时%.2fs\n',i_t+(loop-1)*nt,nt*loop_num,run_time)
            U_loop(:,:,slice_index) = U(:,:,i_t);
            slice_index = slice_index +1;
        end
    end
    %处理四个角点
    KK = 1:nt;
    U(1,1,KK) = 1/2*(U(1,2,KK)+U(2,1,KK));
    U(1,nx,KK) = 1/2*(U(1,nx-1,KK)+U(2,nx,KK));
    U(ny,1,KK) = 1/2*(U(ny-1,1,KK)+U(ny,2,KK));
    U(ny,nx,KK) = 1/2*(U(ny-1,nx,KK)+U(ny,nx-1,KK));
    %% 为下一次loop做准备
    fprintf('step=%d,total=%d,累计耗时%.2fs\n',i_t+1+(loop-1)*nt,nt*loop_num,run_time)
    U_loop(:,:,slice_index) = U(:,:,nt);
    slice_index = slice_index +1;
    U_next_loop(:,:,1)=U(:,:,nt-1);
    U_next_loop(:,:,2)=U(:,:,nt);
    U(:,:,:) = 0;
    U(:,:,1) = U_next_loop(:,:,1);
    U(:,:,2) = U_next_loop(:,:,2);
end

%% 制作动图
fmat=moviein(num_slice);
filename = 'FDM_4_homogenerous.gif';
for II = 1:num_slice
    pcolor(U_loop(:,:,II));
    shading interp;
    axis tight;
    set(gca,'yDir','reverse');
    str_title = ['FDM-4-homogenerous  t=',num2str(delta_t*II*100),'s'];
    title(str_title)
    drawnow; %刷新屏幕
    F = getframe(gcf);%捕获图窗作为影片帧
    I = frame2im(F); %返回图像数据
    [I, map] = rgb2ind(I, 256); %将rgb转换成索引图像
    if II == 1
        imwrite(I,map, filename,'gif', 'Loopcount',inf,'DelayTime',0.1);
    else
        imwrite(I,map, filename,'gif','WriteMode','append','DelayTime',0.1);
    end
    fmat(:,II)=getframe;
end
movie(fmat,10,5);
%% 绘图为gif
pcolor(U_loop(:,:,num_slice))
shading interp;
axis tight;
set(gca,'yDir','reverse');
str_title = ['FDM-4-homogenerous  t=',num2str(delta_t*num_slice*100),'s'];
title(str_title)
colormap('Gray')
filename = [str_title,'.jpg'];
saveas(gcf,filename)

%% 耗时
toc

Guess you like

Origin blog.csdn.net/u014655960/article/details/129181059