Example #1
0
 def _get_hessian(self):
     """
     Build the Hessian matrix by Gauss-Newton approximation.
     """
     log.info("  calculating Hessian matrix:")
     start = time.time()
     if not self.sparse:
         hess = numpy.dot(self.jacobian_T, self.jacobian)
     else:
         hess = self.jacobian_T * self.jacobian
     log.info("    time: %s" % (utils.sec2hms(time.time() - start)))
     return hess
Example #2
0
def _iterator(dms, regs, solver):
    start = time.time()
    try:
        for i, chset in enumerate(solver(dms, regs)):
            yield chset["estimate"], chset["residuals"][0]
    except numpy.linalg.linalg.LinAlgError:
        raise ValueError, ("Oops, the Hessian is a singular matrix." + " Try applying more regularization")
    stop = time.time()
    log.info("  number of iterations: %d" % (i))
    log.info("  final data misfit: %g" % (chset["misfits"][-1]))
    log.info("  final goal function: %g" % (chset["goals"][-1]))
    log.info("  time: %s" % (utils.sec2hms(stop - start)))
Example #3
0
def _solver(dms, solver, log):
    start = time.time()
    try:
        for i, chset in enumerate(solver(dms, [])):
            continue
    except numpy.linalg.linalg.LinAlgError:
        raise ValueError, ("Oops, the Hessian is a singular matrix." +
                           " Try applying more regularization")
    stop = time.time()
    log.info("  number of iterations: %d" % (i))
    log.info("  final data misfit: %g" % (chset['misfits'][-1]))
    log.info("  final goal function: %g" % (chset['goals'][-1]))
    log.info("  time: %s" % (utils.sec2hms(stop - start)))
    return chset['estimate'], chset['residuals'][0]
Example #4
0
 def _get_jacobian(self):
     """
     Build the Jacobian (sensitivity) matrix using the travel-time data
     stored.
     """
     log.info("  calculating Jacobian (sensitivity matrix):")
     start = time.time()
     srcs, recs = self.srcs, self.recs
     if not self.sparse:
         jac = numpy.array([ttime2d.straight([cell], "", srcs, recs, velocity=1.0) for cell in self.mesh]).T
     else:
         shoot = ttime2d.straight
         nonzero = []
         extend = nonzero.extend
         for j, c in enumerate(self.mesh):
             extend((i, j, tt) for i, tt in enumerate(shoot([c], "", srcs, recs, velocity=1.0)) if tt != 0)
         row, col, val = numpy.array(nonzero).T
         shape = (self.ndata, self.nparams)
         jac = scipy.sparse.csr_matrix((val, (row, col)), shape)
     log.info("    time: %s" % (utils.sec2hms(time.time() - start)))
     return jac
area = (0, 500000, 0, 500000)
shape = (30, 30)
model = mesher.SquareMesh(area, shape)
# Fetch the image from the online docs
urllib.urlretrieve("http://fatiando.readthedocs.org/en/latest/_static/logo.png", "logo.png")
model.img2prop("logo.png", 4000, 10000, "vp")

# Make some travel time data and add noise
log.info("Generating synthetic travel-time data")
src_loc = utils.random_points(area, 80)
rec_loc = utils.circular_points(area, 30, random=True)
srcs, recs = utils.connect_points(src_loc, rec_loc)
start = time.time()
tts = seismic.ttime2d.straight(model, "vp", srcs, recs, par=True)
log.info("  time: %s" % (utils.sec2hms(time.time() - start)))
tts, error = utils.contaminate(tts, 0.01, percent=True, return_stddev=True)
# Make the mesh
mesh = mesher.SquareMesh(area, shape)
# and run the inversion
estimate, residuals = seismic.srtomo.run(tts, srcs, recs, mesh, damping=10 ** 9)
# Convert the slowness estimate to velocities and add it the mesh
mesh.addprop("vp", seismic.srtomo.slowness2vel(estimate))

# Calculate and print the standard deviation of the residuals
# it should be close to the data error if the inversion was able to fit the data
log.info("Assumed error: %g" % (error))
log.info("Standard deviation of residuals: %g" % (numpy.std(residuals)))

