The fatiando package has been deprecated. Please check out the new tools in the Fatiando a Terra website: www.fatiando.org

Source code for fatiando.gravmag.tensor

r"""
Utilities for operating on the gradient tensor of potential fields.

**Functions**

* :func:`~fatiando.gravmag.tensor.invariants`: Calculates the first
  (:math:`I_1`), second (:math:`I_2`), and dimensionless (:math:`I`) invariants
* :func:`~fatiando.gravmag.tensor.eigen`: Calculates the eigenvalues and
  eigenvectors of an array of gradient tensor measurements
* :func:`~fatiando.gravmag.tensor.center_of_mass`: Estimate the center of
  mass of sources from the first eigenvector using the method of
  Beiki and Pedersen (2010)

**Theory**

Following Pedersen and Rasmussen (1990), the characteristic polynomial of the
gravity gradient tensor :math:`\mathbf{\Gamma}` is

.. math::

    \lambda^3 + I_1\lambda - I_2 = 0

where :math:`\lambda` is an eigenvalue and :math:`I_1` and :math:`I_2` are
the two invariants. The dimensionless invariant :math:`I` is

.. math::

    I = -\dfrac{(I_2/2)^2}{(I_1/3)^3}

The invariant :math:`I` indicates the dimensionality of the source.
:math:`I = 0` for 2 dimensional bodies and :math:`I = 1` for a monopole.

**References**

Beiki, M., and L. B. Pedersen (2010), Eigenvector analysis of gravity gradient
tensor to locate geologic bodies, Geophysics, 75(6), I37, doi:10.1190/1.3484098

Pedersen, L. B., and T. M. Rasmussen (1990), The gradient tensor of potential
field anomalies: Some implications on data collection and data processing of
maps, Geophysics, 55(12), 1558, doi:10.1190/1.1442807

----

"""
from __future__ import division
import numpy
import numpy.linalg

from .. import gridder
from ..utils import safe_solve


