Region-wise regularization#

In this tutorial we like to demonstrate how to control the regularization of subsurface regions individually by using an ERT field case. The data is a 2d profile that was measured in 2005 on the bottom of a lake. The water body is of course influencing the fields and needs to be treated accordingly.

We first import pygimli and the modules for ERT and mesh building.

import matplotlib.pyplot as plt
import pygimli as pg
from pygimli.physics import ert
import pygimli.meshtools as mt

Data and geometry#

The data was measured across a shallow lake with the most electrodes being on the bottom of a lake. We used cables with 2m spaced takeouts.

data = pg.getExampleData("ert/lake.ohm")
print(data)
Data: Sensors: 48 data: 658, nonzero entries: ['a', 'b', 'err', 'i', 'm', 'n', 'r', 'u', 'valid']

The data consists of 658 data with current and voltage using 48 electrodes. We first have a look at the electrode positions measured by a stick.

plt.plot(pg.x(data), pg.z(data), "o-")
plt.grid()
plot 8 regionWise

On both sides, two electrodes are on shore, but the others are on the bottom of a shallow lake with a maximum depth of 2.5m.

data["k"] = ert.geometricFactors(data)
data["rhoa"] = data["u"] / data["i"] * data["k"]
ax, cb = data.show()
plot 8 regionWise

We combined Wenner-Schlumberger (top) and Wenner-beta (bottom) data. The lowest resistivities correspond with the water resistivity of 22.5 :math:`Omega`m.

The contained errors are measured standard devitations and should not be used for inversion. Instead, we estimate new errors using 2% and 100microVolts.

data["err"] = ert.estimateError(data, relativeError=0.02, absoluteUError=1e-4)
print(max(data["err"]))
# pg.show(data, data["err"]*100, label="error (%)");
0.02584795321637427

Building a mesh with the water body#

# We create a piece-wise linear complex (PLC) as for a case with topography
plc = mt.createParaMeshPLC(data, paraDepth=20, boundary=1)
ax, _ = pg.show(plc, markers=True)
for i, n in enumerate(plc.nodes()[:12]):
    ax.text(n.x(), n.y(), str(i))
    print(i, n.x(), n.y())
plot 8 regionWise
0 -4.0 0.0
1 -4.0 -20.0
2 97.7452 -20.0
3 97.7452 0.0
4 -97.7452 0.0
5 -97.7452 -113.7452
6 191.4904 0.0
7 191.4904 -113.7452
8 0.0 0.0
9 1.0 0.0
10 2.0 0.0
11 2.993365 -0.11499999999999999

So node number 10 is the left one at the shore

for i in range(95, plc.nodeCount()):
    print(i, plc.node(i).x(), plc.node(i).y())
95 86.7804 -0.5833335
96 87.7715 -0.45
97 88.76255 -0.3166665
98 89.75359999999999 -0.183333
99 90.7494 -0.0916665
100 91.7452 0.0
101 92.7452 0.0
102 93.7452 0.0

and 100 the first on the other side. We connect nodes 10 and 100 by an edge

plc.createEdge(plc.node(10), plc.node(100), marker=-1)
plc.addRegionMarker([50, -0.1], marker=3)
ax, _ = pg.show(plc, markers=True)
plot 8 regionWise

As the lake bottom is not a surface boundary (-1) anymore, but an inside boundary, we set its marker >0 by iterating through all boundaries.

mesh = mt.createMesh(plc, quality=34.2)
for b in mesh.boundaries():
    if b.marker() == -1 and not b.outside():
        b.setMarker(2)

print(mesh)
ax, _ = pg.show(mesh, markers=True, showMesh=True)
plot 8 regionWise
Mesh: Nodes: 1188 Cells: 2217 Boundaries: 3404

Inversion with the ERT manager#

