← 返回首页 ← Back to Home

莫霍面反演程序教程

Moho Inversion Tutorial

基于 Uieda & Barbosa 2017 方法与新版球面棱柱体正演的快速反演实现
Fast inversion based on Uieda & Barbosa 2017 with modern spherical tesseroid forward modelling
Github 参考文献 References

页面导航

Page Navigation

程序概览

这个程序的核心思想来自 Uieda 与 Barbosa 在 2017 年提出的球坐标系下快速非线性重力反演方法, 但正演部分已经替换为新版球面棱柱体(tesseroid)(https://www.fatiando.org/)实现。这样做保留了原始方法清晰的反演框架, 同时让整体计算速度有了明显提升,也让代码更容易维护和扩展。

反演的本质是一个带正则化的非线性最小二乘问题:在已知观测重力场 $g_{\mathrm{obs}}$ 的情况下, 寻找一组莫霍面起伏 $x$,使得正演重力场 $g(x)$ 尽可能接近观测,同时保持模型的空间平滑性。

方法来源

Gauss-Newton 迭代、Bott 对角 Jacobian 和 Armijo 线搜索的组合框架,沿用 Uieda & Barbosa (2017) 的反演骨架。

主要改进

使用 harmonica 新版球面棱柱体正演,计算效率更高,适合更大区域或更高分辨率试验。

程序结构

MohoLayer 负责建模和正演,MohoInversion 负责反演、统计与评估。

适用任务

月球或其他行星体壳幔边界反演、合成模型测试、独立测试点(含地震点)外推评估。

Overview

This program is built on the fast nonlinear gravity inversion method proposed by Uieda & Barbosa (2017) in spherical coordinates, but with the forward modelling engine replaced by the modern tesseroid implementation from Fatiando a Terra. This preserves the original clear inversion framework while significantly improving computation speed and code maintainability.

At its core, the inversion solves a regularized nonlinear least-squares problem: given observed gravity $g_{\mathrm{obs}}$, find Moho undulations $x$ such that the forward-modelled gravity $g(x)$ best fits the observations while maintaining spatial smoothness.

Method Origin

Combined framework of Gauss-Newton iteration, Bott diagonal Jacobian, and Armijo line search, following the inversion skeleton of Uieda & Barbosa (2017).

Key Improvement

Uses harmonica's modern tesseroid forward engine for higher efficiency, suitable for larger regions or higher-resolution experiments.

Code Structure

MohoLayer handles modelling and forward computation; MohoInversion handles inversion, statistics, and evaluation.

Applications

Lunar or planetary crust-mantle boundary inversion, synthetic model testing, independent test-point (including seismic) extrapolation evaluation.

数学原理

1. 目标函数

反演把莫霍面起伏 $x \in \mathbb{R}^{N}$($N = n_{\mathrm{lat}} \times n_{\mathrm{lon}}$)作为未知参数, 目标函数采用经典的 Tikhonov 正则化形式:

$$\varphi(x) \;=\; \bigl\lVert g(x) - g_{\mathrm{obs}} \bigr\rVert_2^2 \;+\; \mu^2 \,\bigl\lVert L\,(x - x_0) \bigr\rVert_2^2$$

其中 $g(x)$ 是球面棱柱体正演算子,$x_0$ 是初始模型,$L$ 是一阶有限差分算子, $\mu \ge 0$ 是正则化系数。$\mu = 0$ 时退化为纯数据拟合。

2. 一阶差分算子 $L$

$L$ 把每个相邻网格对的差异写进矩阵的一行,分别覆盖经向和纬向。 对位置 $(i, j)$ 的格点,定义索引 $k(i, j) = i \cdot n_{\mathrm{lon}} + j$,则

$$L_{\mathrm{lon}}: \quad x_{i,\,j+1} - x_{i,\,j}, \qquad L_{\mathrm{lat}}: \quad x_{i+1,\,j} - x_{i,\,j}$$

最终 $L$ 是把这两块拼起来的稀疏矩阵,形状为 $(n_{\mathrm{edges}},\, N)$。 正则项 $\lVert L(x - x_0) \rVert^2$ 实际上是一个离散的"梯度能量",惩罚相邻网格之间过大的起伏跳变。

3. Bott 对角 Jacobian

严格的 Jacobian $\partial g_i / \partial x_j$ 需要对每个体素求偏导,计算量极大。 Bott (1960) 给出的近似是:每个网格点的重力效应主要来自其正下方那块体素,因此 Jacobian 近似为对角矩阵:

$$J_{ii} \;=\; 2\pi G\, \lvert \Delta\rho_i \rvert \,\times\, 10^{5}\quad \text{[mGal/m]}$$

这里取密度差的绝对值是关键 — 这样 $J^\top J$ 才是恒正对角阵,保证后续线性系统正定可解。 系数 $10^{5}$ 是把 m/s² 换算到 mGal 的单位转换因子。

实现细节:原始代码中 Jacobian 直接使用了带符号的 density_array, 会让 $J^\top J$ 的某些对角元为负,破坏系统正定性。本版本统一改为 $\lvert \Delta\rho \rvert$。

4. Gauss-Newton 迭代

对目标函数做二阶近似,得到迭代格式:

$$\bigl(J^\top J + \mu^2\, L^\top L\bigr)\, \delta x \;=\; -\bigl(J^\top r + \mu^2\, L^\top L\,(x - x_0)\bigr)$$

其中残差 $r = g(x) - g_{\mathrm{obs}}$。每步更新通过 Armijo 回溯线搜索确定步长 $\alpha$:

$$x \;\leftarrow\; x + \alpha\, \delta x, \qquad \varphi(x + \alpha\,\delta x) \;\le\; \varphi(x) + c_1\, \alpha\, \nabla\varphi^\top \delta x$$

实现中默认 $c_1 = 10^{-4}$,初值 $\alpha = 1$,每次失败折半,最多 30 次。 每步迭代结束后还会做安全裁剪 $x \le h - r_{\mathrm{ref}}$,保证莫霍面始终位于观测面以下。

5. 收敛判据

满足下列任一条件即停止迭代:

$$\frac{\varphi_k - \varphi_{k+1}}{\lvert \varphi_k \rvert} \;<\; \mathrm{gtol}, \qquad \frac{\lVert \nabla\varphi \rVert}{N} \;<\; 10^{-2}\,\mathrm{gtol}, \qquad k \;\ge\; \mathrm{max\_iter}$$

Mathematical Background

1. Objective Function

The inversion treats Moho undulations $x \in \mathbb{R}^{N}$ ($N = n_{\mathrm{lat}} \times n_{\mathrm{lon}}$) as unknowns. The objective function uses classic Tikhonov regularization:

$$\varphi(x) \;=\; \bigl\lVert g(x) - g_{\mathrm{obs}} \bigr\rVert_2^2 \;+\; \mu^2 \,\bigl\lVert L\,(x - x_0) \bigr\rVert_2^2$$

where $g(x)$ is the tesseroid forward operator, $x_0$ is the initial model, $L$ is the first-order finite-difference operator, and $\mu \ge 0$ is the regularization parameter. When $\mu = 0$, this reduces to pure data fitting.

2. First-Order Difference Operator $L$

$L$ encodes the difference between each pair of adjacent grid nodes in one matrix row, covering both longitudinal and latitudinal directions. For node $(i, j)$, define flattened index $k(i, j) = i \cdot n_{\mathrm{lon}} + j$, then

$$L_{\mathrm{lon}}: \quad x_{i,\,j+1} - x_{i,\,j}, \qquad L_{\mathrm{lat}}: \quad x_{i+1,\,j} - x_{i,\,j}$$

The final $L$ is a sparse matrix of shape $(n_{\mathrm{edges}},\, N)$ formed by stacking both blocks. The regularization term $\lVert L(x - x_0) \rVert^2$ acts as a discrete "gradient energy," penalizing large jumps between neighbouring cells.

3. Bott Diagonal Jacobian

Computing the full Jacobian $\partial g_i / \partial x_j$ requires partial derivatives for every voxel and is prohibitively expensive. Bott (1960) approximated this by noting that each observation point's gravity is dominated by the voxel directly below it, yielding a diagonal Jacobian:

$$J_{ii} \;=\; 2\pi G\, \lvert \Delta\rho_i \rvert \,\times\, 10^{5}\quad \text{[mGal/m]}$$

Taking the absolute value of the density contrast is critical — it ensures $J^\top J$ is always a positive diagonal matrix, guaranteeing the linear system is positive definite. The factor $10^{5}$ converts m/s² to mGal.

Implementation note: The original code used signed density_array values in the Jacobian, which could make some diagonal entries of $J^\top J$ negative, breaking positive definiteness. This version uniformly uses $\lvert \Delta\rho \rvert$.

4. Gauss-Newton Iteration

A second-order approximation of the objective gives the iteration formula:

$$\bigl(J^\top J + \mu^2\, L^\top L\bigr)\, \delta x \;=\; -\bigl(J^\top r + \mu^2\, L^\top L\,(x - x_0)\bigr)$$

where the residual $r = g(x) - g_{\mathrm{obs}}$. Each update uses Armijo backtracking line search to determine step size $\alpha$:

$$x \;\leftarrow\; x + \alpha\, \delta x, \qquad \varphi(x + \alpha\,\delta x) \;\le\; \varphi(x) + c_1\, \alpha\, \nabla\varphi^\top \delta x$$

Defaults: $c_1 = 10^{-4}$, initial $\alpha = 1$, halved on each failure, up to 30 attempts. After each iteration, a safety clipping $x \le h - r_{\mathrm{ref}}$ ensures the Moho stays below the observation surface.

5. Convergence Criteria

Iteration stops when any of the following is satisfied:

$$\frac{\varphi_k - \varphi_{k+1}}{\lvert \varphi_k \rvert} \;<\; \mathrm{gtol}, \qquad \frac{\lVert \nabla\varphi \rVert}{N} \;<\; 10^{-2}\,\mathrm{gtol}, \qquad k \;\ge\; \mathrm{max\_iter}$$

几何与密度约定

程序的几何约定是反演正确性的核心,符号一旦反了,反演就会朝错误方向收敛。 下图展示了观测面、参考莫霍面和实际莫霍面的位置关系:

观测平面 r = R + h 参考面 r_ref 实际莫霍面 r_sur = r_ref + s s > 0 多地幔 正异常 密度对比 > 0 s < 0 多地壳 负异常 密度对比 < 0 地壳(密度小) 地幔(密度大) R = bl.Moon2015.radius
图 1:莫霍面反演的几何约定。观测面位于参考面上方 h;实际莫霍面是参考面加起伏 s。

密度约定

程序中 density_contrast 取正值,表示莫霍面下方比上方更重(地幔 > 地壳)。 实际送入正演的密度数组按照下面的规则确定符号:

起伏方向物理含义异常符号送入正演的密度
$s > 0$(上隆)该体素多了地幔物质正异常$+\lvert\Delta\rho\rvert$
$s \le 0$(下沉)该体素多了地壳物质负异常$-\lvert\Delta\rho\rvert$
符号警告:原始版本里这套符号是反过来的。如果你之前用旧代码做过验证, 移植时务必检查 _update_density() 里的 mask 逻辑是否一致,否则反演会朝错误方向收敛。

Geometry & Density Conventions

The geometric conventions are central to inversion correctness — a sign error will drive convergence in the wrong direction. The figure below shows the spatial relationships among the observation surface, reference Moho, and actual Moho:

Observation surface r = R + h Reference r_ref Actual Moho r_sur = r_ref + s s > 0 extra mantle positive anomaly Δρ > 0 s < 0 extra crust negative anomaly Δρ < 0 Crust (lower ρ) Mantle (higher ρ) R = bl.Moon2015.radius
Fig. 1: Geometric conventions for Moho inversion. The observation surface is at height h above the reference; the actual Moho is the reference plus undulation s.

Density Convention

The density_contrast parameter is positive, meaning the material below the Moho is denser than above (mantle > crust). The sign of the density array fed to the forward engine follows these rules:

UndulationPhysical MeaningAnomaly SignDensity to Forward Engine
$s > 0$ (uplift)Extra mantle material in voxelPositive$+\lvert\Delta\rho\rvert$
$s \le 0$ (depression)Extra crustal material in voxelNegative$-\lvert\Delta\rho\rvert$
Sign warning: The original version had these signs reversed. If you validated against older code, make sure the mask logic in _update_density() matches, otherwise the inversion will converge in the wrong direction.

反演流程图

下图展示了从输入数据到最终反演结果的完整工作流,蓝色框是用户接口,灰色框是内部步骤:

输入数据 lon, lat, height, g_obs reference, density_contrast 初始模型 surface = 0 或先验估计 MohoLayer 构建球面棱柱体层 MohoInversion 设置 μ, max_iter Gauss-Newton 主循环 ① 正演 g(x) tesseroid_layer.gravity ② 计算 r, J, L r = g(x) − g_obs ③ 解 (JᵀJ+μ²LᵀL)δx 稀疏直接法 / LU ④ Armijo 线搜索 α 折半至满足下降 ⑤ 安全裁剪 + 更新 x ≤ h − reference 收敛? Δφ/φ < gtol 输出与评估 inverted_surface · residual_stats · plot_convergence evaluate_testset · evaluate_seismic · xarray 导出
图 2:莫霍面反演完整流程。橙色虚框内是 Gauss-Newton 主循环。

Inversion Flowchart

The diagram below shows the complete workflow from input data to final results. Blue boxes are user interfaces; grey boxes are internal steps:

Input Data lon, lat, height, g_obs reference, density_contrast Initial Model surface = 0 or prior estimate MohoLayer Build tesseroid layer MohoInversion Set μ, max_iter Gauss-Newton Main Loop ① Forward g(x) tesseroid_layer.gravity ② Compute r, J, L r = g(x) − g_obs ③ Solve (JᵀJ+μ²LᵀL)δx Sparse direct / LU ④ Armijo Line Search Halve α until descent ⑤ Safety Clip + Update x ≤ h − reference Converged? Δφ/φ < gtol No Yes Output & Evaluation inverted_surface · residual_stats · plot_convergence evaluate_testset · evaluate_seismic · xarray export
Fig. 2: Complete Moho inversion workflow. The orange dashed box encloses the Gauss-Newton main loop.

依赖与安装

Dependencies & Installation

程序主要依赖以下 Python 库:

The program depends on the following Python libraries:


numpy
scipy
harmonica   # tesseroid forward modelling
boule       # planetary reference ellipsoids (Moon2015, etc.)
xarray
matplotlib

可以直接使用下面的命令安装:

Install with:

pip install numpy scipy harmonica boule xarray matplotlib

当前代码默认使用 bl.Moon2015.radius 作为平均半径,所以更偏向月球场景。 如果后续要迁移到地球或其他天体,建议优先检查参考半径、参考面定义和密度差的地球物理意义, 通常只需改动 MohoLayer.__init__ 里的 self.mean_radius 一行。

The code defaults to bl.Moon2015.radius as the mean radius, so it is primarily tailored for lunar applications. To adapt for Earth or other bodies, check the reference radius, reference surface definition, and the geophysical meaning of the density contrast. Typically only the self.mean_radius line in MohoLayer.__init__ needs modification.

使用流程

正则化系数 μ

$\mu$ 越大,模型越平滑;$\mu = 0$ 时表示不加正则化。典型量级 $0.05 \sim 1$,需结合 L-curve 或经验调节。

安全约束

程序内部做了逐点裁剪 $x \le h - r_{\mathrm{ref}}$,确保莫霍面不会越过观测面,对非均匀参考面特别重要。

Workflow

Regularization μ

Larger $\mu$ yields smoother models; $\mu = 0$ means no regularization. Typical range $0.05$–$1$; tune with L-curve or experience.

Safety Constraint

Per-point clipping $x \le h - r_{\mathrm{ref}}$ ensures the Moho never rises above the observation surface — especially important for non-uniform reference surfaces.

API 速查表

MohoLayer

方法用途
forward(quite=True)规则网格上正演 $g_z$,返回 2D array (mGal)
forward_testset(lon_pts, lat_pts)任意散点位置正演,返回 1D array
set_surface(surface)更新莫霍面起伏并自动同步密度数组
set_reference(reference)更新参考面,保持 $r_{\mathrm{sur}}$ 不变
copy()深拷贝整个 layer
surface_to_xarray()导出当前起伏为 xarray
field_to_xarray()导出当前正演场为 xarray

MohoInversion

方法用途
inversion_gn(gtol, mu, max_iter)主入口,返回反演得到的莫霍起伏
L()构建一阶差分稀疏矩阵
jacobian()构建 Bott 对角 Jacobian(恒正)
residual_2d() / residual_flat()最终残差的 2D / 1D 形式
residual_stats()mean / std / rms / p5 / p95
plot_convergence()绘制 RMS 随迭代的收敛曲线
plot_residual_histogram(bins)残差直方图 + 正态拟合
evaluate_testset(...)独立测试点上评估 RMSE / MAE / R² / bias
evaluate_seismic(...)用地震月壳厚度评估反演结果

API Quick Reference

MohoLayer

MethodPurpose
forward(quite=True)Forward $g_z$ on regular grid; returns 2D array (mGal)
forward_testset(lon_pts, lat_pts)Forward at arbitrary scatter points; returns 1D array
set_surface(surface)Update Moho undulation and sync density array
set_reference(reference)Update reference surface, keeping $r_{\mathrm{sur}}$ unchanged
copy()Deep copy of the entire layer
surface_to_xarray()Export current undulation as xarray
field_to_xarray()Export current forward field as xarray

MohoInversion

MethodPurpose
inversion_gn(gtol, mu, max_iter)Main entry point; returns inverted Moho undulation
L()Build first-order difference sparse matrix
jacobian()Build Bott diagonal Jacobian (always positive)
residual_2d() / residual_flat()Final residuals in 2D / 1D form
residual_stats()mean / std / rms / p5 / p95
plot_convergence()Plot RMS convergence curve
plot_residual_histogram(bins)Residual histogram + normal fit
evaluate_testset(...)Evaluate RMSE / MAE / R² / bias on independent test points
evaluate_seismic(...)Evaluate against seismic crustal thickness

结果评估与地震约束

独立测试点评估

如果你留出了一部分观测点没参与反演,可以用 evaluate_testset 检查模型的外推能力:

go_pred, residual_test, scores = inv.evaluate_testset(
    lon_pts=test_lon,
    lat_pts=test_lat,
    go_test=test_go,
)
# scores = {'rmse': ..., 'mae': ..., 'bias': ..., 'r2': ...}

地震月壳厚度评估

程序提供了 evaluate_seismic 接口,把反演的莫霍面起伏插值到地震点位置, 然后和地震反演的月壳厚度直接对比。几何关系是:

$$\text{thickness}_{\mathrm{inv}} \;=\; \text{topo} \;-\; r_{\mathrm{ref}} \;-\; s$$

其中 topo 是地形高度(相对平均球半径),reference 通常为负(莫霍在平均球面以下), $s$ 是反演得到的莫霍起伏。返回的 diff = thickness_inv − seismic_thickness,正值表示反演偏厚。

thickness_inv, diff, scores = inv.evaluate_seismic(
    lon_pts=seismic_lon,
    lat_pts=seismic_lat,
    seismic_thickness=seismic_h,   # 单位 m
    topo_pts=topo_at_seismic,      # 单位 m
)
# 输出会自动打印 MAE / RMSE / Bias / R²(单位 km)

Evaluation & Seismic Constraints

Independent Test-Point Evaluation

If you held out some observation points from the inversion, use evaluate_testset to check extrapolation quality:

go_pred, residual_test, scores = inv.evaluate_testset(
    lon_pts=test_lon,
    lat_pts=test_lat,
    go_test=test_go,
)
# scores = {'rmse': ..., 'mae': ..., 'bias': ..., 'r2': ...}

Seismic Crustal Thickness Evaluation

The evaluate_seismic method interpolates the inverted Moho undulation to seismic station locations and compares with seismically determined crustal thickness. The geometric relation is:

$$\text{thickness}_{\mathrm{inv}} \;=\; \text{topo} \;-\; r_{\mathrm{ref}} \;-\; s$$

where topo is the topographic height (relative to mean sphere radius), reference is typically negative (Moho below mean sphere), and $s$ is the inverted undulation. The returned diff = thickness_inv − seismic_thickness; positive values indicate the inversion overestimates thickness.

thickness_inv, diff, scores = inv.evaluate_seismic(
    lon_pts=seismic_lon,
    lat_pts=seismic_lat,
    seismic_thickness=seismic_h,   # in metres
    topo_pts=topo_at_seismic,      # in metres
)
# Prints MAE / RMSE / Bias / R² (in km)

使用建议与故障排查

这版代码相比早期版本修复了几个关键的稳定性问题: Jacobian 中密度差取绝对值、非均匀参考面下的逐点安全裁剪、最终残差计算流程的同步更新、线搜索次数从 20 增加到 30。 这些细节直接影响反演的收敛行为,迁移到自己的项目里时建议保留。

Tips & Troubleshooting

This version fixes several critical stability issues compared to earlier releases: absolute-value density contrast in the Jacobian, per-point safety clipping for non-uniform reference surfaces, synchronized final residual computation, and increased line search attempts from 20 to 30. These details directly affect convergence behaviour — keep them when porting to your own project.

参考文献

References