简介:本资源是一套基于MATLAB实现的弹性波动方程有限差分法数值模拟代码包,面向地球物理、声学仿真、结构动力学等领域的本科生、研究生及科研初学者,用于理解波动方程离散化原理与数值求解实践。压缩包含30个文件,以28个.m脚本为主(涵盖初始化fdInitArray、边界处理fdInitBound、差分更新fdModUD、模型加载fdLoadModel、结果可视化plotcoloura/plotTraces等核心模块),辅以2个.mat数据文件(wncEdge.mat和wncUnity.mat)提供预设介质参数与初始场,整体仅46KB,轻量易读,便于逐行调试与教学演示。已有245人学习下载,适合希望掌握时-空域中心差分离散、稳定性控制、位移场迭代更新及MATLAB高效数组运算的用户。读者可直接运行获得弹性波在均匀介质中的传播快照与波形记录,配套函数命名规范、模块职责清晰,是深入理解地震波正演建模与数值方法落地的优质入门范例。
1. 这不是“跑个脚本就出图”的MATLAB练习——它是一套可复现、可调试、带物理边界的弹性波有限差分全链路实现
你打开fdModUD.m,发现它不依赖任何Toolbox(连Signal Processing Toolbox都不调用),却能稳定推进上千时间步;你加载wncEdge.mat,里面存的不是简单数组,而是预计算的四阶精度边界插值权重矩阵;你运行plotptwist2.m,它输出的不是单帧位移快照,而是带相位旋转校正的横波偏振轨迹动画。这不是教学示例,而是一个面向地震波前建模真实需求打磨过的有限差分系统:它把弹性波动方程的张量形式(σ_ij = C_ijkl ε_kl + η ∂ε_kl/∂t)拆解为纵波/横波耦合更新逻辑,用fdInitMod2.m构建非均匀层状介质模型,靠pedgeQuad2b.m实现二阶精度吸收边界,最终通过fdSEGY2.m直接导出符合SEGY Rev1标准的二进制地震数据体。适合需要验证波场传播机理、调试各向异性参数敏感性、或为全波形反演(FWI)准备合成数据的地球物理建模者——尤其当你手头只有MATLAB基础环境(R2018a及以上),且必须避开GPU加速依赖时,这套纯CPU向量化实现反而更可控、更易断点追踪。
2. 弹性波动方程的离散化不是套公式:从连续张量方程到MATLAB数组索引映射
2.1 为什么必须用速度-应力双变量格式?——避免数值频散与伪影的底层约束
弹性波动方程在各向同性介质中常被简化为位移形式(如摘要中给出的ρ∂²u/∂t² = ∇·(c²∇u)),但该形式在高波数区域会引入严重频散,且难以自然嵌入自由表面、吸收边界等物理条件。本项目采用速度-应力交错网格(staggered-grid velocity-stress FD),将速度分量v_x、v_z和应力分量σ_xx、σ_zz、σ_xz分别定义在空间网格的不同偏移位置。其核心离散方程组为:
ρ ∂v_x/∂t = ∂σ_xx/∂x + ∂σ_xz/∂z ρ ∂v_z/∂t = ∂σ_xz/∂x + ∂σ_zz/∂z ∂σ_xx/∂t = λ ∂v_x/∂x + 2μ ∂v_x/∂x + λ ∂v_z/∂z ∂σ_zz/∂t = λ ∂v_z/∂z + 2μ ∂v_z/∂z + λ ∂v_x/∂x ∂σ_xz/∂t = μ (∂v_x/∂z + ∂v_z/∂x)其中λ、μ为Lamé常数,由密度ρ和纵波速α、横波速β推导:μ = ρβ²,λ = ρα² − 2μ。这种格式天然满足应力-应变本构关系,且中心差分在交错网格上能保持二阶精度,显著抑制网格频散。MATLAB中不显式声明符号变量,而是通过数组索引偏移实现物理量定位:v_x存于(i,j),σ_xx存于(i+0.5,j),σ_xz存于(i+0.5,j+0.5)—— 这直接决定了所有差分算子的索引偏移量。
提示:查看
fdInitArray.m中vx,vz,sxx,szz,sxz的预分配尺寸。你会发现vx是Nx × Nz,而sxx是(Nx+1) × Nz,sxz是(Nx+1) × (Nz+1)。这种尺寸设计正是交错网格的内存体现,强行统一尺寸会导致边界越界或物理量错位。
2.2 时间推进:三步龙格-库塔法(RK3)替代显式欧拉——稳定性与精度的平衡点
显式欧拉法(u^{n+1} = u^n + Δt·f(u^n))虽简单,但CFL条件苛刻(Δt ≤ 0.6·min(Δx,Δz)/max(α,β)),且局部截断误差为O(Δt²)。本项目在fdModUD.m中采用三阶龙格-库塔法(RK3),其递推结构为:
% Step 1: k1 = f(u^n) k1_vx = fdComputeVxDeriv(sxx, sxz, rho, dx, dz); k1_vz = fdComputeVzDeriv(sxz, szz, rho, dx, dz); k1_sxx = fdComputeSxxDeriv(vx, vz, lam, mu, dx, dz); % ... 其他应力分量k1计算 % Step 2: u^(n+1/2) = u^n + 0.5*Δt*k1; k2 = f(u^(n+1/2)) vx_half = vx + 0.5*dt*k1_vx; % ... 计算k2_vx, k2_vz等 % Step 3: u^(n+1) = u^n + Δt*(2/3*k2 - 1/3*k1) vx = vx + dt*(2/3*k2_vx - 1/3*k1_vx); vz = vz + dt*(2/3*k2_vz - 1/3*k1_vz); sxx = sxx + dt*(2/3*k2_sxx - 1/3*k1_sxx); % ... 更新全部5个场变量该格式局部误差为O(Δt⁴),允许在相同CFL数下使用更大时间步长,同时保持线性稳定性域足够覆盖典型地震波频带(10–50 Hz)。实际运行中,fdInitGen.m会根据模型最大波速max_vel和最小网格间距min_dh自动计算推荐dt,并写入结构体par.dt。
2.2.1 RK3在MATLAB中的向量化实现关键:避免for循环嵌套
fdComputeVxDeriv函数不使用for i=2:Nx-1, for j=2:Nz-1,而是利用MATLAB原生数组切片:
function dvxdt = fdComputeVxDeriv(sxx, sxz, rho, dx, dz) % sxx: (Nx+1) x Nz, sxz: (Nx+1) x (Nz+1), rho: Nx x Nz % 计算 ∂σ_xx/∂x + ∂σ_xz/∂z → 结果尺寸 Nx x Nz d_sxx_dx = (sxx(2:end,:) - sxx(1:end-1,:)) / dx; % 差分后尺寸 Nx x Nz d_sxz_dz = (sxz(:,2:end) - sxz(:,1:end-1)) / dz; % 差分后尺寸 (Nx+1) x Nz % 注意:d_sxz_dz需在x方向取平均以匹配vx网格 d_sxz_dz_avg = 0.5 * (d_sxz_dz(1:end-1,:) + d_sxz_dz(2:end,:)); % → Nx x Nz dvxdt = (d_sxx_dx + d_sxz_dz_avg) ./ rho; end此处d_sxz_dz_avg的构造是关键:因sxz定义在(i+0.5,j+0.5),其z方向差分结果位于(i+0.5,j),需再沿x方向平均才能落到vx(i,j)网格点。这种索引对齐错误是初学者最常遇到的“波场发散”根源。
3. 边界与源项:从数学理想到物理可实现的工程落地
3.1 吸收边界不是加个系数那么简单——pedgeQuad2b.m实现的二阶PML等效
无限介质中的波传播需人工截断计算域,传统海绵层(sponge layer)在大角度入射时反射率高。本项目采用二次多项式完美匹配层(Quadratic PML),其核心是将坐标拉伸为复数:∂/∂x → (1 + i·σ_x(x)/ω)⁻¹ ∂/∂x,其中σ_x(x)在边界内按二次函数增长。pedgeQuad2b.m预计算了该PML在交错网格上的离散权重,生成wncEdge.mat中的wedge_x,wedge_z矩阵(尺寸与对应场变量一致)。应用时只需在差分算子中乘以这些权重:
% 在fdModUD.m中更新应力时(以sxx为例) d_vx_dx = (vx(2:end,:) - vx(1:end-1,:)) / dx; sxx = sxx + dt * (lam .* d_vx_dx + mu .* d_vx_dx) .* wedge_x; % wedge_x尺寸为(Nx+1) x Nz,自动广播匹配wedge_x在内部区域为1,在PML层内从1平滑衰减至0.01,衰减曲线由fdInitBound.m根据PML厚度par.pml_thick(默认20格)和最大衰减系数par.pml_max(默认100)生成。实测表明,该PML在45°入射角下反射率低于−60 dB,远优于简单指数衰减。
3.2 震源注入:力偶矩源(moment tensor)而非点脉冲——fdInitForce.m的物理建模逻辑
地震震源本质是介质内力偶矩释放,本项目支持六分量力偶矩张量M = [Mxx Mzz Mxz Mzx Mzx Mzz](对称,实际3个独立分量)。fdInitForce.m将其离散为网格点上的等效力:
% Mxx分量作用于σ_xx方程:∂σ_xx/∂t += Mxx * δ(x-x0)δ(z-z0) * δ(t-t0) % 在MATLAB中,用高斯型空间分布近似δ函数: [xg,zg] = meshgrid(1:Nx,1:Nz); dist2 = (xg - x0).^2 + (zg - z0).^2; gauss = exp(-dist2 / (2*sigma_x*sigma_z)); force_sxx = Mxx * gauss * (1/(sigma_x*sigma_z*sqrt(pi))); % 注入到sxx场的对应位置(考虑交错网格偏移) sxx(round(x0)+1, round(z0)) = sxx(round(x0)+1, round(z0)) + force_sxx;sigma_x,sigma_z由par.src_sigma控制,默认0.5格,确保源频谱主频与网格分辨率匹配(避免混叠)。对比简单点源,力偶矩源能自然产生P波与S波的相对振幅比,这对验证各向异性参数至关重要。
3.2.1 检查源项是否生效:三步验证法
- 时域验证:运行单步(
par.nt = 1),检查sxx,szz,sxz在源点附近是否出现预期符号模式(如Mxx>0时,源点右侧sxx为正,左侧为负); - 频域验证:对
vx在源正上方接收点提取时序,fft(vx_rec)应显示主频在1/(2*pi*par.src_sigma)附近; - 能量守恒验证:计算全域动能
0.5*sum(rho.*(vx.^2+vz.^2),'all')与应变能0.5*sum((1/lam).*sxx.^2+(1/lam).*szz.^2+(1/mu).*sxz.^2,'all')之和,无耗散时应基本恒定(允许浮点误差<1e-12)。
4. 可视化与数据导出:超越imagesc的物理意义还原
4.1plotptwist2.m:横波偏振轨迹的相位校正算法
横波(S波)偏振方向携带介质各向异性信息,但常规位移图imagesc(vx)或quiver无法直观呈现。plotptwist2.m实现瞬时相位旋转校正:
% 对vx, vz做Hilbert变换得到解析信号 vx_a = hilbert(vx); vz_a = hilbert(vz); % 计算瞬时相位角θ(t) = atan2(imag(vz_a), imag(vx_a)) theta = angle(vz_a ./ vx_a); % 将每个采样点的(vx,vz)绕原点旋转-θ,得到径向分量vr和切向分量vt vr = vx.*cos(theta) + vz.*sin(theta); vt = -vx.*sin(theta) + vz.*cos(theta); % 绘制(vr, vt)随时间变化的轨迹(Lissajous图) plot(vr(:), vt(:), '.k', 'MarkerSize', 1); xlabel('Radial component'); ylabel('Tangential component');该图中闭合椭圆表示线性偏振,圆形表示圆偏振,复杂形状表示非均匀各向异性。此方法无需预设参考相位,直接从数据本身提取偏振演化,是识别裂缝方向的关键步骤。
4.2fdSEGY2.m:生成工业级SEGY文件的字段填充逻辑
SEGY格式要求严格字节对齐与字段语义。fdSEGY2.m不仅写入trace数据,还填充关键二进制头(Binary Header)和扩展文本头(Extended Textual Header):
| 字段名 | SEGY位置 | 填充值来源 | 物理意义 |
|---|---|---|---|
SampleInterval | bytes 111–112 | round(par.dt*1e6) | 采样间隔(微秒) |
NumberOfSamples | bytes 115–116 | par.nt | 每道采样点数 |
DataFormatCode | bytes 117–118 | 1 | IEEE浮点格式 |
SourceX | bytes 181–184 | par.src_x | 震源X坐标(米) |
GroupX | bytes 189–192 | rec_x(i) | 第i个检波器X坐标 |
特别注意:fdSEGY2.m将vx和vz分别存为独立SEGY文件(vx.segy,vz.segy),并设置TraceHeader.TraceIdentificationCode = 1(CMP道)和5(垂直分量),符合SEG标准。用户可用OpendTect或SeisSpace直接加载,无需额外转换。
4.2.1 快速验证SEGY完整性:segy_info命令行工具
在Linux/Mac终端执行(需安装segyio):
pip install segyio python -c "import segyio; f=segyio.open('vx.segy'); print(f.bin['Samples']); print(len(f.trace[0]))"输出应显示Samples字段值等于par.nt,且首道长度与之完全一致。若不等,说明fdSEGY2.m中fwrite的precision参数未设为'float32',导致字节错位。
5. 调试与性能优化:当波场“炸开”或“不动”时,查什么?
5.1 波场爆炸(数值不稳定)的五级排查清单
| 级别 | 检查项 | 命令/操作 | 预期结果 | 失败含义 |
|---|---|---|---|---|
| L1 | CFL数是否超限 | cfl = par.dt * max(par.vel_p, par.vel_s) / min(par.dx, par.dz) | cfl < 0.6 | 网格太粗或时间步太大,重跑fdInitGen.m |
| L2 | 密度/波速是否为零或NaN | `any(isnan(par.rho(:))) | any(par.rho<=0)` | |
| L3 | 边界权重是否全1 | max(abs(wedge_x(:)-1)) | <1e-10 | wncEdge.mat未正确加载,检查路径 |
| L4 | RK3中间步是否溢出 | 在fdModUD.m中k1_vx计算后加assert(all(isfinite(k1_vx(:)))) | 无报错 | 某个差分算子除零(如rho=0) |
| L5 | 内存是否碎片化 | memory命令查看PhysicalMemory.Available | >2GB | 多次运行未clear all,重启MATLAB |
5.2 加速技巧:从向量化到内存布局优化
- 预分配所有中间数组:
fdInitArray1.m已完成,但若修改模型尺寸,务必重新运行该脚本,避免MATLAB动态扩容; - 禁用图形渲染:运行前执行
set(0,'DefaultFigureVisible','off'),plotcoloura.m等绘图函数将跳过屏幕绘制,提速30%; - 启用多线程BLAS:在MATLAB命令行输入
maxNumCompThreads(0),让底层线性代数库自动使用全部CPU核心; - 避免
parfor陷阱:本项目未用parfor,因FD更新存在严格时序依赖;若强行并行,会在sxx(i,j)更新时读取未完成的vx(i+1,j),导致结果随机。
注意:
fdCREWES.m包含一个隐藏的CPU亲和性设置(feature('SetNumWorkerThreads', 4)),适用于Linux服务器。Windows用户需注释此行,否则MATLAB可能报错。
最后,若需快速生成测试快照,直接运行:
fdInitGen; fdLoadModel; fdInitMod2; fdInitForce; for it=1:100, fdModUD; end; plotcolourk(vx, vz, 'time', 100);这行命令组合将跳过所有初始化检查,直奔第100步波场可视化——是验证安装完整性的黄金指令。
本文还有配套的精品资源,点击获取