目次
波動方程式の数値解法は、波動方程式フォワードモデリング、リバースタイムマイグレーション、フル波形反転の中核技術の1つです。本論文では,2次有限差分を用いて波動方程式を離散化し,次に波動方程式の数値解を実現し,媒質内での伝播過程をシミュレーションした。
NumPy は、通常、SciPy (科学 Python) および Matplotlib (描画ライブラリ) と一緒に使用されます。この組み合わせは、MatLab を置き換えるために広く使用されています。これは、Python を介してデータ サイエンスや機械学習を学習するのに役立つ強力な科学コンピューティング環境です。
SciPy には、最適化、線形代数、積分、補間、特殊関数、高速フーリエ変換、信号および画像処理、常微分方程式解法、および科学と工学で一般的に使用されるその他の計算のためのモジュールが含まれています。
Matplotlib は、Python プログラミング言語とその数値数学拡張パッケージ NumPy のビジュアル インターフェイスです。Tkinter、wxPython、Qt、GTK+ などの一般的な GUI ツールキットを使用してアプリケーションに埋め込まれた描画のためのアプリケーション プログラミング インターフェイス (API) を提供します。
この記事のコード部分では、NumPy パッケージと Matplotlib パッケージのみを使用します。
1. 原則:
1) 2次元音響波方程式:
ここで、u は音圧、f は音源中心の音圧、x/z は x/z 方向のサンプリング点、t は時間、v は速度です。
テイラーの公式を使用して展開すると、次のようになります。
2 つの式を加算すると、次のようになります。
次に、次のとおりです。
近似二次差分演算子:
第 2 中心差分演算子を使用して 2 次導関数を離散化します。
上記の式を音響波方程式に代入して、2 次の中心差分スキームを取得します。
その空間と時間の差の形式の概略図を次の図に示します。
2) 収束条件 (あまり明確ではありません)
3) レイクウェーブレット
Lake ウェーブレットは地震ウェーブレットの 1 つです。震源によって励起され、地下を伝播し、地上や井戸の中にいる人々が受信する地震波は、通常、下図に示すように、振動ウェーブレットと呼ばれる短いパルス振動です。
式は次のとおりです: f(t)=(1-2*(pi*f*t)^2)*exp(-(pi*f*t)^2)、ここで t は時間、f は主周波数です。 。
4) 2次元空間減衰関数
ソースの中心は 1、減衰なし、中心から離れるほど減衰が大きくなります。
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)) # 二维空间衰减函数
下図は、2次元空間全体の減衰係数h_x_z[150,150]を中心(衝撃源)として周囲に減衰していくh_x_z[150]の曲線を示しています。
5) 境界吸収条件 (あまり明確ではありません..)
機能: 音波が伝播するときに境界がないため、境界反射の問題がありません。ただし、フォワードシミュレーションでは観測範囲が限られているため、境界が存在する必要があり、境界吸収条件はエネルギーを可能な限り吸収し、境界反射を最小限に抑えることです。(私の理解です。議論を歓迎します) 以下は 2 つの境界吸収条件です。現在では、PML 条件の方が一般的に使用されています。
Clayton-Engquist モノ波吸収境界条件: Clayton らによって最初に発見され、推進されました。その微分式は次のとおりです。
その内: n は境界の外側法線方向、s は境界の接線方向です。
上記の式を離散化して、次のように上下左右の境界差の形式を取得します。
ここで、 N と M は境界のグリッド番号です。
レイノルズ境界条件: 2 次元音響波方程式の場合、2 次元音響波方程式の微分演算子を使用して次を取得できます。
上記の式を離散化すると、上下左右の境界の計算式が得られます。
2. プログラミングの実装
1) パラメータ設定:
- x/z 方向の長さは 1500m、x/z 方向の空間ステップは 5m、各方向のサンプリング点の数は 301 です。
- シミュレーション時間は 1 秒、時間ステップは 0.001 秒、時間サンプルの数は 1000 です。
- ソース周波数は 25Hz です。
- 空間減衰係数 0.5。
- 波の速度は固定、どの位置でも 3000m/s
- 音源位置は中心にあり、初期音圧はゼロです。
# 区域大小
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 ウェーブレットと 2 次元空間減衰関数
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) 境界吸収条件
# 边界条件
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) 波動方程式、反復公式:
# 迭代公式
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) コード全体は次のとおりです。
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)
参考:
波動方程式の数値解法(1)_音響波方程式の解法_MaT--2018のブログ-CSDNブログ
3. matlabによる2次元波動方程式の実現
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