vis.mpl.figure(figsize=(14, 5))
vis.mpl.subplot(1, 2, 1)
lons, lats, heights = gridder.regular(area, shape, z=250000)

# Divide the model into nproc slices and calculate them in parallel
log.info('Calculating...')
def calculate(chunk):
    return gravmag.tesseroid.gz(lons, lats, heights, chunk)
def split(model, nproc):
    chunksize = len(model)/nproc
    for i in xrange(nproc - 1):
        yield model[i*chunksize : (i + 1)*chunksize]
    yield model[(nproc - 1)*chunksize : ]    
start = time.time()
nproc = 8
pool = Pool(processes=nproc)
gz = sum(pool.map(calculate, split(model, nproc)))
pool.close()
print "Time it took: %s" % (utils.sec2hms(time.time() - start))

log.info('Plotting...')
mpl.figure(figsize=(10, 4))
mpl.title('Crust gravity signal at 250km height')
bm = mpl.basemap(area, 'robin')
mpl.contourf(lons, lats, gz, shape, 35, basemap=bm)
cb = mpl.colorbar()
cb.set_label('mGal')
bm.drawcoastlines()
bm.drawmapboundary()
bm.drawparallels(range(-90, 90, 45), labels=[0, 1, 0, 0])
bm.drawmeridians(range(-180, 180, 60), labels=[0, 0, 0, 1])
mpl.show()
Example #7
0
def migrate(x, y, z, gz, zmin, zmax, meshshape, power=0.5, scale=1):
    """
    3D potential field migration (Zhdanov et al., 2011).

    Actually uses the formula of Fedi and Pilkington (2012), which are
    comprehensible.

    .. note:: Only works on **gravity** data for now.

    .. note:: The data **do not** need to be leveled or on a regular grid.

    .. note:: The coordinate system adopted is x->North, y->East, and z->Down

    Parameters:

    * x, y : 1D-arrays
        The x and y coordinates of the grid points
    * z : float or 1D-array
        The z coordinate of the grid points
    * gz : 1D-array
        The gravity anomaly data at the grid points
    * zmin, zmax : float
        The top and bottom, respectively, of the region where the physical
        property distribution is calculated
    * meshshape : tuple = (nz, ny, nx)
        Number of prisms in the output mesh in the x, y, and z directions,
        respectively
    * power : float
        The power law used for the depth weighting. This controls what depth
        the bulk of the solution will be.
    * scale : float
        A scale factor for the depth weights. Simply changes the scale of the
        physical property values.

    Returns:

    * mesh : :class:`fatiando.mesher.PrismMesh`
        The estimated physical property distribution set in a prism mesh (for
        easy 3D plotting)

    """
    nlayers, ny, nx = meshshape
    mesh = _makemesh(x, y, (ny, nx), zmin, zmax, nlayers)
    log.info("3D migration of gravity data:")
    # This way, if z is not an array, it is now
    z = z*numpy.ones_like(x)
    log.info("  number of data: %d" % (len(gz)))
    log.info("  mesh zmin and zmax: %g, %g" % (zmin, zmax))
    log.info("  mesh shape: %s" % (str(meshshape)))
    log.info("  depth weighting power law: %g" % (power))
    log.info("  depth weighting scale factor: %g" % (scale))
    tstart = time.clock()
    dx, dy, dz = mesh.dims
    # Synthetic tests show that its not good to offset the weights with the data
    # z coordinate. No idea why
    depths = mesh.get_zs()[:-1] + 0.5*dz
    weights = numpy.abs(depths)**power/(2*G*numpy.sqrt(numpy.pi))
    density = []
    for l in xrange(nlayers):
        sensibility_T = numpy.array(
            [pot_prism.gz(x, y, z, [p], dens=1) for p in mesh.get_layer(l)])
        density.extend(scale*weights[l]*numpy.dot(sensibility_T, gz))
    tend = time.clock()
    log.info("  total time for imaging: %s" % (utils.sec2hms(tend - tstart)))
    mesh.addprop('density', numpy.array(density))
    return mesh
