#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Method Manager for Magnetics."""
import numpy as np
import pygimli as pg
import pygimli.meshtools as mt
from pygimli.viewer import pv
from pygimli.frameworks import MeshMethodManager
from .MagneticsModelling import MagneticsModelling
from .tools import depthWeighting
[docs]
class MagManager(MeshMethodManager):
""" Magnetics Manager.
"""
[docs]
def __init__(self, data=None, **kwargs):
""" Create Magnetics Manager instance.
"""
self.DATA = kwargs.pop("DATA", None)
self.x = kwargs.pop("x", None)
self.y = kwargs.pop("y", None)
self.z = kwargs.pop("z", None)
self.igrf = kwargs.pop("igrf", None)
self.mesh_ = kwargs.pop("mesh", None)
# self.inv_ = pg.frameworks.Inversion()
if isinstance(data, str):
self.DATA = np.genfromtxt(data, names=True)
self.x = self.DATA["x"]
self.y = self.DATA["y"]
self.z = np.abs(self.DATA["z"])
self.cmp = [t for t in self.DATA.dtype.names
if t.startswith("B") or t.startswith("T")]
self.cmp = kwargs.pop("cmp", ["TFA"])
super().__init__()
if self.mesh_ is not None:
self.setMesh(self.mesh_)
[docs]
def showData(self, cmp=None, **kwargs):
""" Show data.
"""
cmp = cmp or self.cmp
nc = 2 if len(cmp) > 1 else 1
nr = (len(cmp)+1) // 2
fig, ax = pg.plt.subplots(nr, nc, sharex=True, sharey=True,
squeeze=False, figsize=(7, len(self.cmp)*1+3))
axs = np.atleast_1d(ax.flat)
kwargs.setdefault("cmap", "bwr")
for i, c in enumerate(cmp):
fld = self.DATA[c]
vv = max(-np.min(fld)*1., np.max(fld)*1.)
sc = axs[i].scatter(self.x, self.y, c=fld,
vmin=-vv, vmax=vv, **kwargs)
axs[i].set_title(c)
axs[i].set_aspect(1.0)
fig.colorbar(sc, ax=ax.flat[i])
return ax
[docs]
def createGrid(self, dx:float=50, depth:float=800, bnd:float=0):
""" Create a grid.
TODO
----
* check default values, make them more sensible
and depending on data
Arguments
---------
dx: float=50
Grid spacing in x and y direction.
depth: float=800
Depth of the grid in z direction.
bnd: float=0
Boundary distance to extend the grid in x and y direction.
Returns
-------
mesh: :gimliapi:`GIMLI::Mesh`
Created 3D structured grid.
"""
x = np.arange(min(self.x)-bnd, max(self.x)+bnd+.1, dx)
y = np.arange(min(self.y)-bnd, max(self.y)+bnd+.1, dx)
z = np.arange(-depth, .1, dx)
self.mesh_ = mt.createGrid(x=x, y=y, z=z)
self.fop.setMesh(self.mesh_)
return self.mesh_
[docs]
def createMesh(self, bnd:float=0, area:float=1e5, depth:float=800,
quality:float=1.3, addPLC:pg.Mesh=None, addPoints:bool=True):
""" Create an unstructured 3D mesh.
TODO
----
* check default values, make them more sensible
and depending on data
Arguments
---------
bnd: float=0
Boundary distance to extend the mesh in x and y direction.
area: float=1e5
Maximum area constraint for cells.
depth: float=800
Depth of the mesh in z direction.
quality: float=1.3
Quality factor for mesh generation.
addPLC: :gimliapi:`GIMLI::Mesh`
PLC mesh to add to the mesh.
addPoints: bool=True
Add points from self.x and self.y to the mesh.
Returns
-------
mesh: :gimliapi:`GIMLI::Mesh`
Created 3D unstructured mesh.
"""
geo = mt.createCube(start=[min(self.x)-bnd, min(self.x)-bnd, -depth],
end=[max(self.x)+bnd, max(self.y)+bnd, 0])
if addPoints is True:
for xi, yi in zip(self.x, self.y):
geo.createNode([xi, yi, 0])
if addPLC:
geo += addPLC
self.mesh_ = mt.createMesh(geo, quality=quality, area=area)
self.fop.setMesh(self.mesh_)
return self.mesh_
[docs]
def createForwardOperator(self):
""" Create forward operator (computationally extensive!).
"""
points = np.column_stack([self.x, self.y, -np.abs(self.z)])
self.fwd = MagneticsModelling(points=points,
cmp=self.cmp, igrf=self.igrf)
return self.fwd
[docs]
def inversion(self, noise_level=2, noisify=False, **kwargs):
"""Run Inversion (requires mesh and FOP).
Arguments
---------
noise_level: float|array
absolute noise level (absoluteError)
noisify: bool
add noise before inversion
relativeError: float|array [0.01]
relative error to stabilize very low data
depthWeighting: bool [True]
apply depth weighting after Li&Oldenburg (1996)
z0: float
skin depth for depth weighting
mul: array
multiply constraint weight with
Keyword arguments
-----------------
startModel: float|array=0.001
Starting model (typically homogeneous)
relativeError: float=0.001
Relative error to stabilize very low data.
lam: float=10
regularization strength
verbose: bool=True
Be verbose
symlogThreshold: float [0]
Threshold for symlog data trans.
limits: [float, float]
Lower and upper parameter limits.
C: int|Matrix|[float, float, float] [1]
Constraint order.
C(,cType): int|Matrix|[float, float, float]=C
Constraint order, matrix or correlation lengths.
z0: float=25
Skin depth for depth weighting.
depthWeighting: bool=True
Apply depth weighting after Li&Oldenburg (1996).
mul: float=1
Multiply depth weighting constraint weight with this factor.
**kwargs:
Additional keyword arguments for the inversion.
Returns
-------
model: np.array
Model vector (also saved in self.inv.model).
"""
dataVec = np.concatenate([self.DATA[c] for c in self.cmp])
if noisify:
dataVec += np.random.randn(len(dataVec)) * noise_level
# self.inv_ = pg.Inversion(fop=self.fwd, verbose=True)
self.inv.setForwardOperator(self.fwd)
kwargs.setdefault("startModel", 0.001)
kwargs.setdefault("relativeError", 0.001)
kwargs.setdefault("lam", 10)
kwargs.setdefault("verbose", True)
thrs = kwargs.pop("symlogThreshold", 0)
if thrs > 0:
self.inv.dataTrans = pg.trans.TransSymLog(thrs)
limits = kwargs.pop("limits", [0, 0.1])
self.inv.setRegularization(limits=limits)
C = kwargs.pop("C", 1)
cType = kwargs.pop("cType", C)
if hasattr(C, "__iter__"):
self.inv.setRegularization(correlationLengths=C)
cType = -1
elif isinstance(C, pg.core.MatrixBase):
self.inv.setRegularization(C=C)
else:
self.inv.setRegularization(cType=C)
z0 = kwargs.pop("z0", 25) # Oldenburg&Li(1996)
if kwargs.pop("depthWeighting", True):
cw = self.fwd.regionManager().constraintWeights()
dw = depthWeighting(self.mesh_, cell=not(cType==1), z0=z0)
if len(dw) == len(cw):
dw *= cw
print(min(dw), max(dw))
else:
print("lengths not matching!")
dw *= kwargs.pop("mul", 1)
self.inv.setConstraintWeights(dw)
model = self.inv.run(dataVec, absoluteError=noise_level, **kwargs)
return model
[docs]
def showDataFit(self):
""" Show data, model response and misfit.
"""
nc = len(self.cmp)
_, ax = pg.plt.subplots(ncols=3, nrows=nc, figsize=(12, 3*nc),
sharex=True, sharey=True, squeeze=False)
vals = np.reshape(self.inv.dataVals, [nc, -1])
mm = np.max(np.abs(vals))
resp = np.reshape(self.inv.response, [nc, -1])
errs = np.reshape(self.inv.errorVals, [nc, -1]) # relative!
misf = (vals - resp) / np.abs(errs * vals)
fkw = {'cmap':"bwr", 'vmin':-mm, 'vmax':mm}
mkw = {'cmap':"bwr", 'vmin':-3, 'vmax':3}
for i in range(nc):
ax[i, 0].scatter(self.x, self.y, c=vals[i], **fkw)
ax[i, 1].scatter(self.x, self.y, c=resp[i], **fkw)
ax[i, 2].scatter(self.x, self.y, c=misf[i], **mkw)
[docs]
def show3DModel(self, label:str=None, trsh:float=0.025,
synth:pg.Mesh=None, invert:bool=False,
position:str="yz", elevation:float=10, azimuth:float=25,
zoom:float=1.2, **kwargs):
""" Standard 3D view.
Arguments
---------
label: str='sus'
Label for the mesh data to visualize.
trsh: float=0.025
Threshold for the mesh data to visualize.
synth: :gimliapi:`GIMLI::Mesh`=None
Synthetic model to visualize in wireframe.
invert: bool=False
Invert the threshold filter.
position: str="yz"
Camera position, e.g. "yz", "xz", "xy".
elevation: float=10
Camera elevation angle.
azimuth: float=25
Camera azimuth angle.
zoom: float=1.2
Camera zoom factor.
Keyword arguments
-----------------
cMin: float=0.001
Minimum color value for the mesh data.
cMax: float=None
Maximum color value for the mesh data. If None, it is set to the
maximum value of the mesh data.
cMap: str="Spectral_r"
Colormap for the mesh data visualization.
logScale: bool=False
Use logarithmic scale for the mesh data visualization.
**kwargs:
Additional keyword arguments for the pyvista plot.
Returns
-------
pl: pyvista Plotter
Plot widget with the 3D model visualization.
"""
if label is None:
## ever happen that this is a string?
label = self.inv.model
if not isinstance(label, str):
self.mesh_["sus"] = np.array(label)
label = "sus"
kwargs.setdefault("cMin", 0.001)
kwargs.setdefault("cMax", max(self.mesh_[label]))
kwargs.setdefault("cMap", "Spectral_r")
kwargs.setdefault("logScale", False)
flt = None
pl, _ = pg.show(self.mesh_, style="wireframe", hold=True,
alpha=0.1)
# mm = [min(self.mesh_[label]), min(self.mesh_[label])]
if trsh > 0:
flt = {"threshold": {'value':trsh, 'scalars':label,'invert':invert}}
pv.drawModel(pl, self.mesh_, label=label, style="surface",
filter=flt, **kwargs)
pv.drawMesh(pl, self.mesh_, label=label, style="surface", **kwargs,
filter={"slice": {'normal':[-1, 0, 0], 'origin':[0, 0, 0]}})
if synth:
pv.drawModel(pl, synth, style="wireframe")
pl.camera_position = position
pl.camera.azimuth = azimuth
pl.camera.elevation = elevation
pl.camera.zoom(zoom)
pl.show()
return pl
if __name__ == "__main__":
pass