def test_warn_if_division_makes_too_small(): "gravmag.tesseroid warn if not dividing further bc tesseroid got too small" # When tesseroids get below a threshold, they should not divide further and # compute as is instead. Otherwise results in ZeroDivisionError involving # some trigonometric functions. ds = 1e-6 models = [ [Tesseroid(-ds, ds, -ds, ds, 0, -1000, {'density': 100})], [Tesseroid(-1e-3, 1e-3, -1e-3, 1e-3, 0, -1e-2, {'density': 100})]] lat, lon = np.zeros((2, 1)) h = np.array([0.1]) warning_msg = ( "Stopped dividing a tesseroid because it's dimensions would be below " + "the minimum numerical threshold (1e-6 degrees or 1e-3 m). " + "Will compute without division. Cannot guarantee the accuracy of " + "the solution.") for i, model in enumerate(models): with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") tesseroid.gz(lon, lat, h, model) msg = ("Failed model {}. Got {} warnings.\n\n".format(i, len(w)) + "\n\n".join([str(j.message) for j in w])) assert len(w) >= 1, msg assert any(issubclass(j.category, RuntimeWarning) for j in w), \ "No RuntimeWarning found. " + msg assert any(warning_msg in str(j.message) for j in w), \ "Message mismatch. " + msg
def test_warn_if_too_small(): "gravmag.tesseroid warns if ignoring tesseroid that is too small" ds = 1e-6 / 2 models = [[ Tesseroid(-ds, ds, -ds, ds, 0, -1000, {'density': density_fun}) ], [ Tesseroid(-1e-2, 1e-2, -1e-2, 1e-2, 0, -1e-4, {'density': density_fun}) ]] lat, lon = np.zeros((2, 1)) h = np.array([10]) warning_msg = ("Encountered tesseroid with dimensions smaller than the " + "numerical threshold (1e-6 degrees or 1e-3 m). " + "Ignoring this tesseroid.") for i, model in enumerate(models): with warnings.catch_warnings(record=True) as w: # Cause all warnings to always be triggered. warnings.simplefilter("always") tesseroid.gz(lon, lat, h, model) msg = ("Failed model {}. Got {} warnings.\n\n".format(i, len(w)) + "\n\n".join([str(j.message) for j in w])) assert len(w) >= 1, msg assert any(issubclass(j.category, RuntimeWarning) for j in w), \ "No RuntimeWarning found. " + msg assert any(warning_msg in str(j.message) for j in w), \ "Message mismatch. " + msg
def test_laplace_equation(): "gravmag.tesseroid obeys Laplace equation" model = [ Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': density_fun}), Tesseroid(-1.5, 1.5, -1.5, -1, -1000, -20000, {'density': density_fun_1}), Tesseroid(0.1, 0.6, -0.8, -0.3, 10000, -20000, {'density': density_fun_2}), ] area = [-2, 2, -2, 2] shape = (51, 51) lon, lat, h = gridder.regular(area, shape, z=50000) gxx = tesseroid.gxx(lon, lat, h, model) gyy = tesseroid.gyy(lon, lat, h, model) gzz = tesseroid.gzz(lon, lat, h, model) trace = gxx + gyy + gzz assert_array_almost_equal( trace, np.zeros_like(lon), 9, 'Failed whole model. Max diff %.15g' % (np.abs(trace).max())) for tess in model: gxx = tesseroid.gxx(lon, lat, h, [tess]) gyy = tesseroid.gyy(lon, lat, h, [tess]) gzz = tesseroid.gzz(lon, lat, h, [tess]) trace = gxx + gyy + gzz assert_array_almost_equal( trace, np.zeros_like(lon), 9, 'Failed tesseroid %s. Max diff %.15g' % (str(tess.get_bounds()), np.abs(trace).max()))
def test_detect_invalid_tesseroid_dimensions(): "gravmag.tesseroid raises error when tesseroids with bad dimensions" props = dict(density=2000) model = [Tesseroid(0, -10, 4, 5, 1000, 0, props), Tesseroid(-10, 0, 5, 4, 1000, 0, props), Tesseroid(-10, 0, 5, 4, 0, 1000, props)] lon, lat, height = gridder.regular((-20, 20, -20, 20), (50, 50), z=250e3) for f in 'potential gx gy gz gxx gxy gxz gyy gyz gzz'.split(): func = getattr(tesseroid, f) for t in model: raises(AssertionError, func, lon, lat, height, [t])
def __init__(self, i, location, tess, props): Tesseroid.__init__(self, tess.w, tess.e, tess.s, tess.n, tess.top, tess.bottom, props=props) self.i = i self.seed = i self.x, self.y, self.z = location
def test_overwrite_density(): "gravmag.tesseroid uses given density instead of tesseroid property" model = [Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': 2670})] density = -1000 other = [Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': density})] area = [-2, 2, -2, 2] shape = (51, 51) lon, lat, h = gridder.regular(area, shape, z=250000) funcs = ['potential', 'gx', 'gy', 'gz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz'] for f in funcs: correct = getattr(tesseroid, f)(lon, lat, h, other) effect = getattr(tesseroid, f)(lon, lat, h, model, dens=density) assert_array_almost_equal(correct, effect, 9, 'Failed %s' % (f))
def test_ignore_zero_volume(): "gravmag.tesseroid ignores tesseroids with 0 volume" props = dict(density=2000) model = [Tesseroid(-10, 0, 4, 5, 1000.1, 1000.1, props), Tesseroid(-10, 0, 4, 5, 1000.001, 1000, props), Tesseroid(-10, 0, 3.999999999, 4, 1000, 0, props), Tesseroid(-10, -9.9999999999, 4, 5, 1000, 0, props), Tesseroid(5, 10, -10, -5, 2000.5, 0, props)] lon, lat, height = gridder.regular((-20, 20, -20, 20), (50, 50), z=250e3) for f in 'potential gx gy gz gxx gxy gxz gyy gyz gzz'.split(): with warnings.catch_warnings(record=True) as w: func = getattr(tesseroid, f) f1 = func(lon, lat, height, model) f2 = func(lon, lat, height, [model[-1]]) assert_allclose(f1, f2, err_msg="Mismatch for {}".format(f))
def test_pool_as_argument(): "gravmag.tesseroid takes an open Pool as argument and uses it" class MockPool(object): "Record if the map method of pool was used." def __init__(self, pool): self.pool = pool self.used = False def map(self, *args): res = self.pool.map(*args) self.used = True return res njobs = 2 pool = MockPool(multiprocessing.Pool(njobs)) model = [Tesseroid(0, 1, 0, 1, 2000, 0, {'density': density_fun})] lon, lat, height = gridder.regular((-1, 2, -1, 2), (20, 20), z=250e3) for f in 'potential gx gy gz gxx gxy gxz gyy gyz gzz'.split(): func = getattr(tesseroid, f) f1 = func(lon, lat, height, model) f2 = func(lon, lat, height, model, njobs=njobs, pool=pool) assert_allclose(f1, f2, err_msg="Mismatch for {}".format(f)) assert pool.used, "The given pool was not used in {}".format(f) with raises(AssertionError): func(lon, lat, height, model, njobs=1, pool=pool)
def crust2_to_tesseroids(fname): """ Convert the CRUST2.0 model to tesseroids. Opens the .tar.gz archive and converts the model to :class:`fatiando.mesher.Tesseroid`. Each tesseroid will have its ``props`` set to the apropriate Vp, Vs and density. The CRUST2.0 model includes 7 layers: ice, water, soft sediments, hard sediments, upper crust, middle curst and lower crust. It also includes the mantle below the Moho. The mantle portion is not included in this conversion because there is no way to place a bottom on it. Parameters: * fname : str Name of the model .tar.gz archive (see :func:`~fatiando.io.fetch_crust2`) Returns: * model : list of :class:`fatiando.mesher.Tesseroid` The converted model """ # Needs to be inside the function to avoid circular imports from fatiando.mesher import Tesseroid archive = tarfile.open(fname, 'r:gz') # First get the topography and bathymetry information topogrd = _crust2_get_topo(archive) # Now make a dict with the codec for each type code codec = _crust2_get_codec(archive) # Get the type codes with the actual model types = _crust2_get_types(archive) # Convert to tesseroids size = 2 lons = numpy.arange(-180, 180, size) lats = numpy.arange(90, -90, -size) # This is how lats are in the file model = [] for i in xrange(len(lats)): for j in xrange(len(lons)): t = types[i][j] top = topogrd[i][j] for layer in xrange(7): if codec[t]['thickness'][layer] == 0: continue w, e, s, n = lons[j], lons[j] + size, lats[i] - size, lats[i] bottom = top - codec[t]['thickness'][layer] props = { 'density': codec[t]['density'][layer], 'vp': codec[t]['vp'][layer], 'vs': codec[t]['vs'][layer] } model.append(Tesseroid(w, e, s, n, top, bottom, props)) top = bottom return model
def _split(tesseroid): dlon = 0.5 * (tesseroid.e - tesseroid.w) dlat = 0.5 * (tesseroid.n - tesseroid.s) dh = 0.5 * (tesseroid.top - tesseroid.bottom) wests = [tesseroid.w, tesseroid.w + dlon] souths = [tesseroid.s, tesseroid.s + dlat] bottoms = [tesseroid.bottom, tesseroid.bottom + dh] split = [ Tesseroid(i, i + dlon, j, j + dlat, k + dh, k, props=tesseroid.props) for i in wests for j in souths for k in bottoms ] return split
def test_skip_none_and_missing_properties(): "gravmag.tesseroid ignores Nones and tesseroids without density prop" model = [ Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': density_fun}), None, Tesseroid(-1.5, -0.5, -1.5, -1, -1000, -20000), Tesseroid(0.1, 0.6, -0.8, -0.3, 10000, -20000, {'magnetization': [1, 2, 3]}), None, None, Tesseroid(-1.5, -0.5, -1.5, -1, 1000, -20000, { 'density': density_fun, 'magnetization': [1, 2, 3] }), None ] puremodel = [ Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': density_fun}), Tesseroid(-1.5, -0.5, -1.5, -1, 1000, -20000, {'density': density_fun}) ] area = [-2, 2, -2, 2] shape = (51, 51) lon, lat, h = gridder.regular(area, shape, z=150000) funcs = [ 'potential', 'gx', 'gy', 'gz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz' ] for f in funcs: pure = getattr(tesseroid, f)(lon, lat, h, puremodel) dirty = getattr(tesseroid, f)(lon, lat, h, model) assert_array_almost_equal(pure, dirty, 9, 'Failed %s' % (f))
def test_fails_if_shape_mismatch(): 'gravmag.tesseroid fails if given computation points with different shapes' model = [Tesseroid(0, 1, 0, 1, 1000, -20000, {'density': 2670})] area = [-2, 2, -2, 2] shape = (51, 51) lon, lat, h = gridder.regular(area, shape, z=100000) for f in 'potential gx gy gz gxx gxy gxz gyy gyz gzz'.split(): func = getattr(tesseroid, f) raises(AssertionError, func, lon[:-2], lat, h, model) raises(AssertionError, func, lon, lat[:-2], h, model) raises(AssertionError, func, lon, lat, h[:-2], model) raises(AssertionError, func, lon[:-5], lat, h[:-2], model)
def __getitem__(self, index): nlat, nlon = self.shape dlat, dlon = self.spacing i = index//nlon j = index - i*nlon w = self.lons[j] - dlon/2. e = w + dlon s = self.lats[i] - dlon/2. n = s + dlat top = self.top[index] bottom = self.bottom[index] props = {} for p in self.props: props[p] = self.props[p][index] cell = Tesseroid(w, e, s, n, top, bottom, props) return cell
def tesseroids(self): """ Get a tesseroid representation of this layer. """ assert self.bottom is not None, "Bottomless layer cannot be converted to tesseroids." ds = (self.lon[0, 1] - self.lon[0, 0]) / 2 arrays = [ self.lon, self.lat, self.top, self.bottom, self.vp, self.vs, self.density ] args = zip(*[i.ravel() for i in arrays]) gen = (Tesseroid(lon - ds, lon + ds, lat - ds, lat + ds, top, bottom, dict(vp=vp, vs=vs, density=density)) for lon, lat, top, bottom, vp, vs, density in args if abs(top - bottom) > 10) return gen
def setup(): "Make a spherical shell model with tesseroids" global shellmodel, heights, density, props, top, bottom tlons = np.linspace(-90, 90, 50, endpoint=False) tlats = np.linspace(-90, 90, 50, endpoint=False) wsize = tlons[1] - tlons[0] ssize = tlats[1] - tlats[0] density = 1000. props = {'density': density} top = 0 bottom = -50000 shellmodel = [ Tesseroid(w, w + wsize, s, s + ssize, top, bottom, props) for w in tlons for s in tlats ] heights = np.linspace(250000, 1000000, 10)
def test_stack_overflow(): "gravmag.tesseroid raises exceptions on stack overflow" model = [Tesseroid(0, 1, 0, 1, 0, -20e4, {'density': 2600})] area = [0, 1, 0, 1] shape = [20, 20] lon, lat, h = gridder.regular(area, shape, z=1000) fields = 'potential gx gy gz gxx gxy gxz gyy gyz gzz'.split() backup = tesseroid.STACK_SIZE tesseroid.STACK_SIZE = 5 for f in fields: raises(OverflowError, getattr(tesseroid, f), lon, lat, h, model) # Check if overflows on normal queue size when trying to calculated on top # of the tesseroid tesseroid.STACK_SIZE = 20 lon, lat, h = np.array([0.5]), np.array([0.5]), np.array([0]) for f in fields: raises(OverflowError, getattr(tesseroid, f), lon, lat, h, model) # Restore the module default queue size tesseroid.STACK_SIZE = backup
from fatiando.vis import mpl, myv # Make a "crust" model with some thinker crust and variable density marea = (-70, 70, -70, 70) mshape = (200, 200) mlons, mlats = gridder.regular(marea, mshape) dlon, dlat = gridder.spacing(marea, mshape) depths = (30000 + 70000 * utils.gaussian2d(mlons, mlats, 10, 10, -20, -20) + 20000 * utils.gaussian2d(mlons, mlats, 5, 5, 20, 20)) densities = (2700 + 500 * utils.gaussian2d(mlons, mlats, 40, 40, -20, -20) + -300 * utils.gaussian2d(mlons, mlats, 20, 20, 20, 20)) model = [ Tesseroid(lon - 0.5 * dlon, lon + 0.5 * dlon, lat - 0.5 * dlat, lat + 0.5 * dlat, 0, -depth, props={'density': density}) for lon, lat, depth, density in zip(mlons, mlats, depths, densities) ] # Plot the tesseroid model myv.figure(zdown=False) myv.tesseroids(model, 'density') myv.continents() myv.earth(opacity=0.7) myv.show() # Make the computation grid area = (-50, 50, -50, 50)
import matplotlib.pyplot as plt # This is our custom tesseroid code from tesseroid_density import tesseroid # Configure comparison # -------------------- nruns = 1000 fields = "potential gz".split() # Define Tesseroid # ---------------- w, e, s, n = -10, 10, -10, 10 top, bottom = 0, -1e3 model = [Tesseroid(w, e, s, n, top, bottom)] # Define computation points # ------------------------- heights = np.array([1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6]) computation_points = [[np.array([0.]), np.array([0.]), np.array([height])] for height in heights] # Create results dir if it does not exist # ------------------------- script_path = os.path.dirname(os.path.abspath(__file__)) result_dir = os.path.join(script_path, 'results/computation_time') if not os.path.isdir(result_dir): os.makedirs(result_dir)
""" Vis: Set the colors in figures, prisms, polygonal prisms and tesseroids. """ from fatiando.mesher import Prism, PolygonalPrism, Tesseroid from fatiando.vis import myv prism = Prism(1, 2, 1, 2, 0, 1, {'density': 1}) polyprism = PolygonalPrism([[3, 1], [4, 2], [5, 1]], -1, 2, {'density': 2}) tesseroid = Tesseroid(10, 20, 50, 60, 10**6, 0) red, green, blue = (1, 0, 0), (0, 1, 0), (0, 0, 1) white, black = (1, 1, 1), (0, 0, 0), myv.figure() # Make the prism red with blue edges, despite its density myv.prisms([prism], 'density', color=red, edgecolor=blue) # and the polyprism green with blue edges myv.title('Body + edge colors') myv.figure() # For wireframes, color is usually set by the density. # Overwrite this by setting *color* # *edgecolor* is ignored myv.polyprisms([polyprism], 'density', style='wireframe', color=green, edgecolor=red, linewidth=2) myv.title('Wireframe colors')
""" GravMag: Forward modeling of the gravitational potential and its derivatives using tesseroids """ import time from fatiando import gravmag, gridder, utils from fatiando.mesher import Tesseroid from fatiando.vis import mpl, myv model = [ Tesseroid(-60, -55, -30, -27, 0, -500000, props={'density': 200}), Tesseroid(-66, -62, -18, -12, 0, -300000, props={'density': -500}) ] # Show the model before calculating scene = myv.figure(zdown=False) myv.tesseroids(model, 'density') myv.continents(linewidth=2) myv.earth(opacity=0.8) myv.meridians(range(0, 360, 45), opacity=0.2) myv.parallels(range(-90, 90, 45), opacity=0.2) scene.scene.camera.position = [ 23175275.131412581, -16937347.013663091, -4924328.2822419703 ] scene.scene.camera.focal_point = [0.0, 0.0, 0.0] scene.scene.camera.view_angle = 30.0 scene.scene.camera.view_up = [ 0.083030001958377356, -0.17178720527713925, 0.98162883763562181 ] scene.scene.camera.clipping_range = [9229054.5133903362, 54238225.321054712] scene.scene.camera.compute_view_plane_normal() scene.scene.render()
def __init__(self, i, location, tess, props): Tesseroid.__init__(self, tess.w, tess.e, tess.s, tess.n, tess.top, tess.bottom, props=props) self.i = i self.seed = i self.x, self.y, self.z = location