Example #8
0
def geninv(x, y, z, data, shape, zmin, zmax, nlayers):
    """
    Generalized Inverse imaging in the frequency domain (Cribb, 1976).

    Calculates a physical property distribution given potential field data on a
    **regular grid**.

    .. note:: Only works on **gravity** data for now.

    .. note:: The data **must** be leveled, i.e., on the same height!

    .. note:: The coordinate system adopted is x->North, y->East, and z->Down

    .. warning:: The Generalized Inverse does **not** use depth weights. This
        means that the solution will tend to be concentrated on the surface!

    Parameters:

    * x, y : 1D-arrays
        The x and y coordinates of the grid points
    * z : float or 1D-array
        The z coordinate of the grid points
    * data : 1D-array
        The potential field at the grid points
    * shape : tuple = (ny, nx)
        The shape of the grid
    * zmin, zmax : float
        The top and bottom, respectively, of the region where the physical
        property distribution is calculated
    * nlayers : int
        The number of layers used to divide the region where the physical
        property distribution is calculated

    Returns:

    * mesh : :class:`fatiando.mesher.PrismMesh`
        The estimated physical property distribution set in a prism mesh (for
        easy 3D plotting)

    """
    mesh = _makemesh(x, y, shape, zmin, zmax, nlayers)
    log.info("Generalized Inverse imaging of gravity data:")
    # This way, if z is not an array, it is now
    z = z*numpy.ones_like(x)
    log.info("  data z coordinate: %g" % (z[0]))
    log.info("  data shape: %s" % (str(shape)))
    log.info("  mesh zmin and zmax: %g, %g" % (zmin, zmax))
    log.info("  number of layers in the mesh: %d" % (nlayers))
    tstart = time.clock()
    freq, dataft = _getdataft(x, y, data, shape)
    dx, dy, dz = mesh.dims
    # Remove the last z because I only want depths to the top of the layers
    depths = mesh.get_zs()[:-1] + 0.5*dz - z[0] # Offset by the data height
    density = []
    for depth in depths:
        density.extend(
            numpy.real(
                numpy.fft.ifft2(
                    numpy.exp(-freq*depth)*freq*dataft/(numpy.pi*G)
                ).ravel()
            ))
    tend = time.clock()
    log.info("  total time for imaging: %s" % (utils.sec2hms(tend - tstart)))
    mesh.addprop('density', numpy.array(density))
    return mesh
Example #9
0
def sandwich(x, y, z, data, shape, zmin, zmax, nlayers, power=0.5):
    """
    Sandwich model (Pedersen, 1991).

    Calculates a physical property distribution given potential field data on a
    **regular grid**. Uses depth weights.

    .. note:: Only works on **gravity** data for now.

    .. note:: The data **must** be leveled, i.e., on the same height!

    .. note:: The coordinate system adopted is x->North, y->East, and z->Down

    Parameters:

    * x, y : 1D-arrays
        The x and y coordinates of the grid points
    * z : float or 1D-array
        The z coordinate of the grid points
    * data : 1D-array
        The potential field at the grid points
    * shape : tuple = (ny, nx)
        The shape of the grid
    * zmin, zmax : float
        The top and bottom, respectively, of the region where the physical
        property distribution is calculated
    * nlayers : int
        The number of layers used to divide the region where the physical
        property distribution is calculated
    * power : float
        The power law used for the depth weighting. This controls what depth
        the bulk of the solution will be.

    Returns:

    * mesh : :class:`fatiando.mesher.PrismMesh`
        The estimated physical property distribution set in a prism mesh (for
        easy 3D plotting)

    """
    mesh = _makemesh(x, y, shape, zmin, zmax, nlayers)
    log.info("Sandwich model of gravity data:")
    # This way, if z is not an array, it is now
    z = z*numpy.ones_like(x)
    log.info("  data z coordinate: %g" % (z[0]))
    log.info("  data shape: %s" % (str(shape)))
    log.info("  mesh zmin and zmax: %g, %g" % (zmin, zmax))
    log.info("  number of layers in the mesh: %d" % (nlayers))
    log.info("  depth weighting power law: %g" % (power))
    tstart = time.clock()
    freq, dataft = _getdataft(x, y, data, shape)
    dx, dy, dz = mesh.dims
    # Remove the last z because I only want depths to the top of the layers
    depths = mesh.get_zs()[:-1]
    weights = (numpy.abs(depths) + 0.5*dz)**(power)
    density = []
    # Offset by the data z because in the paper the data is at z=0
    for depth, weight in zip(depths - z[0], weights):
        density.extend(
            numpy.real(numpy.fft.ifft2(
                weight*(numpy.exp(-freq*depth) - numpy.exp(-freq*(depth + dz)))
                *freq*dataft /
                (numpy.pi*G*
                 reduce(numpy.add,
                     [w*(numpy.exp(-freq*h) - numpy.exp(-freq*(h + dz)))**2
                      + 10.**(-10) # To avoid zero division when freq[i]==0
                      for h, w in zip(depths, weights)])
                )
            ).ravel()))
    tend = time.clock()
    log.info("  total time for imaging: %s" % (utils.sec2hms(tend - tstart)))
    mesh.addprop('density', numpy.array(density))
    return mesh
