Note
Go to the end to download the full example code.
Electrical Resistivity Tomography with PyGIMLi#
Using the ERT (Electrical Resistivity Tomography) solver implemented
provided by PyGIMLi, we use different
cofi solvers to solve the corresponding inverse problem.
If you are running this notebook locally, make sure you’ve followed steps here to set up the environment. (This environment.yml file specifies a list of packages required to run the notebooks)
Note: This notebook is adapted from a PyGIMLi example: Naive complex-valued electrical inversion
0. Import modules#
Note (pyGIMLi + Python 3.13): This notebook uses pyGIMLi (pygimli).pyGIMLi’s compiled core (pgcore) does not currently ship wheels for Python 3.13, so this notebook won’t run on 3.13 unless you build from source.Use Python 3.12 (recommended) or 3.11 for install.
# -------------------------------------------------------- #
# #
# Uncomment below to set up environment on "colab" #
# #
# -------------------------------------------------------- #
# !pip install -U cofi pygimli tetgen
# !git clone https://github.com/inlab-geo/cofi-examples.git
# %cd cofi-examples/examples/pygimli_ert
We will need the following packages:
numpyfor matrices and matrix-related functionsmatplotlibfor plottingpygimlifor forward modelling of the problemcofifor accessing different inference solvers
Additionally, we wrap some pygimli code in file
pygimli_ert_lib.py and import it here for conciseness.
import numpy as np
import matplotlib.pyplot as plt
import pygimli
from pygimli.physics import ert
from cofi import BaseProblem, InversionOptions, Inversion
from cofi.tools import BaseInferenceTool
from pygimli_ert_lib import *
np.random.seed(42)
1. Define the problem#
We first define the true model, the survey and map it on a computational mesh designed for the survey and true anomaly.
# PyGIMLi - define measuring scheme, geometry, forward mesh and true model
scheme = survey_scheme()
mesh, rhomap = model_true(scheme)
# convert rhomap to actual model vector for plotting
rho_true = model_vec(rhomap, mesh)
# plot the true model
ax = pygimli.show(mesh, data=rho_true, label="$\Omega m$", showMesh=True)
ax[0].set_title("True model")
Text(0.5, 1.0, 'True model')
Generate the synthetic data as a container with all the necessary information for plotting.
In ERT problems, the model and data are by convention treated in log space.
# PyGIMLi - generate data
data, log_data, data_cov_inv = ert_simulate(mesh, scheme, rhomap)
ax = ert.show(data)
ax[0].set_title("Provided data")
relativeError set to a value > 0.5 .. assuming this is a percentage Error level dividing them by 100
Text(0.5, 1.0, 'Provided data')
Further, we create a pygimli.ert.ERTManager instance to keep record
of problem-specific information like the inversion mesh, and to perform
forward operation for the inversion solvers.
# create PyGIMLi's ERT manager
ert_manager = ert_manager(data)
The inversion can use a different mesh and the mesh to be used should
know nothing about the mesh that was designed based on the true model.
We wrap two kinds of mesh as examples in the library code
pygimli_ert_lib.py, namely triangular and rectangular mesh.
Use imesh_tri = inversion_mesh(scheme) to initialise a triangular
mesh. This function uses PyGIMLi’s own mesh generator and generates
triangular mesh automatically from given sensor locations. The resulting
mesh will have a smaller area as unknowns to be inverted, as well as a
background part with values prolongated outside from the parametric
domain by PyGIMLi. You will see an example plot in the code cell below.
Use imesh_rect = inversion_mesh_rect(ert_manager) to initislise a
rectangular mesh. The grid mesh is created from these x and y nodes:
x = np.linspace(start=-5, stop=55, num=61), and
y = np.linspace(start=-20,stop=0,num=10). And again, there’s a
triangular background with values prolongated outside from the
parametric domain by PyGIMLi.
Here we first demonstrate how to use a triangular mesh. Note that this makes the inversion problem under-determined.
inv_mesh = inversion_mesh(ert_manager)
ax = pygimli.show(inv_mesh, showMesh=True, markers=True)
ax[0].set_title("Mesh used for inversion")
Text(0.5, 1.0, 'Mesh used for inversion')
This folder contains examples scripts that run inversion for triangular or rectangular meshes, with different inversion approaches.
With the inversion mesh created, we now define a starting model, forward operator and weighting matrix for regularization using PyGIMLi.
Recall that both our model and data will be in log space when we perform inversion.
# PyGIMLi's forward operator (ERTModelling)
forward_oprt = ert_forward_operator(ert_manager, scheme, inv_mesh)
# extract regularization matrix
Wm = reg_matrix(forward_oprt)
# initialise a starting model for inversion
start_model, start_model_log = starting_model(ert_manager)
ax = pygimli.show(ert_manager.paraDomain, data=start_model, label="$\Omega m$", showMesh=True)
ax[0].set_title("Starting model")
Text(0.5, 1.0, 'Starting model')
CoFI and other inference packages require a set of functions that
provide the misfit, the jacobian the residual within the case of scipy
standardised interfaces. All these functions are defined in the library
file pygimli_ert_lib.py, so open this file if you’d like to find out
the details. These functions are:
get_responseget_jacobianget_residualsget_data_misfitget_regularizationget_gradientget_hessian
With all the above forward operations set up with PyGIMLi, we now define
the problem in cofi by setting the problem information for a
BaseProblem object.
# hyperparameters
lamda = 0.0001
# CoFI - define BaseProblem
ert_problem = BaseProblem()
ert_problem.name = "Electrical Resistivity Tomography defined through PyGIMLi"
ert_problem.set_forward(get_response, args=[forward_oprt])
ert_problem.set_jacobian(get_jacobian, args=[forward_oprt])
ert_problem.set_residual(get_residual, args=[log_data, forward_oprt])
ert_problem.set_data_misfit(get_data_misfit, args=[log_data, forward_oprt, data_cov_inv])
ert_problem.set_regularization(get_regularization, args=[Wm, lamda])
ert_problem.set_gradient(get_gradient, args=[log_data, forward_oprt, Wm, lamda, data_cov_inv])
ert_problem.set_hessian(get_hessian, args=[log_data, forward_oprt, Wm, lamda, data_cov_inv])
ert_problem.set_initial_model(start_model_log)
Review what information is included in the BaseProblem object:
ert_problem.summary()
========================================================================================
Summary for inversion problem: Electrical Resistivity Tomography defined through PyGIMLi
========================================================================================
Model shape: (831,)
----------------------------------------------------------------------------------------
List of functions/properties set by you:
['gradient', 'hessian', 'residual', 'jacobian', 'data_misfit', 'regularization', 'forward', 'initial_model', 'model_shape']
----------------------------------------------------------------------------------------
List of functions/properties created based on what you have provided:
['objective', 'hessian_times_vector', 'jacobian_times_vector']
----------------------------------------------------------------------------------------
List of functions/properties that can be further set for the problem:
( not all of these may be relevant to your inversion workflow )
['objective', 'log_posterior', 'log_posterior_with_blobs', 'log_likelihood', 'log_prior', 'hessian_times_vector', 'jacobian_times_vector', 'regularization_matrix', 'data', 'data_covariance', 'data_covariance_inv', 'blobs_dtype', 'bounds', 'constraints']
2. Define the inversion options and run#
SciPy’s optimizer (trust-exact)#
inv_options_scipy = InversionOptions()
inv_options_scipy.set_tool("scipy.optimize.minimize")
inv_options_scipy.set_params(method="trust-exact", options={"maxiter": 10})
Review what’s been defined for the inversion we are about to run:
inv_options_scipy.summary()
=============================
Summary for inversion options
=============================
Solving method: None set
Use `suggest_solving_methods()` to check available solving methods.
-----------------------------
Backend tool: `<class 'cofi.tools._scipy_opt_min.ScipyOptMin'>` - SciPy's optimizers that minimizes a scalar function with respect to one or more variables, check SciPy's documentation page for a list of methods
References: ['https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.minimize.html']
Use `suggest_tools()` to check available backend tools.
-----------------------------
Solver-specific parameters:
method = trust-exact
options = {'maxiter': 10}
Use `suggest_solver_params()` to check required/optional solver-specific parameters.
inv = Inversion(ert_problem, inv_options_scipy)
inv_result = inv.run()
# inv_result.summary()
print(f"\nSolver message: {inv_result.message}")
Solver message: Maximum number of iterations has been exceeded.
inv_result.success
False
Plot the results:
# convert back to normal space from log space
model = np.exp(inv_result.model)
# plot inferred model
ax = pygimli.show(ert_manager.paraDomain, data=model, label=r"$\Omega m$")
ax[0].set_title("Inferred model")
Text(0.5, 1.0, 'Inferred model')
We can now also compare the synthetic data with provided observations.
_, axes = plt.subplots(1, 2, figsize=(12,4))
# plot synthetic data
d = forward_oprt.response(model)
ert.showERTData(scheme, vals=d, cMin=np.min(data["rhoa"]), cMax=np.max(data["rhoa"]), ax=axes[0])
axes[0].set_title("Synthetic data from inferred model")
# plot given data
ert.show(data, ax=axes[1])
axes[1].set_title("Provided data")
Text(0.5, 1.0, 'Provided data')
3. What’s next?#
Now that we’ve seen the PyGIMLi ERT problem solved by two different inversion approaches through CoFI, it would be nice to see more inversion solvers (even a sampler!) and a similar problem defined with a rectangular mesh. If you’d like to see some self-contained examples, head to this GitHub folder to explore more.
Watermark#
watermark_list = ["cofi", "numpy", "scipy", "pygimli", "matplotlib"]
for pkg in watermark_list:
pkg_var = __import__(pkg)
print(pkg, getattr(pkg_var, "__version__"))
cofi 0.2.11+71.gb28b5b0
numpy 2.2.6
scipy 1.17.1
pygimli 1.5.5.post2+56.ge792d84b
matplotlib 3.10.9
sphinx_gallery_thumbnail_number = -1
Total running time of the script: (0 minutes 24.181 seconds)