인터페이스는 현지화되어 있으며, Skill 소개와 설명 문서는 선택한 언어로 AI가 자동 번역합니다. 코드·명령어·경로·전문 용어는 원문을 유지하며 원작자 문서가 우선합니다.
Skills 둘러보기
디자인 및 창작 · SKILL.md

geophysical-inversion

geophysical-inversion

Geophysical data inversion workflow from data loading through mesh creation, forward modelling, inversion, and result visualization. Use when inverting ERT, magnetics, gravity, or EM survey data.

Independent GitHub snapshot · Automated classification · Original documentation governs

Skill 문서

GitHub

name: geophysical-inversion description: | Geophysical data inversion workflow from data loading through mesh creation, forward modelling, inversion, and result visualization. Use when inverting ERT, magnetics, gravity, or EM survey data. license: MIT metadata: skill_type: workflow version: 1.0.2 author: Geoscience Skills tags: '["Geophysical Inversion", "ERT", "Magnetics", "Gravity", "EM", "Workflow"]' dependencies: '["simpeg==0.25.2", "pygimli==1.6.0", "verde==1.9.0", "pyvista==0.49.0"]' complements: '["simpeg", "pygimli", "verde", "pyvista"]' workflow_role: modelling

Geophysical Inversion Workflow

End-to-end pipeline for inverting geophysical data, from survey data loading through mesh creation, forward modelling, inversion, gridding, and 3D visualization of recovered physical property models.

Skill Chain

simpeg / pygimli      verde            pyvista
[Mesh + Inversion] --> [Gridding]    --> [3D Visualization]
  |                     |                |
  Survey geometry       Interpolate      Volume render
  Forward model         Grid to raster   Slice views
  Misfit + reg          Trend removal    Overlay data
  Recover model         Cross-validate   Export mesh

Decision Points: SimPEG vs pyGIMLi

CriterionSimPEGpyGIMLi
DC resistivity / ERTYesYes (simpler API)
MagneticsYesLimited
GravityYesLimited
Electromagnetics (TDEM, FDEM)YesNo
Seismic refraction (SRT)NoYes
Induced polarizationYesYes
Built-in electrode arraysManual setupBuilt-in (Wenner, Schlumberger, etc.)
Mesh typesTensorMesh, TreeMesh, CurvilinearMeshTriangular, tetrahedral, structured
Joint inversionYes (Wires maps)Limited
API complexityMore boilerplate, more flexibleLess boilerplate, opinionated

Rule of thumb: Use pyGIMLi for standard near-surface ERT/SRT surveys with conventional arrays. Use SimPEG for multi-physics, EM methods, potential fields, or research-grade custom inversions.

Step-by-Step Orchestration

Choose one inversion branch. Each branch explicitly produces cell_centers_xz_m, resistivity_ohm_m and model_vtk for the shared stages. X is distance along the profile in metres, Z is elevation in metres (negative below the surface); resistivity is in ohm metres. These short synthetic runs check API and data flow, not survey resolution or field-model reliability.

Stage 1a: Inversion with SimPEG (DC Resistivity Example)

# example: simpeg-inversion
import numpy as np
from discretize import TensorMesh
from simpeg.electromagnetics.static import resistivity as dc
from simpeg import maps, data, data_misfit, regularization
from simpeg import optimization, inverse_problem, inversion, directives

# 1. Small synthetic 2D mesh. SimPEG's second coordinate represents elevation.
hx = np.full(24, 5.0)
hz = np.full(12, 5.0)
mesh = TensorMesh([hx, hz], origin='CN')

# 2. Build survey (dipole-dipole)
n_electrodes = 8
electrode_spacing = 5.0
elec_x = (np.arange(n_electrodes) - (n_electrodes - 1) / 2) * electrode_spacing
elec_locs = np.c_[elec_x, np.zeros(n_electrodes)]

source_list = []
for i in range(n_electrodes - 3):
    rx = dc.receivers.Dipole(elec_locs[[i+2]], elec_locs[[i+3]])
    src = dc.sources.Dipole([rx], elec_locs[i], elec_locs[i+1])
    source_list.append(src)
survey = dc.Survey(source_list)

# 3. Forward model (for synthetic test)
sigma_true = np.ones(mesh.nC) * 0.01  # 100 ohm-m background
sigma_true[mesh.cell_centers[:, 1] < -20] = 0.1  # Conductive layer
simulation = dc.Simulation2DNodal(
    mesh, survey=survey, sigmaMap=maps.ExpMap(mesh)
)
clean_data = simulation.dpred(np.log(sigma_true))
# Voltage / unit-current uncertainties: fractional term plus nonzero floor.
standard_deviation = 0.02 * np.abs(clean_data) + 1e-6
rng = np.random.default_rng(42)
dobs = clean_data + standard_deviation * rng.standard_normal(survey.nD)

