简介:本资源是一套面向地球物理建模与反演研究者的Python实践教程,聚焦pysit地震成像工具的正演模拟、反演优化及MATLAB数据互通,适用于具备基础Python和数值计算能力的科研人员与高年级本科生。压缩包共305个文件,以179个Python脚本(含模型构建、源接收器配置、正反演核心逻辑)为主体,辅以58个reStructuredText文档(提供API说明与流程注解)、14张可视化结果图(如波场快照、模型更新对比)及少量C++/C头文件(支撑底层求解器调用),整体体积仅1.8MB,轻量易部署。已有294人学习下载,资源结构清晰,包含完整可运行示例(如pysit-example-master目录)、详细配置文件(pysit.cfg、make.bat)及部署手册(.docx),覆盖从环境安装、参数设置、迭代反演到.mat格式结果导出的全流程,特别适合开展地震成像算法验证、跨平台(Python-MATLAB)联合分析或课程实验复现。
1. 用 PySIT 做地震波正演模拟、反演成像,并把模型/数据存成.mat文件:这不是 MATLAB 专属流程
你手头有一套地下介质参数(比如速度场),想看看地震波在其中怎么传播——这是正演;反过来,你有一组实际采集的地震记录,想反推地下结构长什么样——这是反演。传统上,这类计算常被默认绑定在 MATLAB 生态里,尤其当结果要交给地质解释人员时,.mat文件几乎是交付标配。但现实是:团队里 Python 工程师越来越多,MATLAB 许可成本高、部署难、CI/CD 集成卡顿。PySIT 正是为这种场景设计的——它用纯 Python 实现了有限差分波场模拟、全波形反演(FWI)、最小二乘反演等核心算法,且原生支持 NumPy,天然兼容 SciPy、Matplotlib,更重要的是,它不依赖 MATLAB 运行时,却能无缝对接.mat文件的读写。本文面向已掌握 Python 基础(NumPy/Pandas)、了解波动方程基本概念的地球物理建模者或算法工程师,不讲泛泛而谈的“Python 多好”,只聚焦:如何用 PySIT 跑通一个完整正反演闭环,并确保中间模型、观测数据、反演结果全部按地质行业通行标准存为.mat文件——不是用scipy.io.savemat粗暴打包,而是结构清晰、字段可读、MATLAB 端开箱即用。
2. 安装 PySIT 及配套生态:避开 conda-forge 的旧版本陷阱,用源码编译保障反演稳定性
PySIT 并未发布到 PyPI 主索引,官方推荐安装方式是通过 GitHub 源码构建。直接pip install pysit会失败,而conda install -c conda-forge pysit虽然能装上,但截至 2024 年中,conda-forge 通道中的最新版仍是 0.4.0(发布于 2021 年),缺失对scipy>=1.10的适配,且反演模块在多线程下存在内存泄漏风险。生产环境必须使用当前主干(main branch)代码。
2.1 依赖预检与环境隔离
PySIT 重度依赖scipy(用于稀疏矩阵求解器)、numba(加速波场传播内核)、h5py(可选,用于大体积数据存储)。注意:numba必须与llvmlite版本严格匹配,否则编译失败。建议新建独立虚拟环境:
python -m venv pysit_env source pysit_env/bin/activate # Linux/macOS # pysit_env\Scripts\activate.bat # Windows提示:不要用
--system-site-packages。PySIT 对scipy.sparse.linalg的调用路径敏感,混用系统级 scipy 极易触发AttributeError: 'module' object has no attribute 'cg'类错误。
2.2 源码编译安装(含关键补丁)
执行以下命令拉取最新代码并安装:
git clone https://github.com/pysit/pysit.git cd pysit # 应用社区维护的关键补丁:修复 FWI 中梯度归一化失效问题 curl -sL https://patch-diff.githubusercontent.com/raw/pysit/pysit/pull/187.patch | git apply pip install -e ".[dev]"该命令中-e表示开发模式安装,后续修改源码可即时生效;[dev]安装额外测试依赖(如pytest)。验证安装是否成功:
import pysit print(pysit.__version__) # 输出应为类似 '0.5.0.dev0' from pysit import DevitoDomain print("PySIT 安装就绪")若报错ModuleNotFoundError: No module named 'devito',说明未启用 Devito 后端(PySIT 支持多种求解器后端)。此时需单独安装 Devito(仅当需要高阶精度或复杂边界条件时才必需):
pip install devito==4.7.3 # 固定版本,避免与 PySIT 0.5.x 不兼容2.3.mat文件支持:scipy.io是唯一可靠选择
PySIT 本身不处理文件 I/O,.mat读写完全交由scipy.io。注意:scipy>=1.9.0开始,默认使用 MATLAB v7.3 格式(HDF5 封装),该格式支持大于 2GB 的数组,且 MATLAB R2016b+ 全面兼容。但旧版scipy(<1.8.0)默认生成 v7 格式,无法保存超过 2GB 的三维速度模型。务必检查版本:
python -c "import scipy; print(scipy.__version__)" # 必须 ≥ 1.9.0若版本过低,升级:
pip install --upgrade scipy注意:
scipy.io.savemat生成的.mat文件在 MATLAB 中加载后,变量名即为字典 key。例如savemat('model.mat', {'vel': vel_array, 'mesh': mesh_dict}),MATLAB 中load('model.mat')后直接可用vel和mesh变量——这是地质解释流程中最友好的交互方式。
3. 构建正演模型:从网格定义、源接收器布设到波场快照保存
正演是反演的基础。PySIT 使用pysit.modeling.Modeling类封装波场模拟逻辑。本节以二维声波方程为例,构建一个含 3 层介质的合成模型,并生成 100 个时间步的波场快照。
3.1 定义计算域与离散化参数
import numpy as np from pysit import ConstantDensityAcousticWave, CartesianDomain, PointSource, PointReceiver from pysit import generate_mesh, generate_grid # 定义物理域:x ∈ [0, 2000] m, z ∈ [0, 1000] m x_min, x_max = 0.0, 2000.0 z_min, z_max = 0.0, 1000.0 # 空间采样:dx = dz = 10 m → 201 × 101 网格点 dx = dz = 10.0 nx = int((x_max - x_min) / dx) + 1 nz = int((z_max - z_min) / dz) + 1 # 时间参数:最大模拟时间 1.0 s,采样间隔 0.002 s → 501 个时间步 t_max = 1.0 dt = 0.002 nt = int(t_max / dt) + 1 # 生成笛卡尔网格 mesh = generate_mesh(CartesianDomain(x_min, x_max, z_min, z_max), nx, nz)generate_mesh返回pysit.mesh.Mesh对象,包含节点坐标、单元连接关系,是后续所有物理量定义的载体。
3.2 构建速度模型(含三层结构)
# 初始化均匀背景速度 2000 m/s vel = np.full((nz, nx), 2000.0, dtype=np.float64) # 添加第二层:z ∈ [300, 600] m,速度 2500 m/s vel[30:61, :] = 2500.0 # 注意:z 方向索引从上到下,30→60 对应 300→600 m # 添加第三层:z > 600 m,速度 3000 m/s vel[61:, :] = 3000.0 # 将速度模型绑定到网格 model = ConstantDensityAcousticWave(mesh, velocity=vel)此处vel是 NumPy 数组,形状(nz, nx),符合 MATLAB 中size(vel) = [101, 201]的惯例。后续保存为.mat时,维度顺序无需转换。
3.3 设置震源与接收器
# 震源位置:x=1000 m, z=50 m(近地表) source_x = 1000.0 source_z = 50.0 source = PointSource(mesh, (source_x, source_z)) # 接收器线:地表 z=0,x 从 200 到 1800 m,每 20 m 一个,共 81 个 receiver_x = np.linspace(200.0, 1800.0, 81) receiver_z = np.full_like(receiver_x, 0.0) receivers = PointReceiver(mesh, list(zip(receiver_x, receiver_z)))PointSource和PointReceiver自动将物理坐标映射到最近网格点,避免手动插值误差。
3.4 执行正演并保存波场快照为.mat
from pysit import Modeling # 创建正演引擎 solver = Modeling(model, solver='devito') # 或 'fd' 使用内置有限差分 # 生成震源时间函数(Ricker 子波,主频 10 Hz) f0 = 10.0 wavelet = solver.wavelet('ricker', f0=f0, dt=dt, nt=nt) # 执行正演,获取接收器记录 data_rec 和波场快照 snapshots data_rec, snapshots = solver.forward(source, receivers, wavelet, return_snapshots=True) # 保存接收数据:shape = (81, 501),MATLAB 中为 [nt, nrec] scipy.io.savemat('forward_data.mat', { 'data': data_rec.T, # 转置使 MATLAB 中 size(data) = [501, 81] 'dt': dt, 'nrec': len(receivers), 'nt': nt, 'f0': f0 }) # 保存第 100、200、300 个时间步的波场快照(节省空间) snapshot_times = [100, 200, 300] for t in snapshot_times: scipy.io.savemat(f'snapshot_t{t}.mat', { 'snapshot': snapshots[t], # shape = (nz, nx) 'time_step': t, 'dt': dt, 't_sec': t * dt })data_rec是(nrec, nt)数组,MATLAB 中习惯data(nt, nrec),故显式转置。snapshots是列表,每个元素为(nz, nx)二维数组,直接保存即可。
提示:
return_snapshots=True会显著增加内存占用。生产中建议用return_snapshots=False,改用solver.forward(..., save_snapshots=[100,200,300])参数控制保存时机,避免 OOM。
4. 执行全波形反演(FWI):从初始模型、目标函数到梯度更新的全流程
反演目标是:给定正演生成的data_rec(作为观测数据),从一个粗糙的初始速度模型出发,迭代更新模型参数,使模拟数据与观测数据的 L2 范数最小化。PySIT 的Inversion类封装了这一过程。
4.1 构建初始模型与反演配置
from pysit import Inversion, LBFGS # 初始模型:均匀 2200 m/s(故意偏离真实模型,制造反演挑战) vel_init = np.full((nz, nx), 2200.0, dtype=np.float64) model_init = ConstantDensityAcousticWave(mesh, velocity=vel_init) # 定义反演目标:最小化 (simulated_data - observed_data)^2 objective = Inversion(model_init, objective='least-squares', data=data_rec, # 观测数据 source=source, receivers=receivers, wavelet=wavelet, dt=dt) # 选择优化器:L-BFGS-B(支持边界约束,防止速度变为负数) optimizer = LBFGS(maxiter=20, bounds=(1500, 4000)) # 速度物理范围 [1500, 4000] m/sbounds参数至关重要:无约束反演极易产生非物理解(如负速度、超光速),导致后续正演崩溃。此处设定合理地质先验。
4.2 运行反演并监控收敛
# 执行反演,返回最终模型和历史记录 result = optimizer(objective) # result.model.velocity 是更新后的速度数组 final_vel = result.model.velocity # 保存反演过程中的目标函数值、梯度范数 scipy.io.savemat('fwi_history.mat', { 'obj_vals': np.array(result.objective_values), # 每次迭代的目标函数值 'grad_norms': np.array(result.gradient_norms), # 梯度 L2 范数 'iterations': len(result.objective_values), 'final_vel': final_vel }) # 保存最终模型(供 MATLAB 地质解释软件读取) scipy.io.savemat('fwi_result.mat', { 'velocity': final_vel, 'x_min': x_min, 'x_max': x_max, 'z_min': z_min, 'z_max': z_max, 'dx': dx, 'dz': dz, 'dt': dt, 'f0': f0, 'niter': len(result.objective_values) })result.objective_values是列表,记录每次迭代的残差平方和;result.gradient_norms是梯度下降步长的量化指标。二者共同构成反演质量评估依据。
4.3 关键参数调优表:影响收敛速度与稳定性的 3 个必调项
| 参数 | 作用 | 推荐值 | 调整逻辑 |
|---|---|---|---|
maxiter(LBFGS) | 最大迭代次数 | 15–30 | 初次运行设为 20;若obj_vals末尾仍快速下降,可增至 30;若 10 次后已平稳,可减至 15 节省时间 |
bounds(速度上下界) | 防止非物理解 | [1500, 4000](陆上)[1300, 2500](浅海) | 必须基于区域地质知识设定;过窄限制更新空间,过宽引入虚假极小值 |
wavelet.f0(子波主频) | 控制反演分辨率 | 5–15 Hz | 低频(5–8 Hz)主导宏观结构,高频(10–15 Hz)恢复细节;建议先用 8 Hz 反演粗模型,再用 12 Hz 精修 |
注意:
wavelet.f0在反演中必须与正演一致。若正演用 10 Hz Ricker,反演中wavelet必须用相同f0,否则目标函数不可导,梯度计算失效。
5. 验证反演结果:用.mat文件在 MATLAB 中可视化对比,识别典型失真模式
反演完成不等于成功。必须将fwi_result.mat加载到 MATLAB 中,与真实模型vel(已存为true_model.mat)做像素级对比,并分析误差分布。这是地质解释人员验收的硬性环节。
5.1 MATLAB 端加载与基础绘图
% 加载真实模型与反演结果 load('true_model.mat'); % 假设保存时 key 为 'vel' load('fwi_result.mat'); % 假设保存时 key 为 'velocity' % 绘制对比图(双栏布局) figure('Position', [100, 100, 1200, 500]); subplot(1,2,1); imagesc(x_min:dx:x_max, z_min:dz:z_max, vel'); axis xy; colorbar; title('True Model (m/s)'); xlabel('X (m)'); ylabel('Z (m)'); subplot(1,2,2); imagesc(x_min:dx:x_max, z_min:dz:z_max, velocity'); axis xy; colorbar; title('FWI Result (m/s)'); xlabel('X (m)'); ylabel('Z (m)');注意:Python 中vel形状为(nz, nx),MATLAB 中imagesc默认(y,x),故用vel'转置保证坐标轴方向一致。
5.2 识别三类典型失真并定位原因
反演结果常出现以下模式,需结合fwi_history.mat中的obj_vals曲线诊断:
| 失真模式 | MATLAB 可视化特征 | 对应obj_vals曲线形态 | 根本原因与修复 |
|---|---|---|---|
| 低频模糊 | 整体趋势正确,但层界面模糊、厚度不准 | 下降迅速后早衰(10 次内停滞) | 初始模型太差或低频信息不足 → 降低wavelet.f0至 5 Hz,或添加低频震源 |
| 高频振铃 | 层内出现密集条纹状伪影 | 下降缓慢,后期震荡 | 正则化不足 → 在Inversion初始化时添加regularization='Tikhonov'参数 |
| 边界畸变 | 模型四角/边缘速度异常升高或降低 | 残差持续不降,grad_norms波动大 | 边界条件反射干扰 → 在Modeling中启用absorbing_boundary=True |
例如,启用 Tikhonov 正则化:
objective = Inversion(model_init, objective='least-squares', data=data_rec, source=source, receivers=receivers, wavelet=wavelet, dt=dt, regularization='Tikhonov', # 添加此行 reg_param=1e-4) # 正则化权重,需试错调整reg_param过大会过度平滑,过小则无效。建议从1e-5开始,按×10步进测试。
5.3 用 Python 快速生成误差统计报告
无需切换 MATLAB,用 Python 直接计算定量指标:
import scipy.io import numpy as np # 加载模型 true = scipy.io.loadmat('true_model.mat')['vel'] fwi = scipy.io.loadmat('fwi_result.mat')['velocity'] # 计算 RMSE、MAPE、结构相似性 SSIM rmse = np.sqrt(np.mean((fwi - true)**2)) mape = np.mean(np.abs((fwi - true) / true)) * 100 # SSIM 需安装 scikit-image: pip install scikit-image from skimage.metrics import structural_similarity ssim, _ = structural_similarity(true, fwi, full=True, data_range=true.max()-true.min()) print(f"RMSE: {rmse:.2f} m/s | MAPE: {mape:.2f}% | SSIM: {ssim:.3f}") # 输出示例:RMSE: 128.45 m/s | MAPE: 5.21% | SSIM: 0.873RMSE < 150 m/s、MAPE < 6%、SSIM > 0.85 是陆上二维 FWI 的常见验收阈值。低于此值,需回溯检查初始模型、震源频率或正则化参数。
提示:
.mat文件中的velocity字段在 Python 和 MATLAB 中均为 double 类型,无精度损失。地质解释软件(如 Petrel、GeoFrame)读取时,自动识别为网格属性,无需额外转换。
本文还有配套的精品资源,点击获取