简介:Geoist是一款面向地球科学原型研究的开源Python库,由CEA地球物理研究所重力团队维护,聚焦势场分析法、重力数据处理与调整、地球物理学反演、大地测量数据同化与地震目录分析等方向。其算法基于贝叶斯反演框架,并深度整合NumPy、SciPy、Pandas、Matplotlib等主流科学计算组件,适合科研人员与地球物理工程师快速验证重力、地震相关原型思路。资源包共805个文件,压缩包大小64.39MB,以py源码、ipynb示例、csv观测数据、h5/cof网格模型文件以及rst/html文档为主,另含shp矢量、txt说明等辅助材料,目录结构便于按模块检索。目前已有1354人学习或下载。压缩包内可直接获取完整Geoist代码库与应用示例,结合其贝叶斯反演实现、势场正演与延拓工具,可快速开展重力异常分析、地震目录挖掘和时间序列建模;MIT许可也方便在自有项目中二次修改与集成。
1. 一条实测剖面数据,怎样在半小时内变成可汇报的异常图
手里攥着一条实测重力剖面的 CSV,几十万行,项目组只给你半小时,要判断异常体大概埋多深、倾向哪边、边界在哪。用通用科学计算库当然能算,但从数据读取、散点网格化到滤波延拓、最后出图,每一步都得自己攒代码,遇到坐标系不一致还要现场调半天。geoist 就是冲这个场景来的——一个面向地球科学原型研究的开源地球物理 Python 库,把重力、磁法数据处理里的高频操作打包成顺手可调的工具,让物探人员把时间花在看结果而不是造轮子。这篇文章按我实际使用路径展开:先看它的数据组织与算子设计,再走一遍从网格化到反演的最小可复现流程,最后把让我翻过车的几个坑和验证套路一并列出来。新手可以直接照着跑通第一版,熟手可以重点关注参数边界和反演可信度验证。
2. geoist 的数据组织与算子设计:原型研究为什么需要专用地球物理库
2.1 通用科学计算库做物探,痛点到底在哪
很多人第一次接触 geoist 时会问:NumPy、SciPy、Matplotlib 已经覆盖了数值计算和绘图,为什么还要多此一举?答案是它们确实覆盖了底层运算,但没有覆盖地球物理数据的语义。物探数据的处理链条里,散点测量数据要规则化成网格,网格要做向上延拓、求导、化极,这些操作里的坐标变换、边缘处理、单位换算,如果全手写,一个延拓算子就要几百行,而且每换一份数据就要重调一次边界条件。
我刚开始做模拟项目X的重磁数据处理时,纯用 NumPy 写过一次向上延拓。流程是:先做 FFT,构造波数响应滤波器,乘完再逆变换,再处理边界。逻辑不复杂,但坐标系一换成 UTM 投影,发现波数单位从弧度/米变成了弧度/千米,整套结果全错。这种跟物探语义强相关的坑,通用库不会替你兜着。geoist 把这类操作收敛成了带明确地球物理含义的算子,你传进去的是数据网格和延拓高度,出来的是延拓后的网格,中间单位换算和波数域滤波由库替你管。
另一个痛点是原型迭代的速度。做方法研究时,今天要试向上延拓,明天要试解析信号,后天要跑欧拉反褶积。用通用库,每次都要把数据重新读进来、重新组织、重新画图。geoist 的算子式结构刚好适配这种流程:数据进了标准容器,算子直接传数据对象,出图函数原样接住,三步连跑,半天能把一套思路验证完。
2.2 数据容器与坐标体系:geoist 怎么组织网格和剖面数据
用 geoist 做原型研究前,最好先花十分钟搞懂它的数据组织方式,不然后面写代码会频繁撞到数据结构不匹配的报错。通常一个标准网格化数据有三个并行的数组组成:经度(或 X)坐标数组、纬度(或 Y)坐标数组、物理场值数组。它们形状完全一致,映射关系是坐标数组里同位置的元素对应着场值。
这里有个关键习惯:geoist 系列工具里,网格数据普遍优先使用坐标数组 + 场值数组的组合,而不是直接塞给函数一个二维矩阵。这样设计的好处是坐标和值分离,做掩膜、裁剪、变网格间距时,不需要反复用索引去猜某个点的实际坐标。剖面数据则相反,通常只有一维的长度数组和对应的场值数组,没有网格化的坐标矩阵。后面第 5 章会讲到,把剖面当网格用是最容易犯的数据结构错误。
坐标体系上,geoist 的处理函数一般默认网格坐标是投影后的平面坐标(比如 UTM 坐标,单位是米)。如果你手里是经纬度,最好先做投影转换,尤其在计算延拓高度和场源深度时,单位不一致会让结果出现量级上的离谱偏差。延拓 500 米,用经纬度算的代码跑得通,但答案已经不是“米”尺度了。
2.3 算子式设计:从网格化、滤波到延拓的统一调用结构
geoist 内部的常用模块主要包括数据模拟与网格生成、散点插值网格化、位场处理、反演框架等几个大类。位场处理里的典型算子有向上延拓、方向导数解析信号、总梯度模、化极等,反演框架里则把正演算子、反演目标函数、正则化项组织在了一起。
示例目录结构(版本不同会有差异,用 dir() 自查) geoist.gridder → 网格生成、散点网格化 geoist.pf → 位场处理:延拓、导数、解析信号、化极 geoist.inversion → 反演框架:最小二乘、正则化与约束 geoist.gravity → 重力异常正演模拟除了目录结构,真正的算子式风格体现在参数组织上。经典写法是把一个完整的处理对象(网格数据、坐标系、参数)打包后再调用求解器,而不是把每个参数当独立散件传来传去。代码维护上这一点收益很明显:同一份实测数据要对比不同延拓高度时,只需复制一行算子并修改高度参数,不用改数据读取逻辑。网格化、滤波、延拓在这个框架里都遵循相似的输入输出约定,组网式的调用方式很适合反复做“参数对比试验”的原型阶段。
3. 用 geoist 跑通最小工作流:从散点到网格再到异常图
3.1 安装与最小导入:版本差异和依赖检查
常见做法是用 pip 直接安装:
# 建议先建一个干净的虚拟环境,避免和已有科学计算包互相干扰 python -m venv geoist_env source geoist_env/bin/activate # Windows 下用 geoist_env\Scripts\activate pip install geoist依赖方面,geoist 对 NumPy、SciPy、Matplotlib 有硬性依赖,安装时 pip 一般会自动带进来。如果本地 Python 版本较新而安装过程报编译错误,通常要手动先把 NumPy 升到和当前 Python 版本匹配的版本,再装 geoist;在较老的 Linux 环境下,缺了系统级 BLAS/LAPACK 库会导致 SciPy 装不上,这时用系统包管理器先装libopenblas-dev之类的再重试。
装完后做一个最简导入验证,顺便看版本:
import numpy as np import matplotlib.pyplot as plt from geoist import gridder print(np.__version__) print(plt.get_backend()) print(dir(gridder)[:10]) # 常规操作:拉出模块里可用的函数这里主要做两件事:一是确认所有依赖都能加载;二是用dir()看一眼当前版本的 gridder 里有哪些函数。geoist 的不同版本之间函数命名有差异,与其死记教程里的函数名,不如先建立“装好后自己拉取函数列表”的习惯。最后这行只打印前 10 个名字,避免终端输出刷屏。
3.2 散点测量数据网格化:参数怎么设才不翻车
野外采集的数据几乎都是散点:测点间距不均匀、局部缺数、个别坏点带有很大的尖峰。直接拿去画等值线会看到一团乱麻,所以网格化是几乎一切后续处理的前置步骤。下面这组代码演示了从一份模拟的 CSV 散点数据生成规则网格的全过程:
import pandas as pd import numpy as np from geoist import gridder # data.csv 中简化包含三列:X(m)、Y(m)、gravity_anomaly(mGal) data = pd.read_csv('data.csv') x = data['X'].values y = data['Y'].values anom = data['gravity_anomaly'].values # 网格化范围直接取散点范围,注意留 5% 左右的边距,避免延拓时边缘畸变 area = (x.min() - 50, x.max() + 50, y.min() - 50, y.max() + 50) # 输出网格规模:影响后续 FFT 类算子的空间分辨率 shape = (128, 128) # 常见网格化算法:minimum_curvature 对区域重力数据稳定,适合原型期快速出图 gx, gy, gz = gridder.interpolate( x, y, anom, shape=shape, area=area, algorithm='minimum_curvature', damping=1e-8 )这里几个参数值得展开说:
shape=(128, 128):输出网格在 X、Y 两个方向上的点数。点数越多,网格越细,但计算量和内存占用也越大。对原型验证来说,先跑 128×128 通常足够,不要一上来就 512×512。algorithm='minimum_curvature':最小曲率法能生成比较光滑的曲面,适合位场数据。如果原始数据里存在明显的线性构造,可以换成近邻法或克里金法试一版,不做过多假设。damping=1e-8:最小曲率迭代求解中的阻尼系数。值调大,结果会更平滑但会削弱真实异常细节;调太小,迭代容易发散。原型阶段先保持默认,等画图后看到明显的细碎抖动,再逐步加大。
网格化的结果直接决定了后续向上延拓和反演的输入质量。这里最容易踩的坑是区域范围取得太贴近散点边界,导致边缘处网格化插值外推产生很大的假异常,后续延拓会把边缘噪点进一步放大。留边距是最简单的缓解手段。
3.3 出图:等值线、色标与坐标标注
网格化完成后的第一步永远是出图,眼见不对就不要继续往下做延拓和反演。用 Matplotlib 直接画等值线:
import matplotlib.pyplot as plt fig, ax = plt.subplots(figsize=(8, 6)) # 用 pcolormesh 或 contourf 都行;原型研究里 contourf 能更直观地看梯度变化 cs = ax.contourf(gx, gy, gz, levels=30, cmap='jet') fig.colorbar(cs, ax=ax, label='Gravity Anomaly (mGal)') ax.set_xlabel('X (m)') ax.set_ylabel('Y (m)') ax.set_aspect('equal') # 保证 X、Y 方向比例一致,否则异常体形态会被拉伸 ax.grid(alpha=0.3) plt.show()set_aspect('equal')这句务必加上。如果 X、Y 单位都是米却缺了这句,图会默认按画布比例拉伸,圆形异常体看着像椭圆形,给你一个错误的第一印象。原型阶段判断异常体的形态、倾向、边界,全依赖图面比例正确。
另外建议把色标范围vmin、vmax设为百分位数而不是默认的最小最大值。实测数据里常有几个坏点把色标拉得很开,导致主体异常全部糊在一片颜色里:
vmin, vmax = np.percentile(gz, [2, 98]) cs = ax.contourf(gx, gy, gz, levels=30, cmap='jet', vmin=vmin, vmax=vmax)这样画出来的图才能看清异常的主体形态,也方便和下一节的解析信号结果做对比。
4. 原型研究三板斧:向上延拓、解析信号与欧拉反褶积
4.1 向上延拓:压制浅部噪声的第一个滤波算子
向上延拓是把观测平面向上抬高到一定高度后重新计算异常场。物理含义很直接:浅部小规模异常衰减快,深部大规模异常相对保留更多,所以延拓相当于一个低通滤波器。原型研究里,它常被用来先把数据里的高频毛刺压下去,让后续反演更稳定。
from geoist.pf import upward_continue # 注意:height 的单位必须和网格坐标一致。 # 这里网格坐标是米,height=500 表示上延 500 米。 gz_up = upward_continue(gz, height=500, shape=shape) fig, ax = plt.subplots(figsize=(8, 6)) cs = ax.contourf(gx, gy, gz_up, levels=30, cmap='jet', vmin=vmin, vmax=vmax) fig.colorbar(cs, ax=ax, label='Upward Continued (mGal)') plt.show()参数上最重要的就是height和网格坐标的单位匹配。我有一次用经纬度坐标系的数据直接做上延,height 填了 500,结果出来的异常形态完全没变化,后来意识到经纬度和米差了三个数量级,500 在这种坐标体系里几乎等于没延拓。换成投影坐标重跑,效果立竿见影。
上延高度的选择没有绝对标准,常见做法是用一组高度去试:比如 200 米、500 米、1000 米各出一张图,对比看高频噪声什么时候被压住,同时看大尺度背景异常有没有被削出畸变。选一个“浅层细节还能看到但噪声被压制”的高度即可。
4.2 解析信号与总梯度模:圈定异常体边界时看什么
解析信号(也叫总梯度模)是把水平导数和垂向导数组合成一个不依赖磁化方向的量。对重力数据来说,它的极大值位置通常对应密度异常体的边界投影;对磁法数据,它对场源位置指示更稳健。原型研究阶段,解析信号图比原始异常图更容易判断边界。
from geoist.pf import total_gradient_amplitude # 输入需要网格的 X、Y 坐标和场值 tga = total_gradient_amplitude(gx, gy, gz_up) fig, ax = plt.subplots(figsize=(8, 6)) cs = ax.contourf(gx, gy, tga, levels=30, cmap='hot') fig.colorbar(cs, ax=ax, label='TGA') ax.set_aspect('equal') plt.show()画出来后重点找 tga 值带里的分界线,而不是看颜色深浅本身。边界通常表现为条带状高值区,高值带最陡的位置就是异常体边界在平面上的投影。这个结果对网格质量很敏感,在上一步已经做了上延的情况下,毛刺被压平,tga 的边界线会连贯很多。
对比原始异常图和 tga 图是一个好习惯。原始异常图上,边界靠肉眼估值;tga 图上,边界靠高值带定位,相互印证才能下结论。原型研究里不要只看一张图就写进报告。
4.3 欧拉反褶积:场源深度快速估计与两个关键参数
欧拉反褶积是半自动估算场源位置的经典方法。它利用异常场及其导数满足的欧拉齐次方程,通过滑动窗口拟合出场源坐标和深度。它不需要精确知道场源形态,但需要你先给一个结构指数(structural index),这个参数直接决定拟合出来的深度可不可信。
import numpy as np from geoist.pf import euler # 将网格数据按台站列表形式传给 euler 反演;窗口大小取 20~30 个数据点 # structural_index 是结构指数:球体约 3、圆柱体约 2、接触带约 1 result = euler.euler_3d( gx.ravel(), gy.ravel(), gz_up.ravel(), structural_index=3, windows=25, size=20 ) # 返回内容通常是多个解坐标,筛选时先按误差和位置范围过滤 print(result.shape)windows和structural_index是两个最需要调的经验参数:
structural_index:取值越小,解的深度越浅;取值越大,解的深度偏向更深。如果你不确定场源形态,就用球体(3)做一次、接触带(1)做一次,看两个结果的结构是否都落在同一区域。若两者给出的位置截然不同,说明数据信噪比不够,欧拉解的可靠性要打问号。windows:滑动窗口的数据点数。窗口太小,拟合噪声大;窗口太大,会把不同源的异常混在一个窗口里。对 128×128 的网格,常规在 20 至 30 之间试,先固定窗口遍历投影范围,看解在平面上的聚集程度。
欧拉反褶积的输出通常很散,直接全部展示会看到满屏乱点。我的筛选习惯是先画出解的平面位置,再按反演误差阈值丢弃误差最大的 30%,只保留空间上聚集度高的解。这一步是原型研究里最接近“玄学”的环节,多试几组参数,找到稳定的解集才可信。
5. 避坑指南:geoist 实战中最容易翻车的 5 个环节
5.1 网格化方法选错导致边缘异常畸变
现象:延拓后的图在四角出现极强的正负异常条纹,中间区域的异常形态反而变化不大。
原因:网格化时area取得边界刚好顶到散点边缘,或者使用了不适合外推的插值方法。最小曲率在散点覆盖范围外的外推区域通常会产生不稳定的振荡,延拓的 FFT 计算会把这种振荡放大到刺眼。
解决:把网格化area外扩 5% 到 10% 的边长距离,给边缘留缓冲。延拓后裁剪回原始测区范围,再画图。如果外扩还压不住边缘条纹,就把damping调大一个数量级,并换一种网格化算法(如近邻插值)做交叉验证。
5.2 反演结果对网格间距极其敏感
现象:只是把网格shape从 128×128 调成 256×256,欧拉反褶积给出的深度就从 500 米变成了 900 米。
原因:欧拉方程拟合依赖导数的数值近似,网格间距变了,水平导数和垂向导数的数值精度都变了,窗口覆盖数据的物理范围也变了。反演的目标物源不一定变了,但拟合解被带偏。
解决:在原型阶段固定一个网格间距,横向对比不同算法和不同参数,而不要频繁改网格间距。如果一定要细化网格,就同步缩小欧拉窗口的物理尺寸(即调小windows或size),尽量让每次窗口内的采样范围保持一致。
5.3 坐标单位混用:米、度、弧度的经典事故
现象:延拓高度填 500,图出来和原图几乎一样;欧拉深度反演出 5000,远超测区范围。
原因:坐标数据是经纬度,但代码里假设成米。FFT 域的波数计算最怕这种情况,一旦单位不一致,整个振幅谱都被替换到错误的空间频率尺度。
解决:进入 geoist 流程前先把经纬度投影到 UTM 或高斯-克吕格坐标,确保gx、gy、height、depth都是同一个长度单位。检查方法很简单:print(gx.min(), gx.max()),如果数值范围是几百万量级但坐标跨度只有几度,说明投影没做。
5.4 剖面数据当成网格数据用
现象:调用位场处理算子时报array has incompatible shape,或者画等值线时提示二维数组和一维数组混用。
原因:一条实测剖面的数据是一维的,只有里程和场值两列,但网格化算子期望的是二维网格坐标。直接把一维数组丢进去,很多算子内部会尝试按网格形状 reshape,报错只是时间问题。
解决:无论最终目标是延拓还是欧拉反演,先把剖面散点网格化成二维网格,再进算子。对纯剖面目标,考虑直接用一维滤波和滑动拟合,不必强行套网格化算子的接口。至少要准备一版二维网格数据作为统一输入格式。
5.5 版本升级后 API 对不上:别硬背函数名
现象:照着某份旧资料敲from geoist.pf import upward_continue,显示ImportError;改成from geoist.pf import upward_continuation也不行,来回试到崩溃。
原因:这类开源库在 0.x 版本迭代中经常改模块名和函数名,旧资料已经过期。教程里给的名字分散在不同版本里,没有一个能直接对上。
解决:每次换版本后,先用dir(gridder)和dir(pf)列出当前模块的可用接口。不要死记函数名,而是按功能找名字:看到包含continuat、analytic、euler的条目,基本就能对上。再不行就看文档字符串:print(module.func.__doc__)直接读参数说明。
6. 反演结果靠不靠谱:用合成模型做闭环验证
原型研究里最忌讳的场景是:拿到一块陌生数据,跑完欧拉反褶积,给了个深度 600 米,大家围过来都觉得合理,但没人验证过这个 600 米到底是不是算法凑出来的。我一般会养成一个习惯,在正式处理实测数据前,先构造一个已知答案的合成模型,走一遍从正演到反演的完整链路。
下面是用简单球体模型做的合成测试:
import numpy as np from geoist import gridder G = 6.674e-11 # 万有引力常数 rho = 500.0 # 剩余密度 500 kg/m³ R = 50.0 # 球体半径 50 m z = 150.0 # 球心埋深 150 m # 生成规则网格,网格范围扩大到球体位置之外,便于观察异常衰减 gx, gy = gridder.regular(area=(-300, 300, -300, 300), shape=(61, 61)) # 球体重力异常公式:正演生成理论异常,并转换为 mGal 数量级 r_sq = (gx - 100)**2 + (gy - 80)**2 + z**2 anom = 4 * np.pi * G * rho * R**3 * z / (3 * r_sq**1.5) * 1e5 # 叠加高斯噪声,模拟真实观测误差 rng = np.random.default_rng(42) noise = rng.normal(0, 0.05, size=anom.shape) anom_noisy = anom + noise这段代码里,正演公式用的是球体的经典重力异常近似,球心放在 (100, 80, 150) 处,埋深 150 米是本轮测试的已知答案。正演完成后,把噪声叠加进去,噪声标准差为 0.05 mGal,接近实测数据中常见的仪器噪声水平。
然后把anom_noisy当作“实测数据”,正常走一遍网格化、向上延拓、欧拉反褶积,最后对比欧拉解的位置和深度与真实值的偏差。如果合成模型的深度解落在 110 到 190 米之间,说明参数组合对这个数据质量是适用的,可以把同一组窗口和结构指数放心地用到实测数据上。如果偏差很大,就反过来调参数,直到合成模型先过关。
这个闭环验证就是我的“后悔药”:实测数据处理前先交点学费,好过处理到最后一版出个没法解释的深度。养成这个习惯之后,欧拉反褶积的“玄学感”会少很多,因为你知道算法在受控条件下的表现边界。希望这个套路帮你也少走几次弯路。
本文还有配套的精品资源,点击获取