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.seismic.ttime2d

"""
Calculate travel-times of seismic waves in 2D.

* :func:~fatiando.seismic.ttime2d.straight: Calculate the travel-time of a
straight ray through a mesh of square cells

----

"""
import multiprocessing
import math
import numpy

try:
from fatiando.seismic import _ttime2d
except ImportError:
_ttime2d = None

[docs]def straight(cells, prop, srcs, recs, velocity=None, par=False):
"""
Calculate the travel times inside a mesh of square cells between source and
receiver pairs assuming the rays are straight lines (no refraction or
reflection).

.. note:: Don't care about the units as long they are compatible.

For a homogeneous model, *cells* can be a list with only one big cell.

Parameters:

* cells : list of :func:fatiando.mesher.Square
The velocity model to use to trace the straight rays. Cells must have
the physical property given in parameter *prop*. This will be used
as the velocity of each cell. (*cells* can also be a
:class:~fatiando.mesher.SquareMesh)
* prop : str
Which physical property of the cells to use as velocity.
Normaly one would choose 'vp' or 'vs'
* srcs : list fo lists
List with [x, y] coordinate pairs of the wave sources.
* recs : list fo lists
List with [x, y] coordinate pairs of the receivers sources
* velocity : float or None
If not None, will use this value instead of the prop of cells as the
velocity. Useful when building sensitivity matrices (use velocity = 1).
* par : True or False
If True, will run the calculations in parallel using all the cores
available. Not recommended for Jacobian matrix building!

*srcs* and *recs* are lists of source-receiver pairs. Each source in *srcs*
is associated with the corresponding receiver in *recs* for a given travel
time.

For example::

>>> # One source was recorded at 3 receivers.
>>> # The medium is homogeneous and can be
>>> # represented by a single Square
>>> from fatiando.mesher import Square
>>> cells = [Square([0, 10, 0, 10], {'vp':2})]
>>> src = (5, 0)
>>> srcs = [src, src, src]
>>> recs = [(0, 0), (5, 10), (10, 0)]
>>> print straight(cells, 'vp', srcs, recs)
[ 2.5  5.   2.5]

Returns:

* times : array
The total times each ray took to get from a source to a receiver (in
compatible units with *prop*)

"""
if len(srcs) != len(recs):
raise ValueError("Must have the same number of sources and receivers")
if not par:
if _ttime2d is not None:
x_src, y_src = numpy.transpose(srcs).astype(numpy.float)
x_rec, y_rec = numpy.transpose(recs).astype(numpy.float)
times = _ttime2d.straight(x_src, y_src, x_rec, y_rec, len(srcs),
cells, velocity, prop)
else:
times = _straight(cells, prop, srcs, recs, velocity)
return times
# Divide the workload into jobs and run them in different processes
jobs = multiprocessing.cpu_count()
start = 0
size = len(srcs)
perjob = size / jobs
processes = []
pipes = []
for i in xrange(jobs):
if i == jobs - 1:
end = size
else:
end = start + perjob
outpipe, inpipe = multiprocessing.Pipe()
args = (inpipe, srcs[start:end], recs[
start:end], cells, velocity, prop)
proc = multiprocessing.Process(target=_straight_job, args=args)
proc.start()
processes.append(proc)
pipes.append(outpipe)
start = end
times = []
for proc, pipe in zip(processes, pipes):
times.extend(pipe.recv())
proc.join()
return numpy.array(times)

def _straight_job(pipe, srcs, recs, cells, velocity, prop):
if _ttime2d is not None:
x_src, y_src = numpy.transpose(srcs).astype(numpy.float)
x_rec, y_rec = numpy.transpose(recs).astype(numpy.float)
times = _ttime2d.straight(x_src, y_src, x_rec, y_rec, len(srcs), cells,
velocity, prop)
else:
times = _straight(cells, prop, srcs, recs, velocity)
pipe.send(times)
pipe.close()

def _straight(cells, prop, srcs, recs, velocity):
"""
Calculate the travel time of a straight ray.
"""
times = numpy.zeros(len(srcs), dtype=numpy.float)
for l in xrange(len(times)):
x_src, y_src = srcs[l]
x_rec, y_rec = recs[l]
maxx = max(x_src, x_rec)
maxy = max(y_src, y_rec)
minx = min(x_src, x_rec)
miny = min(y_src, y_rec)
for cell in cells:
if cell is None or (prop not in cell.props and velocity is None):
continue
x1, x2, y1, y2 = cell.x1, cell.x2, cell.y1, cell.y2
if velocity is None:
vel = cell.props[prop]
else:
vel = velocity
# Check if the cell is in the rectangle with the ray path as a
# diagonal. If not, then the ray doesn't go through the cell.
if x2 < minx or x1 > maxx or y2 < miny or y1 > maxy:
continue
# Now need to find the places where the ray intersects the cell
# If the ray is vertical
if (x_rec - x_src) == 0:
xps = [x_rec] * 4
yps = [y_rec, y_src, y1, y2]
# If the ray is horizontal
elif (y_rec - y_src) == 0:
xps = [x_rec, x_src, x1, x2]
yps = [y_rec] * 4
else:
# Angular and linear coefficients of the ray
a_ray = float(y_rec - y_src) / (x_rec - x_src)
b_ray = y_src - a_ray * (x_src)
# Add the src and rec locations so that the travel time of a
# src or rec inside a cell is accounted for
xps = [x1,  x2, (y1 - b_ray) / a_ray, (y2 - b_ray) / a_ray,
x_src, x_rec]
yps = [a_ray * x1 + b_ray, a_ray * x2 + b_ray, y1, y2, y_src,
y_rec]
# Find out how many points are inside both the cell and the
# rectangle with the ray path as a diagonal
cross = [[x, y] for x, y in zip(xps, yps)
if _crosses(x, y, x1, x2, y1, y2, maxx, minx, maxy, miny)]
# Remove the duplicates
cross = [p for i, p in enumerate(cross) if p not in cross[0:i]]
if len(cross) > 2:
raise ValueError('More than 2 crossings ' +
'for cell %s and ray src:%s rec:%s'
% (str(cell), str(srcs[l]), str(recs[l])))
if len(cross) == 2:
p1, p2 = cross
distance = math.sqrt(
(p2[0] - p1[0]) ** 2 + (p2[1] - p1[1]) ** 2)
times[l] += distance / float(vel)
return times

def _crosses(x, y, x1, x2, y1, y2, maxx, minx, maxy, miny):
"""
Check if (x, y) is inside both the cell and the rectangle with the ray path
as a diagonal.
"""
incell = x <= x2 and x >= x1 and y <= y2 and y >= y1
inray = x <= maxx and x >= minx and y <= maxy and y >= miny
return incell and inray