Example #10
0
def harvest(data, seeds, mesh, compactness, threshold):
    """
    Run the inversion algorithm and produce an estimate physical property
    distribution (density and/or magnetization).

    Paramters:

    * data : list of data (e.g., :class:`~fatiando.gravmag.harvester.Gz`)
        The data that will be inverted. Data used must match the physical
        properties given to the seeds (e.g., gravity data requires seeds to have
        ``'density'`` prop)

    * seeds : list of :class:`~fatiando.gravmag.harvester.Seed`
        Lits of seeds used to start the growth process of the inversion. Use
        :func:`~fatiando.gravmag.harvester.sow` to generate seeds.

    * mesh : :class:`fatiando.mesher.PrismMesh`
        The mesh used in the inversion. Will estimate the physical property
        distribution on this mesh

    * compactness : float
        The compactness regularing parameter (i.e., how much should the estimate
        be consentrated around the seeds). Must be positive. To find a good
        value for this, start with a small value (like 0.001), run the inversion
        and increase the value until desired compactness is achieved.

    * threshold : float
        Control how much the solution can grow (usually downward). In order for
        estimate to grow by the accretion of 1 prism, this prism must decrease
        the data misfit measure by *threshold* decimal percent. Depends on the
        size of the cells in the *mesh* and the distance from a cell to the
        observations. Use values between 0.001 and 0.000001.
        If cells are small and *threshold* is large (0.001), the seeds won't
        grow. If cells are large and *threshold* is small (0.000001), the seeds
        will grow too much.

    Returns:

    * estimate, predicted_data : a dict and a list
        *estimate* is a dict like::

            {'physical_property':array, ...}

        *estimate* contains the estimates physical properties. The properties
        present in *estimate* are the ones given to the seeds. Include the
        properties in the *mesh* using::

            mesh.addprop('density', estimate['density'])

        This way you can plot the estimate using :mod:`fatiando.vis.myv`.

        *predicted_data* is a list of numpy arrays with the predicted (model)
        data. The list is in the same order as *data*. To plot a map of the fit
        for visual inspection and a histogram of the residuals::

            from fatiando.vis import mpl
            mpl.figure()
            # Plot the observed and predicted data as contours for visual
            # inspection
            mpl.subplot(1, 2, 1)
            mpl.axis('scaled')
            mpl.title('Observed and predicted data')
            levels = mpl.contourf(x, y, gz, (ny, nx), 10)
            mpl.colorbar()
            # Assuming gz is the only data used
            mpl.contour(x, y, predicted[0], (ny, nx), levels)
            # Plot a histogram of the residuals
            residuals = gz - predicted[0]
            mpl.subplot(1, 2, 2)
            mpl.title('Residuals')
            mpl.hist(residuals, bins=10)
            mpl.show()
            # It's also good to see the mean and standard deviation of the
            # residuals
            print "Residuals mean:", residuals.mean()
            print "Residuals stddev:", residuals.std()


    """
    log.info('Harvesting inversion results:')
    nseeds = len(seeds)
    log.info('  compactness: %g' % (compactness))
    log.info('  threshold: %g' % (threshold))
    log.info('  # of seeds: %d' % (nseeds))
    log.info('  # of data types: %d' % (len(data)))
    tstart = time.time()
    # Initialize the estimate with the seeds
    estimate = dict((s.i, s.props) for s in seeds)
    # Initialize the neighbors list
    neighbors = []
    for seed in seeds:
        neighbors.append(_get_neighbors(seed, neighbors, estimate, mesh, data))
    # Initialize the predicted data
    predicted = _init_predicted(data, seeds, mesh)
    # Start the goal function, data-misfit function and regularizing function
    totalgoal = _shapefunc(data, predicted)
    totalmisfit = _misfitfunc(data, predicted)
    regularizer = 0.
    log.info('  initial goal function: %g' % (totalgoal))
    log.info('  initial data misfit: %g' % (totalmisfit))
    # Weight the regularizing function by the mean extent of the mesh
    mu = compactness*1./(sum(mesh.shape)/3.)
    # Begin the growth process
    log.info('  Running...')
    accretions = 0
    for iteration in xrange(mesh.size - nseeds):
        grew = False # To check if at least one seed grew (stopping criterion)
        for s in xrange(nseeds):
            best, bestgoal, bestmisfit, bestregularizer = _grow(neighbors[s],
                data, predicted, totalmisfit, mu, regularizer, threshold)
            # If there was a best, add to estimate, remove it, and add its
            # neighbors
            if best is not None:
                if best.i not in estimate:
                    estimate[best.i] = {}
                estimate[best.i].update(best.props)
                totalgoal = bestgoal
                totalmisfit = bestmisfit
                regularizer = bestregularizer
                for p, e in zip(predicted, best.effect):
                    p += e
                neighbors[s].pop(best.i)
                neighbors[s].update(
                    _get_neighbors(best, neighbors, estimate, mesh, data))
                del best
                grew = True
                accretions += 1
        if not grew:
            break
    log.info('  # of accretions: %d' % (accretions))
    log.info('  final goal function: %g' % (totalgoal))
    log.info('  final compactness regularizing function: %g' % (regularizer))
    log.info('  final data misfit: %g' % (totalmisfit))
    log.info('  time it took: %s' % (utils.sec2hms(time.time() - tstart)))
    return _fmt_estimate(estimate, mesh.size), predicted
