#!/usr/bin/env python
# coding: utf-8
"""Classes for modelling helicopter electromagnetics (HEM) using VMD solvers"""
from math import sqrt, pi
import numpy as np
import pygimli as pg
from pygimli.physics.constants import Constants
from pygimli.frameworks import Block1DModelling, MeshModelling
def registerDAEROcmap():
"""Standardized colormap from A-AERO projects (purple=0.3 to red=500).
Example
-------
>>> import pygimli as pg
>>> cmap = pg.physics.em.hemmodelling.registerDAEROcmap()
>>> mesh = pg.createGrid(20,2)
>>> data = pg.x(mesh.cellCenters())
>>> _ = pg.show(mesh, data, cMap=cmap)
"""
from matplotlib.colors import LinearSegmentedColormap
import matplotlib as mpl
CMY = np.array([
[127, 255, 31], [111, 255, 47], [95, 255, 63], [79, 255, 79],
[63, 255, 95], [47, 255, 111], [31, 255, 127], [16, 255, 159],
[0, 223, 159], [0, 191, 159], [0, 159, 207], [0, 127, 175],
[0, 95, 175], [0, 63, 175], [0, 47, 175], [0, 31, 191], [0, 0, 255],
[0, 0, 159], [15, 0, 127], [47, 0, 143], [79, 0, 143], [111, 0, 143],
[143, 0, 127], [159, 31, 63], [175, 47, 31], [207, 63, 0],
[223, 111, 0], [231, 135, 0], [239, 159, 0], [255, 191, 47],
[239, 199, 63], [223, 207, 79], [207, 239, 111]], dtype=float)
RGB = 1.0 - CMY/255
daero = LinearSegmentedColormap.from_list('D-AERO', RGB)
mpl.colormaps.register(name='daero', cmap=daero)
return daero
# class HEMmodelling(pg.Modelling):
[docs]
class HEMmodelling(Block1DModelling):
"""HEM Airborne modelling class based on the BGR RESOLVE system."""
ep0 = Constants.e0
mu0 = Constants.mu0
c0 = sqrt(1. / ep0 / mu0)
fdefault = np.array([387.0, 1821.0, 8388.0, 41460.0, 133300.0], float)
rdefault = np.array([7.94, 7.93, 7.93, 7.91, 7.92], float)
scaling = 1e6
[docs]
def __init__(self, nlay, height, f=None, r=None, **kwargs):
"""Initialize class with geometry.
Parameters
----------
nlay : int
number of layers
height : float
helicopter
f : array [BGR RESOLVE system 387Hz-133kHz]
frequency vector
r : array [BGR RESOLVE system 7.91-7.94]
distance vector
scaling : float
scaling factor or string (ppm=1e6, percent=1e2)
"""
self.nlay = nlay
self.height = height
self.f = np.asarray(f)
if r is None:
raise Exception("Specify separation value or vector!")
if 'scaling' in kwargs:
if kwargs['scaling'] == 'ppm':
self.scaling = 1e6
elif kwargs['scaling'] in ['percent', '%']:
self.scaling = 1e2
else:
self.scaling = kwargs['scaling']
if self.f is None:
self.f = self.fdefault
if isinstance(r, float) or isinstance(r, int):
self.r = np.ones_like(f, dtype=float) * r
else:
if len(r) == len(self.f):
self.r = r
else:
raise Exception('Length vector have to be matching!')
if self.r is None:
self.r = self.rdefault
self.wem = (2.0 * pi * self.f) ** 2 * self.ep0 * self.mu0
self.iwm = 1.0j * 2.0 * pi * self.f * self.mu0
mesh = pg.meshtools.createMesh1DBlock(nlay)
super().__init__()
self.setMesh(mesh)
[docs]
def response(self, model):
"""Compute response vector by pasting in-phase and out-phase data."""
ip, op = self.vmd_hem(self.height,
np.asarray(model[self.nlay-1:self.nlay*2-1]),
np.asarray(model[:self.nlay-1]))
return pg.cat(ip, op)
[docs]
def response_mt(self, par, i=0):
"""Multi-threaded forward response."""
return self.response(par)
[docs]
def calc_forward(self, x, h, rho, d, epr, mur, quasistatic=False):
"""Calculate forward response."""
field = np.zeros((self.f.size, x.size), complex)
# Forward calculation for background model
if d.size:
for m in range(x.size):
field[:, m] = self.vmd_hem(np.array([h[m]], float),
rho[:, m], d[:, m], epr[:, m],
mur[:, m], quasistatic).T[:, 0]
else:
for n in range(self.f.size):
for m in range(x.size):
field[n, m] = self.vmd_hem(np.array([h[n, m]], float),
np.array([rho[n, m]], float),
d,
np.array([epr[n, m]], float),
np.array([mur[n, m]], float),
quasistatic).T[n, 0]
return field
[docs]
def downward(self, rho, d, z, epr, mur, lam):
"""Downward continuation of fields."""
nl = rho.size
alpha = np.zeros((nl, lam.shape[1], self.f.size), complex)
b = np.zeros((nl, lam.shape[1], self.f.size), complex)
aa = np.zeros((nl, lam.shape[1], self.f.size), complex)
aap = np.zeros((nl, lam.shape[1], self.f.size), complex)
rho = rho[:, np.newaxis, np.newaxis] * np.ones(
(rho.size, lam.shape[1], self.f.size), float)
d = d[:, np.newaxis, np.newaxis] * np.ones(
(d.size, lam.shape[1], self.f.size), float)
h = np.insert(np.cumsum(d[:, 0, 0]), 0, 0)
epr = epr[:, np.newaxis, np.newaxis] * np.ones(
(epr.size, lam.shape[1], self.f.size), float)
mur = mur[:, np.newaxis, np.newaxis] * np.ones(
(mur.size, lam.shape[1], self.f.size), float)
lam = np.tile(lam, (nl, 1, 1))
# progression constant
alpha = np.sqrt(lam ** 2 - np.tile(self.wem, (nl, lam.shape[1], 1)) *
epr * mur + np.tile(self.iwm, (nl, lam.shape[1], 1)) *
mur / rho)
if nl == 1: # homogenous halfspace
b1 = alpha.copy() # (1, 100, nfreq)
a = np.exp(-alpha * z)
ap = a.copy()
return b1, a, ap
elif nl > 1: # multi-layer case
# tanh num unstable tanh(x)=(exp(x)-exp(-x))/(exp(x)+exp(-x))
ealphad = np.exp(-2.0 * alpha[0:-1, :, :] * d)
talphad = (1.0 - ealphad) / (1.0 + ealphad)
b[-1, :, :] = np.copy(alpha[-1, :, :])
# recursive admittance computation at upper layer boundary
# from bottom to top, for nl-1 layers
for n in range(nl-2, -1, -1):
b[n, :, :] = alpha[n, :, :] * \
(b[n+1, :, :] + alpha[n, :, :] * talphad[n, :, :]) / \
(alpha[n, :, :] + b[n+1, :, :] * talphad[n, :, :])
# Impedance
c = 1.0 / b
# b1 == 1. row in b (nl, 100, nfreq)
b1 = np.copy(b[0, :, :][np.newaxis, :, :]) # (1, 100, nfreq)
# Variation from one layer boundary to the other
for n in range(0, nl-1):
aa[n, :, :] = (b[n, :, :] + alpha[n, :, :]) / (
b[n+1, :, :] + alpha[n, :, :]) * \
np.exp(-alpha[n, :, :] * d[n, :, :])
aap[n, :, :] = (1.0 + alpha[n, :, :] * c[n, :, :]) / (
1.0 + alpha[n, :, :] * c[n+1, :, :]) * \
np.exp(-alpha[n, :, :] * d[n, :, :])
# Determine layer Index where z is
for n in range(0, nl-1):
if np.logical_and(z >= h[n], z < h[n+1]):
ind = n
try:
ind
except NameError:
ind = nl - 1
if (ind + 1) < nl:
a = np.prod(aa[:ind, :, :], 0) * 0.5 * \
(1.0 + b[ind, :, :] / alpha[ind, :, :]) * \
(np.exp(-alpha[ind, :, :] * (z - h[ind])) -
(b[ind+1, :, :] - alpha[ind, :, :]) /
(b[ind+1, :, :] + alpha[ind, :, :]) *
np.exp(-alpha[ind, :, :] *
(d[ind, :, :] + h[ind+1] - z)))
ap = np.prod(aap[:ind, :, :], 0) * 0.5 * \
(1.0 + alpha[ind, :, :] * c[ind, :, :]) * \
(np.exp(-alpha[ind, :, :] * (z - h[ind])) +
(1.0 - alpha[ind, :, :] * c[ind+1, :, :]) /
(1.0 + alpha[ind, :, :] * c[ind+1, :, :]) *
np.exp(-alpha[ind, :, :] *
(d[ind, :, :] + h[ind+1] - z)))
else:
a = np.prod(aa, 0) * np.exp(-alpha[ind, :, :] * (z - h[ind]))
ap = np.prod(aap, 0) * np.exp(-alpha[ind, :, :] * (z - h[ind]))
a = a[np.newaxis, :, :] # (1, 100, nfreq)
ap = ap[np.newaxis, :, :] # (1, 100, nfreq)
return b1, a, ap
[docs]
def vmd_hem(self, h, rho, d, epr=1., mur=1., quasistatic=False):
"""Vertical magnetic dipole (VMD) response.
Parameters
----------
h : float
flight height
rho : array
resistivity vector
d : array
thickness vector
"""
# filter coefficients
if isinstance(epr, float):
epr = np.ones((len(rho),), float)*epr
if isinstance(mur, float):
mur = np.ones((len(rho),), float)*mur
fc0, nc, nc0 = hankelfc(3)
fc1, nc, nc0 = hankelfc(4)
# allocate arrays
nf = len(self.f)
lam = np.zeros((1, nc, nf), float)
alpha0 = np.zeros((1, nc, nf), complex)
delta0 = np.zeros((1, nc, nf), complex)
delta1 = np.zeros((1, nc, nf), complex)
delta2 = np.zeros((1, nc, nf), complex)
delta3 = np.zeros((1, nc, nf), complex)
aux0 = np.zeros((1, nf), complex)
aux1 = np.zeros((1, nf), complex)
aux2 = np.zeros((1, nf), complex)
aux3 = np.zeros((1, nf), complex)
Z = np.zeros(nf, complex)
# r0
r0 = np.copy(self.r)
# determine optimum r0 (shift nodes) for f > 1e4 and h > 100
if quasistatic:
index = np.zeros(self.f.shape, np.bool)
else:
index = np.logical_and(self.f >= 1e4, h >= 100.0)
if np.any(index):
opt = np.floor(10.0 * np.log10(
self.r[index] * 2.0 * np.pi * self.f[index] / self.c0) + nc0)
r0[index] = self.c0 / (2.0 * np.pi * self.f[index]) * 10.0 ** (
(opt + 0.5 - nc0) / 10.0)
# Wave numbers
n = np.arange(nc0 - nc, nc0, 1, float)
q = 0.1 * np.log(10)
lam = np.reshape(np.exp(-n[np.newaxis, :, np.newaxis] * q) /
r0[np.newaxis, np.newaxis, :], (-1, nc, r0.size))
# (1, 100, nfreq)
# wave number in air, quasistationary approximation
alpha0 = np.copy(lam) * complex(1, 0) # (1, 100, nfreq)
# wave number in air, full solution for f > 1e4
if quasistatic:
index = np.zeros(self.f.shape, np.bool)
else:
index = self.f >= 1e4
if np.any(index):
alpha0[:, :, index] = np.sqrt(
lam[:, :, index]**2 - np.tile(self.wem[index], (1, nc, 1)) +
np.tile(self.iwm[index], (1, nc, 1)) / 1e9) # (1, 100 , nfreq)
# Admittanzen an der Oberfläche eines geschichteten Halbraums
b1, _, _ = self.downward(rho, d, 0.0, epr, mur, lam)
# Kernel functions
e = np.exp(-2.0 * h * alpha0) # (1, 100, nfreq)
delta0 = (b1 - alpha0 * mur[0]) / (b1 + alpha0 * mur[0]) * e
delta1 = (2 * mur[0]) / (b1 + alpha0 * mur[0]) * e # (1, 100, nfreq)
delta2 = 1 / h * e # (1, 100, nfreq)
delta3 = 1 / (2 * h) * e # (1, 100, nfreq)
# convolution
# quasistationary approximation
aux0 = np.sum(delta0 * lam ** 3 / alpha0 *
np.tile(fc0[::-1].T[:, :, np.newaxis],
(1, 1, self.f.size)), 1, complex) / r0
# full solution, partial integration
if np.any(index):
aux1 = np.sum(delta1 * lam ** 3 *
np.tile(fc0[::-1].T[:, :, np.newaxis],
(1, 1, self.f.size)), 1, complex) / r0
aux2 = np.sum(
delta2 * lam * np.tile(fc0[::-1].T[:, :, np.newaxis],
(1, 1, self.f.size)), 1, complex)/r0
aux3 = np.sum(delta3 * lam ** 2 *
np.tile(fc1[::-1].T[:, :, np.newaxis],
(1, 1, self.f.size)), 1, complex) / r0
# normed secondary field
# quasistationary approximation
Z = self.r ** 3 * aux0 * self.scaling
# full solution
if np.any(index):
Z[:, index] = (-self.r[index]**3 * aux1[:, index] +
self.r[index]**3 * aux2[:, index] -
self.r[index]**4 * aux3[:, index]) * self.scaling
return np.real(Z[0]), np.imag(Z[0])
[docs]
def vmd_total_Ef(self, h, z, rho, d, epr, mur, tm):
"""VMD E-phi field (not used actively)."""
# only halfspace
# Filter coefficients
fc1, nc, nc0 = hankelfc(4)
lam = np.zeros((1, nc, self.f.size), float)
alpha0 = np.zeros((1, nc, self.f.size), complex)
delta = np.zeros((1, nc, self.f.size), complex)
aux = np.zeros((1, self.f.size), complex)
Ef = np.zeros(self.f.size, complex)
r0 = np.copy(self.r)
# wave numbers
n = np.arange(nc0 - nc, nc0, 1, float)
q = 0.1 * np.log(10)
lam = np.reshape(np.exp(-n[np.newaxis, :, np.newaxis] * q) /
r0[np.newaxis, np.newaxis, :], (-1, nc, r0.size))
# wave numbers in air, full solution
alpha0 = np.sqrt(lam ** 2 - np.tile(self.wem, (1, nc, 1)) +
np.tile(self.iwm, (1, nc, 1)) / 1e9)
# admittances on surface of layered halfspace
b1, aa, _ = self.downward(rho, d, z, epr, mur, lam)
# Kernel functions
e = np.exp(-h * alpha0) # (1, 100, nfreq)
delta = 2.0 / (alpha0 + b1) * e # (1, 100, nfreq)
# convolution
# quasistationary approximation
aux = np.sum(delta*lam**2*aa*np.tile(fc1[::-1].T[:, :, np.newaxis],
(1, 1, self.f.size)), 1,
complex) / r0 # (1, nfreq)
# absolute fields, full solution
Ef = -tm * self.iwm / (4.0 * np.pi) * aux
return Ef
def hankelfc(order):
"""Filter coefficients for Hankel transformation."""
if order == 1: # sin
fc = np.array([
2.59526236e-7, 3.66544843e-7, 5.17830795e-7, 7.31340622e-7,
1.03322805e-6, 1.45918500e-6, 2.06161065e-6, 2.91137793e-6,
4.11357863e-6, 5.80876420e-6, 8.20798075e-6, 1.15895083e-5,
1.63778560e-5, 2.31228459e-5, 3.26800649e-5, 4.61329334e-5,
6.52101085e-5, 9.20390575e-5, 1.30122935e-4, 1.83620431e-4,
2.59656626e-4, 3.66311982e-4, 5.18141184e-4, 7.30717340e-4,
1.03392184e-3, 1.45742714e-3, 2.06292302e-3, 2.90599911e-3,
4.11471902e-3, 5.79042763e-3, 8.20004722e-3, 1.15192930e-2,
1.63039133e-2, 2.28257757e-2, 3.22249222e-2, 4.47864328e-2,
6.27329625e-2, 8.57059100e-2, 1.17418314e-1, 1.53632655e-1,
1.97717964e-1, 2.28849849e-1, 2.40311038e-1, 1.65409220e-1,
2.84701476e-3, -2.88016057e-1, -3.69097406e-1, -2.50107514e-2,
5.71811256e-1, -3.92261572e-1, 7.63280044e-2, 5.16233994e-2,
-6.48012082e-2, 4.89047141e-2, -3.26936331e-2, 2.10539842e-2,
-1.33862549e-2, 8.47124695e-3, -5.35123972e-3, 3.37796651e-3,
-2.13174466e-3, 1.34513833e-3, -8.48749612e-4, 5.35531006e-4,
-3.37898780e-4, 2.13200109e-4, -1.34520273e-4, 8.48765787e-5,
-5.35535069e-5, 3.37899801e-5, -2.13200365e-5, 1.34520337e-5,
-8.48765949e-6, 5.35535110e-6, -3.37899811e-6, 2.13200368e-6,
-1.34520338e-6, 8.48765951e-7, -5.35535110e-7, 3.37899811e-7],
float)
nc = int(80)
nc0 = int(40)
elif order == 2: # cos
fc = np.array([
1.63740363e-7, 1.83719709e-7, 2.06136904e-7, 2.31289411e-7,
2.59510987e-7, 2.91176117e-7, 3.26704977e-7, 3.66569013e-7,
4.11297197e-7, 4.61483045e-7, 5.17792493e-7, 5.80972733e-7,
6.51862128e-7, 7.31401337e-7, 8.20645798e-7, 9.20779729e-7,
1.03313185e-6, 1.15919300e-6, 1.30063594e-6, 1.45933752e-6,
1.63740363e-6, 1.83719709e-6, 2.06136904e-6, 2.31289411e-6,
2.59510987e-6, 2.91176117e-6, 3.26704977e-6, 3.66569013e-6,
4.11297197e-6, 4.61483045e-6, 5.17792493e-6, 5.80972733e-6,
6.51862128e-6, 7.31401337e-6, 8.20645798e-6, 9.20779729e-6,
1.03313185e-5, 1.15919300e-5, 1.30063594e-5, 1.45933752e-5,
1.63740363e-5, 1.83719709e-5, 2.06136904e-5, 2.31289411e-5,
2.59510987e-5, 2.91176117e-5, 3.26704977e-5, 3.66569013e-5,
4.11297197e-5, 4.61483045e-5, 5.17792493e-5, 5.80972733e-5,
6.51862128e-5, 7.31401337e-5, 8.20645798e-5, 9.20779729e-5,
1.03313185e-4, 1.15919300e-4, 1.30063594e-4, 1.45933752e-4,
1.63740363e-4, 1.83719709e-4, 2.06136904e-4, 2.31289411e-4,
2.59510987e-4, 2.91176117e-4, 3.26704976e-4, 3.66569013e-4,
4.11297197e-4, 4.61483045e-4, 5.17792493e-4, 5.80972733e-4,
6.51862127e-4, 7.31401337e-4, 8.20645797e-4, 9.20779730e-4,
1.03313185e-3, 1.15919300e-3, 1.30063593e-3, 1.45933753e-3,
1.63740362e-3, 1.83719710e-3, 2.06136901e-3, 2.31289411e-3,
2.59510977e-3, 2.91176115e-3, 3.26704948e-3, 3.66569003e-3,
4.11297114e-3, 4.61483003e-3, 5.17792252e-3, 5.80972566e-3,
6.51861416e-3, 7.31400728e-3, 8.20643673e-3, 9.20777603e-3,
1.03312545e-2, 1.15918577e-2, 1.30061650e-2, 1.45931339e-2,
1.63734419e-2, 1.83711757e-2, 2.06118614e-2, 2.31263461e-2,
2.59454421e-2, 2.91092045e-2, 3.26529302e-2, 3.66298115e-2,
4.10749753e-2, 4.60613861e-2, 5.16081994e-2, 5.78193646e-2,
6.46507780e-2, 7.22544422e-2, 8.03873578e-2, 8.92661837e-2,
9.80670729e-2, 1.07049506e-1, 1.13757572e-1, 1.18327217e-1,
1.13965041e-1, 1.00497783e-1, 6.12958082e-2, -1.61234222e-4,
-1.11788551e-1, -2.27536948e-1, -3.39004453e-1, -2.25128800e-1,
8.98279919e-2, 5.12510388e-1, -1.31991937e-1, -3.35136479e-1,
3.64868100e-1, -2.34039961e-1, 1.32085237e-1, -7.56739672e-2,
4.52296662e-2, -2.78297002e-2, 1.73727753e-2, -1.09136894e-2,
6.87397283e-3, -4.33413470e-3, 2.73388730e-3, -1.72477355e-3,
1.08821012e-3, -6.86602007e-4, 4.33213523e-4, -2.73338487e-4,
1.72464733e-4, -1.08817842e-4, 6.86594042e-5, -4.33211523e-5,
2.73337984e-5, -1.72464607e-5, 1.08817810e-5, -6.86593962e-6,
4.33211503e-6, -2.73337979e-6, 1.72464606e-6, -1.08817810e-6,
6.86593961e-7, -4.33211503e-7, 2.73337979e-7, -1.72464606e-7],
float)
nc = int(164)
nc0 = int(122)
elif order == 3: # J0
fc = np.array([
2.89878288e-7, 3.64935144e-7, 4.59426126e-7, 5.78383226e-7,
7.28141338e-7, 9.16675639e-7, 1.15402625e-6, 1.45283298e-6,
1.82900834e-6, 2.30258511e-6, 2.89878286e-6, 3.64935148e-6,
4.59426119e-6, 5.78383236e-6, 7.28141322e-6, 9.16675664e-6,
1.15402621e-5, 1.45283305e-5, 1.82900824e-5, 2.30258527e-5,
2.89878259e-5, 3.64935186e-5, 4.59426051e-5, 5.78383329e-5,
7.28141144e-5, 9.16675882e-5, 1.15402573e-4, 1.45283354e-4,
1.82900694e-4, 2.30258630e-4, 2.89877891e-4, 3.64935362e-4,
4.59424960e-4, 5.78383437e-4, 7.28137738e-4, 9.16674828e-4,
1.15401453e-3, 1.45282561e-3, 1.82896826e-3, 2.30254535e-3,
2.89863979e-3, 3.64916703e-3, 4.59373308e-3, 5.78303238e-3,
7.27941497e-3, 9.16340705e-3, 1.15325691e-2, 1.45145832e-2,
1.82601199e-2, 2.29701042e-2, 2.88702619e-2, 3.62691810e-2,
4.54794031e-2, 5.69408192e-2, 7.09873072e-2, 8.80995426e-2,
1.08223889e-1, 1.31250483e-1, 1.55055715e-1, 1.76371506e-1,
1.85627738e-1, 1.69778044e-1, 1.03405245e-1, -3.02583233e-2,
-2.27574393e-1, -3.62173217e-1, -2.05500446e-1, 3.37394873e-1,
3.17689897e-1, -5.13762160e-1, 3.09130264e-1, -1.26757592e-1,
4.61967890e-2, -1.80968674e-2, 8.35426050e-3, -4.47368304e-3,
2.61974783e-3, -1.60171357e-3, 9.97717882e-4, -6.26275815e-4,
3.94338818e-4, -2.48606354e-4, 1.56808604e-4, -9.89266288e-5,
6.24152398e-5, -3.93805393e-5, 2.48472358e-5, -1.56774945e-5,
9.89181741e-6, -6.24131160e-6, 3.93800058e-6, -2.48471018e-6,
1.56774609e-6, -9.89180896e-7, 6.24130948e-7, -3.93800005e-7,
2.48471005e-7, -1.56774605e-7, 9.89180888e-8, -6.24130946e-8],
float)
nc = int(100)
nc0 = int(60)
elif order == 4: # J1
fc = np.array([
1.84909557e-13, 2.85321327e-13, 4.64471808e-13, 7.16694771e-13,
1.16670043e-12, 1.80025587e-12, 2.93061898e-12, 4.52203829e-12,
7.36138206e-12, 1.13588466e-11, 1.84909557e-11, 2.85321327e-11,
4.64471808e-11, 7.16694771e-11, 1.16670043e-10, 1.80025587e-10,
2.93061898e-10, 4.52203829e-10, 7.36138206e-10, 1.13588466e-9,
1.84909557e-9, 2.85321326e-9, 4.64471806e-9, 7.16694765e-9,
1.16670042e-8, 1.80025583e-8, 2.93061889e-8, 4.52203807e-8,
7.36138149e-8, 1.13588452e-7, 1.84909521e-7, 2.85321237e-7,
4.64471580e-7, 7.16694198e-7, 1.16669899e-6, 1.80025226e-6,
2.93060990e-6, 4.52201549e-6, 7.36132477e-6, 1.13587027e-5,
1.84905942e-5, 2.85312247e-5, 4.64449000e-5, 7.16637480e-5,
1.16655653e-4, 1.79989440e-4, 2.92971106e-4, 4.51975783e-4,
7.35565435e-4, 1.13444615e-3, 1.84548306e-3, 2.84414257e-3,
4.62194743e-3, 7.10980590e-3, 1.15236911e-2, 1.76434485e-2,
2.84076233e-2, 4.29770596e-2, 6.80332569e-2, 9.97845929e-2,
1.51070544e-1, 2.03540581e-1, 2.71235377e-1, 2.76073871e-1,
2.16691977e-1, -7.83723737e-2, -3.40675627e-1, -3.60693673e-1,
5.13024526e-1, -5.94724729e-2, -1.95117123e-1, 1.99235600e-1,
-1.38521553e-1, 8.79320859e-2, -5.50697146e-2, 3.45637848e-2,
-2.17527180e-2, 1.37100291e-2, -8.64656417e-3, 5.45462758e-3,
-3.44138864e-3, 2.17130686e-3, -1.36998628e-3, 8.64398952e-4,
-5.45397874e-4, 3.44122545e-4, -2.17126585e-4, 1.36997597e-4,
-8.64396364e-5, 5.45397224e-5, -3.44122382e-5, 2.17126544e-5,
-1.36997587e-5, 8.64396338e-6, -5.45397218e-6, 3.44122380e-6,
-2.17126543e-6, 1.36997587e-6, -8.64396337e-7, 5.45397218e-7],
float)
nc = int(100)
nc0 = int(60)
return (np.reshape(fc, (-1, 1)), nc, nc0) # (100,) -> (100, 1)
class HEMRhoModelling(HEMmodelling):
"""Airborne EM (HEM) Forward modelling class for Occam inversion."""
def __init__(self, dvec, height=1., **kwargs):
"""Init class by specifying frequencies and distances (s. HEMMod)."""
nlay = len(dvec) + 1
self.dvec = np.asarray(dvec)
self.zvec = np.hstack((0, np.cumsum(dvec)))
HEMmodelling.__init__(self, nlay, height, **kwargs)
self.mymesh = pg.meshtools.createMesh1D(nlay)
self.setMesh(self.mymesh) # only for inversion
def response(self, model):
"""Forward response as combined in-phase and out-of-phase."""
ip, op = self.vmd_hem(self.height, rho=model, d=self.dvec)
return pg.cat(ip, op)
class FDEMResSusModelling(HEMmodelling):
"""FDEM block modelling class using both conductivity & permittivity."""
def __init__(self, nlay, height=1, **kwargs):
"""Init class (see HEMmodelling)."""
HEMmodelling.__init__(self, nlay, height, **kwargs)
self.scaling = 1e2
self.mymesh = pg.meshtools.createMesh1DBlock(nlay, nProperties=2)
self.setMesh(self.mymesh) # only for inversion
# pg.core.ModellingBase.__init__(self, self.mymesh)
def response(self, model):
"""Response vector as combined in-phase and out-phase data."""
thk = np.asarray(model[:self.nlay-1], dtype=float)
res = np.asarray(model[self.nlay-1:2*self.nlay-1], dtype=float)
mur = np.asarray(model[2*self.nlay-1:3*self.nlay-1], dtype=float) + 1
ip, op = self.vmd_hem(self.height, rho=res, d=thk, mur=mur)
return pg.cat(ip, op)
class HEMRhoSusModelling(HEMmodelling):
"""Airborne EM (HEM) smooth forward modelling including susceptibility."""
def __init__(self, dvec, *args, **kwargs):
"""Initialize (not yet working)."""
self.nlay = len(dvec) + 1
self.dvec = np.asarray(dvec)
self.zvec = np.hstack((0, np.cumsum(dvec)))
HEMmodelling.__init__(self, self.nlay, *args, **kwargs)
self.mymesh = pg.meshtools.createMesh1D(self.nlay, nProperties=2)
self.setMesh(self.mymesh) # only for inversion
def response(self, model):
"""Response vector as combined in-phase and out-phase data."""
res = np.asarray(model[:self.nlay])
mur = np.asarray(model[self.nlay:]) + 1
ip, op = self.vmd_hem(self.height, rho=res, d=self.dvec, mur=mur)
return pg.cat(ip, op)
class FDEMLCIFOP(pg.core.ModellingBase):
"""FDEM 2d-LCI modelling class based on Block matrices."""
def __init__(self, data, nlay=2, verbose=False, f=None, r=None):
"""Parameters: FDEM data class and number of layers."""
super(FDEMLCIFOP, self).__init__(verbose)
self.nlay = nlay
self.FOP = data.FOP(nlay)
self.nx = len(data.x)
self.nf = len(data.freq())
self.np = 2 * nlay - 1
self.mesh2d = pg.meshtools.createMesh2D(self.np, self.nx)
self.mesh2d.rotate(pg.RVector3(0, 0, -np.pi/2))
self.setMesh(self.mesh2d)
self.J = pg.matrix.BlockMatrix()
self.FOP1d = []
for i in range(self.nx):
self.FOP1d.append(HEMmodelling(nlay, data.z[i], f, r))
n = self.J.addMatrix(self.FOP1d[-1].jacobian())
self.J.addMatrixEntry(n, self.nf*2*i, self.np*i)
self.setJacobian(self.J)
def response(self, model):
"""Cut together forward responses of all soundings."""
modA = np.reshape(model, (self.nx, self.np))
resp = pg.Vector(0)
for i, modi in enumerate(modA):
resp = pg.cat(resp, self.FOP1d[i].response(modi))
return resp
def createJacobian(self, model):
"""Fill the individual blocks of the Block-Jacobi matrix."""
modA = np.reshape(model, (self.nx, self.np))
for i, modi in enumerate(modA):
self.FOP1d[i].createJacobian(modi)
class FDEM2dFOP(pg.core.ModellingBase):
"""FDEM 2d-LCI modelling class based on BlockMatrices."""
def __init__(self, data, nlay=2, verbose=False):
"""Parameters: FDEM data class and number of layers."""
super(FDEM2dFOP, self).__init__(verbose)
self.nlay = nlay
self.FOP = data.FOP(nlay)
self.nx = len(data.x)
self.nf = len(data.freq())
npar = 2 * nlay - 1
self.mesh2d = pg.Mesh()
self.mesh2d.create2DGrid(range(npar+1), range(self.nx+1))
self.setMesh(self.mesh2d)
self.J = pg.matrix.BlockMatrix()
self.FOP1d = []
for i in range(self.nx):
self.FOP1d.append(HEMmodelling(nlay, data.z[i]))
n = self.J.addMatrix(self.FOP1d[-1].jacobian())
self.J.addMatrixEntry(n, self.nf*2*i, npar*i)
self.J.recalcMatrixSize()
print(self.J.rows(), self.J.cols())
self.setJacobian(self.J)
def response(self, model):
"""Response as pasted forward responses from all soundings."""
modA = np.reshape(model, (self.nx, self.nlay*2-1))
resp = pg.Vector(0)
for i, modi in enumerate(modA):
resp = pg.cat(resp, self.FOP1d[i].response(modi))
return resp
def createJacobian(self, model):
"""Fill Jacobian (block) matrix by computing each block."""
modA = np.reshape(model, (self.nx, self.nlay*2-1))
for i in range(self.nx):
self.FOP1d[i].createJacobian(modA[i])
class FDEMSmoothModelling(MeshModelling):
"""Occam-style (smooth) inversion."""
def __init__(self, thk, **kwargs):
super().__init__()
self.thk_ = thk
self.nlay_ = len(thk)+1
self.core = HEMmodelling(**kwargs, nLayers=self.nlay_)
self.mesh_ = pg.meshtools.createMesh1D(self.nlay_)
self.setMesh(self.mesh_)
def response(self, par):
"""Model response (forward modelling)."""
return self.core.response(pg.cat(self.thk_, par))
if __name__ == '__main__':
numlay = 3
elevation = float(30.0)
resistivity = np.array([1000.0, 100.0, 1000.0], float)
thickness = np.array([22.0, 29.0], float)
fop = HEMmodelling(numlay, elevation, r=10) # frequency, separation)
IP, OP = fop.vmd_hem(elevation, resistivity, thickness)
pg.info(IP, OP)