# 4. Set up inversion
obs_data = data.Data(survey, dobs=dobs, standard_deviation=standard_deviation)
dmis = data_misfit.L2DataMisfit(data=obs_data, simulation=simulation)
reg = regularization.WeightedLeastSquares(
    mesh, alpha_s=1e-4, alpha_x=1, alpha_y=1
)
opt = optimization.InexactGaussNewton(maxIter=3)
inv_prob = inverse_problem.BaseInvProblem(dmis, reg, opt)
dir_list = [
    directives.BetaEstimate_ByEig(beta0_ratio=1.0, random_seed=42),
    directives.BetaSchedule(coolingFactor=2),
    directives.TargetMisfit()
]
inv = inversion.BaseInversion(inv_prob, directiveList=dir_list)

# 5. Run inversion
m0 = np.log(np.ones(mesh.nC) * 0.01)  # Starting model
mrec = inv.run(m0)
sigma_rec = np.exp(mrec)  # Recovered conductivity

Convert this branch's result on its own mesh. RectilinearGrid also preserves nonuniform cell widths if padding cells are added later. VTK uses coordinates (x, 0, z) for this vertical section; SimPEG's cell order already has X varying fastest.

# example: simpeg-result
import numpy as np
import pyvista as pv

cell_centers_xz_m = mesh.cell_centers.copy()
resistivity_ohm_m = 1.0 / np.asarray(sigma_rec)
if resistivity_ohm_m.shape != (mesh.nC,) or not np.all(np.isfinite(resistivity_ohm_m) & (resistivity_ohm_m > 0)):
    raise ValueError('Expected one finite positive resistivity per SimPEG cell')
model_vtk = pv.RectilinearGrid(mesh.nodes_x, np.array([0.0]), mesh.nodes_y)
model_vtk.cell_data['resistivity_ohm_m'] = resistivity_ohm_m

Stage 1b: Inversion with pyGIMLi (ERT Example)

Use an existing ert_survey.dat with rhoa and fractional uncertainty err, or create this small synthetic homogeneous-earth survey first:

# example: pygimli-synthetic
import numpy as np
import pygimli as pg
from pygimli.physics import ert

scheme = ert.createData(elecs=np.linspace(0., 35., 8), schemeName='dd')
# Include ERT boundary markers and padding around the parameter domain.
forward_mesh = ert.createInversionMesh(scheme, paraMaxCellSize=10)
synthetic = ert.simulate(
    mesh=forward_mesh, scheme=scheme, res=100.0,
    noiseLevel=0.03, noiseAbs=1e-6, seed=42, verbose=False,
)
synthetic.save('ert_survey.dat')
# example: pygimli-inversion
import numpy as np
import pygimli as pg
from pygimli.physics import ert

# 1. Load and validate positive apparent resistivity and fractional uncertainty.
ert_data = ert.load('ert_survey.dat')
rhoa = np.asarray(ert_data['rhoa'])
relative_error = np.asarray(ert_data['err'])
valid = np.isfinite(rhoa) & (rhoa > 0) & np.isfinite(relative_error) & (relative_error > 0)
ert_data.remove(~valid)
if ert_data.size() == 0:
    raise ValueError('No valid ERT measurements remain')

# 2. Short demonstration inversion; increase iterations for convergence studies.
mgr = ert.ERTManager(ert_data)
mgr.invert(lam=20, paraMaxCellSize=20, maxIter=3, verbose=False)

The model belongs to mgr.paraDomain, not the forward mesh or a SimPEG mesh. The following block needs only this branch and never reads sigma_rec.

# example: pygimli-result
import numpy as np
import pygimli as pg
import pyvista as pv

parameter_mesh = pg.Mesh(mgr.paraDomain)
cell_centers_xz_m = np.asarray(parameter_mesh.cellCenters())[:, :2]
resistivity_ohm_m = np.asarray(mgr.model)
if resistivity_ohm_m.shape != (parameter_mesh.cellCount(),) or not np.all(np.isfinite(resistivity_ohm_m) & (resistivity_ohm_m > 0)):
    raise ValueError('Expected one finite positive resistivity per pyGIMLi parameter cell')
parameter_mesh['resistivity_ohm_m'] = resistivity_ohm_m
parameter_mesh.exportVTK('pygimli_parameter_model.vtk')
model_vtk = pv.read('pygimli_parameter_model.vtk')
# pyGIMLi's vertical 2D coordinate is Y; map it explicitly to VTK Z.
points = model_vtk.points.copy()
model_vtk.points = np.column_stack((points[:, 0], points[:, 2], points[:, 1]))