Example #11
0
shape = (50, 50)
lons, lats, heights = gridder.regular(area, shape, z=250000)

start = time.time()
fields = [
    gravmag.tesseroid.potential(lons, lats, heights, model),
    gravmag.tesseroid.gx(lons, lats, heights, model),
    gravmag.tesseroid.gy(lons, lats, heights, model),
    gravmag.tesseroid.gz(lons, lats, heights, model),
    gravmag.tesseroid.gxx(lons, lats, heights, model),
    gravmag.tesseroid.gxy(lons, lats, heights, model),
    gravmag.tesseroid.gxz(lons, lats, heights, model),
    gravmag.tesseroid.gyy(lons, lats, heights, model),
    gravmag.tesseroid.gyz(lons, lats, heights, model),
    gravmag.tesseroid.gzz(lons, lats, heights, model)
]
print "Time it took: %s" % (utils.sec2hms(time.time() - start))

titles = [
    'potential', 'gx', 'gy', 'gz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz'
]
mpl.figure()
bm = mpl.basemap(area, 'ortho')
for i, field in enumerate(fields):
    mpl.subplot(4, 3, i + 3)
    mpl.title(titles[i])
    mpl.contourf(lons, lats, field, shape, 15, basemap=bm)
    bm.drawcoastlines()
    mpl.colorbar()
