def fwd_grav_fatiando(): """ GravMag: 3D imaging using the migration method on synthetic gravity data (more complex model + noisy data) """ # Make some synthetic gravity data from a simple prism model za = 5000 zb = 7000 model = [mesher.Prism(-4000, 0, -4000, -2000, za, zb, {'density': 1200})] #, # mesher.Prism(-1000, 1000, -1000, 1000, 1000, 7000, {'density': -800}), # mesher.Prism(2000, 4000, 3000, 4000, 0, 2000, {'density': 600})] # Calculate on a scatter of points to show that migration doesn't need gridded # data # xp, yp, zp = gridder.scatter((-6000, 6000, -6000, 6000), 1000, z=0) shape = (25, 25) xp, yp, zp = gridder.regular((-5000, 5000, -5000, 5000), shape, z=0) #gz = utils.contaminate(prism.gz(xp, yp, zp, model), 0.1) gz = prism.gz(xp, yp, zp, model) # Plot the data shape = (50, 50) mpl.figure() mpl.axis('scaled') mpl.contourf(yp, xp, gz, shape, 30, interp=True) mpl.colorbar() mpl.plot(yp, xp, '.k') mpl.xlabel('East (km)') mpl.ylabel('North (km)') mpl.m2km() mpl.show() return xp, yp, zp, gz, shape, model
def test_gz_prism(): ''' This test compare the results obtained by both function that calculates the vertical gravitational attraction due to a rectangular prism. The model has the same dimensions and same value for the density. We use the function from Fatiando a Terra in order to compare with our function. ''' density = 2600. # Modelo para o Fatiando xp, yp, zp = gridder.regular((-1000, 1000, -1000, 1000), (200, 200), z=-345.) model = [ mesher.Prism(-200., 200., -250., 180., 120., 1000., {'density': density}) ] gz = prism.gz(xp, yp, zp, model) gz = gz.reshape(200, 200) # Modelo para minha funcao x, y = numpy.meshgrid(numpy.linspace(-1000., 1000., 200), numpy.linspace(-1000., 1000., 200)) z = -345. * numpy.ones((200, 200)) mymodel = [-200., 200., -250., 180., 120., 1000., density] mygz = prism_gz(y, x, z, mymodel) assert_almost_equal(gz, mygz, decimal=5)
def test_gz(): "polyprism.gz against prism" resprism = prism.gz(xp, yp, zp, prismmodel) respoly = polyprism.gz(xp, yp, zp, model) diff = np.abs(resprism - respoly) errormsg = 'max diff: %g | max polyprism: %g | max prism: %g' % ( max(diff), max(respoly), max(resprism)) assert np.all(diff <= precision), errormsg
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) # This way, if z is not an array, it is now z = z * numpy.ones_like(x) 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)) mesh.addprop('density', numpy.array(density)) return mesh
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) # This way, if z is not an array, it is now z = z*numpy.ones_like(x) 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)) mesh.addprop('density', numpy.array(density)) return mesh
def test_upcontinue_warning(): "gravmag.transform upward continuation raises warning if height <= 0" model = [Prism(-1000, 1000, -500, 500, 0, 1000, {'density': 1000})] shape = (100, 100) x, y, z = gridder.regular([-5000, 5000, -5000, 5000], shape, z=-500) data = prism.gz(x, y, z, model) with pytest.warns(UserWarning): up = transform.upcontinue(x, y, data, shape, height=0) with pytest.warns(UserWarning): up = transform.upcontinue(x, y, data, shape, height=-100)
def test_eqlgrav_prism_interp(): "EQLGravity can interpolate data from a prism" model = [Prism(-300, 300, -500, 500, 100, 600, {'density': 400})] shape = (30, 30) n = shape[0]*shape[1] area = [-2000, 2000, -2000, 2000] x, y, z = gridder.scatter(area, n, z=-100, seed=42) data = prism.gz(x, y, z, model) layer = PointGrid(area, 200, shape) eql = EQLGravity(x, y, z, data, layer) + 1e-23*Damping(layer.size) eql.fit() layer.addprop('density', eql.estimate_) assert_allclose(eql[0].predicted(), data, rtol=0.01) xp, yp, zp = gridder.regular(area, shape, z=-100) true = prism.gz(xp, yp, zp, model) calc = sphere.gz(xp, yp, zp, layer) assert_allclose(calc, true, rtol=0.05)
def test_derivatives_uneven_shape(): "gravmag.transform FFT derivatives work if grid spacing is uneven" model = [Prism(-1000, 1000, -500, 500, 0, 2000, {'density': 100})] shape = (150, 300) x, y, z = gridder.regular([-10000, 10000, -10000, 10000], shape, z=-100) grav = utils.mgal2si(prism.gz(x, y, z, model)) analytical = prism.gzz(x, y, z, model) calculated = utils.si2eotvos(transform.derivz(x, y, grav, shape, method='fft')) diff = _trim(np.abs(analytical - calculated), shape) assert np.all(diff <= 0.005*np.abs(analytical).max()), \ "Failed for gzz"
def test_tilt_analytical_derivatives(): "gravmag.transform tilt returns same values given analytical derivatives" model = [Prism(-100, 100, -100, 100, 0, 100, {'density': 1000})] shape = (400, 400) x, y, z = gridder.regular([-10000, 10000, -10000, 10000], shape, z=-100) data = utils.mgal2si(prism.gz(x, y, z, model)) dx = utils.eotvos2si(prism.gxz(x, y, z, model)) dy = utils.eotvos2si(prism.gyz(x, y, z, model)) dz = utils.eotvos2si(prism.gzz(x, y, z, model)) tilt_analytical = transform.tilt(x, y, data, shape, dx, dy, dz) tilt_numerical = transform.tilt(x, y, data, shape) npt.assert_allclose(tilt_numerical, tilt_analytical, rtol=0.10)
def test_derivatives_uneven_shape(): "gravmag.transform FFT derivatives work if grid spacing is uneven" model = [Prism(-1000, 1000, -500, 500, 0, 2000, {'density': 100})] shape = (150, 300) x, y, z = gridder.regular([-10000, 10000, -10000, 10000], shape, z=-100) grav = utils.mgal2si(prism.gz(x, y, z, model)) analytical = prism.gzz(x, y, z, model) calculated = utils.si2eotvos( transform.derivz(x, y, grav, shape, method='fft')) diff = _trim(np.abs(analytical - calculated), shape) assert np.all(diff <= 0.005*np.abs(analytical).max()), \ "Failed for gzz"
def test_pelgrav_prism_interp(): "PELGravity can interpolate data from a prism" model = [Prism(-300, 300, -500, 500, 100, 600, {'density': 400})] shape = (40, 40) n = shape[0]*shape[1] area = [-2000, 2000, -2000, 2000] x, y, z = gridder.scatter(area, n, z=-100, seed=42) data = prism.gz(x, y, z, model) layer = PointGrid(area, 100, shape) windows = (20, 20) degree = 1 eql = (PELGravity(x, y, z, data, layer, windows, degree) + 5e-22*PELSmoothness(layer, windows, degree)) eql.fit() layer.addprop('density', eql.estimate_) assert_allclose(eql[0].predicted(), data, rtol=0.01) xp, yp, zp = gridder.regular(area, shape, z=-100) true = prism.gz(xp, yp, zp, model) calc = sphere.gz(xp, yp, zp, layer) assert_allclose(calc, true, atol=0.001, rtol=0.05)
def test_horizontal_derivatives_fd(): "gravmag.transform 1st xy derivatives by finite diff against analytical" model = [Prism(-1000, 1000, -500, 500, 0, 2000, {'density': 100})] shape = (300, 300) x, y, z = gridder.regular([-5000, 5000, -5000, 5000], shape, z=-200) derivatives = 'x y'.split() grav = utils.mgal2si(prism.gz(x, y, z, model)) for deriv in derivatives: analytical = getattr(prism, 'g{}z'.format(deriv))(x, y, z, model) func = getattr(transform, 'deriv' + deriv) calculated = utils.si2eotvos(func(x, y, grav, shape, method='fd')) diff = np.abs(analytical - calculated) assert np.all(diff <= 0.005*np.abs(analytical).max()), \ "Failed for g{}. Max: {} Mean: {} STD: {}".format( deriv, diff.max(), diff.mean(), diff.std())
def test_gz_derivatives(): "gravmag.transform FFT 1st derivatives of gz against analytical solutions" model = [Prism(-1000, 1000, -500, 500, 0, 2000, {'density': 100})] shape = (300, 300) x, y, z = gridder.regular([-10000, 10000, -10000, 10000], shape, z=-100) derivatives = 'x y z'.split() grav = utils.mgal2si(prism.gz(x, y, z, model)) for deriv in derivatives: analytical = getattr(prism, 'g{}z'.format(deriv))(x, y, z, model) calculated = utils.si2eotvos( getattr(transform, 'deriv' + deriv)(x, y, grav, shape, method='fft')) diff = _trim(np.abs(analytical - calculated), shape) assert np.all(diff <= 0.005*np.abs(analytical).max()), \ "Failed for g{}z".format(deriv)
def anomaly_calculation(x, y, w, t, rho): from fatiando import utils from fatiando.mesher import Prism from fatiando.gravmag import prism ny, nx = np.shape(x) prismas = [] for i in xrange(ny-1): for j in xrange(nx-1): prisma = Prism(y[i][j], y[i+1][j+1], x[i][j], x[i+1][j+1], t, t + (w[i][j] + w[i+1][j] + w[i][j+1] + w[i+1][j+1])/4.) prismas.append(prisma) gz = -prism.gz(y.ravel(),x.ravel(),np.zeros(nx*ny),prismas,dens=rho) gz = utils.mgal2si(np.reshape(gz,(ny,nx))) return gz
def test_harvest_restrict(): def fill(i, case): # Returns density of 10 for center prism and prism given by 'case' cdir = {'above': 4, 'below': 22, 'north': 14, 'south': 12, 'east': 16, 'west': 10} if i == 13: return 10 for key in cdir: if case == key and i == cdir.get(key): return 10 return 0 # The test cases as string list cases = ['above', 'below', 'north', 'south', 'east', 'west'] # Create reference model bounds = (0, 3, 0, 3, 0, 3) shape = (3, 3, 3) shapegz = (10, 10) for testcase in cases: mref = PrismMesh(bounds, shape) mesh = mref.copy() mref.addprop('density', [fill(i, testcase) for i in xrange(mref.size)]) # Calculate reference gravity field xp, yp, zp = gridder.regular(bounds[:4], shapegz, z=-1) gzref = prism.gz(xp, yp, zp, mref) # Initiate harvest hgref = [harvester.Gz(xp, yp, zp, gzref)] loc = [[1.5, 1.5, 1.5, {'density': 10}]] seeds = harvester.sow(loc, mesh) # est0 should be incorrect and thus fail wilst est1 should yield the # same geometry as mref est0, pred0 = harvester.harvest(hgref, seeds, mesh, compactness=0.1, threshold=0.001, restrict=[testcase]) est1, pred1 = harvester.harvest(hgref, seeds, mesh, compactness=0.1, threshold=0.001) res0 = mesh.copy() res0.addprop('density', est0['density']) res1 = mesh.copy() res1.addprop('density', est1['density']) l0 = [] l1 = [] for i, p in enumerate(res0): l0.append(p.props['density'] == mref[i].props['density']) for i, p in enumerate(res1): l1.append(p.props['density'] == mref[i].props['density']) assert not np.all(l0) assert np.all(l1)
Here we'll show how the Generalized Inverse imaging method can be used on some synthetic data. We'll plot the final result as slices across the x, y, z axis. """ from __future__ import division from fatiando import gridder, mesher from fatiando.gravmag import prism, imaging from fatiando.vis.mpl import square import matplotlib.pyplot as plt import numpy as np # Make some synthetic gravity data from a simple prism model model = [mesher.Prism(-1000, 1000, -3000, 3000, 0, 2000, {'density': 800})] shape = (25, 25) xp, yp, zp = gridder.regular((-5000, 5000, -5000, 5000), shape, z=-10) data = prism.gz(xp, yp, zp, model) # Run the Generalized Inverse mesh = imaging.geninv(xp, yp, zp, data, shape, zmin=0, zmax=5000, nlayers=25) # Plot the results fig = plt.figure() X, Y = xp.reshape(shape)/1000, yp.reshape(shape)/1000 image = mesh.props['density'].reshape(mesh.shape) # First plot the original gravity data ax = plt.subplot(2, 2, 1) ax.set_title('Gravity data (mGal)') ax.set_aspect('equal') scale = np.abs([data.min(), data.max()]).max()
from fatiando.vis import myv, mpl area = (-150, 150, -300, 300) shape = (30, 15) x, y = gridder.regular(area, shape) height = (-80 * utils.gaussian2d(x, y, 100, 200, x0=-50, y0=-100, angle=-60) + 200 * utils.gaussian2d(x, y, 50, 100, x0=80, y0=170)) nodes = (x, y, -1 * height) relief = mesher.PrismRelief(0, gridder.spacing(area, shape), nodes) relief.addprop('density', (2670 for i in xrange(relief.size))) gridarea = (-80, 80, -220, 220) gridshape = (100, 100) xp, yp, zp = gridder.regular(gridarea, gridshape, z=-200) gz = prism.gz(xp, yp, zp, relief) mpl.figure(figsize=(10, 7)) mpl.subplot(1, 2, 1) mpl.title("Synthetic topography") mpl.axis('scaled') mpl.pcolor(x, y, height, shape) cb = mpl.colorbar() cb.set_label("meters") mpl.square(gridarea, label='Computation grid') mpl.legend() mpl.subplot(1, 2, 2) mpl.title("Topographic effect") mpl.axis('scaled') mpl.pcolor(xp, yp, gz, gridshape) cb = mpl.colorbar()
Here we'll show how the Generalized Inverse imaging method can be used on some synthetic data. We'll plot the final result as slices across the x, y, z axis. """ from __future__ import division from fatiando import gridder, mesher from fatiando.gravmag import prism, imaging from fatiando.vis.mpl import square import matplotlib.pyplot as plt import numpy as np # Make some synthetic gravity data from a simple prism model model = [mesher.Prism(-1000, 1000, -3000, 3000, 0, 2000, {'density': 800})] shape = (25, 25) xp, yp, zp = gridder.regular((-5000, 5000, -5000, 5000), shape, z=-10) data = prism.gz(xp, yp, zp, model) # Run the Generalized Inverse mesh = imaging.geninv(xp, yp, zp, data, shape, zmin=0, zmax=5000, nlayers=25) # Plot the results fig = plt.figure() X, Y = xp.reshape(shape) / 1000, yp.reshape(shape) / 1000 image = mesh.props['density'].reshape(mesh.shape) # First plot the original gravity data ax = plt.subplot(2, 2, 1) ax.set_title('Gravity data (mGal)') ax.set_aspect('equal') scale = np.abs([data.min(), data.max()]).max()
GravMag: Upward continuation of noisy gz data """ from fatiando import mesher, gridder, utils from fatiando.gravmag import prism, transform from fatiando.vis import mpl import numpy as np model = [ mesher.Prism(-3000, -2000, -3000, -2000, 500, 2000, {'density': 1000}), mesher.Prism(-1000, 1000, -1000, 1000, 0, 2000, {'density': -800}), mesher.Prism(1000, 3000, 2000, 3000, 0, 1000, {'density': 900})] area = (-5000, 5000, -5000, 5000) shape = (50, 50) z0 = -100 x, y, z = gridder.regular(area, shape, z=z0) gz = utils.contaminate(prism.gz(x, y, z, model), 0.5, seed=0) height = 1000 # How much higher to go gzcontf = transform.upcontinue(x, y, gz, shape, height) # Compute the true value at the new height for comparison gztrue = prism.gz(x, y, z - height, model) args = dict(shape=shape, levels=20, cmap=mpl.cm.RdBu_r) fig, axes = mpl.subplots(1, 3, figsize=(12, 3.5)) axes = axes.ravel() mpl.sca(axes[0]) mpl.title("Original") mpl.axis('scaled') mpl.contourf(x, y, gz, **args) mpl.colorbar(pad=0).set_label('mGal')
""" GravMag: Use an equivalent layer to upward continue gravity data """ from fatiando.gravmag import prism, sphere from fatiando.gravmag.eqlayer import EQLGravity from fatiando.inversion.regularization import Damping, LCurve from fatiando import gridder, utils, mesher from fatiando.vis import mpl # Make synthetic data props = {'density':1000} model = [mesher.Prism(-500, 500, -1000, 1000, 500, 4000, props)] shape = (25, 25) x, y, z = gridder.regular([-5000, 5000, -5000, 5000], shape, z=0) gz = utils.contaminate(prism.gz(x, y, z, model), 0.1, seed=0) # Setup the layer layer = mesher.PointGrid([-6000, 6000, -6000, 6000], 1000, (50, 50)) # Estimate the density # Need to apply enough damping so that won't try to fit the error as well misfit = EQLGravity(x, y, z, gz, layer) regul = Damping(layer.size) # Use an L-curve analysis to find the best regularization parameter solver = LCurve(misfit, regul, [10**i for i in range(-30, -20)]).fit() layer.addprop('density', solver.estimate_) residuals = solver.residuals() print "Residuals:" print "mean:", residuals.mean() print "stddev:", residuals.std() # Now I can forward model the layer at a greater height and check against the # true solution of the prism
simname = 'za' + str(za) + '_zb' + str(zb) + '_l' + str(l) + '_ofs' + str( offset) + '_dens' + str(dens) # model = [mesher.Prism(-l-offset, l-offset, -l-offset/20, l-offset, za, zb, {'density': dens})] # simname= 'mod_asy_rect_z3.2km_rectArea' simname = 'sym_square_z3.2km_rectArea' # model = [mesher.Prism(-4000, 0, -4000, -3500, za, zb, {'density': 1200})] model = [mesher.Prism(-1000, 1000, -1000, 1000, za, zb, {'density': 1200})] shape = (200, 200) #xp, yp, zp = gridder.scatter((-6000, 6000, -6000, 6000), shape[0]*shape[1], z=0) xp, yp, zp = gridder.regular((-12000, 12000, -6000, 6000), shape, z=0) # gz = utils.contaminate(prism.gz(xp, yp, zp, model), 0.1) gz = prism.gz(xp, yp, zp, model) x1, x2, y1, y2, z1, z2 = np.array(model[0].get_bounds()) import pickle d = {"xyzg": [xp, yp, zp, gz], "shape": shape, "model": model, "density": dens} afile = open(simname + '.pkl', 'wb') pickle.dump(d, afile) afile.close() # import pickle #reload object from file # file2 = open(simname + '.pkl', 'rb') # u = pickle._Unpickler(file2)
""" from fatiando import mesher, gridder, utils from fatiando.gravmag import prism, transform from fatiando.vis import mpl import numpy as np model = [ mesher.Prism(-3000, -2000, -3000, -2000, 500, 2000, {'density': 1000}), mesher.Prism(-1000, 1000, -1000, 1000, 0, 2000, {'density': -800}), mesher.Prism(1000, 3000, 2000, 3000, 0, 1000, {'density': 900}) ] area = (-5000, 5000, -5000, 5000) shape = (50, 50) z0 = -100 x, y, z = gridder.regular(area, shape, z=z0) gz = utils.contaminate(prism.gz(x, y, z, model), 0.5, seed=0) height = 1000 # How much higher to go gzcontf = transform.upcontinue(x, y, gz, shape, height) # Compute the true value at the new height for comparison gztrue = prism.gz(x, y, z - height, model) args = dict(shape=shape, levels=20, cmap=mpl.cm.RdBu_r) fig, axes = mpl.subplots(1, 3, figsize=(12, 3.5)) axes = axes.ravel() mpl.sca(axes[0]) mpl.title("Original") mpl.axis('scaled') mpl.contourf(x, y, gz, **args) mpl.colorbar(pad=0).set_label('mGal')
import matplotlib.pyplot as plt from fatiando.gravmag import prism, sphere from fatiando.gravmag.eqlayer import EQLGravity from fatiando.inversion import Damping from fatiando import gridder, utils, mesher # First thing to do is make some synthetic data to test the method. We'll use a # single prism to keep it simple props = {'density': 500} model = [mesher.Prism(-5000, 5000, -200, 200, 100, 4000, props)] # The synthetic data will be generated on a random scatter of points area = [-8000, 8000, -5000, 5000] x, y, z = gridder.scatter(area, 300, z=0, seed=42) # Generate some noisy data from our model gz = utils.contaminate(prism.gz(x, y, z, model), 0.2, seed=0) # Now for the equivalent layer. We must setup a layer of point masses where # we'll estimate a density distribution that fits our synthetic data layer = mesher.PointGrid(area, 500, (20, 20)) # Estimate the density using enough damping so that won't try to fit the error eql = EQLGravity(x, y, z, gz, layer) + 1e-22*Damping(layer.size) eql.fit() # Now we add the estimated densities to our layer layer.addprop('density', eql.estimate_) # and print some statistics of how well the estimated layer fits the data residuals = eql[0].residuals() print("Residuals:") print(" mean:", residuals.mean(), 'mGal') print(" stddev:", residuals.std(), 'mGal')
""" GravMag: Upward continuation of noisy gz data using the analytical formula """ from fatiando import mesher, gridder, utils from fatiando.gravmag import prism, transform from fatiando.vis import mpl model = [mesher.Prism(-3000,-2000,-3000,-2000,500,2000,{'density':1000}), mesher.Prism(-1000,1000,-1000,1000,0,2000,{'density':-800}), mesher.Prism(1000,3000,2000,3000,0,1000,{'density':500})] area = (-5000, 5000, -5000, 5000) shape = (50, 50) z0 = -100 xp, yp, zp = gridder.regular(area, shape, z=z0) gz = utils.contaminate(prism.gz(xp, yp, zp, model), 0.5) # Now do the upward continuation using the analytical formula height = 2000 dims = gridder.spacing(area, shape) gzcont = transform.upcontinue(gz, height, xp, yp, dims) gztrue = prism.gz(xp, yp, zp - height, model) mpl.figure(figsize=(14,6)) mpl.subplot(1, 2, 1) mpl.title("Original") mpl.axis('scaled') mpl.contourf(xp, yp, gz, shape, 15) mpl.contour(xp, yp, gz, shape, 15) mpl.subplot(1, 2, 2) mpl.title("Continued + true")
# Make a model bounds = [-5000, 5000, -5000, 5000, 0, 5000] model = [ Prism(-1500, -500, -1500, -500, 500, 1500, {'density': 1000}), Prism(500, 1500, 1000, 2000, 500, 1500, {'density': 1000}) ] # Generate some data from the model shape = (100, 100) area = bounds[0:4] xp, yp, zp = gridder.regular(area, shape, z=-1) # Add a constant baselevel baselevel = 10 # Convert the data from mGal to SI because Euler and FFT derivation require # data in SI gz = utils.mgal2si(prism.gz(xp, yp, zp, model)) + baselevel xderiv = transform.derivx(xp, yp, gz, shape) yderiv = transform.derivy(xp, yp, gz, shape) zderiv = transform.derivz(xp, yp, gz, shape) mpl.figure() titles = ['Gravity anomaly', 'x derivative', 'y derivative', 'z derivative'] for i, f in enumerate([gz, xderiv, yderiv, zderiv]): mpl.subplot(2, 2, i + 1) mpl.title(titles[i]) mpl.axis('scaled') mpl.contourf(yp, xp, f, shape, 50) mpl.colorbar() mpl.m2km() mpl.show()
""" GravMag: Calculating the derivatives of the gravity anomaly using FFT """ from fatiando import mesher, gridder, utils from fatiando.gravmag import prism, transform from fatiando.vis import mpl model = [mesher.Prism(-1000, 1000, -1000, 1000, 0, 2000, {'density': 100})] area = (-5000, 5000, -5000, 5000) shape = (51, 51) z0 = -500 xp, yp, zp = gridder.regular(area, shape, z=z0) gz = utils.contaminate(prism.gz(xp, yp, zp, model), 0.001) # Need to convert gz to SI units so that the result can be converted to Eotvos gxz = utils.si2eotvos(transform.derivx(xp, yp, utils.mgal2si(gz), shape)) gyz = utils.si2eotvos(transform.derivy(xp, yp, utils.mgal2si(gz), shape)) gzz = utils.si2eotvos(transform.derivz(xp, yp, utils.mgal2si(gz), shape)) gxz_true = prism.gxz(xp, yp, zp, model) gyz_true = prism.gyz(xp, yp, zp, model) gzz_true = prism.gzz(xp, yp, zp, model) mpl.figure() mpl.title("Original gravity anomaly") mpl.axis('scaled') mpl.contourf(xp, yp, gz, shape, 15) mpl.colorbar(shrink=0.7) mpl.m2km() mpl.figure(figsize=(14, 10))
""" GravMag: Iterate through a 3D gravity inversion by planting anomalous densities """ from fatiando import gridder from fatiando.gravmag import prism, harvester from fatiando.mesher import Prism, PrismMesh from fatiando.vis import myv model = [Prism(200, 800, 400, 600, 200, 400, {'density': 1000})] shape = (20, 20) bounds = [0, 1000, 0, 1000, 0, 1000] area = bounds[0:4] x, y, z = gridder.regular(area, shape, z=-1) gz = prism.gz(x, y, z, model) mesh = PrismMesh(bounds, (10, 10, 10)) data = [harvester.Gz(x, y, z, gz)] seeds = harvester.sow([[500, 500, 250, {'density': 1000}]], mesh) fig = myv.figure(size=(700, 700)) plot = myv.prisms(model, style='wireframe', linewidth=4) plot.actor.mapper.scalar_visibility = False myv.prisms(seeds, 'density') myv.outline(bounds) myv.wall_bottom(bounds) myv.wall_east(bounds) for update in harvester.iharvest(data, seeds, mesh, compactness=0.5, threshold=0.001): best, neighborhood = update[2:4] if best is not None: myv.prisms([mesh[best.i]]) plot = myv.prisms(
if case == key and i == cdir.get(key): return 10 return 0 # The test cases as string list cases = ['above', 'below', 'north', 'south', 'east', 'west'] # Create reference model bounds = (0, 3, 0, 3, 0, 3) shape = (3, 3, 3) shapegz = (10, 10) for testcase in cases: mref = PrismMesh(bounds, shape) mesh = mref.copy() mref.addprop('density', [fill(i, testcase) for i in xrange(mref.size)]) # Calculate reference gravity field xp, yp, zp = gridder.regular(bounds[:4], shapegz, z=-1) gzref = prism.gz(xp, yp, zp, mref) # Initiate harvest hgref = [harvester.Gz(xp, yp, zp, gzref)] loc = [[1.5, 1.5, 1.5, {'density': 10}]] seeds = harvester.sow(loc, mesh) # est0 should be incorrect and thus fail wilst est1 should yield the # same geometry as mref est0, pred0 = harvester.harvest(hgref, seeds, mesh, compactness=0.1, threshold=0.001, restrict=[testcase]) est1, pred1 = harvester.harvest(hgref, seeds, mesh, compactness=0.1, threshold=0.001) res0 = mesh.copy() res0.addprop('density', est0['density']) res1 = mesh.copy() res1.addprop('density', est1['density']) l0 = []
""" GravMag: Use the polynomial equivalent layer to upward continue gravity data """ from fatiando.gravmag import prism, sphere from fatiando.gravmag.eqlayer import PELGravity, PELSmoothness from fatiando import gridder, utils, mesher from fatiando.vis import mpl # Make synthetic data props = {'density': 1000} model = [mesher.Prism(-500, 500, -1000, 1000, 500, 4000, props)] shape = (50, 50) x, y, z = gridder.regular([-5000, 5000, -5000, 5000], shape, z=0) gz = utils.contaminate(prism.gz(x, y, z, model), 0.1, seed=0) # Setup the layer layer = mesher.PointGrid([-5000, 5000, -5000, 5000], 200, (100, 100)) # Estimate the density using the PEL (it is faster and more memory efficient # than the traditional equivalent layer). windows = (20, 20) degree = 1 misfit = PELGravity(x, y, z, gz, layer, windows, degree) # Apply a smoothness constraint to the borders of the equivalent layer windows # to avoid gaps in the physical property distribution solver = misfit + 1e-18 * PELSmoothness(layer, windows, degree) solver.fit() # Add the estimated density distribution to the layer object for plotting and # forward modeling layer.addprop('density', solver.estimate_) residuals = solver[0].residuals() print("Residuals:") print("mean:", residuals.mean())
def test_around(): "gravmag.prism gravitational results are consistent around the prism" funcs = ['potential', 'gx', 'gy', 'gz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz'] model = [Prism(-300, 300, -300, 300, -300, 300, {'density': 1000})] # Make the computation points surround the prism shape = (101, 101) area = [-600, 600, -600, 600] distance = 310 grids = [gridder.regular(area, shape, z=-distance), gridder.regular(area, shape, z=distance), gridder.regular(area, shape, z=distance)[::-1], gridder.regular(area, shape, z=-distance)[::-1], np.array(gridder.regular(area, shape, z=distance))[[0, 2, 1]], np.array(gridder.regular(area, shape, z=-distance))[[0, 2, 1]]] xp, yp, zp = grids[0] # Test if each component is consistent # POTENTIAL face = [prism.potential(x, y, z, model) for x, y, z in grids] for i in range(6): for j in range(i + 1, 6): assert_almost(face[i], face[j], 10, 'Failed potential, faces %d and %d' % (i, j)) # GX top, bottom, north, south, east, west = [prism.gx(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 10, 'Failed gx, top and bottom') assert_almost(north, -south, 10, 'Failed gx, north and south') assert_almost(east, west, 10, 'Failed gx, east and west') assert_almost(east, top, 10, 'Failed gx, east and top') assert_almost(north, -prism.gz(xp, yp, zp, model), 10, 'Failed gx, north and gz') assert_almost(south, prism.gz(xp, yp, zp, model), 10, 'Failed gx, south and gz') # GY top, bottom, north, south, east, west = [prism.gy(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 10, 'Failed gy, top and bottom') assert_almost(north, south, 10, 'Failed gy, north and south') assert_almost(east, -west, 10, 'Failed gy, east and west') assert_almost(north, top, 10, 'Failed gy, north and top') assert_almost(east, -prism.gz(xp, yp, zp, model), 10, 'Failed gy, east and gz') assert_almost(west, prism.gz(xp, yp, zp, model), 10, 'Failed gy, west and gz') # GZ top, bottom, north, south, east, west = [prism.gz(x, y, z, model) for x, y, z in grids] assert_almost(top, -bottom, 10, 'Failed gz, top and bottom') assert_almost(north, south, 10, 'Failed gz, north and south') assert_almost(east, west, 10, 'Failed gz, east and west') assert_almost(north, prism.gx(xp, yp, zp, model), 10, 'Failed gz, north and gx') assert_almost(south, prism.gx(xp, yp, zp, model), 10, 'Failed gz, south and gx') assert_almost(east, prism.gy(xp, yp, zp, model), 10, 'Failed gz, east and gy') assert_almost(west, prism.gy(xp, yp, zp, model), 10, 'Failed gz, west and gy') # GXX top, bottom, north, south, east, west = [prism.gxx(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 10, 'Failed gxx, top and bottom') assert_almost(north, south, 10, 'Failed gxx, north and south') assert_almost(east, west, 10, 'Failed gxx, east and west') assert_almost(east, top, 10, 'Failed gxx, east and top') assert_almost(north, prism.gzz(xp, yp, zp, model), 10, 'Failed gxx, north and gzz') assert_almost(south, prism.gzz(xp, yp, zp, model), 10, 'Failed gxx, south and gzz') # GXY top, bottom, north, south, east, west = [prism.gxy(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 4, 'Failed gxy, top and bottom') assert_almost(north, -south, 10, 'Failed gxy, north and south') assert_almost(east, -west, 10, 'Failed gxy, east and west') assert_almost(north, -prism.gyz(xp, yp, zp, model), 10, 'Failed gxy, north and gyz') assert_almost(south, prism.gyz(xp, yp, zp, model), 10, 'Failed gxy, south and gyz') # GXZ top, bottom, north, south, east, west = [prism.gxz(x, y, z, model) for x, y, z in grids] assert_almost(top, -bottom, 10, 'Failed gxz, top and bottom') assert_almost(north, -south, 10, 'Failed gxz, north and south') assert_almost(east, west, 4, 'Failed gxz, east and west') assert_almost(bottom, north, 10, 'Failed gxz, bottom and north') assert_almost(top, south, 10, 'Failed gxz, top and south') assert_almost(east, prism.gxy(xp, yp, zp, model), 4, 'Failed gxz, east and gxy') assert_almost(west, prism.gxy(xp, yp, zp, model), 10, 'Failed gxz, west and gxy') # GYY top, bottom, north, south, east, west = [prism.gyy(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 10, 'Failed gyy, top and bottom') assert_almost(north, south, 10, 'Failed gyy, north and south') assert_almost(east, west, 10, 'Failed gyy, east and west') assert_almost(top, north, 10, 'Failed gyy, top and north') assert_almost(east, prism.gzz(xp, yp, zp, model), 10, 'Failed gyy, east and gzz') assert_almost(west, prism.gzz(xp, yp, zp, model), 10, 'Failed gyy, west and gzz') # GYZ top, bottom, north, south, east, west = [prism.gyz(x, y, z, model) for x, y, z in grids] assert_almost(top, -bottom, 10, 'Failed gyz, top and bottom') assert_almost(north, south, 4, 'Failed gyz, north and south') assert_almost(east, -west, 10, 'Failed gyz, east and west') assert_almost(top, west, 10, 'Failed gyz, top and west') assert_almost(bottom, east, 10, 'Failed gyz, bottom and east') assert_almost(north, prism.gxy(xp, yp, zp, model), 4, 'Failed gyz, north and gxy') assert_almost(south, prism.gxy(xp, yp, zp, model), 10, 'Failed gyz, south and gxy') # GZZ top, bottom, north, south, east, west = [prism.gzz(x, y, z, model) for x, y, z in grids] assert_almost(top, bottom, 10, 'Failed gzz, top and bottom') assert_almost(north, south, 10, 'Failed gzz, north and south') assert_almost(east, west, 10, 'Failed gzz, east and west') assert_almost(north, prism.gxx(xp, yp, zp, model), 10, 'Failed gzz, north and gxx') assert_almost(south, prism.gxx(xp, yp, zp, model), 10, 'Failed gzz, south and gxx') assert_almost(east, prism.gyy(xp, yp, zp, model), 10, 'Failed gzz, east and gyy') assert_almost(west, prism.gyy(xp, yp, zp, model), 10, 'Failed gzz, west and gyy')
(more complex model + noisy data) """ from fatiando import gridder, mesher, utils from fatiando.gravmag import prism, imaging from fatiando.vis import mpl, myv # Make some synthetic gravity data from a simple prism model model = [ mesher.Prism(-4000, 0, -4000, -2000, 2000, 5000, {'density': 1200}), mesher.Prism(-1000, 1000, -1000, 1000, 1000, 7000, {'density': -800}), mesher.Prism(2000, 4000, 3000, 4000, 0, 2000, {'density': 600}) ] # Calculate on a scatter of points to show that migration doesn't need gridded # data xp, yp, zp = gridder.scatter((-6000, 6000, -6000, 6000), 1000, z=-10) gz = utils.contaminate(prism.gz(xp, yp, zp, model), 0.1) # Plot the data shape = (50, 50) mpl.figure() mpl.axis('scaled') mpl.contourf(yp, xp, gz, shape, 30, interp=True) mpl.colorbar() mpl.plot(yp, xp, '.k') mpl.xlabel('East (km)') mpl.ylabel('North (km)') mpl.m2km() mpl.show() mesh = imaging.migrate(xp, yp, zp, gz, 0, 10000, (30, 30, 30), power=0.8)
from fatiando.vis import mpl, myv # Generate some synthetic total field anomaly data bounds = [0, 10000, 0, 10000, 0, 5000] props = {'density': 500} props2 = {'density': 1000} model = [ mesher.Prism(4000, 6000, 4000, 6000, 500, 2500, props), mesher.Prism(2000, 2500, 2000, 2500, 500, 1000, props2), mesher.Prism(7500, 8000, 5500, 6500, 500, 1000, props2), mesher.Prism(1500, 2000, 4000, 5000, 500, 1000, props2) ] area = bounds[:4] shape = (50, 50) x, y, z = gridder.regular(area, shape, z=-1) gz = utils.contaminate(prism.gz(x, y, z, model), 0.1) mesh = mesher.PrismMesh(bounds, (20, 40, 40)) seeds = harvester.sow([[5000, 5000, 1000, props]], mesh) # Run the inversion without using weights data = [harvester.Gz(x, y, z, gz)] estimate, predicted = harvester.harvest(data, seeds, mesh, compactness=1.5, threshold=0.001) mesh.addprop('density', estimate['density']) bodies = mesher.vremove(0, 'density', mesh) mpl.figure() mpl.axis('scaled') mpl.title('No weights: Observed (color) vs Predicted (black)')
GravMag: Forward modeling of the gravitational potential and its derivatives using 3D model """ from fatiando import mesher, gridder from fatiando.gravmag import prism from fatiando.vis import mpl, myv model = [mesher.Prism(-4000,-3000,-4000,-3000,0,2000,{'density':1000}), mesher.Prism(-1000,1000,-1000,1000,0,2000,{'density':-900}), mesher.Prism(2000,4000,3000,4000,0,2000,{'density':1300})] shape = (100,100) xp, yp, zp = gridder.regular((-5000, 5000, -5000, 5000), shape, z=-150) fields = [prism.potential(xp, yp, zp, model), prism.gx(xp, yp, zp, model), prism.gy(xp, yp, zp, model), prism.gz(xp, yp, zp, model), prism.gxx(xp, yp, zp, model), prism.gxy(xp, yp, zp, model), prism.gxz(xp, yp, zp, model), prism.gyy(xp, yp, zp, model), prism.gyz(xp, yp, zp, model), prism.gzz(xp, yp, zp, model)] titles = ['potential', 'gx', 'gy', 'gz', 'gxx', 'gxy', 'gxz', 'gyy', 'gyz', 'gzz'] mpl.figure(figsize=(8, 9)) mpl.subplots_adjust(left=0.03, right=0.95, bottom=0.05, top=0.92, hspace=0.3) mpl.suptitle("Potential fields produced by a 3 prism model") for i, field in enumerate(fields): mpl.subplot(4, 3, i + 3) mpl.axis('scaled') mpl.title(titles[i])
iVoxelCount = 0 oTimeBeforeMapping = datetime.now() for oVoxel in lModel: #NOTE each oVoxel can be sent to a different mapper #for each prism: #calculate forward model for each prism and create mFmodelDiff #mFmodelDiff contains all prisms' forward modelled vectors #also return superposition of all prisms' signals in form of lCalc1 #Each one of these calcs could be done by a mapreduce task oVoxel.z2 += aMetricChange[ iVoxelCount] #update the voxel metric by the residual vector from the inversion oVoxel.z2 = iMinDepth if oVoxel.z2 < 0 else oVoxel.z2 #make sure depths are non negative #calculate first forward model aPrismCalc1 = prism.gz(aXGridCoords, aYGridCoords, aZGridCoords, [oVoxel]) oVoxel.z2 += iDeltaZ #calculate second forward model aPrismCalc2 = prism.gz(aXGridCoords, aYGridCoords, aZGridCoords, [oVoxel]) #compare computed signal change due to the model change and store for each prism mFmodelDiff[:, iVoxelCount] = (aPrismCalc2 - aPrismCalc1) / iDeltaZ iVoxelCount += 1 #superimpose the signal for each prism into an array for the whole area aCalcSignal += aPrismCalc1 #NOTE this would be done in the reducer #--- end forward modelling each voxel
from fatiando import utils, gridder, mesher from fatiando.gravmag import prism, harvester from fatiando.vis import mpl, myv # Generate some synthetic total field anomaly data bounds = [0, 10000, 0, 10000, 0, 5000] props = {'density':500} props2 = {'density':1000} model = [mesher.Prism(4000, 6000, 4000, 6000, 500, 2500, props), mesher.Prism(2000, 2500, 2000, 2500, 500, 1000, props2), mesher.Prism(7500, 8000, 5500, 6500, 500, 1000, props2), mesher.Prism(1500, 2000, 4000, 5000, 500, 1000, props2)] area = bounds[:4] shape = (50, 50) x, y, z = gridder.regular(area, shape, z=-1) gz = utils.contaminate(prism.gz(x, y, z, model), 0.1) mesh = mesher.PrismMesh(bounds, (20, 40, 40)) seeds = harvester.sow([[5000, 5000, 1000, props]], mesh) # Run the inversion without using weights data = [harvester.Gz(x, y, z, gz)] estimate, predicted = harvester.harvest(data, seeds, mesh, compactness=1.5, threshold=0.001) mesh.addprop('density', estimate['density']) bodies = mesher.vremove(0, 'density', mesh) mpl.figure() mpl.axis('scaled') mpl.title('No weights: Observed (color) vs Predicted (black)') levels = mpl.contourf(y, x, gz, shape, 17) mpl.colorbar() mpl.contour(y, x, predicted[0], shape, levels, color='k')
from fatiando.vis import mpl, myv # Make a model bounds = [-5000, 5000, -5000, 5000, 0, 5000] model = [ Prism(-1500, -500, -1500, -500, 500, 1500, {'density': 1000}), Prism(500, 1500, 1000, 2000, 500, 1500, {'density': 1000})] # Generate some data from the model shape = (100, 100) area = bounds[0:4] xp, yp, zp = gridder.regular(area, shape, z=-1) # Add a constant baselevel baselevel = 10 # Convert the data from mGal to SI because Euler and FFT derivation require # data in SI gz = utils.mgal2si(prism.gz(xp, yp, zp, model)) + baselevel xderiv = transform.derivx(xp, yp, gz, shape) yderiv = transform.derivy(xp, yp, gz, shape) zderiv = transform.derivz(xp, yp, gz, shape) mpl.figure() titles = ['Gravity anomaly', 'x derivative', 'y derivative', 'z derivative'] for i, f in enumerate([gz, xderiv, yderiv, zderiv]): mpl.subplot(2, 2, i + 1) mpl.title(titles[i]) mpl.axis('scaled') mpl.contourf(yp, xp, f, shape, 50) mpl.colorbar() mpl.m2km() mpl.show()
``harvester`` (simple example) """ from fatiando import gridder, utils from fatiando.gravmag import prism, harvester from fatiando.mesher import Prism, PrismMesh, vremove from fatiando.vis import mpl, myv # Create a synthetic model model = [Prism(250, 750, 250, 750, 200, 700, {'density': 1000})] # and generate synthetic data from it shape = (25, 25) bounds = [0, 1000, 0, 1000, 0, 1000] area = bounds[0:4] xp, yp, zp = gridder.regular(area, shape, z=-1) noise = 0.1 # 0.1 mGal noise gz = utils.contaminate(prism.gz(xp, yp, zp, model), noise) # plot the data mpl.figure() mpl.title("Synthetic gravity anomaly (mGal)") mpl.axis('scaled') levels = mpl.contourf(yp, xp, gz, shape, 12) mpl.colorbar() mpl.xlabel('Horizontal coordinate y (km)') mpl.ylabel('Horizontal coordinate x (km)') mpl.m2km() mpl.show() # Inversion setup # Create a mesh mesh = PrismMesh(bounds, (25, 25, 25)) # Wrap the data so that harvester can use it
def test_gz(): "gravmag.prism.gz python vs cython implementation" py = _prism_numpy.gz(xp, yp, zp, model) cy = prism.gz(xp, yp, zp, model) diff = np.abs(py - cy) assert np.all(diff <= precision), 'max diff: %g' % (max(diff))
import matplotlib.pyplot as plt from fatiando.gravmag import prism, sphere from fatiando.gravmag.eqlayer import EQLGravity from fatiando.inversion import Damping from fatiando import gridder, utils, mesher # First thing to do is make some synthetic data to test the method. We'll use a # single prism to keep it simple props = {'density': 500} model = [mesher.Prism(-5000, 5000, -200, 200, 100, 4000, props)] # The synthetic data will be generated on a random scatter of points area = [-8000, 8000, -5000, 5000] x, y, z = gridder.scatter(area, 300, z=0, seed=42) # Generate some noisy data from our model gz = utils.contaminate(prism.gz(x, y, z, model), 0.2, seed=0) # Now for the equivalent layer. We must setup a layer of point masses where # we'll estimate a density distribution that fits our synthetic data layer = mesher.PointGrid(area, 500, (20, 20)) # Estimate the density using enough damping so that won't try to fit the error eql = EQLGravity(x, y, z, gz, layer) + 1e-22 * Damping(layer.size) eql.fit() # Now we add the estimated densities to our layer layer.addprop('density', eql.estimate_) # and print some statistics of how well the estimated layer fits the data residuals = eql[0].residuals() print("Residuals:") print(" mean:", residuals.mean(), 'mGal') print(" stddev:", residuals.std(), 'mGal')
iVoxelCount = 0 oTimeBeforeMapping = datetime.now() for oVoxel in lModel: #NOTE each oVoxel can be sent to a different mapper #for each prism: #calculate forward model for each prism and create mFmodelDiff #mFmodelDiff contains all prisms' forward modelled vectors #also return superposition of all prisms' signals in form of lCalc1 #Each one of these calcs could be done by a mapreduce task oVoxel.z2 += aMetricChange[iVoxelCount] #update the voxel metric by the residual vector from the inversion oVoxel.z2 = iMinDepth if oVoxel.z2 < 0 else oVoxel.z2 #make sure depths are non negative #calculate first forward model aPrismCalc1 = prism.gz(aXGridCoords, aYGridCoords, aZGridCoords, [oVoxel]) oVoxel.z2 += iDeltaZ #calculate second forward model aPrismCalc2 = prism.gz(aXGridCoords, aYGridCoords, aZGridCoords, [oVoxel]) #compare computed signal change due to the model change and store for each prism mFmodelDiff[:, iVoxelCount] = (aPrismCalc2 - aPrismCalc1)/iDeltaZ iVoxelCount += 1 #superimpose the signal for each prism into an array for the whole area aCalcSignal += aPrismCalc1 #NOTE this would be done in the reducer #--- end forward modelling each voxel