Smoothing parameters and time-lapse inversion

In this notebook, we will see what it the impact of the different smoothing parameters and how we can use them to perform time-lapse inversion.

import matplotlib.pyplot as plt
import numpy as np
import sys
sys.path.append('../src')
from emagpy import Problem
datadir = '../src/examples/'

Smoothing parameters

There are three different smoothing parameters: - alpha: linked to the vertical smoothing - beta: linked to lateral smoothing - gamma: linked to smoothing with the first inverted survey (usefull for time-lapse inversion)

k = Problem()
k.createSurvey(datadir + 'cover-crop/coverCrop.csv')
k.surveys[0].df = k.surveys[0].df[:30] # truncate dataset
k.show()
Removing 1 NaN from survey
../_images/nb_smoothing_0.png

Vertical smoothing (alpha)

The alpha parameter regulates the vertical smoothing of the profile. It is needed to regularize the inversion. It plays an important role in smooth inversion with many layers such as used with Problem.invertGN(). To determine the optimal value of alpha a common method is to plot the so-called ‘L-curve’ that show the size of the model and data misfit as a function of alpha. It can be plotted using the Problem.lcurve() method.

k.lcurve()
../_images/nb_smoothing_1.png

From the L-curve, we look for the alpha value that offers the best trade-off between the model and data misfit. This value is usually taken at the maximum curvature point of the curve. In this case it would corresponds to 0.07. This value can then be used in the call to Problem.invert(alpha=alpha).

The graphs below shows the impact of different values of alpha. Larger alpha values will give a smoother model, while smaller alpha values can show very high/low EC values.

fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i, alpha in enumerate([0, 0.07, 0.5]):
    ax = axs[i]
    k.invert(alpha=alpha)
    k.showResults(ax=ax)
    ax.set_title('alpha = {:.2f}'.format(alpha))
30/30 inverted
../_images/nb_smoothing_2.png

Lateral smoothing (beta)

We can see that the data present some similarity between continguous measurements. To force lateral smoothing we can use the beta parameters. It will penalize the objective function with the difference between two continuous profiles. Note that this can be dangerous in some conditions, for instance if the first profile is badly inverted.

The figure below show the effect of lateral smoothing.

fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i, beta in enumerate([0, 0.1, 0.5]):
    ax = axs[i]
    k.invert(beta=beta)
    k.showResults(ax=ax)
    ax.set_title('beta = {:.2f}'.format(beta))
30/30 inverted
../_images/nb_smoothing_3.png

Reference survey smoothing (gamma) or time-lapse inversion

gamma controls the smoothing between the first survey and subsequent survey. This way a quasi time-lapse inversion can be performed.

k = Problem()
k.createTimeLapseSurvey([datadir + 'timelapse-wheat/170316.csv',
                        datadir + 'timelapse-wheat/170427.csv',
                        datadir + 'timelapse-wheat/170516.csv'])
for s in k.surveys:
    s.df = s.df[:20]

# show apparent values collected
fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i in range(len(k.surveys)):
    ax = axs[i]
    k.show(index=i, ax=ax)
ax.set_ylim([12, 42])
k.setInit(depths0=[0.7], fixedDepths=[False])
bounds = [(0.1, 1),(2, 60),(2, 60)]
---------------------------------------------------------------------------

FileNotFoundError                         Traceback (most recent call last)

<ipython-input-1-5c138adf3fe3> in <module>
      2 k.createTimeLapseSurvey([datadir + 'timelapse-wheat/170316.csv',
      3                         datadir + 'timelapse-wheat/170427.csv',
----> 4                         datadir + 'timelapse-wheat/170516.csv'])
      5 for s in k.surveys:
      6     s.df = s.df[:20]


/builds/hkex/emagpy/src/emagpy/Problem.py in createTimeLapseSurvey(self, fnames, targetProjection, unit)
    162             targetProjection = self.projection
    163         for fname in fnames:
--> 164             self.createSurvey(fname, targetProjection=targetProjection, unit=unit)
    165
    166


/builds/hkex/emagpy/src/emagpy/Problem.py in createSurvey(self, fname, freq, hx, targetProjection, unit)
    107             targetProjection = self.projection
    108
--> 109         survey = Survey(fname, freq=freq, hx=hx, targetProjection=targetProjection, unit=unit)
    110
    111         # remove NaN from survey


/builds/hkex/emagpy/src/emagpy/Survey.py in __init__(self, fname, freq, hx, targetProjection, unit)
    170         self.projection = None # store the project
    171         if fname is not None:
--> 172             self.readFile(fname, targetProjection=targetProjection, unit=unit)
    173             if freq is not None:
    174                 self.freqs = np.ones(len(self.coils))*freq


/builds/hkex/emagpy/src/emagpy/Survey.py in readFile(self, fname, sensor, targetProjection, unit)
    201         if fname.find('.DAT')!=-1:
    202             delimiter = '\t'