mpl.show()
Example #12
0
def run(ttimes, srcs, recs, mesh, solver=None, sparse=False, damping=0.0, smooth=0.0, sharp=0.0, beta=10.0 ** (-5)):
    """
    Perform a 2D straight-ray travel-time tomography. Estimates the slowness
    (1/velocity) of cells in mesh (because slowness is linear and easier)

    Regularization is usually **not** optional. At least some amount of damping
    is required.

    Parameters:

    * ttimes : array
        The travel-times of the straight seismic rays.
    * srcs : list of lists
        List of the [x, y] positions of the sources.
    * recs : list of lists
        List of the [x, y] positions of the receivers.
    * mesh : :class:`~fatiando.mesher.SquareMesh` or compatible
        The mesh where the inversion (tomography) will take place.
    * solver : function
        A linear or non-linear inverse problem solver generated by a factory
        function from a module of package :mod:`fatiando.inversion`. If None,
        will use the default solver.
    * sparse : True or False
        If True, will use sparse matrices from `scipy.sparse`.

        .. note:: If you provided a solver function, don't forget to turn on
            sparcity in the inversion solver module **BEFORE** creating the
            solver function! The usual way of doing this is by calling the
            ``use_sparse`` function. Ex:
            ``fatiando.inversion.gradient.use_sparse()``

        .. warning:: Jacobian matrix building using sparse matrices isn't very
            optimized. It will be slow but won't overflow the memory.

    * damping : float
        Damping regularizing parameter (i.e., how much damping to apply).
        Must be a positive scalar.
    * smooth : float
        Smoothness regularizing parameter (i.e., how much smoothness to apply).
        Must be a positive scalar.
    * sharp : float
        Sharpness (total variation) regularizing parameter (i.e., how much
        sharpness to apply). Must be a positive scalar.
    * beta : float
        Total variation parameter. See
        :class:`fatiando.inversion.regularizer.TotalVariation` for details

    Returns:

    * results : list = [slowness, residuals]:

        * slowness : array
            The slowness of each cell in *mesh*
        * residuals : array
            The inversion residuals (observed travel-times minus predicted
            travel-times by the slowness estimate)

    """
    if len(ttimes) != len(srcs) != len(recs):
        msg = "Must have same number of travel-times, sources and receivers"
        raise ValueError(msg)
    if damping < 0:
        raise ValueError("Damping must be positive")
    # If no solver is given, generate custom ones
    if solver is None:
        if sharp == 0:
            if sparse:
                inversion.linear.use_sparse()
            solver = inversion.linear.overdet(mesh.size)
        else:
            if sparse:
                inversion.gradient.use_sparse()
            solver = inversion.gradient.levmarq(initial=numpy.ones(mesh.size, dtype="f"))
    log.info("Running 2D straight-ray travel-time tomography (SrTomo):")
    log.info("  number of parameters: %d" % (len(mesh)))
    log.info("  number of data: %d" % (len(ttimes)))
    log.info("  use sparse matrices: %s" % (str(sparse)))
    log.info("  regularizing parameters:")
    log.info("    damping: %g" % (damping))
    log.info("    smoothness: %g" % (smooth))
    log.info("    sharpness: %g" % (sharp))
    log.info("    beta (total variation aux parameter): %g" % (beta))
    nparams = len(mesh)
    # Make the data modules and regularizers
    dms = [TravelTime(ttimes, srcs, recs, mesh, sparse)]
    regs = []
    if damping:
        regs.append(inversion.regularizer.Damping(damping, nparams, sparse=sparse))
    if smooth:
        regs.append(inversion.regularizer.Smoothness2D(smooth, mesh.shape, sparse=sparse))
    if sharp:
        regs.append(inversion.regularizer.TotalVariation2D(sharp, mesh.shape, beta, sparse=sparse))
    start = time.time()
    try:
        for i, chset in enumerate(solver(dms, regs)):
            continue
    except numpy.linalg.linalg.LinAlgError:
        raise ValueError("Oops, the Hessian is a singular matrix." + " Try applying more regularization")
    stop = time.time()
    log.info("  number of iterations: %d" % (i))
    log.info("  final data misfit: %g" % (chset["misfits"][-1]))
    log.info("  final goal function: %g" % (chset["goals"][-1]))
    log.info("  time: %s" % (utils.sec2hms(stop - start)))
    slowness = chset["estimate"]
    residuals = chset["residuals"][0]
    return slowness, residuals