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
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)))
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]
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()
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
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
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
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
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()
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