[docs]def invariants(tensor): """ Calculates the first, second, and dimensionless invariants of the gradient tensor. .. note:: The coordinate system used is x->North, y->East, z->Down Parameters: * tensor : list A list of arrays with the 6 components of the gradient tensor measured on a set of points. The order of the list should be: [gxx, gxy, gxz, gyy, gyz, gzz] Returns: * invariants : list = [:math:`I_1`, :math:`I_2`, :math:`I`] The invariants calculated for each point """ gxx, gxy, gxz, gyy, gyz, gzz = tensor gyyzz = gyy * gzz gyz_sqr = gyz ** 2 inv1 = gxx * gyy + gyyzz + gxx * gzz - gxy ** 2 - gyz_sqr - gxz ** 2 inv2 = (gxx * (gyyzz - gyz_sqr) + gxy * (gyz * gxz - gxy * gzz) + gxz * (gxy * gyz - gxz * gyy)) inv = -((0.5 * inv2) ** 2) / ((inv1 / 3.) ** 3) return [inv1, inv2, inv]
[docs]def eigen(tensor): """ Calculates the eigenvalues and eigenvectors of the gradient tensor. .. note:: The coordinate system used is x->North, y->East, z->Down Parameters: * tensor : list A list of arrays with the 6 components of the gradient tensor measured on a set of points. The order of the list should be: [gxx, gxy, gxz, gyy, gyz, gzz] Returns: * result : list of lists The eigenvalues and eigenvectors at each observation point. ``[[eigval1, eigval2, eigval3], [eigvec1, eigvec2, eigvec3]]`` * eigval1,2,3 : array The first, second, and third eigenvalues * eigvec1,2,3 : array (shape = (N, 3) where N is the number of points) The first, second, and third eigenvectors Example: >>> tensor = [[2], [0], [0], [3], [0], [1]] >>> eigenvals, eigenvecs = eigen(tensor) >>> print eigenvals[0], eigenvecs[0] [ 3.] [[ 0. 1. 0.]] >>> print eigenvals[1], eigenvecs[1] [ 2.] [[ 1. 0. 0.]] >>> print eigenvals[2], eigenvecs[2] [ 1.] [[ 0. 0. 1.]] """ eigvals = [] eigvec1 = [] eigvec2 = [] eigvec3 = [] for gxx, gxy, gxz, gyy, gyz, gzz in numpy.transpose(tensor): matrix = numpy.array([[gxx, gxy, gxz], [gxy, gyy, gyz], [gxz, gyz, gzz]]) eigval, eigvec = numpy.linalg.eig(matrix) args = numpy.argsort(eigval)[::-1] eigvals.append([eigval[i] for i in args]) eigvec1.append(eigvec[:, args[0]]) eigvec2.append(eigvec[:, args[1]]) eigvec3.append(eigvec[:, args[2]]) eigvec1 = numpy.array(eigvec1) eigvec2 = numpy.array(eigvec2) eigvec3 = numpy.array(eigvec3) return numpy.transpose(eigvals), [eigvec1, eigvec2, eigvec3]
[docs]def center_of_mass(x, y, z, eigvec1, windows=1, wcenter=None, wmin=None, wmax=None): """ Estimates the center of mass of a source using the 1st eigenvector Uses the method of Beiki and Pedersen (2010) with an expanding window scheme to get the best estimate and deal with multiple sources. Parameters: * x, y, z : arrays The x, y, and z coordinates of the observation points * eigvec1 : array (shape = (N, 3) where N is the number of observations) The first eigenvector of the gravity gradient tensor at each observation point * windows : int The number of expanding windows to use * wcenter : list = [x, y] The [x, y] coordinates of the center of the expanding windows. Will default to the middle of the data area if None * wmin, wmax : float Minimum and maximum size of the expanding windows. Will default to 10% data area and 100% data area, respectively, if None Returns: * [xo, yo, zo] : floats xo, yo, zo are the coordinates of the estimated center of mass Examples: Estimate the center of a sphere using some synthetic data: >>> from fatiando import gridder >>> from fatiando.mesher import Sphere >>> from fatiando.gravmag import sphere, tensor >>> # Generate synthetic data using a sphere model >>> # The center of the sphere is (-100, 0, 100) >>> model = [Sphere(-100, 20, 100, 100, {'density':1000})] >>> x, y, z = gridder.regular((-500, 500, -500, 500), (20, 20), z=-100) >>> data = [sphere.gxx(x, y, z, model), ... sphere.gxy(x, y, z, model), ... sphere.gxz(x, y, z, model), ... sphere.gyy(x, y, z, model), ... sphere.gyz(x, y, z, model), ... sphere.gzz(x, y, z, model)] >>> # Get the first eigenvector >>> eigenvals, eigenvecs = tensor.eigen(data) >>> # Now estimate the center of mass >>> cm = tensor.center_of_mass(x, y, z, eigenvecs[0]) >>> print('{:.1f} {:.1f} {:.1f}'.format(cm[0], cm[1], cm[2])) -100.0 20.0 100.0 """ if wmin is None: wmin = 0.1 * numpy.mean([x.max() - x.min(), y.max() - y.min()]) if wmax is None: wmax = numpy.mean([x.max() - x.min(), y.max() - y.min()]) # To ensure that if there is only one window, it will use the largest # possible if windows == 1: wmin = wmax if wcenter is None: wcenter = [0.5 * (x.min() + x.max()), 0.5 * (y.min() + y.max())] xc, yc = wcenter best = None for size in numpy.linspace(wmin, wmax, windows): area = [xc - 0.5 * size, xc + 0.5 * size, yc - 0.5 * size, yc + 0.5 * size] wx, wy, scalars = gridder.cut(x, y, [z, eigvec1], area) wz, weigvec1 = scalars # Estimate the center of mass for the data in this window vx, vy, vz = numpy.transpose(weigvec1) m11 = numpy.sum(1 - vx ** 2) m12 = numpy.sum(-vx * vy) m13 = numpy.sum(-vx * vz) m22 = numpy.sum(1 - vy ** 2) m23 = numpy.sum(-vy * vz) m33 = numpy.sum(1 - vz ** 2) matrix = numpy.array( [[m11, m12, m13], [m12, m22, m23], [m13, m23, m33]]) vector = numpy.array([ numpy.sum((1 - vx ** 2) * wx - vx * vy * wy - vx * vz * wz), numpy.sum(-vx * vy * wx + (1 - vy ** 2) * wy - vy * vz * wz), numpy.sum(-vx * vz * wx - vy * vz * wy + (1 - vz ** 2) * wz)]) # Might be a complex number, but I just want the real part cm = safe_solve(matrix, vector).real xo, yo, zo = cm dists = ((xo - wx) ** 2 + (yo - wy) ** 2 + (zo - wz) ** 2 - ((xo - wx) * vx + (yo - wy) * vy + (zo - wz) * vz) ** 2) sigma = numpy.sqrt(numpy.sum(dists) / len(wx)) if best is None or sigma < best[1]: best = [cm, sigma] return best[0]