mgr = ert.ERTManager(data, verbose=True)
mgr.setMesh(mesh)  # use this mesh for all subsequent runs
mgr.invert()
# mgr.invert(mesh=mesh) would only temporally use the mesh
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation: Logarithmic transform
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   60.46 (dPhi = 71.32%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   19.19 (dPhi = 67.98%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    7.60 (dPhi = 59.41%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    4.68 (dPhi = 37.36%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 5 ... chi² =    3.16 (dPhi = 31.18%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 6 ... chi² =    1.79 (dPhi = 39.75%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 7 ... chi² =    1.80 (dPhi = -0.29%) lam: 20.0
################################################################################
#                Abort criterion reached: dPhi = -0.29 (< 2.0%)                #
################################################################################

1751 [243.52363111528703,...,129.5681768956373]

The fit is obviously not perfect. So we have a look at data and model response.

ax = mgr.showFit()
plot 8 regionWise

Both look very similar, but let us look at the misfit function in detail.

mgr.showMisfit(errorWeighted=True)
plot 8 regionWise

There is still systematics in the misfit. Ideally it should be a random distribution of Gaussian noise.

kw = dict(cMin=20, cMax=300, logScale=True, cMap="Spectral_r")
ax, cb = mgr.showResult(**kw)
plot 8 regionWise

Apparently, the two regions are already decoupled from each other which makes sense. Let us look in detail at the water cells by extracting the water body. Note. The manager class performs a model value permutation to fit the parametric mesh cell. So if you want to relate model values to the input mesh, you need to use the unpermutated model values directly from the inversion framework instance: mgr.fw.model`

water = mesh.createSubMesh(mesh.cells(mesh.cellMarkers() == 3))
resWater = mgr.fw.model[len(mgr.model)-water.cellCount():]
ax, cb = pg.show(water, resWater)
plot 8 regionWise

Apparently, all values are below the expected 22.5 :math:`Omega`m and some are implausibly low. Therefore we should try to limit them. Moreover, the subsurface structures do not look very “layered”, which is why we make the smoothness anisotropic.

mgr.inv.setRegularization(zWeight=0.1)
mgr.invert()
# mgr.invert(zWeight=0.1)  # only temporarily
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 0.0, upper bound 0.0
         1 Logarithmic LU transform, lower bound 0.0, upper bound 0.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   54.11 (dPhi = 74.35%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   19.94 (dPhi = 63.10%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    5.53 (dPhi = 72.05%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    2.42 (dPhi = 55.70%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 5 ... chi² =    0.74 (dPhi = 67.43%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.74)                   #
################################################################################

Region-specific regularization#

We first want to limit the resistivity of the water between some plausible bounds around our measurements.

mgr.inv.setRegularization(3, limits=[20, 25], trans="log")
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 0.0, upper bound 0.0
         1 Logarithmic LU transform, lower bound 20.0, upper bound 25.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   65.57 (dPhi = 68.91%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   24.23 (dPhi = 62.89%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    3.31 (dPhi = 85.91%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    0.46 (dPhi = 83.40%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.46)                   #
################################################################################

As a result of the log-log transform, we have a homogeneous body but below the lake bottom values below 20, maybe due to clay content or maybe as compensation of limiting the water resistivity too strong. We could limit the subsurface, too.

mgr.inv.setRegularization(2, limits=[20, 2000], trans="log")
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 20.0, upper bound 2000.0
         1 Logarithmic LU transform, lower bound 20.0, upper bound 25.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   43.38 (dPhi = 79.21%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   24.17 (dPhi = 43.41%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    2.57 (dPhi = 87.85%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    0.84 (dPhi = 59.99%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.84)                   #
################################################################################

Apparently, this makes it harder to fit the data accurately. So maybe an increased clay content can be responsible for resistivity below 20:math:`Omega`m in the mud.

Model reduction#

Another option is to treat the water body as a homogeneous body with only one unknown in the inversion.

mgr.inv.setRegularization(limits=[0, 0], trans="log")
mgr.inv.setRegularization(3, single=True)
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 20.0, upper bound 2000.0
         1 Logarithmic LU transform, lower bound 20.0, upper bound 25.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   42.97 (dPhi = 80.01%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   25.11 (dPhi = 41.20%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    3.13 (dPhi = 86.20%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    0.98 (dPhi = 63.15%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.98)                   #
################################################################################

The last value represents the value for the lake, close to our measurement. This value can, however, also be set beforehand.

mgr.inv.setRegularization(3, fix=22.5)
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 20.0, upper bound 2000.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  145.34
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   29.66 (dPhi = 79.47%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   19.21 (dPhi = 34.56%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    2.30 (dPhi = 86.99%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    0.96 (dPhi = 53.09%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.96)                   #
################################################################################

We see that the lake does not appear anymore as it is not a part of the inversion mesh mgr.paraDomain anymore.

Instead of the standard smoothness we use geostatistical regularization.

mgr.inv.setRegularization(2, correlationLengths=[30, 2])
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8c2dad0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 20.0, upper bound 2000.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  145.34
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   21.41 (dPhi = 85.15%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =    9.30 (dPhi = 55.48%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    1.79 (dPhi = 78.93%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    0.99 (dPhi = 40.13%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.99)                   #
################################################################################

Region coupling#

In case (does not make sense here) the two regions should be coupled to each other, you can set so-called inter-region constraints.

mgr = ert.ERTManager(data, verbose=True)
mgr.setMesh(mesh)
print("Number of regions: ", mgr.fop.regionManager().regionCount())
mgr.inv.setRegularization(cType=1, zWeight=0.2)
mgr.fop.setInterRegionCoupling(2, 3, 1.0)  # normal coupling
mgr.invert()
ax, cb = mgr.showResult(**kw)
plot 8 regionWise
Number of regions:  3
fop: <pygimli.physics.ert.ertModelling.ERTModelling object at 0x7f90d8e4fec0>
Data transformation: Logarithmic LU transform, lower bound 0.0, upper bound 0.0
Model transformation (cumulative):
         0 Logarithmic LU transform, lower bound 0.0, upper bound 0.0
         1 Logarithmic LU transform, lower bound 0.0, upper bound 0.0
min/max (data): 22.44/87.12
min/max (error): 2%/2.58%
min/max (start model): 47.2/47.2
--------------------------------------------------------------------------------
inv.iter 0 ... chi² =  211.20
--------------------------------------------------------------------------------
inv.iter 1 ... chi² =   37.52 (dPhi = 82.16%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 2 ... chi² =   17.20 (dPhi = 54.00%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 3 ... chi² =    3.65 (dPhi = 78.09%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 4 ... chi² =    1.44 (dPhi = 57.94%) lam: 20.0
--------------------------------------------------------------------------------
inv.iter 5 ... chi² =    0.39 (dPhi = 65.51%) lam: 20.0


################################################################################
#                  Abort criterion reached: chi² <= 1 (0.39)                   #
################################################################################

The general image is of course similar, but the structures are mirrored around the lake bottom. Moreover the resistivity in the lake is far too high. Note that all of the obtained images are equivalent with respect to data and errors.

Take-away messages#

  • always have a look at the data fit and get hands on data errors

  • a lot of different models are able to fit the data, particularly in the full space

  • regions can be very specifically controlled

  • constrain or fix whenever possibly (and reliable)

  • sometimes geostatistic constraints outperform classical smoothness but sometimes not

  • play with regularization and keep looking at data fit

Total running time of the script: (2 minutes 55.796 seconds)

Gallery generated by Sphinx-Gallery