Source code for pyrocko.gato.grid.slowness

# http://pyrocko.org - GPLv3
#
# The Pyrocko Developers, 21st Century
# ---|P------/S----------~Lg----------


import numpy as num

from pyrocko.guts import Float
from pyrocko.moment_tensor import euler_to_matrix
from .base import Grid, GridSnap, grid_coordinates
from ..error import GatoError

guts_prefix = 'gato'

d2r = num.pi / 180.


[docs]class SlownessGrid(Grid): pass
[docs]class CartesianSlownessGrid(SlownessGrid): ''' Regular cartesian slowness grid with optional rotation. Unrotated coordinate system is NED (north-east-down). 3D-indexing is ordered from sz (slow dimension) to sx (fast dimension) ``f[iz, iy, ix]``. 1D-indexing uses :py:func:`numpy.unravel_index` on ``(nz, ny, nx)``. If any attribute is changed after initialization, :py:meth:`update` must be called. ''' azimuth = Float.T( default=0.0, help='Angle of x against north [deg].') dip = Float.T( default=0.0, help='Angle of y against horizontal [deg], rotation around x') sx_min = Float.T( default=0.0, help='Sx axis minimum [s/m].') sx_max = Float.T( default=0.0, help='Sx axis maximum [s/m].') sy_min = Float.T( default=0.0, help='Sy axis minimum [s/m].') sy_max = Float.T( default=0.0, help='Sy axis maximum [s/m].') sz_min = Float.T( default=0.0, help='Sz axis minimum [s/m].') sz_max = Float.T( default=0.0, help='Sz axis maximum [s/m].') sx_delta = Float.T( default=1.0, help='Sx axis spacing [s/m].') sy_delta = Float.T( default=1.0, help='Sy axis spacing [s/m].') sz_delta = Float.T( default=1.0, help='Sz axis spacing [s/m].') snap = GridSnap.T( default='both', help='Flag to indicate how grid inconsistencies are handled.') @classmethod def from_smax_2d(cls, s_max, s_delta): return cls( sx_min=-s_max, sx_max=s_max, sx_delta=s_delta, sy_min=-s_max, sy_max=s_max, sy_delta=s_delta) def update(self): self.clear_cached() self._x = grid_coordinates( self.sx_min, self.sx_max, self.sx_delta, self.snap) self._y = grid_coordinates( self.sy_min, self.sy_max, self.sy_delta, self.snap) self._z = grid_coordinates( self.sz_min, self.sz_max, self.sz_delta, self.snap) self._nx = self._x.size self._ny = self._y.size self._nz = self._z.size def clear_cached(self): self._xyz = None self._ned = None def _get_xyz(self): if self._xyz is None: z2, y2, x2 = [v.flatten() for v in num.meshgrid( self._z, self._y, self._x, indexing='ij')] self._xyz = num.vstack([x2, y2, z2]).T return self._xyz def _get_ned(self): if self._ned is None: rotmat = euler_to_matrix(self.dip * d2r, self.azimuth * d2r, 0.0) self._ned = num.dot(rotmat.T, self._get_xyz().T).T return self._ned @property def shape(self): ''' Logical shape of the grid. ''' return (self._nz, self._ny, self._nx) @property def size(self): ''' Number of grid nodes. ''' return self._nz * self._ny * self._nx @property def effective_dimension(self): ''' Number of non-flat dimensions. ''' return sum(int(x > 1) for x in self.shape)
[docs] def get_nodes(self, system): ''' Get node coordinates. :param system: Coordinate system: ``'xyz'``, unrotated coordinate system, ``'ned'``: rotated coordinate system in ``(north, east, down)``. :type system: str :returns: Point coordinates in requested coordinate system. :rtype: :py:class:`numpy.ndarray` of shape ``(size, 3)``, where size is the number of nodes ''' if system == 'ned': return self._get_ned() elif system == 'xyz': return self._get_xyz() else: raise GatoError( 'Coordinate system not supported for %s: %s' % ( self.__class__.__name__ + '.get_nodes', system))
def get_coords(self, system): if system == 'xyz': return self._x, self._y, self._z else: raise GatoError( 'Coordinate system not supported for %s: %s' % ( self.__class__.__name__ + '.get_coords', system))
[docs]class SphericalSlownessGrid(SlownessGrid): ''' Regular (3D) slowness grid in spherical coordinates with optional rotation. Unrotated coordinate system is NED (north-east-down). 3D-indexing is ordered from sr (slow dimension) via stheta to sphi (fast dimension) ``f[ir, itheta, iphi]``. 1D-indexing uses :py:func:`numpy.unravel_index` on ``(nr, ntheta, nphi)``. If any attribute is changed after initialization, :py:meth:`update` must be called. ''' azimuth = Float.T( default=0.0, help='Angle of 0. azimuth direction against north [deg].') sr_min = Float.T( default=0.0, help='Sr axis minimum [s/m].') sr_max = Float.T( default=0.0, help='Sr axis maximum [s/m].') stheta_min = Float.T( default=0.0, help='Lower angle limit of incidence angle [deg].') stheta_max = Float.T( default=0.0, help='Upper angle limit of incidence angle [deg].') sphi_min = Float.T( default=-180.0, help='Lower angle limit of azimuth angle [deg].') sphi_max = Float.T( default=180.0, help='Upper angle limit of azimuth angle [deg].') sr_delta = Float.T( default=1.0, help='Sr axis spacing [s/m].') stheta_delta = Float.T( default=1.0, help='Angle spacing in incidence [deg].') sphi_delta = Float.T( default=1.0, help='Angle spacing in azimuth [deg].') snap = GridSnap.T( default='both', help='Flag to indicate how grid inconsistencies are handled.') def update(self): self.clear_cached() self._r = grid_coordinates( self.sr_min, self.sr_max, self.sr_delta, self.snap) self._phi = grid_coordinates( self.sphi_min, self.sphi_max, self.sphi_delta, self.snap) self._theta = grid_coordinates( self.stheta_min, self.stheta_max, self.stheta_delta, self.snap) self._nr = self._r.size self._nphi = self._phi.size self._ntheta = self._theta.size def clear_cached(self): self._xyz = None self._ned = None self._rtp = None def _get_rtp(self): ''' gets nodes in order phi, theta, radius, angles are in radian ''' if self._rtp is None: r2, theta2, phi2 = [v.flatten() for v in num.meshgrid( self._r, self._theta, self._phi, indexing='ij')] self._rtp = num.vstack([phi2, theta2, r2]).T return self._rtp def _get_xyz(self): if self._xyz is None: rtp = self._get_rtp() x2 = rtp[:, 2] * num.sin(rtp[:, 1]*d2r) * num.cos(rtp[:, 0]*d2r) y2 = rtp[:, 2] * num.sin(rtp[:, 1]*d2r) * num.sin(rtp[:, 0]*d2r) z2 = rtp[:, 2] * num.cos(rtp[:, 1]*d2r) self._xy = num.vstack([x2, y2, z2]).T return self._xyz def _get_ned(self): if self._ned is None: rtp = self._get_rtp() n2 = rtp[:, 2] * num.sin(rtp[:, 1]*d2r) \ * num.cos((rtp[:, 0] - self.azimuth)*d2r) e2 = rtp[:, 2] * num.sin(rtp[:, 1]*d2r) \ * num.sin((rtp[:, 0] - self.azimuth)*d2r) d2 = rtp[:, 2] * num.cos(rtp[:, 1]*d2r) self._ned = num.vstack([n2, e2, d2]).T return self._ned @property def shape(self): ''' Logical shape of the grid. ''' return (self._nr, self._ntheta, self._nphi) @property def size(self): ''' Number of grid nodes. ''' return self._nr * self._nphi * self._ntheta @property def effective_dimension(self): ''' Number of non-flat dimensions. ''' return sum(int(x > 1) for x in self.shape)
[docs] def get_nodes(self, system): ''' Get node coordinates. :param system: Coordinate system: ``'xy'``, unrotated coordinate system, ``'ne'``: rotated coordinate system in ``(north, east)``, ``'rtp'``, spherical coordinates in ``(radius, theta, phi)``. :type system: str :returns: Point coordinates in requested coordinate system. :rtype: :py:class:`numpy.ndarray` of shape ``(size, 2)``, where size is the number of nodes ''' if system == 'ned': return self._get_ned() elif system == 'xyz': return self._get_xyz() elif system == 'rtp': return self._get_rtp() else: raise GatoError( 'Coordinate system not supported for %s: %s' % ( self.__class__.__name__, system))
__all__ = [ 'SlownessGrid', 'CartesianSlownessGrid', 'SphericalSlownessGrid', ]