Stage 2: Gridding and Interpolation (verde)

# example: inversion-gridding
import numpy as np
import verde as vd

# Both branches supply this common representation.
coordinates = (cell_centers_xz_m[:, 0], cell_centers_xz_m[:, 1])
region = vd.get_region(coordinates)

# Spline gridding
spline = vd.Spline(damping=1e-3)
spline.fit(coordinates, data=np.log10(resistivity_ohm_m))

raster = spline.grid(spacing=2.5, region=region,
                     dims=['elevation_m', 'distance_m'],
                     data_names=['log10_resistivity'])

This optional raster is a smoothed display product. Mask areas outside the parameter domain and poorly constrained cells using the inversion's coverage; it is not a replacement for the original mesh or a new inversion result.

Stage 3: Visualization (pyvista)

Export either branch without changing its cell geometry or property association:

# example: inversion-export
exported_model = model_vtk.cast_to_unstructured_grid()
exported_model.save('recovered_resistivity.vtu')
import pyvista as pv

plotter = pv.Plotter()
plotter.add_mesh(model_vtk, scalars='resistivity_ohm_m', cmap='Spectral',
                  log_scale=True, clim=[10, 1000])
plotter.add_scalar_bar('Resistivity (ohm-m)')
plotter.view_xz()
plotter.show()

Common Pipelines

Standard ERT Inversion

- [ ] Load ERT data (electrode positions, configurations, apparent resistivity)
- [ ] QC data: remove negative rhoa, check reciprocals, filter by error threshold
- [ ] Choose framework: pyGIMLi for standard arrays, SimPEG for custom setups
- [ ] Create mesh appropriate for electrode layout and target depth
- [ ] Set up forward simulation with survey geometry
- [ ] Configure inversion: data misfit, regularization weight, starting model
- [ ] Run inversion, monitor convergence (target chi-squared ~ 1)
- [ ] Plot recovered resistivity model
- [ ] Overlay electrode positions and data fit
- [ ] Export model to VTK or gridded raster

Gravity or Magnetics Inversion (SimPEG)

- [ ] Load survey data (station locations, observed field, regional correction)
- [ ] Remove regional trend if needed (polynomial or upward continuation)
- [ ] Create TensorMesh or TreeMesh covering survey area and target depth
- [ ] Build survey object with receiver locations and source field
- [ ] Set up Simulation3DIntegral with appropriate physical property map
- [ ] Configure L2 data misfit and Tikhonov regularization
- [ ] Add depth weighting to counteract resolution decay
- [ ] Run inversion from homogeneous starting model
- [ ] Visualize recovered density/susceptibility with pyvista
- [ ] Validate against known geology or borehole constraints

Synthetic Study (Forward + Inversion)

- [ ] Define true model with target anomaly on mesh
- [ ] Create survey geometry matching planned field layout
- [ ] Run forward simulation to generate synthetic data
- [ ] Add realistic noise (percentage + floor)
- [ ] Run inversion on noisy synthetic data
- [ ] Compare recovered model to true model
- [ ] Test sensitivity to regularization parameters
- [ ] Assess resolution by varying survey design

When to Use

Use the geophysical inversion workflow when:

  • Inverting ERT, magnetics, gravity, or EM survey data for subsurface models
  • Designing geophysical surveys through synthetic forward-inversion studies
  • Processing and visualizing inverted physical property models
  • Comparing results from different inversion parameters or methods

Use individual domain skills when:

  • Only creating meshes or survey geometries (use simpeg or pygimli alone)
  • Only gridding scattered data (use verde alone)
  • Only visualizing existing model files (use pyvista alone)

Common Issues

IssueSolution
Inversion does not convergeCheck data uncertainties; increase regularization (lam or alpha_s)
Artifacts near surfaceAdd depth weighting or refine mesh near electrodes
Memory error on 3D meshUse TreeMesh (SimPEG) with adaptive refinement near sources
Negative apparent resistivityRemove bad data points before inversion
Over-fitting (chi-squared << 1)Increase trade-off parameter; check noise estimates
Model too smoothReduce regularization weight; try sparse norms (L1)
pyGIMLi mesh too coarseReduce maximum cell size and inspect mgr.paraDomain; quality controls triangle angles

API Sources and Verification

  • SimPEG DC resistivity tutorial.
  • pyGIMLi ERTManager model export associates the recovered model with paraDomain.
  • PyVista RectilinearGrid preserves the node coordinates of nonuniform tensor meshes.
  • tests/science/test_model_examples.py executes the marked short synthetic inversions, each branch's adapter, the shared gridding and VTK export. This verifies API/data-flow and geometry, not field-data convergence, uncertainty, depth of investigation, or the interactive renderer.
저장한 Skills