--> 203         df = pd.read_csv(fname, delimiter=delimiter)
    204         self.readDF(df, name, sensor, targetProjection, unit)
    205


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, dialect, error_bad_lines, warn_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
    603     kwds.update(kwds_defaults)
    604
--> 605     return _read(filepath_or_buffer, kwds)
    606
    607


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _read(filepath_or_buffer, kwds)
    455
    456     # Create the parser.
--> 457     parser = TextFileReader(filepath_or_buffer, **kwds)
    458
    459     if chunksize or iterator:


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in __init__(self, f, engine, **kwds)
    812             self.options["has_index_names"] = kwds["has_index_names"]
    813
--> 814         self._engine = self._make_engine(self.engine)
    815
    816     def close(self):


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _make_engine(self, engine)
   1043             )
   1044         # error: Too many arguments for "ParserBase"
-> 1045         return mapping[engine](self.f, **self.options)  # type: ignore[call-arg]
   1046
   1047     def _failover_to_python(self):


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in __init__(self, src, **kwds)
   1860
   1861         # open handles
-> 1862         self._open_handles(src, kwds)
   1863         assert self.handles is not None
   1864         for key in ("storage_options", "encoding", "memory_map", "compression"):


/usr/local/lib/python3.7/site-packages/pandas/io/parsers.py in _open_handles(self, src, kwds)
   1361             compression=kwds.get("compression", None),
   1362             memory_map=kwds.get("memory_map", False),
-> 1363             storage_options=kwds.get("storage_options", None),
   1364         )
   1365


/usr/local/lib/python3.7/site-packages/pandas/io/common.py in get_handle(path_or_buf, mode, encoding, compression, memory_map, is_text, errors, storage_options)
    645                 encoding=ioargs.encoding,
    646                 errors=errors,
--> 647                 newline="",
    648             )
    649         else:


FileNotFoundError: [Errno 2] No such file or directory: '../src/examples/timelapse-wheat/170316.csv'
# inversion with gamma = 0 (no smoothing between surveys)
k.invert(gamma=0, bnds=bounds)
fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i in range(3):
    ax = axs[i]
    k.showResults(index=i, ax=ax, maxDepth=1.5)
bounds = [(0.1, 1), (2, 60), (2, 60)]
Survey 1/3
20/20 inverted
Survey 2/3
20/20 inverted
Survey 3/3
20/20 inverted
../_images/nb_smoothing_4.png
# inversion with constrain to the first survey
k.invert(gamma=0.1, bnds=bounds)
fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i in range(3):
    ax = axs[i]
    k.showResults(index=i, ax=ax, maxDepth=1.5)
bounds = [(0.1, 1), (2, 60), (2, 60)]
Survey 1/3
20/20 inverted
Survey 2/3
20/20 inverted
Survey 3/3
20/20 inverted
../_images/nb_smoothing_5.png

Observations

In the second inversion with between survey constrain we can clearly see that the depths obtained during the first inversion constrain the depth of the other survey. We can also notice that the last two profiles are badly inverted in the first survey and so this propagate for the other subsequent surveys.

Inverting change in ECa

An other way of producing time-lapse inversion is to actually invert for the change in ECa. This can be done using the CS function and the Problem.invertGN() function. See appendix of Whalley et al. (2017) for more details.

k = Problem()
k.createTimeLapseSurvey([datadir + 'timelapse-wheat/170316.csv',
                        datadir + 'timelapse-wheat/170427.csv',
                        datadir + 'timelapse-wheat/170516.csv'])
for s in k.surveys:
    s.df = s.df[:20]

k.computeApparentChange()
# show change in apparent values collected
fig, axs = plt.subplots(1, 3, sharex=True, figsize=(12,3))
for i in range(len(k.surveys)):
    ax = axs[i]
    k.show(index=i, ax=ax)
    if i > 0:
        ax.set_ylabel('Change in EC [mS/m]')
fig.tight_layout()
Trimming surveys and only keep common positions
Matching positions between surveys for time-lapse inversion...20 in common...done in 0.011s
Computing relative ECa compared to background (1st survey).
../_images/nb_smoothing_6.png
k.setInit(depths0=np.linspace(0.2, 2, 10)) # use a smooth model
k.invertGN(alpha=0.1)
fig, axs = plt.subplots(1, 3, sharex=True, sharey=True, figsize=(12,3))
for i in range(3):
    ax = axs[i]
    if i == 0: # reference survey
        k.showResults(index=i, ax=ax)
    else:
        k.showResults(index=i, ax=ax, cmap='bwr_r', vmin=-8, vmax=2)
        fig.axes[-1].set_ylabel('EC change [mS/m]')
Survey 1/3
20/20 inverted
Survey 2/3
20/20 inverted
Survey 3/3
20/20 inverted
../_images/nb_smoothing_7.png

Download python script: nb_smoothing.py

Download Jupyter notebook: nb_smoothing.ipynb

View the notebook in the Jupyter nbviewer

Run this example interactively: binder