diff --git a/.travis.yml b/.travis.yml index 6a8a995f..e28889f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ env: - TEST_DIR=tests/base - TEST_DIR=tests/examples - TEST_DIR=tests/flow + - TEST_DIR=tests/em # Setup anaconda before_install: diff --git a/SimPEG/EM/Analytics/FDEM.py b/SimPEG/EM/Analytics/FDEM.py new file mode 100644 index 00000000..ce7e623d --- /dev/null +++ b/SimPEG/EM/Analytics/FDEM.py @@ -0,0 +1,153 @@ +from __future__ import division +import numpy as np +from scipy.constants import mu_0, pi +from scipy.special import erf +import matplotlib.pyplot as plt +from SimPEG import Utils + + +def hzAnalyticDipoleF(r, freq, sigma, secondary=True, mu=mu_0): + """ + 4.56 in Ward and Hohmann + + .. plot:: + + import matplotlib.pyplot as plt + from SimPEG import EM + freq = np.logspace(-1, 6, 61) + test = EM.Analytics.FDEM.hzAnalyticDipoleF(100, freq, 0.001, secondary=False) + plt.loglog(freq, abs(test.real)) + plt.loglog(freq, abs(test.imag)) + plt.title('Response at $r$=100m') + plt.xlabel('Frequency') + plt.ylabel('Response') + plt.legend(('real','imag')) + plt.show() + + """ + r = np.abs(r) + k = np.sqrt(-1j*2.*np.pi*freq*mu*sigma) + + m = 1 + front = m / (2. * np.pi * (k**2) * (r**5) ) + back = 9 - ( 9 + 9j * k * r - 4 * (k**2) * (r**2) - 1j * (k**3) * (r**3)) * np.exp(-1j*k*r) + hz = front*back + + if secondary: + hp =-1/(4*np.pi*r**3) + hz = hz-hp + + if hz.ndim == 1: + hz = Utils.mkvc(hz,2) + + return hz + +def MagneticDipoleWholeSpace(XYZ, srcLoc, sig, f, moment=1., orientation='X', mu = mu_0): + """ + Analytical solution for a dipole in a whole-space. + + Equation 2.57 of Ward and Hohmann + + TODOs: + - set it up to instead take a mesh & survey + - add E-fields + - handle multiple frequencies + - add divide by zero safety + + + .. plot:: + + from SimPEG import EM + import matplotlib.pyplot as plt + freqs = np.logspace(-2,5,100) + Bx, By, Bz = EM.Analytics.FDEM.AnalyticMagDipoleWholeSpace([0,100,0], [0,0,0], 1e-2, freqs, m=1, orientation='Z') + plt.loglog(freqs, np.abs(Bz.real)/mu_0, 'b') + plt.loglog(freqs, np.abs(Bz.imag)/mu_0, 'r') + plt.legend(('real','imag')) + plt.show() + + + """ + + XYZ = Utils.asArray_N_x_Dim(XYZ, 3) + + dx = XYZ[:,0]-srcLoc[0] + dy = XYZ[:,1]-srcLoc[1] + dz = XYZ[:,2]-srcLoc[2] + + r = np.sqrt( dx**2. + dy**2. + dz**2.) + k = np.sqrt( -1j*2.*np.pi*f*mu*sig ) + kr = k*r + + front = moment / (4.*pi * r**3.) * np.exp(-1j*kr) + mid = -kr**2. + 3.*1j*kr + 3. + + if orientation.upper() == 'X': + Hx = front*( (dx/r)**2. * mid + (kr**2. - 1j*kr - 1.) ) + Hy = front*( (dx*dy/r**2.) * mid ) + Hz = front*( (dx*dz/r**2.) * mid ) + + elif orientation.upper() == 'Y': + Hx = front*( (dy*dx/r**2.) * mid ) + Hy = front*( (dy/r)**2. * mid + (kr**2. - 1j*kr - 1.) ) + Hz = front*( (dy*dz/r**2.) * mid ) + + elif orientation.upper() == 'Z': + Hx = front*( (dx*dz/r**2.) * mid ) + Hy = front*( (dy*dz/r**2.) * mid ) + Hz = front*( (dz/r)**2. * mid + (kr**2. - 1j*kr - 1.) ) + + Bx = mu*Hx + By = mu*Hy + Bz = mu*Hz + + if Bx.ndim is 1: + Bx = Utils.mkvc(Bx,2) + + if By.ndim is 1: + By = Utils.mkvc(By,2) + + if Bz.ndim is 1: + Bz = Utils.mkvc(Bz,2) + + return Bx, By, Bz + + +def ElectricDipoleWholeSpace(XYZ, srcLoc, sig, f, current=1., length=1., orientation='X', mu=mu_0): + XYZ = Utils.asArray_N_x_Dim(XYZ, 3) + + dx = XYZ[:,0]-srcLoc[0] + dy = XYZ[:,1]-srcLoc[1] + dz = XYZ[:,2]-srcLoc[2] + + r = np.sqrt( dx**2. + dy**2. + dz**2.) + k = np.sqrt( -1j*2.*np.pi*f*mu*sig ) + kr = k*r + + front = current * length / (4. * np.pi * sig * r**3) * np.exp(-1j*k*r) + mid = -k**2 * r**2 + 3*1j*k*r + 3 + + # Ex = front*((dx**2 / r**2)*mid + (k**2 * r**2 -1j*k*r)) + # Ey = front*(dx*dy / r**2)*mid + # Ez = front*(dx*dz / r**2)*mid + + if orientation.upper() == 'X': + Ex = front*((dx**2 / r**2)*mid + (k**2 * r**2 -1j*k*r-1.)) + Ey = front*(dx*dy / r**2)*mid + Ez = front*(dx*dz / r**2)*mid + return Ex, Ey, Ez + + elif orientation.upper() == 'Y': + # x--> y, y--> z, z-->x + Ey = front*((dy**2 / r**2)*mid + (k**2 * r**2 -1j*k*r-1.)) + Ez = front*(dy*dz / r**2)*mid + Ex = front*(dy*dx / r**2)*mid + return Ex, Ey, Ez + + elif orientation.upper() == 'Z': + # x --> z, y --> x, z --> y + Ez = front*((dz**2 / r**2)*mid + (k**2 * r**2 -1j*k*r-1.)) + Ex = front*(dz*dx / r**2)*mid + Ey = front*(dz*dy / r**2)*mid + return Ex, Ey, Ez + # return Ey, Ez, Ex diff --git a/SimPEG/EM/Analytics/FDEMcasing.py b/SimPEG/EM/Analytics/FDEMcasing.py new file mode 100644 index 00000000..b6c7acf1 --- /dev/null +++ b/SimPEG/EM/Analytics/FDEMcasing.py @@ -0,0 +1,98 @@ +from SimPEG import Utils, np +from scipy.constants import mu_0, epsilon_0 +from simpegEM.Utils.EMUtils import k + +def getKc(freq,sigma,a,b,mu=mu_0,eps=epsilon_0): + a = float(a) + b = float(b) + # return 1./(2*np.pi) * np.sqrt(b / a) * np.exp(-1j*k(freq,sigma,mu,eps)*(b-a)) + return np.sqrt(b / a) * np.exp(-1j*k(freq,sigma,mu,eps)*(b-a)) + +def _r2(xyz): + return np.sum(xyz**2,1) + +def _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + Kc1 = getKc(freq,sigma[1],a,b,mu[1],eps) + + nobs = obsloc.shape[0] + dxyz = obsloc - np.c_[np.ones(nobs)]*np.r_[srcloc] + + r2 = _r2(dxyz[:,:2]) + sqrtr2z2 = np.sqrt(r2 + dxyz[:,2]**2) + k2 = k(freq,sigma[2],mu[2],eps) + + return Kc1 * moment / (4.*np.pi) *np.exp(-1j*k2*sqrtr2z2) / sqrtr2z2 + + +def _getCasingHertzMagDipoleDeriv_r(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + HertzZ = _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + + nobs = obsloc.shape[0] + dxyz = obsloc - np.c_[np.ones(nobs)]*np.r_[srcloc] + + r2 = _r2(dxyz[:,:2]) + sqrtr2z2 = np.sqrt(r2 + dxyz[:,2]**2) + k2 = k(freq,sigma[2],mu[2],eps) + + return -HertzZ * np.sqrt(r2) / sqrtr2z2 * (1j*k2 + 1./ sqrtr2z2) + + +def _getCasingHertzMagDipoleDeriv_z(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + HertzZ = _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + + nobs = obsloc.shape[0] + dxyz = obsloc - np.c_[np.ones(nobs)]*np.r_[srcloc] + + r2z2 = _r2(dxyz) + sqrtr2z2 = np.sqrt(r2z2) + k2 = k(freq,sigma[2],mu[2],eps) + + return -HertzZ*dxyz[:,2] /sqrtr2z2 * (1j*k2 + 1./sqrtr2z2) + +def _getCasingHertzMagDipole2Deriv_z_r(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + HertzZ = _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + dHertzZdr = _getCasingHertzMagDipoleDeriv_r(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + + nobs = obsloc.shape[0] + dxyz = obsloc - np.c_[np.ones(nobs)]*np.r_[srcloc] + + r2 = _r2(dxyz[:,:2]) + r = np.sqrt(r2) + z = dxyz[:,2] + sqrtr2z2 = np.sqrt(r2 + z**2) + k2 = k(freq,sigma[2],mu[2],eps) + + return dHertzZdr*(-z/sqrtr2z2)*(1j*k2+1./sqrtr2z2) + HertzZ*(z*r/sqrtr2z2**3)*(1j*k2 + 2./sqrtr2z2) + +def _getCasingHertzMagDipole2Deriv_z_z(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + HertzZ = _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + dHertzZdz = _getCasingHertzMagDipoleDeriv_z(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + + nobs = obsloc.shape[0] + dxyz = obsloc - np.c_[np.ones(nobs)]*np.r_[srcloc] + + r2 = _r2(dxyz[:,:2]) + r = np.sqrt(r2) + z = dxyz[:,2] + sqrtr2z2 = np.sqrt(r2 + z**2) + k2 = k(freq,sigma[2],mu[2],eps) + + return (dHertzZdz*z + HertzZ)/sqrtr2z2*(-1j*k2 - 1./sqrtr2z2) + HertzZ*z/sqrtr2z2**3*(1j*k2*z + 2.*z/sqrtr2z2) + +def getCasingEphiMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + return 1j * omega(freq) * mu * _getCasingHertzMagDipoleDeriv_r(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + +def getCasingHrMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + return _getCasingHertzMagDipole2Deriv_z_r(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + +def getCasingHzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + d2HertzZdz2 = _getCasingHertzMagDipole2Deriv_z_z(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + k2 = k(freq,sigma[2],mu[2],eps) + HertzZ = _getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + return d2HertzZdz2 + k2**2 * HertzZ + +def getCasingBrMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + return mu_0 * getCasingHrMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) + +def getCasingBzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu=mu_0*np.ones(3),eps=epsilon_0,moment=1.): + return mu_0 * getCasingHzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu,eps,moment) \ No newline at end of file diff --git a/SimPEG/EM/Analytics/TDEM.py b/SimPEG/EM/Analytics/TDEM.py new file mode 100644 index 00000000..b20e8191 --- /dev/null +++ b/SimPEG/EM/Analytics/TDEM.py @@ -0,0 +1,12 @@ +import numpy as np +from scipy.constants import mu_0, pi +from scipy.special import erf + +def hzAnalyticDipoleT(r, t, sigma): + theta = np.sqrt((sigma*mu_0)/(4*t)) + tr = theta*r + etr = erf(tr) + t1 = (9/(2*tr**2) - 1)*etr + t2 = (1/np.sqrt(pi))*(9/tr + 4*tr)*np.exp(-tr**2) + hz = (t1 - t2)/(4*pi*r**3) + return hz diff --git a/SimPEG/EM/Analytics/__init__.py b/SimPEG/EM/Analytics/__init__.py new file mode 100644 index 00000000..5b7a8851 --- /dev/null +++ b/SimPEG/EM/Analytics/__init__.py @@ -0,0 +1,3 @@ +from TDEM import hzAnalyticDipoleT +from FDEM import hzAnalyticDipoleF +from FDEMcasing import * diff --git a/SimPEG/EM/Base.py b/SimPEG/EM/Base.py new file mode 100644 index 00000000..32018f7e --- /dev/null +++ b/SimPEG/EM/Base.py @@ -0,0 +1,186 @@ +from SimPEG import Survey, Problem, Utils, Models, Maps, PropMaps, np, sp, Solver as SimpegSolver +from scipy.constants import mu_0 + +class EMPropMap(Maps.PropMap): + """ + Property Map for EM Problems. The electrical conductivity (\\(\\sigma\\)) is the default inversion property, and the default value of the magnetic permeability is that of free space (\\(\\mu = 4\\pi\\times 10^{-7} \\) H/m) + """ + + sigma = Maps.Property("Electrical Conductivity", defaultInvProp = True, propertyLink=('rho',Maps.ReciprocalMap)) + mu = Maps.Property("Inverse Magnetic Permeability", defaultVal = mu_0, propertyLink=('mui',Maps.ReciprocalMap)) + + rho = Maps.Property("Electrical Resistivity", propertyLink=('sigma', Maps.ReciprocalMap)) + mui = Maps.Property("Inverse Magnetic Permeability", defaultVal = 1./mu_0, propertyLink=('mu', Maps.ReciprocalMap)) + + +class BaseEMProblem(Problem.BaseProblem): + + def __init__(self, mesh, **kwargs): + Problem.BaseProblem.__init__(self, mesh, **kwargs) + + + surveyPair = Survey.BaseSurvey + dataPair = Survey.Data + + PropMap = EMPropMap + + Solver = SimpegSolver + solverOpts = {} + + verbose = False + + #################################################### + # Make A Symmetric + #################################################### + @property + def _makeASymmetric(self): + if getattr(self, '__makeASymmetric', None) is None: + self.__makeASymmetric = True + return self.__makeASymmetric + + + #################################################### + # Mass Matrices + #################################################### + + @property + def deleteTheseOnModelUpdate(self): + toDelete = [] + if self.mapping.sigmaMap is not None or self.mapping.rhoMap is not None: + toDelete += ['_MeSigma', '_MeSigmaI','_MfRho','_MfRhoI'] + if self.mapping.muMap is not None or self.mapping.muiMap is not None: + toDelete += ['_MeMu', '_MeMuI','_MfMui','_MfMuiI'] + return toDelete + + @property + def Me(self): + """ + Edge inner product matrix + """ + if getattr(self, '_Me', None) is None: + self._Me = self.mesh.getEdgeInnerProduct() + return self._Me + + @property + def Mf(self): + """ + Face inner product matrix + """ + if getattr(self, '_Mf', None) is None: + self._Mf = self.mesh.getFaceInnerProduct() + return self._Mf + + + # ----- Magnetic Permeability ----- # + @property + def MfMui(self): + """ + Face inner product matrix for \\(\\mu^{-1}\\). Used in the E-B formulation + """ + if getattr(self, '_MfMui', None) is None: + self._MfMui = self.mesh.getFaceInnerProduct(self.curModel.mui) + return self._MfMui + + @property + def MfMuiI(self): + """ + Inverse of :code:`MfMui`. + """ + if getattr(self, '_MfMuiI', None) is None: + self._MfMuiI = self.mesh.getFaceInnerProduct(self.curModel.mui, invMat=True) + return self._MfMuiI + + @property + def MeMu(self): + """ + Edge inner product matrix for \\(\\mu\\). Used in the H-J formulation + """ + if getattr(self, '_MeMu', None) is None: + self._MeMu = self.mesh.getEdgeInnerProduct(self.curModel.mu) + return self._MeMu + + @property + def MeMuI(self): + """ + Inverse of :code:`MeMu` + """ + if getattr(self, '_MeMuI', None) is None: + self._MeMuI = self.mesh.getEdgeInnerProduct(self.curModel.mu, invMat=True) + return self._MeMuI + + + # ----- Electrical Conductivity ----- # + #TODO: hardcoded to sigma as the model + @property + def MeSigma(self): + """ + Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation + """ + if getattr(self, '_MeSigma', None) is None: + self._MeSigma = self.mesh.getEdgeInnerProduct(self.curModel.sigma) + return self._MeSigma + + # TODO: This should take a vector + def MeSigmaDeriv(self, u): + """ + Derivative of MeSigma with respect to the model + """ + return self.mesh.getEdgeInnerProductDeriv(self.curModel.sigma)(u) * self.curModel.sigmaDeriv + + + @property + def MeSigmaI(self): + """ + Inverse of the edge inner product matrix for \\(\\sigma\\). + """ + if getattr(self, '_MeSigmaI', None) is None: + self._MeSigmaI = self.mesh.getEdgeInnerProduct(self.curModel.sigma, invMat=True) + return self._MeSigmaI + + # TODO: This should take a vector + def MeSigmaIDeriv(self, u): + """ + Derivative of :code:`MeSigma` with respect to the model + """ + # TODO: only works for diagonal tensors. getEdgeInnerProductDeriv, invMat=True should be implemented in SimPEG + + dMeSigmaI_dI = -self.MeSigmaI**2 + dMe_dsig = self.mesh.getEdgeInnerProductDeriv(self.curModel.sigma)(u) + dsig_dm = self.curModel.sigmaDeriv + return dMeSigmaI_dI * ( dMe_dsig * ( dsig_dm)) + # return self.mesh.getEdgeInnerProductDeriv(self.curModel.sigma, invMat=True)(u) + + + @property + def MfRho(self): + """ + Face inner product matrix for \\(\\rho\\). Used in the H-J formulation + """ + if getattr(self, '_MfRho', None) is None: + self._MfRho = self.mesh.getFaceInnerProduct(self.curModel.rho) + return self._MfRho + + # TODO: This should take a vector + def MfRhoDeriv(self,u): + """ + Derivative of :code:`MfRho` with respect to the model. + """ + return self.mesh.getFaceInnerProductDeriv(self.curModel.rho)(u) * (-Utils.sdiag(self.curModel.rho**2) * self.curModel.sigmaDeriv) + # self.curModel.rhoDeriv + + @property + def MfRhoI(self): + """ + Inverse of :code:`MfRho` + """ + if getattr(self, '_MfRhoI', None) is None: + self._MfRhoI = self.mesh.getFaceInnerProduct(self.curModel.rho, invMat=True) + return self._MfRhoI + + # TODO: This isn't going to work yet + # TODO: This should take a vector + def MfRhoIDeriv(self,u): + """ + Derivative of :code:`MfRhoI` with respect to the model. + """ + return self.mesh.getFaceInnerProductDeriv(self.curModel.rho, invMat=True)(u) * self.curModel.rhoDeriv diff --git a/SimPEG/EM/Examples/CylInversion.py b/SimPEG/EM/Examples/CylInversion.py new file mode 100644 index 00000000..cfcfcfc1 --- /dev/null +++ b/SimPEG/EM/Examples/CylInversion.py @@ -0,0 +1,97 @@ +from SimPEG import * +import SimPEG.EM as EM +from scipy.constants import mu_0 +import matplotlib.pyplot as plt + +def run(plotIt=True): + + cs, ncx, ncz, npad = 5., 25, 15, 15 + hx = [(cs,ncx), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,ncz), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hz], '00C') + + active = mesh.vectorCCz<0. + layer = (mesh.vectorCCz<0.) & (mesh.vectorCCz>=-100.) + actMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * actMap + sig_half = 2e-3 + sig_air = 1e-8 + sig_layer = 1e-3 + sigma = np.ones(mesh.nCz)*sig_air + sigma[active] = sig_half + sigma[layer] = sig_layer + mtrue = np.log(sigma[active]) + + + if plotIt: + fig, ax = plt.subplots(1,1, figsize = (3, 6)) + plt.semilogx(sigma[active], mesh.vectorCCz[active]) + ax.set_ylim(-600, 0) + ax.set_xlim(1e-4, 1e-2) + ax.set_xlabel('Conductivity (S/m)', fontsize = 14) + ax.set_ylabel('Depth (m)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + + + rxOffset=1e-3 + rx = EM.TDEM.RxTDEM(np.array([[rxOffset, 0., 30]]), np.logspace(-5,-3, 31), 'bz') + src = EM.TDEM.SrcTDEM_VMD_MVP([rx], np.array([0., 0., 80])) + survey = EM.TDEM.SurveyTDEM([src]) + prb = EM.TDEM.ProblemTDEM_b(mesh, mapping=mapping) + + prb.Solver = SolverLU + prb.timeSteps = [(1e-06, 20),(1e-05, 20), (0.0001, 20)] + prb.pair(survey) + dtrue = survey.dpred(mtrue) + + + survey.dtrue = dtrue + std = 0.05 + noise = std*abs(survey.dtrue)*np.random.randn(*survey.dtrue.shape) + survey.dobs = survey.dtrue+noise + survey.std = survey.dobs*0 + std + survey.Wd = 1/(abs(survey.dobs)*std) + + if plotIt: + fig, ax = plt.subplots(1,1, figsize = (10, 6)) + ax.loglog(rx.times, dtrue, 'b.-') + ax.loglog(rx.times, survey.dobs, 'r.-') + ax.legend(('Noisefree', '$d^{obs}$'), fontsize = 16) + ax.set_xlabel('Time (s)', fontsize = 14) + ax.set_ylabel('$B_z$ (T)', fontsize = 16) + ax.set_xlabel('Time (s)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + + dmisfit = DataMisfit.l2_DataMisfit(survey) + regMesh = Mesh.TensorMesh([mesh.hz[mapping.maps[-1].indActive]]) + reg = Regularization.Tikhonov(regMesh) + opt = Optimization.InexactGaussNewton(maxIter = 5) + invProb = InvProblem.BaseInvProblem(dmisfit, reg, opt) + # Create an inversion object + beta = Directives.BetaSchedule(coolingFactor=5, coolingRate=2) + betaest = Directives.BetaEstimate_ByEig(beta0_ratio=1e0) + inv = Inversion.BaseInversion(invProb, directiveList=[beta,betaest]) + m0 = np.log(np.ones(mtrue.size)*sig_half) + reg.alpha_s = 1e-2 + reg.alpha_x = 1. + prb.counter = opt.counter = Utils.Counter() + opt.LSshorten = 0.5 + opt.remember('xc') + + mopt = inv.run(m0) + + if plotIt: + fig, ax = plt.subplots(1,1, figsize = (3, 6)) + plt.semilogx(sigma[active], mesh.vectorCCz[active]) + plt.semilogx(np.exp(mopt), mesh.vectorCCz[active]) + ax.set_ylim(-600, 0) + ax.set_xlim(1e-4, 1e-2) + ax.set_xlabel('Conductivity (S/m)', fontsize = 14) + ax.set_ylabel('Depth (m)', fontsize = 14) + ax.grid(color='k', alpha=0.5, linestyle='dashed', linewidth=0.5) + plt.legend(['$\sigma_{true}$', '$\sigma_{pred}$']) + plt.show() + + +if __name__ == '__main__': + run() diff --git a/SimPEG/EM/Examples/__init__.py b/SimPEG/EM/Examples/__init__.py new file mode 100644 index 00000000..eb36678d --- /dev/null +++ b/SimPEG/EM/Examples/__init__.py @@ -0,0 +1 @@ +import CylInversion diff --git a/SimPEG/EM/FDEM/FDEM.py b/SimPEG/EM/FDEM/FDEM.py new file mode 100644 index 00000000..c9296ec9 --- /dev/null +++ b/SimPEG/EM/FDEM/FDEM.py @@ -0,0 +1,661 @@ +from SimPEG import Survey, Problem, Utils, np, sp, Solver as SimpegSolver +from scipy.constants import mu_0 +from SurveyFDEM import SurveyFDEM +from FieldsFDEM import FieldsFDEM, FieldsFDEM_e, FieldsFDEM_b, FieldsFDEM_h, FieldsFDEM_j +from simpegEM.Base import BaseEMProblem +from simpegEM.Utils.EMUtils import omega + + +class BaseFDEMProblem(BaseEMProblem): + """ + We start by looking at Maxwell's equations in the electric + field \\\(\\\mathbf{e}\\\) and the magnetic flux + density \\\(\\\mathbf{b}\\\) + + .. math :: + + \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\\\ + {\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{M_{\sigma}^e} \mathbf{e} = \mathbf{M^e} \mathbf{s_e}} + + if using the E-B formulation (:code:`ProblemFDEM_e` + or :code:`ProblemFDEM_b`) or the magnetic field + \\\(\\\mathbf{h}\\\) and current density \\\(\\\mathbf{j}\\\) + + .. math :: + + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + i \omega \mathbf{M_{\mu}^e} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\\\ + \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} + + if using the H-J formulation (:code:`ProblemFDEM_j` or :code:`ProblemFDEM_h`). + + The problem performs the elimination so that we are solving the system for \\\(\\\mathbf{e},\\\mathbf{b},\\\mathbf{j} \\\) or \\\(\\\mathbf{h}\\\) + """ + + surveyPair = SurveyFDEM + fieldsPair = FieldsFDEM + + def fields(self, m=None): + """ + Solve the forward problem for the fields. + """ + + self.curModel = m + F = self.fieldsPair(self.mesh, self.survey) + + for freq in self.survey.freqs: + A = self.getA(freq) + rhs = self.getRHS(freq) + Ainv = self.Solver(A, **self.solverOpts) + sol = Ainv * rhs + Srcs = self.survey.getSrcByFreq(freq) + ftype = self._fieldType + 'Solution' + F[Srcs, ftype] = sol + + return F + + def Jvec(self, m, v, f=None): + """ + Sensitivity times a vector + """ + + if f is None: + f = self.fields(m) + + self.curModel = m + + Jv = self.dataPair(self.survey) + + for freq in self.survey.freqs: + dA_du = self.getA(freq) # + dA_duI = self.Solver(dA_du, **self.solverOpts) + + for src in self.survey.getSrcByFreq(freq): + ftype = self._fieldType + 'Solution' + u_src = f[src, ftype] + dA_dm = self.getADeriv_m(freq, u_src, v) + dRHS_dm = self.getRHSDeriv_m(src, v) + if dRHS_dm is None: + du_dm = dA_duI * ( - dA_dm ) + else: + du_dm = dA_duI * ( - dA_dm + dRHS_dm ) + for rx in src.rxList: + # df_duFun = u.deriv_u(rx.fieldsUsed, m) + df_duFun = getattr(f, '_%sDeriv_u'%rx.projField, None) + df_du = df_duFun(src, du_dm, adjoint=False) + if df_du is not None: + du_dm = df_du + + df_dmFun = getattr(f, '_%sDeriv_m'%rx.projField, None) + df_dm = df_dmFun(src, v, adjoint=False) + if df_dm is not None: + du_dm += df_dm + + P = lambda v: rx.projectFieldsDeriv(src, self.mesh, f, v) # wrt u, also have wrt m + + + Jv[src, rx] = P(du_dm) + + return Utils.mkvc(Jv) + + def Jtvec(self, m, v, f=None): + """ + Sensitivity transpose times a vector + """ + + if f is None: + f = self.fields(m) + + self.curModel = m + + # Ensure v is a data object. + if not isinstance(v, self.dataPair): + v = self.dataPair(self.survey, v) + + Jtv = np.zeros(m.size) + + for freq in self.survey.freqs: + AT = self.getA(freq).T + ATinv = self.Solver(AT, **self.solverOpts) + + for src in self.survey.getSrcByFreq(freq): + ftype = self._fieldType + 'Solution' + u_src = f[src, ftype] + + for rx in src.rxList: + PTv = rx.projectFieldsDeriv(src, self.mesh, f, v[src, rx], adjoint=True) # wrt u, need possibility wrt m + + df_duTFun = getattr(f, '_%sDeriv_u'%rx.projField, None) + df_duT = df_duTFun(src, PTv, adjoint=True) + if df_duT is not None: + dA_duIT = ATinv * df_duT + else: + dA_duIT = ATinv * PTv + + dA_dmT = self.getADeriv_m(freq, u_src, dA_duIT, adjoint=True) + + dRHS_dmT = self.getRHSDeriv_m(src, dA_duIT, adjoint=True) + + if dRHS_dmT is None: + du_dmT = - dA_dmT + else: + du_dmT = -dA_dmT + dRHS_dmT + + df_dmFun = getattr(f, '_%sDeriv_m'%rx.projField, None) + dfT_dm = df_dmFun(src, PTv, adjoint=True) + if dfT_dm is not None: + du_dmT += dfT_dm + + real_or_imag = rx.projComp + if real_or_imag == 'real': + Jtv += du_dmT.real + elif real_or_imag == 'imag': + Jtv += - du_dmT.real + else: + raise Exception('Must be real or imag') + + return Jtv + + def getSourceTerm(self, freq): + """ + Evaluates the sources for a given frequency and puts them in matrix form + + :param float freq: Frequency + :rtype: numpy.ndarray (nE or nF, nSrc) + :return: S_m, S_e + """ + Srcs = self.survey.getSrcByFreq(freq) + if self._eqLocs is 'FE': + S_m = np.zeros((self.mesh.nF,len(Srcs)), dtype=complex) + S_e = np.zeros((self.mesh.nE,len(Srcs)), dtype=complex) + elif self._eqLocs is 'EF': + S_m = np.zeros((self.mesh.nE,len(Srcs)), dtype=complex) + S_e = np.zeros((self.mesh.nF,len(Srcs)), dtype=complex) + + for i, src in enumerate(Srcs): + smi, sei = src.eval(self) + if smi is not None: + S_m[:,i] = Utils.mkvc(smi) + if sei is not None: + S_e[:,i] = Utils.mkvc(sei) + + return S_m, S_e + + +########################################################################################## +################################ E-B Formulation ######################################### +########################################################################################## + +class ProblemFDEM_e(BaseFDEMProblem): + """ + By eliminating the magnetic flux density using + + .. math :: + + \mathbf{b} = \\frac{1}{i \omega}\\left(-\mathbf{C} \mathbf{e} + \mathbf{s_m}\\right) + + + we can write Maxwell's equations as a second order system in \\\(\\\mathbf{e}\\\) only: + + .. math :: + + \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C}+ i \omega \mathbf{M^e_{\sigma}} \\right)\mathbf{e} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M^e}\mathbf{s_e} + + which we solve for \\\(\\\mathbf{e}\\\). + """ + + _fieldType = 'e' + _eqLocs = 'FE' + fieldsPair = FieldsFDEM_e + + def __init__(self, mesh, **kwargs): + BaseFDEMProblem.__init__(self, mesh, **kwargs) + + def getA(self, freq): + """ + .. math :: + \mathbf{A} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{C} + i \omega \mathbf{M^e_{\sigma}} + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A + """ + MfMui = self.MfMui + MeSigma = self.MeSigma + C = self.mesh.edgeCurl + + return C.T*MfMui*C + 1j*omega(freq)*MeSigma + + + def getADeriv_m(self, freq, u, v, adjoint=False): + dsig_dm = self.curModel.sigmaDeriv + dMe_dsig = self.MeSigmaDeriv(u) + + if adjoint: + return 1j * omega(freq) * ( dMe_dsig.T * v ) + + return 1j * omega(freq) * ( dMe_dsig * v ) + + def getRHS(self, freq): + """ + .. math :: + \mathbf{RHS} = \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f}\mathbf{s_m} -i\omega\mathbf{M_e}\mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray (nE, nSrc) + :return: RHS + """ + + S_m, S_e = self.getSourceTerm(freq) + C = self.mesh.edgeCurl + MfMui = self.MfMui + + # RHS = C.T * (MfMui * S_m) -1j * omega(freq) * Me * S_e + RHS = C.T * (MfMui * S_m) -1j * omega(freq) * S_e + + return RHS + + def getRHSDeriv_m(self, src, v, adjoint=False): + C = self.mesh.edgeCurl + MfMui = self.MfMui + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + + if adjoint: + dRHS = MfMui * (C * v) + S_mDerivv = S_mDeriv(dRHS) + S_eDerivv = S_eDeriv(v) + if S_mDerivv is not None and S_eDerivv is not None: + return S_mDerivv - 1j * omega(freq) * S_eDerivv + elif S_mDerivv is not None: + return S_mDerivv + elif S_eDerivv is not None: + return - 1j * omega(freq) * S_eDerivv + else: + return None + else: + S_mDerivv, S_eDerivv = S_mDeriv(v), S_eDeriv(v) + + if S_mDerivv is not None and S_eDerivv is not None: + return C.T * (MfMui * S_mDerivv) -1j * omega(freq) * S_eDerivv + elif S_mDerivv is not None: + return C.T * (MfMui * S_mDerivv) + elif S_eDerivv is not None: + return -1j * omega(freq) * S_eDerivv + else: + return None + + +class ProblemFDEM_b(BaseFDEMProblem): + """ + We eliminate \\\(\\\mathbf{e}\\\) using + + .. math :: + + \mathbf{e} = \mathbf{M^e_{\sigma}}^{-1} \\left(\mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} \mathbf{b} - \mathbf{s_e}\\right) + + and solve for \\\(\\\mathbf{b}\\\) using: + + .. math :: + + \\left(\mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega \\right)\mathbf{b} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{M^e}\mathbf{s_e} + + .. note :: + The inverse problem will not work with full anisotropy + """ + + _fieldType = 'b' + _eqLocs = 'FE' + fieldsPair = FieldsFDEM_b + + def __init__(self, mesh, **kwargs): + BaseFDEMProblem.__init__(self, mesh, **kwargs) + + def getA(self, freq): + """ + .. math :: + \mathbf{A} = \mathbf{C} \mathbf{M^e_{\sigma}}^{-1} \mathbf{C}^T \mathbf{M_{\mu^{-1}}^f} + i \omega + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A + """ + + MfMui = self.MfMui + MeSigmaI = self.MeSigmaI + C = self.mesh.edgeCurl + iomega = 1j * omega(freq) * sp.eye(self.mesh.nF) + + A = C * (MeSigmaI * (C.T * MfMui)) + iomega + + if self._makeASymmetric is True: + return MfMui.T*A + return A + + def getADeriv_m(self, freq, u, v, adjoint=False): + + MfMui = self.MfMui + C = self.mesh.edgeCurl + MeSigmaIDeriv = self.MeSigmaIDeriv + vec = C.T * (MfMui * u) + + MeSigmaIDeriv = MeSigmaIDeriv(vec) + + if adjoint: + if self._makeASymmetric is True: + v = MfMui * v + return MeSigmaIDeriv.T * (C.T * v) + + if self._makeASymmetric is True: + return MfMui.T * ( C * ( MeSigmaIDeriv * v ) ) + return C * ( MeSigmaIDeriv * v ) + + + def getRHS(self, freq): + """ + .. math :: + \mathbf{RHS} = \mathbf{s_m} + \mathbf{M^e_{\sigma}}^{-1}\mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray (nE, nSrc) + :return: RHS + """ + + S_m, S_e = self.getSourceTerm(freq) + C = self.mesh.edgeCurl + MeSigmaI = self.MeSigmaI + # Me = self.Me + + RHS = S_m + C * ( MeSigmaI * S_e ) + + if self._makeASymmetric is True: + MfMui = self.MfMui + return MfMui.T * RHS + + return RHS + + def getRHSDeriv_m(self, src, v, adjoint=False): + C = self.mesh.edgeCurl + S_m, S_e = src.eval(self) + MfMui = self.MfMui + # Me = self.Me + + if self._makeASymmetric and adjoint: + v = self.MfMui * v + + if S_e is not None: + MeSigmaIDeriv = self.MeSigmaIDeriv(S_e) + if not adjoint: + RHSderiv = C * (MeSigmaIDeriv * v) + elif adjoint: + RHSderiv = MeSigmaIDeriv.T * (C.T * v) + else: + RHSderiv = None + + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + S_mDeriv, S_eDeriv = S_mDeriv(v), S_eDeriv(v) + if S_mDeriv is not None and S_eDeriv is not None: + if not adjoint: + SrcDeriv = S_mDeriv + C * (self.MeSigmaI * S_eDeriv) + elif adjoint: + SrcDeriv = S_mDeriv + Self.MeSigmaI.T * ( C.T * S_eDeriv) + elif S_mDeriv is not None: + SrcDeriv = S_mDeriv + elif S_eDeriv is not None: + if not adjoint: + SrcDeriv = C * (self.MeSigmaI * S_eDeriv) + elif adjoint: + SrcDeriv = self.MeSigmaI.T * ( C.T * S_eDeriv) + else: + SrcDeriv = None + + if RHSderiv is not None and SrcDeriv is not None: + RHSderiv += SrcDeriv + elif SrcDeriv is not None: + RHSderiv = SrcDeriv + + if RHSderiv is not None: + if self._makeASymmetric is True and not adjoint: + return MfMui.T * RHSderiv + + return RHSderiv + + + +########################################################################################## +################################ H-J Formulation ######################################### +########################################################################################## + + +class ProblemFDEM_j(BaseFDEMProblem): + """ + We eliminate \\\(\\\mathbf{h}\\\) using + + .. math :: + + \mathbf{h} = \\frac{1}{i \omega} \mathbf{M_{\mu}^e}^{-1} \\left(-\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{j} + \mathbf{M^e} \mathbf{s_m} \\right) + + and solve for \\\(\\\mathbf{j}\\\) using + + .. math :: + + \\left(\mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{C}^T \mathbf{M_{\\rho}^f} + i \omega\\right)\mathbf{j} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1} \mathbf{M^e} \mathbf{s_m} -i\omega\mathbf{s_e} + + .. note:: + This implementation does not yet work with full anisotropy!! + + """ + + _fieldType = 'j' + _eqLocs = 'EF' + fieldsPair = FieldsFDEM_j + + def __init__(self, mesh, **kwargs): + BaseFDEMProblem.__init__(self, mesh, **kwargs) + + def getA(self, freq): + """ + .. math :: + \\mathbf{A} = \\mathbf{C} \\mathbf{M^e_{mu^{-1}}} \\mathbf{C}^T \\mathbf{M^f_{\\sigma^{-1}}} + i\\omega + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A + """ + + MeMuI = self.MeMuI + MfRho = self.MfRho + C = self.mesh.edgeCurl + iomega = 1j * omega(freq) * sp.eye(self.mesh.nF) + + A = C * MeMuI * C.T * MfRho + iomega + + if self._makeASymmetric is True: + return MfRho.T*A + return A + + + def getADeriv_m(self, freq, u, v, adjoint=False): + """ + In this case, we assume that electrical conductivity, \\\(\\\sigma\\\) is the physical property of interest (i.e. \\\(\\\sigma\\\) = model.transform). Then we want + + .. math :: + + \\frac{\mathbf{A(\sigma)} \mathbf{v}}{d \\mathbf{m}} &= \\mathbf{C} \\mathbf{M^e_{mu^{-1}}} \\mathbf{C^T} \\frac{d \\mathbf{M^f_{\\sigma^{-1}}}}{d \\mathbf{m}} + &= \\mathbf{C} \\mathbf{M^e_{mu}^{-1}} \\mathbf{C^T} \\frac{d \\mathbf{M^f_{\\sigma^{-1}}}}{d \\mathbf{\\sigma^{-1}}} \\frac{d \\mathbf{\\sigma^{-1}}}{d \\mathbf{\\sigma}} \\frac{d \\mathbf{\\sigma}}{d \\mathbf{m}} + """ + + MeMuI = self.MeMuI + MfRho = self.MfRho + C = self.mesh.edgeCurl + MfRhoDeriv_m = self.MfRhoDeriv(u) + + if adjoint: + if self._makeASymmetric is True: + v = MfRho * v + return MfRhoDeriv_m.T * (C * (MeMuI.T * (C.T * v))) + + if self._makeASymmetric is True: + return MfRho.T * (C * ( MeMuI * (C.T * (MfRhoDeriv_m * v) ))) + return C * (MeMuI * (C.T * (MfRhoDeriv_m * v))) + + + def getRHS(self, freq): + """ + .. math :: + + \mathbf{RHS} = \mathbf{C} \mathbf{M_{\mu}^e}^{-1}\mathbf{s_m} -i\omega \mathbf{s_e} + :param float freq: Frequency + :rtype: numpy.ndarray (nE, nSrc) + :return: RHS + """ + + S_m, S_e = self.getSourceTerm(freq) + C = self.mesh.edgeCurl + MeMuI = self.MeMuI + + RHS = C * (MeMuI * S_m) - 1j * omega(freq) * S_e + if self._makeASymmetric is True: + MfRho = self.MfRho + return MfRho.T*RHS + + return RHS + + def getRHSDeriv_m(self, src, v, adjoint=False): + C = self.mesh.edgeCurl + MeMuI = self.MeMuI + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + + if adjoint: + if self._makeASymmetric: + MfRho = self.MfRho + v = MfRho*v + S_mDerivv = S_mDeriv(MeMuI.T * (C.T * v)) + S_eDerivv = S_eDeriv(v) + if S_mDerivv is not None and S_eDerivv is not None: + return S_mDerivv - 1j * omega(freq) * S_eDerivv + elif S_mDerivv is not None: + return S_mDerivv + elif S_eDerivv is not None: + return - 1j * omega(freq) * S_eDerivv + else: + return None + else: + S_mDerivv, S_eDerivv = S_mDeriv(v), S_eDeriv(v) + + if S_mDerivv is not None and S_eDerivv is not None: + RHSDeriv = C * (MeMuI * S_mDerivv) - 1j * omega(freq) * S_eDerivv + elif S_mDerivv is not None: + RHSDeriv = C * (MeMuI * S_mDerivv) + elif S_eDerivv is not None: + RHSDeriv = - 1j * omega(freq) * S_eDerivv + else: + return None + + if self._makeASymmetric: + MfRho = self.MfRho + return MfRho.T * RHSDeriv + return RHSDeriv + + + + +class ProblemFDEM_h(BaseFDEMProblem): + """ + We eliminate \\\(\\\mathbf{j}\\\) using + + .. math :: + + \mathbf{j} = \mathbf{C} \mathbf{h} - \mathbf{s_e} + + and solve for \\\(\\\mathbf{h}\\\) using + + .. math :: + + \\left(\mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e}\\right) \mathbf{h} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + + """ + + _fieldType = 'h' + _eqLocs = 'EF' + fieldsPair = FieldsFDEM_h + + def __init__(self, mesh, **kwargs): + BaseFDEMProblem.__init__(self, mesh, **kwargs) + + def getA(self, freq): + """ + .. math :: + + \mathbf{A} = \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{C} + i \omega \mathbf{M_{\mu}^e} + + :param float freq: Frequency + :rtype: scipy.sparse.csr_matrix + :return: A + """ + + MeMu = self.MeMu + MfRho = self.MfRho + C = self.mesh.edgeCurl + + return C.T * (MfRho * C) + 1j*omega(freq)*MeMu + + def getADeriv_m(self, freq, u, v, adjoint=False): + + MeMu = self.MeMu + C = self.mesh.edgeCurl + MfRhoDeriv_m = self.MfRhoDeriv(C*u) + + if adjoint: + return MfRhoDeriv_m.T * (C * v) + return C.T * (MfRhoDeriv_m * v) + + def getRHS(self, freq): + """ + .. math :: + + \mathbf{RHS} = \mathbf{M^e} \mathbf{s_m} + \mathbf{C}^T \mathbf{M_{\\rho}^f} \mathbf{s_e} + + :param float freq: Frequency + :rtype: numpy.ndarray (nE, nSrc) + :return: RHS + """ + + S_m, S_e = self.getSourceTerm(freq) + C = self.mesh.edgeCurl + MfRho = self.MfRho + + RHS = S_m + C.T * ( MfRho * S_e ) + + return RHS + + def getRHSDeriv_m(self, src, v, adjoint=False): + _, S_e = src.eval(self) + C = self.mesh.edgeCurl + MfRho = self.MfRho + + RHSDeriv = None + + if S_e is not None: + MfRhoDeriv = self.MfRhoDeriv(S_e) + if not adjoint: + RHSDeriv = C.T * (MfRhoDeriv * v) + elif adjoint: + RHSDeriv = MfRhoDeriv.T * (C * v) + + S_mDeriv, S_eDeriv = src.evalDeriv(self, adjoint) + + S_mDeriv = S_mDeriv(v) + S_eDeriv = S_eDeriv(v) + + if S_mDeriv is not None: + if RHSDeriv is not None: + RHSDeriv += S_mDeriv(v) + else: + RHSDeriv = S_mDeriv(v) + if S_eDeriv is not None: + if RHSDeriv is not None: + RHSDeriv += C.T * (MfRho * S_e) + else: + RHSDeriv = C.T * (MfRho * S_e) + + return RHSDeriv + diff --git a/SimPEG/EM/FDEM/FieldsFDEM.py b/SimPEG/EM/FDEM/FieldsFDEM.py new file mode 100644 index 00000000..ce655e2e --- /dev/null +++ b/SimPEG/EM/FDEM/FieldsFDEM.py @@ -0,0 +1,374 @@ +from SimPEG import Survey, Problem, Utils, np, sp +from simpegEM.Utils.EMUtils import omega + + +class FieldsFDEM(Problem.Fields): + """Fancy Field Storage for a FDEM survey.""" + knownFields = {} + dtype = complex + +class FieldsFDEM_e(FieldsFDEM): + knownFields = {'eSolution':'E'} + aliasFields = { + 'e' : ['eSolution','E','_e'], + 'ePrimary' : ['eSolution','E','_ePrimary'], + 'eSecondary' : ['eSolution','E','_eSecondary'], + 'b' : ['eSolution','F','_b'], + 'bPrimary' : ['eSolution','F','_bPrimary'], + 'bSecondary' : ['eSolution','F','_bSecondary'] + } + + def __init__(self,mesh,survey,**kwargs): + FieldsFDEM.__init__(self,mesh,survey,**kwargs) + + def startup(self): + self.prob = self.survey.prob + self._edgeCurl = self.survey.prob.mesh.edgeCurl + + def _ePrimary(self, eSolution, srcList): + ePrimary = np.zeros_like(eSolution) + for i, src in enumerate(srcList): + ep = src.ePrimary(self.prob) + if ep is not None: + ePrimary[:,i] = ep + return ePrimary + + def _eSecondary(self, eSolution, srcList): + return eSolution + + def _e(self, eSolution, srcList): + return self._ePrimary(eSolution,srcList) + self._eSecondary(eSolution,srcList) + + def _eDeriv_u(self, src, v, adjoint = False): + return None + + def _eDeriv_m(self, src, v, adjoint = False): + # assuming primary does not depend on the model + return None + + def _bPrimary(self, eSolution, srcList): + bPrimary = np.zeros([self._edgeCurl.shape[0],eSolution.shape[1]],dtype = complex) + for i, src in enumerate(srcList): + bp = src.bPrimary(self.prob) + if bp is not None: + bPrimary[:,i] += bp + return bPrimary + + def _bSecondary(self, eSolution, srcList): + C = self._edgeCurl + b = (C * eSolution) + for i, src in enumerate(srcList): + b[:,i] *= - 1./(1j*omega(src.freq)) + S_m, _ = src.eval(self.prob) + if S_m is not None: + b[:,i] += 1./(1j*omega(src.freq)) * S_m + return b + + def _bSecondaryDeriv_u(self, src, v, adjoint = False): + C = self._edgeCurl + if adjoint: + return - 1./(1j*omega(src.freq)) * (C.T * v) + return - 1./(1j*omega(src.freq)) * (C * v) + + def _bSecondaryDeriv_m(self, src, v, adjoint = False): + S_mDeriv, _ = src.evalDeriv(self.prob, adjoint) + S_mDeriv = S_mDeriv(v) + if S_mDeriv is not None: + return 1./(1j * omega(src.freq)) * S_mDeriv + return None + + def _b(self, eSolution, srcList): + return self._bPrimary(eSolution, srcList) + self._bSecondary(eSolution, srcList) + + def _bDeriv_u(self, src, v, adjoint=False): + # Primary does not depend on u + return self._bSecondaryDeriv_u(src, v, adjoint) + + def _bDeriv_m(self, src, v, adjoint=False): + # Assuming the primary does not depend on the model + return self._bSecondaryDeriv_m(src, v, adjoint) + + +class FieldsFDEM_b(FieldsFDEM): + knownFields = {'bSolution':'F'} + aliasFields = { + 'b' : ['bSolution','F','_b'], + 'bPrimary' : ['bSolution','F','_bPrimary'], + 'bSecondary' : ['bSolution','F','_bSecondary'], + 'e' : ['bSolution','E','_e'], + 'ePrimary' : ['bSolution','E','_ePrimary'], + 'eSecondary' : ['bSolution','E','_eSecondary'], + } + + def __init__(self,mesh,survey,**kwargs): + FieldsFDEM.__init__(self,mesh,survey,**kwargs) + + def startup(self): + self.prob = self.survey.prob + self._edgeCurl = self.survey.prob.mesh.edgeCurl + self._MeSigmaI = self.survey.prob.MeSigmaI + self._MfMui = self.survey.prob.MfMui + self._MeSigmaIDeriv = self.survey.prob.MeSigmaIDeriv + self._Me = self.survey.prob.Me + + def _bPrimary(self, bSolution, srcList): + bPrimary = np.zeros_like(bSolution) + for i, src in enumerate(srcList): + bp = src.bPrimary(self.prob) + if bp is not None: + bPrimary[:,i] = bp + return bPrimary + + def _bSecondary(self, bSolution, srcList): + return bSolution + + def _b(self, bSolution, srcList): + return self._bPrimary(bSolution, srcList) + self._bSecondary(bSolution, srcList) + + def _bDeriv_u(self, src, v, adjoint=False): + return None + + def _bDeriv_m(self, src, v, adjoint=False): + # assuming primary does not depend on the model + return None + + def _ePrimary(self, bSolution, srcList): + ePrimary = np.zeros([self._edgeCurl.shape[1],bSolution.shape[1]],dtype = complex) + for i,src in enumerate(srcList): + ep = src.ePrimary(self.prob) + if ep is not None: + ePrimary[:,i] = ep + return ePrimary + + def _eSecondary(self, bSolution, srcList): + e = self._MeSigmaI * ( self._edgeCurl.T * ( self._MfMui * bSolution)) + for i,src in enumerate(srcList): + _,S_e = src.eval(self.prob) + if S_e is not None: + e[:,i] += -self._MeSigmaI * S_e + return e + + def _eSecondaryDeriv_u(self, src, v, adjoint=False): + if not adjoint: + return self._MeSigmaI * ( self._edgeCurl.T * ( self._MfMui * v) ) + else: + return self._MfMui.T * (self._edgeCurl * (self._MeSigmaI.T * v)) + + def _eSecondaryDeriv_m(self, src, v, adjoint=False): + bSolution = self[[src],'bSolution'] + _,S_e = src.eval(self.prob) + Me = self._Me + + if adjoint: + Me = Me.T + + w = self._edgeCurl.T * (self._MfMui * bSolution) + if S_e is not None: + w += -Utils.mkvc(Me * S_e,2) + + if not adjoint: + de_dm = self._MeSigmaIDeriv(w) * v + elif adjoint: + de_dm = self._MeSigmaIDeriv(w).T * v + + _, S_eDeriv = src.evalDeriv(self.prob, adjoint) + Se_Deriv = S_eDeriv(v) + + if Se_Deriv is not None: + de_dm += -self._MeSigmaI * Se_Deriv + + return de_dm + + def _e(self, bSolution, srcList): + return self._ePrimary(bSolution, srcList) + self._eSecondary(bSolution, srcList) + + def _eDeriv_u(self, src, v, adjoint=False): + return self._eSecondaryDeriv_u(src, v, adjoint) + + def _eDeriv_m(self, src, v, adjoint=False): + # assuming primary doesn't depend on model + return self._eSecondaryDeriv_m(src, v, adjoint) + + +class FieldsFDEM_j(FieldsFDEM): + knownFields = {'jSolution':'F'} + aliasFields = { + 'j' : ['jSolution','F','_j'], + 'jPrimary' : ['jSolution','F','_jPrimary'], + 'jSecondary' : ['jSolution','F','_jSecondary'], + 'h' : ['jSolution','E','_h'], + 'hPrimary' : ['jSolution','E','_hPrimary'], + 'hSecondary' : ['jSolution','E','_hSecondary'], + } + + def __init__(self,mesh,survey,**kwargs): + FieldsFDEM.__init__(self,mesh,survey,**kwargs) + + def startup(self): + self.prob = self.survey.prob + self._edgeCurl = self.survey.prob.mesh.edgeCurl + self._MeMuI = self.survey.prob.MeMuI + self._MfRho = self.survey.prob.MfRho + self._MfRhoDeriv = self.survey.prob.MfRhoDeriv + self._Me = self.survey.prob.Me + + def _jPrimary(self, jSolution, srcList): + jPrimary = np.zeros_like(jSolution,dtype = complex) + for i, src in enumerate(srcList): + jp = src.jPrimary(self.prob) + if jp is not None: + jPrimary[:,i] += jp + return jPrimary + + def _jSecondary(self, jSolution, srcList): + return jSolution + + def _j(self, jSolution, srcList): + return self._jPrimary(jSolution, srcList) + self._jSecondary(jSolution, srcList) + + def _jDeriv_u(self, src, v, adjoint=False): + return None + + def _jDeriv_m(self, src, v, adjoint=False): + # assuming primary does not depend on the model + return None + + def _hPrimary(self, jSolution, srcList): + hPrimary = np.zeros([self._edgeCurl.shape[1],jSolution.shape[1]],dtype = complex) + for i, src in enumerate(srcList): + hp = src.hPrimary(self.prob) + if hp is not None: + hPrimary[:,i] = hp + return hPrimary + + def _hSecondary(self, jSolution, srcList): + h = self._MeMuI * (self._edgeCurl.T * (self._MfRho * jSolution) ) + for i, src in enumerate(srcList): + h[:,i] *= -1./(1j*omega(src.freq)) + S_m,_ = src.eval(self.prob) + if S_m is not None: + h[:,i] += 1./(1j*omega(src.freq)) * self._MeMuI * (S_m) + return h + + def _hSecondaryDeriv_u(self, src, v, adjoint=False): + if not adjoint: + return -1./(1j*omega(src.freq)) * self._MeMuI * (self._edgeCurl.T * (self._MfRho * v) ) + elif adjoint: + return -1./(1j*omega(src.freq)) * self._MfRho.T * (self._edgeCurl * ( self._MeMuI.T * v)) + + def _hSecondaryDeriv_m(self, src, v, adjoint=False): + jSolution = self[[src],'jSolution'] + MeMuI = self._MeMuI + C = self._edgeCurl + MfRho = self._MfRho + MfRhoDeriv = self._MfRhoDeriv + Me = self._Me + + if not adjoint: + hDeriv_m = -1./(1j*omega(src.freq)) * MeMuI * (C.T * (MfRhoDeriv(jSolution)*v ) ) + elif adjoint: + hDeriv_m = -1./(1j*omega(src.freq)) * MfRhoDeriv(jSolution).T * ( C * (MeMuI.T * v ) ) + + S_mDeriv,_ = src.evalDeriv(self.prob, adjoint) + + if not adjoint: + S_mDeriv = S_mDeriv(v) + if S_mDeriv is not None: + hDeriv_m += 1./(1j*omega(src.freq)) * MeMuI * (Me * S_mDeriv) + elif adjoint: + S_mDeriv = S_mDeriv(Me.T * (MeMuI.T * v)) + if S_mDeriv is not None: + hDeriv_m += 1./(1j*omega(src.freq)) * S_mDeriv + return hDeriv_m + + + def _h(self, jSolution, srcList): + return self._hPrimary(jSolution, srcList) + self._hSecondary(jSolution, srcList) + + def _hDeriv_u(self, src, v, adjoint=False): + return self._hSecondaryDeriv_u(src, v, adjoint) + + def _hDeriv_m(self, src, v, adjoint=False): + # assuming the primary doesn't depend on the model + return self._hSecondaryDeriv_m(src, v, adjoint) + + +class FieldsFDEM_h(FieldsFDEM): + knownFields = {'hSolution':'E'} + aliasFields = { + 'h' : ['hSolution','E','_h'], + 'hPrimary' : ['hSolution','E','_hPrimary'], + 'hSecondary' : ['hSolution','E','_hSecondary'], + 'j' : ['hSolution','F','_j'], + 'jPrimary' : ['hSolution','F','_jPrimary'], + 'jSecondary' : ['hSolution','F','_jSecondary'] + } + + def __init__(self,mesh,survey,**kwargs): + FieldsFDEM.__init__(self,mesh,survey,**kwargs) + + def startup(self): + self.prob = self.survey.prob + self._edgeCurl = self.survey.prob.mesh.edgeCurl + self._MeMuI = self.survey.prob.MeMuI + self._MfRho = self.survey.prob.MfRho + + def _hPrimary(self, hSolution, srcList): + hPrimary = np.zeros_like(hSolution,dtype = complex) + for i, src in enumerate(srcList): + hp = src.hPrimary(self.prob) + if hp is not None: + hPrimary[:,i] += hp + return hPrimary + + def _hSecondary(self, hSolution, srcList): + return hSolution + + def _h(self, hSolution, srcList): + return self._hPrimary(hSolution, srcList) + self._hSecondary(hSolution, srcList) + + def _hDeriv_u(self, src, v, adjoint=False): + return None + + def _hDeriv_m(self, src, v, adjoint=False): + # assuming primary does not depend on the model + return None + + def _jPrimary(self, hSolution, srcList): + jPrimary = np.zeros([self._edgeCurl.shape[0], hSolution.shape[1]], dtype = complex) + for i, src in enumerate(srcList): + jp = src.jPrimary(self.prob) + if jp is not None: + jPrimary[:,i] = jp + return jPrimary + + def _jSecondary(self, hSolution, srcList): + j = self._edgeCurl*hSolution + for i, src in enumerate(srcList): + _,S_e = src.eval(self.prob) + if S_e is not None: + j[:,i] += -S_e + return j + + def _jSecondaryDeriv_u(self, src, v, adjoint=False): + if not adjoint: + return self._edgeCurl*v + elif adjoint: + return self._edgeCurl.T*v + + def _jSecondaryDeriv_m(self, src, v, adjoint=False): + _,S_eDeriv = src.evalDeriv(self.prob, adjoint) + S_eDeriv = S_eDeriv(v) + if S_eDeriv is not None: + return -S_eDeriv + return None + + def _j(self, hSolution, srcList): + return self._jPrimary(hSolution, srcList) + self._jSecondary(hSolution, srcList) + + def _jDeriv_u(self, src, v, adjoint=False): + return self._jSecondaryDeriv_u(src,v,adjoint) + + def _jDeriv_m(self, src, v, adjoint=False): + # assuming the primary does not depend on the model + return self._jSecondaryDeriv_m(src,v,adjoint) \ No newline at end of file diff --git a/SimPEG/EM/FDEM/SurveyFDEM.py b/SimPEG/EM/FDEM/SurveyFDEM.py new file mode 100644 index 00000000..61711bdd --- /dev/null +++ b/SimPEG/EM/FDEM/SurveyFDEM.py @@ -0,0 +1,491 @@ +from SimPEG import Survey, Problem, Utils, np, sp +from simpegEM.Utils import SrcUtils +from simpegEM.Utils.EMUtils import omega, e_from_j, j_from_e, b_from_h, h_from_b +from scipy.constants import mu_0 + +#################################################### +# Receivers +#################################################### + +class RxFDEM(Survey.BaseRx): + + knownRxTypes = { + 'exr':['e', 'Ex', 'real'], + 'eyr':['e', 'Ey', 'real'], + 'ezr':['e', 'Ez', 'real'], + 'exi':['e', 'Ex', 'imag'], + 'eyi':['e', 'Ey', 'imag'], + 'ezi':['e', 'Ez', 'imag'], + + 'bxr':['b', 'Fx', 'real'], + 'byr':['b', 'Fy', 'real'], + 'bzr':['b', 'Fz', 'real'], + 'bxi':['b', 'Fx', 'imag'], + 'byi':['b', 'Fy', 'imag'], + 'bzi':['b', 'Fz', 'imag'], + + 'jxr':['j', 'Fx', 'real'], + 'jyr':['j', 'Fy', 'real'], + 'jzr':['j', 'Fz', 'real'], + 'jxi':['j', 'Fx', 'imag'], + 'jyi':['j', 'Fy', 'imag'], + 'jzi':['j', 'Fz', 'imag'], + + 'hxr':['h', 'Ex', 'real'], + 'hyr':['h', 'Ey', 'real'], + 'hzr':['h', 'Ez', 'real'], + 'hxi':['h', 'Ex', 'imag'], + 'hyi':['h', 'Ey', 'imag'], + 'hzi':['h', 'Ez', 'imag'], + } + radius = None + + def __init__(self, locs, rxType): + Survey.BaseRx.__init__(self, locs, rxType) + + @property + def projField(self): + """Field Type projection (e.g. e b ...)""" + return self.knownRxTypes[self.rxType][0] + + @property + def projGLoc(self): + """Grid Location projection (e.g. Ex Fy ...)""" + return self.knownRxTypes[self.rxType][1] + + @property + def projComp(self): + """Component projection (real/imag)""" + return self.knownRxTypes[self.rxType][2] + + def projectFields(self, src, mesh, u): + P = self.getP(mesh) + u_part_complex = u[src, self.projField] + # get the real or imag component + real_or_imag = self.projComp + u_part = getattr(u_part_complex, real_or_imag) + return P*u_part + + def projectFieldsDeriv(self, src, mesh, u, v, adjoint=False): + P = self.getP(mesh) + + if not adjoint: + Pv_complex = P * v + real_or_imag = self.projComp + Pv = getattr(Pv_complex, real_or_imag) + elif adjoint: + Pv_real = P.T * v + + real_or_imag = self.projComp + if real_or_imag == 'imag': + Pv = 1j*Pv_real + elif real_or_imag == 'real': + Pv = Pv_real.astype(complex) + else: + raise NotImplementedError('must be real or imag') + + return Pv + + +#################################################### +# Sources +#################################################### + +class SrcFDEM(Survey.BaseSrc): + freq = None + rxPair = RxFDEM + integrate = True + + def eval(self, prob): + S_m = self.S_m(prob) + S_e = self.S_e(prob) + return S_m, S_e + + def evalDeriv(self, prob, v, adjoint=False): + return lambda v: self.S_mDeriv(prob,v,adjoint), lambda v: self.S_eDeriv(prob,v,adjoint) + + def bPrimary(self, prob): + return None + + def hPrimary(self, prob): + return None + + def ePrimary(self, prob): + return None + + def jPrimary(self, prob): + return None + + def S_m(self, prob): + return None + + def S_e(self, prob): + return None + + def S_mDeriv(self, prob, v, adjoint = False): + return None + + def S_eDeriv(self, prob, v, adjoint = False): + return None + + +class SrcFDEM_RawVec_e(SrcFDEM): + """ + RawVec electric source. It is defined by the user provided vector S_e + + :param numpy.array S_e: electric source term + :param float freq: frequency + :param rxList: receiver list + """ + + def __init__(self, rxList, freq, S_e, ePrimary=None, bPrimary=None, hPrimary=None, jPrimary=None): + self._S_e = np.array(S_e,dtype=complex) + self._ePrimary = ePrimary + self._bPrimary = bPrimary + self._hPrimary = hPrimary + self._jPrimary = jPrimary + self.freq = float(freq) + SrcFDEM.__init__(self, rxList) + + def S_e(self, prob): + return self._S_e + + def ePrimary(self, prob): + return self._ePrimary + + def bPrimary(self, prob): + return self._bPrimary + + def hPrimary(self, prob): + return self._hPrimary + + def jPrimary(self, prob): + return self._jPrimary + + +class SrcFDEM_RawVec_m(SrcFDEM): + """ + RawVec magnetic source. It is defined by the user provided vector S_m + + :param numpy.array S_m: magnetic source term + :param float freq: frequency + :param rxList: receiver list + """ + + def __init__(self, rxList, freq, S_m, integrate = True, ePrimary=None, bPrimary=None, hPrimary=None, jPrimary=None): + self._S_m = np.array(S_m,dtype=complex) + self.freq = float(freq) + self.integrate = integrate + self._ePrimary = np.array(ePrimary,dtype=complex) + self._bPrimary = np.array(bPrimary,dtype=complex) + self._hPrimary = np.array(hPrimary,dtype=complex) + self._jPrimary = np.array(jPrimary,dtype=complex) + + SrcFDEM.__init__(self, rxList) + + def S_m(self, prob): + return self._S_m + + def ePrimary(self, prob): + return self._ePrimary + + def bPrimary(self, prob): + return self._bPrimary + + def hPrimary(self, prob): + return self._hPrimary + + def jPrimary(self, prob): + return self._jPrimary + + +class SrcFDEM_RawVec(SrcFDEM): + """ + RawVec source. It is defined by the user provided vectors S_m, S_e + + :param numpy.array S_m: magnetic source term + :param numpy.array S_e: electric source term + :param float freq: frequency + :param rxList: receiver list + """ + def __init__(self, rxList, freq, S_m, S_e, integrate = True): + self._S_m = np.array(S_m,dtype=complex) + self._S_e = np.array(S_e,dtype=complex) + self.freq = float(freq) + self.integrate = integrate + SrcFDEM.__init__(self, rxList) + + def S_m(self, prob): + if prob._eqLocs is 'EF' and self.integrate is True: + return prob.Me * self._S_m + return self._S_m + + def S_e(self, prob): + if prob._eqLocs is 'FE' and self.integrate is True: + return prob.Me * self._S_e + return self._S_e + + +class SrcFDEM_MagDipole(SrcFDEM): + + #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that + def __init__(self, rxList, freq, loc, orientation='Z', moment=1., mu = mu_0): + self.freq = float(freq) + self.loc = loc + self.orientation = orientation + self.moment = moment + self.mu = mu + self.integrate = False + SrcFDEM.__init__(self, rxList) + + def bPrimary(self, prob): + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + gridX = prob.mesh.gridEx + gridY = prob.mesh.gridEy + gridZ = prob.mesh.gridEz + C = prob.mesh.edgeCurl + + elif eqLocs is 'EF': + gridX = prob.mesh.gridFx + gridY = prob.mesh.gridFy + gridZ = prob.mesh.gridFz + C = prob.mesh.edgeCurl.T + + + if prob.mesh._meshType is 'CYL': + if not prob.mesh.isSymmetric: + # TODO ? + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + a = SrcUtils.MagneticDipoleVectorPotential(self.loc, gridY, 'y', mu=self.mu, moment=self.moment) + + else: + srcfct = SrcUtils.MagneticDipoleVectorPotential + ax = srcfct(self.loc, gridX, 'x', mu=self.mu, moment=self.moment) + ay = srcfct(self.loc, gridY, 'y', mu=self.mu, moment=self.moment) + az = srcfct(self.loc, gridZ, 'z', mu=self.mu, moment=self.moment) + a = np.concatenate((ax, ay, az)) + + return C*a + + def hPrimary(self, prob): + b = self.bPrimary(prob) + return h_from_b(prob,b) + + def S_m(self, prob): + b_p = self.bPrimary(prob) + return -1j*omega(self.freq)*b_p + + def S_e(self, prob): + if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): + return None + else: + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + mui_s = prob.curModel.mui - 1./self.mu + MMui_s = prob.mesh.getFaceInnerProduct(mui_s) + C = prob.mesh.edgeCurl + elif eqLocs is 'EF': + mu_s = prob.curModel.mu - self.mu + MMui_s = prob.mesh.getEdgeInnerProduct(mu_s,invMat=True) + C = prob.mesh.edgeCurl.T + + return -C.T * (MMui_s * self.bPrimary(prob)) + + +class SrcFDEM_MagDipole_Bfield(SrcFDEM): + + #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that + #TODO: neither does moment + def __init__(self, rxList, freq, loc, orientation='Z', moment=1., mu = mu_0): + self.freq = float(freq) + self.loc = loc + self.orientation = orientation + self.moment = moment + self.mu = mu + SrcFDEM.__init__(self, rxList) + + def bPrimary(self, prob): + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + gridX = prob.mesh.gridFx + gridY = prob.mesh.gridFy + gridZ = prob.mesh.gridFz + C = prob.mesh.edgeCurl + + elif eqLocs is 'EF': + gridX = prob.mesh.gridEx + gridY = prob.mesh.gridEy + gridZ = prob.mesh.gridEz + C = prob.mesh.edgeCurl.T + + srcfct = SrcUtils.MagneticDipoleFields + if prob.mesh._meshType is 'CYL': + if not prob.mesh.isSymmetric: + # TODO ? + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + bx = srcfct(self.loc, gridX, 'x', mu=self.mu, moment=self.moment) + bz = srcfct(self.loc, gridZ, 'z', mu=self.mu, moment=self.moment) + b = np.concatenate((bx,bz)) + else: + bx = srcfct(self.loc, gridX, 'x', mu=self.mu, moment=self.moment) + by = srcfct(self.loc, gridY, 'y', mu=self.mu, moment=self.moment) + bz = srcfct(self.loc, gridZ, 'z', mu=self.mu, moment=self.moment) + b = np.concatenate((bx,by,bz)) + + return b + + def hPrimary(self, prob): + b = self.bPrimary(prob) + return h_from_b(prob, b) + + def S_m(self, prob): + b = self.bPrimary(prob) + return -1j*omega(self.freq)*b + + def S_e(self, prob): + if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): + return None + else: + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + mui_s = prob.curModel.mui - 1./self.mu + MMui_s = prob.mesh.getFaceInnerProduct(mui_s) + C = prob.mesh.edgeCurl + elif eqLocs is 'EF': + mu_s = prob.curModel.mu - self.mu + MMui_s = prob.mesh.getEdgeInnerProduct(mu_s,invMat=True) + C = prob.mesh.edgeCurl.T + + return -C.T * (MMui_s * self.bPrimary(prob)) + + +class SrcFDEM_CircularLoop(SrcFDEM): + + #TODO: right now, orientation doesn't actually do anything! The methods in SrcUtils should take care of that + def __init__(self, rxList, freq, loc, orientation='Z', radius = 1., mu=mu_0): + self.freq = float(freq) + self.orientation = orientation + self.radius = radius + self.mu = mu + self.loc = loc + self.integrate = False + SrcFDEM.__init__(self, rxList) + + def bPrimary(self, prob): + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + gridX = prob.mesh.gridEx + gridY = prob.mesh.gridEy + gridZ = prob.mesh.gridEz + C = prob.mesh.edgeCurl + + elif eqLocs is 'EF': + gridX = prob.mesh.gridFx + gridY = prob.mesh.gridFy + gridZ = prob.mesh.gridFz + C = prob.mesh.edgeCurl.T + + if prob.mesh._meshType is 'CYL': + if not prob.mesh.isSymmetric: + # TODO ? + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + a = SrcUtils.MagneticDipoleVectorPotential(self.loc, gridY, 'y', moment=self.radius, mu=self.mu) + + else: + srcfct = SrcUtils.MagneticDipoleVectorPotential + ax = srcfct(self.loc, gridX, 'x', self.radius, mu=self.mu) + ay = srcfct(self.loc, gridY, 'y', self.radius, mu=self.mu) + az = srcfct(self.loc, gridZ, 'z', self.radius, mu=self.mu) + a = np.concatenate((ax, ay, az)) + + return C*a + + def hPrimary(self, prob): + b = self.bPrimary(prob) + return 1./self.mu*b + + def S_m(self, prob): + b = self.bPrimary(prob) + return -1j*omega(self.freq)*b + + def S_e(self, prob): + if all(np.r_[self.mu] == np.r_[prob.curModel.mu]): + return None + else: + eqLocs = prob._eqLocs + + if eqLocs is 'FE': + mui_s = prob.curModel.mui - 1./self.mu + MMui_s = prob.mesh.getFaceInnerProduct(mui_s) + C = prob.mesh.edgeCurl + elif eqLocs is 'EF': + mu_s = prob.curModel.mu - self.mu + MMui_s = prob.mesh.getEdgeInnerProduct(mu_s,invMat=True) + C = prob.mesh.edgeCurl.T + + return -C.T * (MMui_s * self.bPrimary(prob)) + + +#################################################### +# Survey +#################################################### + +class SurveyFDEM(Survey.BaseSurvey): + """ + docstring for SurveyFDEM + """ + + srcPair = SrcFDEM + + def __init__(self, srcList, **kwargs): + # Sort these by frequency + self.srcList = srcList + Survey.BaseSurvey.__init__(self, **kwargs) + + _freqDict = {} + for src in srcList: + if src.freq not in _freqDict: + _freqDict[src.freq] = [] + _freqDict[src.freq] += [src] + + self._freqDict = _freqDict + self._freqs = sorted([f for f in self._freqDict]) + + @property + def freqs(self): + """Frequencies""" + return self._freqs + + @property + def nFreq(self): + """Number of frequencies""" + return len(self._freqDict) + + @property + def nSrcByFreq(self): + if getattr(self, '_nSrcByFreq', None) is None: + self._nSrcByFreq = {} + for freq in self.freqs: + self._nSrcByFreq[freq] = len(self.getSrcByFreq(freq)) + return self._nSrcByFreq + + def getSrcByFreq(self, freq): + """Returns the sources associated with a specific frequency.""" + assert freq in self._freqDict, "The requested frequency is not in this survey." + return self._freqDict[freq] + + def projectFields(self, u): + data = Survey.Data(self) + for src in self.srcList: + for rx in src.rxList: + data[src, rx] = rx.projectFields(src, self.mesh, u) + return data + + def projectFieldsDeriv(self, u): + raise Exception('Use Sources to project fields deriv.') diff --git a/SimPEG/EM/FDEM/__init__.py b/SimPEG/EM/FDEM/__init__.py new file mode 100644 index 00000000..110b4d1e --- /dev/null +++ b/SimPEG/EM/FDEM/__init__.py @@ -0,0 +1,3 @@ +from SurveyFDEM import * +from FDEM import BaseFDEMProblem, ProblemFDEM_e, ProblemFDEM_b, ProblemFDEM_j, ProblemFDEM_h +from FieldsFDEM import * \ No newline at end of file diff --git a/SimPEG/EM/TDEM/BaseTDEM.py b/SimPEG/EM/TDEM/BaseTDEM.py new file mode 100644 index 00000000..8f4be4cd --- /dev/null +++ b/SimPEG/EM/TDEM/BaseTDEM.py @@ -0,0 +1,155 @@ +from SimPEG import Solver, Problem +from SimPEG.Problem import BaseTimeProblem +from simpegEM.Utils import SrcUtils +from scipy.constants import mu_0 +from SimPEG.Utils import sdiag, mkvc +from SimPEG import Utils, Mesh +from simpegEM.Base import BaseEMProblem +import numpy as np + + +class FieldsTDEM(Problem.TimeFields): + """Fancy Field Storage for a TDEM survey.""" + knownFields = {'b': 'F', 'e': 'E'} + + def tovec(self): + nSrc, nF, nE = self.survey.nSrc, self.mesh.nF, self.mesh.nE + u = np.empty((0,nSrc)) #((0,1) if nSrc == 1 else (0, nSrc)) + + for i in range(self.survey.prob.nT): + if 'b' in self: + b = self[:,'b',i+1] + else: + b = np.zeros((nF,nSrc)) # if nSrc == 1 else (nF, nSrc)) + + if 'e' in self: + e = self[:,'e',i+1] + else: + e = np.zeros((nE,nSrc)) # if nSrc == 1 else (nE, nSrc)) + u = np.concatenate((u, b, e)) + return Utils.mkvc(u,nSrc) + + +class BaseTDEMProblem(BaseTimeProblem, BaseEMProblem): + """docstring for ProblemTDEM1D""" + def __init__(self, mesh, mapping=None, **kwargs): + BaseTimeProblem.__init__(self, mesh, mapping=mapping, **kwargs) + + _FieldsForward_pair = FieldsTDEM #: used for the forward calculation only + + def fields(self, m): + if self.verbose: print '%s\nCalculating fields(m)\n%s'%('*'*50,'*'*50) + self.curModel = m + # Create a fields storage object + F = self._FieldsForward_pair(self.mesh, self.survey) + for src in self.survey.srcList: + # Set the initial conditions + F[src,:,0] = src.getInitialFields(self.mesh) + F = self.forward(m, self.getRHS, F=F) + if self.verbose: print '%s\nDone calculating fields(m)\n%s'%('*'*50,'*'*50) + return F + + def forward(self, m, RHS, F=None): + self.curModel = m + F = F or FieldsTDEM(self.mesh, self.survey) + + dtFact = None + Ainv = None + for tInd, dt in enumerate(self.timeSteps): + if dt != dtFact: + dtFact = dt + if Ainv is not None: + Ainv.clean() + A = self.getA(tInd) + if self.verbose: print 'Factoring... (dt = %e)'%dt + Ainv = self.Solver(A, **self.solverOpts) + if self.verbose: print 'Done' + rhs = RHS(tInd, F) + if self.verbose: print ' Solving... (tInd = %d)'%tInd + sol = Ainv * rhs + if self.verbose: print ' Done...' + if sol.ndim == 1: + sol.shape = (sol.size,1) + F[:,self.solType,tInd+1] = sol + Ainv.clean() + return F + + def adjoint(self, m, RHS, F=None): + self.curModel = m + F = F or FieldsTDEM(self.mesh, self.survey) + + dtFact = None + Ainv = None + for tInd, dt in reversed(list(enumerate(self.timeSteps))): + if dt != dtFact: + dtFact = dt + if Ainv is not None: + Ainv.clean() + A = self.getA(tInd) + if self.verbose: print 'Factoring (Adjoint)... (dt = %e)'%dt + Ainv = self.Solver(A, **self.solverOpts) + if self.verbose: print 'Done' + rhs = RHS(tInd, F) + if self.verbose: print ' Solving (Adjoint)... (tInd = %d)'%tInd + sol = Ainv * rhs + if self.verbose: print ' Done...' + if sol.ndim == 1: + sol.shape = (sol.size,1) + F[:,self.solType,tInd+1] = sol + Ainv.clean() + return F + + def Jvec(self, m, v, u=None): + """ + :param numpy.array m: Conductivity model + :param numpy.ndarray v: vector (model object) + :param simpegEM.TDEM.FieldsTDEM u: Fields resulting from m + :rtype: numpy.ndarray + :return: w (data object) + + Multiplying \\\(\\\mathbf{J}\\\) onto a vector can be broken into three steps + + * Compute \\\(\\\\vec{p} = \\\mathbf{G}v\\\) + * Solve \\\(\\\hat{\\\mathbf{A}} \\\\vec{y} = \\\\vec{p}\\\) + * Compute \\\(\\\\vec{w} = -\\\mathbf{Q} \\\\vec{y}\\\) + + """ + if self.verbose: print '%s\nCalculating J(v)\n%s'%('*'*50,'*'*50) + self.curModel = m + if u is None: + u = self.fields(m) + p = self.Gvec(m, v, u) + y = self.solveAh(m, p) + Jv = self.survey.projectFieldsDeriv(u, v=y) + if self.verbose: print '%s\nDone calculating J(v)\n%s'%('*'*50,'*'*50) + return - mkvc(Jv) + + def Jtvec(self, m, v, u=None): + """ + :param numpy.array m: Conductivity model + :param numpy.ndarray,SimPEG.Survey.Data v: vector (data object) + :param simpegEM.TDEM.FieldsTDEM u: Fields resulting from m + :rtype: numpy.ndarray + :return: w (model object) + + Multiplying \\\(\\\mathbf{J}^\\\\top\\\) onto a vector can be broken into three steps + + * Compute \\\(\\\\vec{p} = \\\mathbf{Q}^\\\\top \\\\vec{v}\\\) + * Solve \\\(\\\hat{\\\mathbf{A}}^\\\\top \\\\vec{y} = \\\\vec{p}\\\) + * Compute \\\(\\\\vec{w} = -\\\mathbf{G}^\\\\top y\\\) + + """ + if self.verbose: print '%s\nCalculating J^T(v)\n%s'%('*'*50,'*'*50) + self.curModel = m + if u is None: + u = self.fields(m) + + if not isinstance(v, self.dataPair): + v = self.dataPair(self.survey, v) + + p = self.survey.projectFieldsDeriv(u, v=v, adjoint=True) + y = self.solveAht(m, p) + w = self.Gtvec(m, y, u) + if self.verbose: print '%s\nDone calculating J^T(v)\n%s'%('*'*50,'*'*50) + return - mkvc(w) + diff --git a/SimPEG/EM/TDEM/SurveyTDEM.py b/SimPEG/EM/TDEM/SurveyTDEM.py new file mode 100644 index 00000000..bcd83962 --- /dev/null +++ b/SimPEG/EM/TDEM/SurveyTDEM.py @@ -0,0 +1,162 @@ +from SimPEG import Utils, Survey, np +from SimPEG.Survey import BaseSurvey +from simpegEM.Utils import SrcUtils +from BaseTDEM import FieldsTDEM + + +class RxTDEM(Survey.BaseTimeRx): + + knownRxTypes = { + 'ex':['e', 'Ex', 'N'], + 'ey':['e', 'Ey', 'N'], + 'ez':['e', 'Ez', 'N'], + + 'bx':['b', 'Fx', 'N'], + 'by':['b', 'Fy', 'N'], + 'bz':['b', 'Fz', 'N'], + + 'dbxdt':['b', 'Fx', 'CC'], + 'dbydt':['b', 'Fy', 'CC'], + 'dbzdt':['b', 'Fz', 'CC'], + } + + def __init__(self, locs, times, rxType): + Survey.BaseTimeRx.__init__(self, locs, times, rxType) + + @property + def projField(self): + """Field Type projection (e.g. e b ...)""" + return self.knownRxTypes[self.rxType][0] + + @property + def projGLoc(self): + """Grid Location projection (e.g. Ex Fy ...)""" + return self.knownRxTypes[self.rxType][1] + + @property + def projTLoc(self): + """Time Location projection (e.g. CC N)""" + return self.knownRxTypes[self.rxType][2] + + def getTimeP(self, timeMesh): + """ + Returns the time projection matrix. + + .. note:: + + This is not stored in memory, but is created on demand. + """ + if self.rxType in ['dbxdt','dbydt','dbzdt']: + return timeMesh.getInterpolationMat(self.times, self.projTLoc)*timeMesh.faceDiv + else: + return timeMesh.getInterpolationMat(self.times, self.projTLoc) + + def projectFields(self, src, mesh, timeMesh, u): + P = self.getP(mesh, timeMesh) + u_part = Utils.mkvc(u[src, self.projField, :]) + return P*u_part + + def projectFieldsDeriv(self, src, mesh, timeMesh, u, v, adjoint=False): + P = self.getP(mesh, timeMesh) + + if not adjoint: + return P * Utils.mkvc(v[src, self.projField, :]) + elif adjoint: + return P.T * v[src, self] + + +class SrcTDEM(Survey.BaseSrc): + rxPair = RxTDEM + radius = None + + def getInitialFields(self, mesh): + F0 = getattr(self, '_getInitialFields_' + self.srcType)(mesh) + return F0 + + def getJs(self, mesh, time): + return None + + +class SrcTDEM_VMD_MVP(SrcTDEM): + + def __init__(self,rxList,loc): + self.loc = loc + SrcTDEM.__init__(self,rxList) + + def getInitialFields(self, mesh): + """Vertical magnetic dipole, magnetic vector potential""" + if mesh._meshType is 'CYL': + if mesh.isSymmetric: + MVP = SrcUtils.MagneticDipoleVectorPotential(self.loc, mesh, 'Ey') + else: + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + elif mesh._meshType is 'TENSOR': + MVP = SrcUtils.MagneticDipoleVectorPotential(self.loc, mesh, ['Ex','Ey','Ez']) + else: + raise Exception('Unknown mesh for VMD') + + return {"b": mesh.edgeCurl*MVP} + + +class SrcTDEM_CircularLoop_MVP(SrcTDEM): + + def __init__(self,rxList,loc,radius): + self.loc = loc + self.radius = radius + SrcTDEM.__init__(self,rxList) + + def getInitialFields(self, mesh): + """Circular Loop, magnetic vector potential""" + if mesh._meshType is 'CYL': + if mesh.isSymmetric: + MVP = SrcUtils.MagneticLoopVectorPotential(self.loc, mesh, 'Ey', self.radius) + else: + raise NotImplementedError('Non-symmetric cyl mesh not implemented yet!') + elif mesh._meshType is 'TENSOR': + MVP = SrcUtils.MagneticLoopVectorPotential(self.loc, mesh, ['Ex','Ey','Ez'], self.radius) + else: + raise Exception('Unknown mesh for CircularLoop') + + return {"b": mesh.edgeCurl*MVP} + + +class SurveyTDEM(Survey.BaseSurvey): + """ + docstring for SurveyTDEM + """ + srcPair = SrcTDEM + + def __init__(self, srcList, **kwargs): + # Sort these by frequency + self.srcList = srcList + Survey.BaseSurvey.__init__(self, **kwargs) + + def projectFields(self, u): + data = Survey.Data(self) + for src in self.srcList: + for rx in src.rxList: + data[src, rx] = rx.projectFields(src, self.mesh, self.prob.timeMesh, u) + return data + + def projectFieldsDeriv(self, u, v=None, adjoint=False): + assert v is not None, 'v to multiply must be provided.' + + if not adjoint: + data = Survey.Data(self) + for src in self.srcList: + for rx in src.rxList: + data[src, rx] = rx.projectFieldsDeriv(src, self.mesh, self.prob.timeMesh, u, v) + return data + else: + f = FieldsTDEM(self.mesh, self) + for src in self.srcList: + for rx in src.rxList: + Ptv = rx.projectFieldsDeriv(src, self.mesh, self.prob.timeMesh, u, v, adjoint=True) + Ptv = Ptv.reshape((-1, self.prob.timeMesh.nN), order='F') + if rx.projField not in f: # first time we are projecting + f[src, rx.projField, :] = Ptv + else: # there are already fields, so let's add to them! + f[src, rx.projField, :] += Ptv + return f + + diff --git a/SimPEG/EM/TDEM/TDEM_b.py b/SimPEG/EM/TDEM/TDEM_b.py new file mode 100644 index 00000000..cd38660c --- /dev/null +++ b/SimPEG/EM/TDEM/TDEM_b.py @@ -0,0 +1,356 @@ +from BaseTDEM import BaseTDEMProblem, FieldsTDEM +from SimPEG.Utils import mkvc, sdiag +import numpy as np +from SurveyTDEM import SurveyTDEM + + +class FieldsTDEM_e_from_b(FieldsTDEM): + """Fancy Field Storage for a TDEM survey.""" + knownFields = {'b': 'F'} + aliasFields = {'e': ['b','E','e_from_b']} + + def startup(self): + self.MeSigmaI = self.survey.prob.MeSigmaI + self.edgeCurlT = self.survey.prob.mesh.edgeCurl.T + self.MfMui = self.survey.prob.MfMui + + def e_from_b(self, b, srcInd, timeInd): + # TODO: implement non-zero js + return self.MeSigmaI*(self.edgeCurlT*(self.MfMui*b)) + +class FieldsTDEM_e_from_b_Ah(FieldsTDEM): + """Fancy Field Storage for a TDEM survey. + + This is used when solving Ahat and AhatT + """ + knownFields = {'b': 'F'} + aliasFields = {'e': ['b','E','e_from_b']} + p = None + + def startup(self): + self.MeSigmaI = self.survey.prob.MeSigmaI + self.edgeCurlT = self.survey.prob.mesh.edgeCurl.T + self.MfMui = self.survey.prob.MfMui + + def e_from_b(self, y_b, srcInd, tInd): + y_e = self.MeSigmaI*(self.edgeCurlT*(self.MfMui*y_b)) + if 'e' in self.p: + y_e = y_e - self.MeSigmaI*self.p[srcInd,'e',tInd] + return y_e + +class ProblemTDEM_b(BaseTDEMProblem): + """ + Time-Domain EM problem - B-formulation + + TDEM_b treats the following discretization of Maxwell's equations + + .. math:: + \dcurl \e^{(t+1)} + \\frac{\\b^{(t+1)} - \\b^{(t)}}{\delta t} = 0 \\\\ + \dcurl^\\top \MfMui \\b^{(t+1)} - \MeSig \e^{(t+1)} = \Me \j_s^{(t+1)} + + with \\\(\\b\\\) defined on cell faces and \\\(\e\\\) defined on edges. + """ + def __init__(self, mesh, mapping=None, **kwargs): + BaseTDEMProblem.__init__(self, mesh, mapping=mapping, **kwargs) + + solType = 'b' #: Type of the solution, in this case the 'b' field + + surveyPair = SurveyTDEM + _FieldsForward_pair = FieldsTDEM_e_from_b #: used for the forward calculation only + + #################################################### + # Internal Methods + #################################################### + + def getA(self, tInd): + """ + :param int tInd: Time index + :rtype: scipy.sparse.csr_matrix + :return: A + """ + dt = self.timeSteps[tInd] + return self.MfMui*self.mesh.edgeCurl*self.MeSigmaI*self.mesh.edgeCurl.T*self.MfMui + (1.0/dt)*self.MfMui + + def getRHS(self, tInd, F): + dt = self.timeSteps[tInd] + B_n = np.c_[[F[src,'b',tInd] for src in self.survey.srcList]].T + if B_n.shape[0] is not 1: + raise NotImplementedError('getRHS not implemented for this shape of B_n') + RHS = (1.0/dt)*self.MfMui*B_n[0,:,:] #TODO: This is a hack + return RHS + + #################################################### + # Derivatives + #################################################### + + def Gvec(self, m, vec, u=None): + """ + :param numpy.array m: Conductivity model + :param numpy.array vec: vector (like a model) + :param simpegEM.TDEM.FieldsTDEM u: Fields resulting from m + :rtype: simpegEM.TDEM.FieldsTDEM + :return: f + + Multiply G by a vector + """ + if u is None: + u = self.fields(m) + self.curModel = m + + # Note: Fields has shape (nF/E, nSrc, nT+1) + # However, p will only really fill (:,:,1:nT+1) + # meaning the 'initial fields' are zero (:,:,0) + p = FieldsTDEM(self.mesh, self.survey) + # 'b' at all times is zero. + # However, to save memory we will **not** do: + # + # p[:, 'b', :] = 0.0 + + # fake initial 'e' fields + p[:, 'e', 0] = 0.0 + dMdsig = self.MeSigmaDeriv + # self.mesh.getEdgeInnerProductDeriv(self.curModel.transform) + # dsigdm_x_v = self.curModel.sigmaDeriv*vec + # dsigdm_x_v = self.curModel.transformDeriv*vec + for i in range(1,self.nT+1): + # TODO: G[1] may be dependent on the model + # for a galvanic source (deriv of the dc problem) + # + # Do multiplication for all src in self.survey.srcList + for src in self.survey.srcList: + p[src, 'e', i] = - dMdsig(u[src,'e',i]) * vec + return p + + def Gtvec(self, m, vec, u=None): + """ + :param numpy.array m: Conductivity model + :param numpy.array vec: vector (like a fields) + :param simpegEM.TDEM.FieldsTDEM u: Fields resulting from m + :rtype: np.ndarray (like a model) + :return: p + + Multiply G.T by a vector + """ + if u is None: + u = self.fields(m) + self.curModel = m + # dMdsig = self.mesh.getEdgeInnerProductDeriv(self.curModel.transform) + # dsigdm = self.curModel.transformDeriv + MeSigmaDeriv = self.MeSigmaDeriv + + nSrc = self.survey.nSrc + VUs = None + # Here we can do internal multiplications of Gt*v and then multiply by MsigDeriv.T in one go. + for i in range(1,self.nT+1): + vu = None + for src in self.survey.srcList: + vusrc = MeSigmaDeriv(u[src,'e',i]).T * vec[src,'e',i] + vu = vusrc if vu is None else vu + vusrc + VUs = vu if VUs is None else VUs + vu + # p = -dsigdm.T*VUs + return -VUs + + def solveAh(self, m, p): + """ + :param numpy.array m: Conductivity model + :param simpegEM.TDEM.FieldsTDEM p: Fields object + :rtype: simpegEM.TDEM.FieldsTDEM + :return: y + + Solve the block-matrix system \\\(\\\hat{A} \\\hat{y} = \\\hat{p}\\\): + + .. math:: + \mathbf{\hat{A}} = \left[ + \\begin{array}{cccc} + A & 0 & & \\\\ + B & A & & \\\\ + & \ddots & \ddots & \\\\ + & & B & A + \end{array} + \\right] \\\\ + \mathbf{A} = + \left[ + \\begin{array}{cc} + \\frac{1}{\delta t} \MfMui & \MfMui\dcurl \\\\ + \dcurl^\\top \MfMui & -\MeSig + \end{array} + \\right] \\\\ + \mathbf{B} = + \left[ + \\begin{array}{cc} + -\\frac{1}{\delta t} \MfMui & 0 \\\\ + 0 & 0 + \end{array} + \\right] \\\\ + """ + + def AhRHS(tInd, y): + rhs = self.MfMui*(self.mesh.edgeCurl*(self.MeSigmaI*p[:,'e',tInd+1])) + if 'b' in p: + rhs = rhs + p[:,'b',tInd+1] + if tInd == 0: + return rhs + dt = self.timeSteps[tInd] + return rhs + 1.0/dt*self.MfMui*y[:,'b',tInd] + + F = FieldsTDEM_e_from_b_Ah(self.mesh, self.survey, p=p) + + return self.forward(m, AhRHS, F) + + def solveAht(self, m, p): + """ + :param numpy.array m: Conductivity model + :param simpegEM.TDEM.FieldsTDEM p: Fields object + :rtype: simpegEM.TDEM.FieldsTDEM + :return: y + + Solve the block-matrix system \\\(\\\hat{A}^\\\\top \\\hat{y} = \\\hat{p}\\\): + + .. math:: + \mathbf{\hat{A}}^\\top = \left[ + \\begin{array}{cccc} + A & B & & \\\\ + & \ddots & \ddots & \\\\ + & & A & B \\\\ + & & 0 & A + \end{array} + \\right] \\\\ + \mathbf{A} = + \left[ + \\begin{array}{cc} + \\frac{1}{\delta t} \MfMui & \MfMui\dcurl \\\\ + \dcurl^\\top \MfMui & -\MeSig + \end{array} + \\right] \\\\ + \mathbf{B} = + \left[ + \\begin{array}{cc} + -\\frac{1}{\delta t} \MfMui & 0 \\\\ + 0 & 0 + \end{array} + \\right] \\\\ + """ + + # Mini Example: + # + # nT = 3, len(times) == 4, fields stored in F[:,:,1:4] + # + # 0 is held for initial conditions (this shifts the storage by +1) + # ^ + # fLoc 0 1 2 3 + # |-----|-----|-----| + # tInd 0 1 2 + # / ___/ + # 2 (tInd=2 uses fields 3 and would use 4 but it doesn't exist) + # / ___/ + # 1 (tInd=1 uses fields 2 and 3) + + def AhtRHS(tInd, y): + nSrc, nF = self.survey.nSrc, self.mesh.nF + rhs = np.zeros((nF,1) if nSrc == 1 else (nF, nSrc)) + + if 'e' in p: + rhs += self.MfMui*(self.mesh.edgeCurl*(self.MeSigmaI*p[:,'e',tInd+1])) + if 'b' in p: + rhs += p[:,'b',tInd+1] + + if tInd == self.nT-1: + return rhs + dt = self.timeSteps[tInd+1] + return rhs + 1.0/dt*self.MfMui*y[:,'b',tInd+2] + + F = FieldsTDEM_e_from_b_Ah(self.mesh, self.survey, p=p) + + return self.adjoint(m, AhtRHS, F) + + #################################################### + # Functions for tests + #################################################### + + def _AhVec(self, m, vec): + """ + :param numpy.array m: Conductivity model + :param simpegEM.TDEM.FieldsTDEM vec: Fields object + :rtype: simpegEM.TDEM.FieldsTDEM + :return: f + + Multiply the matrix \\\(\\\hat{A}\\\) by a fields vector where + + .. math:: + \mathbf{\hat{A}} = \left[ + \\begin{array}{cccc} + A & 0 & & \\\\ + B & A & & \\\\ + & \ddots & \ddots & \\\\ + & & B & A + \end{array} + \\right] \\\\ + \mathbf{A} = + \left[ + \\begin{array}{cc} + \\frac{1}{\delta t} \MfMui & \MfMui\dcurl \\\\ + \dcurl^\\top \MfMui & -\MeSig + \end{array} + \\right] \\\\ + \mathbf{B} = + \left[ + \\begin{array}{cc} + -\\frac{1}{\delta t} \MfMui & 0 \\\\ + 0 & 0 + \end{array} + \\right] \\\\ + """ + + self.curModel = m + f = FieldsTDEM(self.mesh, self.survey) + for i in range(1,self.nT+1): + dt = self.timeSteps[i-1] + b = 1.0/dt*self.MfMui*vec[:,'b',i] + self.MfMui*(self.mesh.edgeCurl*vec[:,'e',i]) + if i > 1: + b = b - 1.0/dt*self.MfMui*vec[:,'b',i-1] + f[:,'b',i] = b + f[:,'e',i] = self.mesh.edgeCurl.T*(self.MfMui*vec[:,'b',i]) - self.MeSigma*vec[:,'e',i] + return f + + def _AhtVec(self, m, vec): + """ + :param numpy.array m: Conductivity model + :param simpegEM.TDEM.FieldsTDEM vec: Fields object + :rtype: simpegEM.TDEM.FieldsTDEM + :return: f + + Multiply the matrix \\\(\\\hat{A}\\\) by a fields vector where + + .. math:: + \mathbf{\hat{A}}^\\top = \left[ + \\begin{array}{cccc} + A & B & & \\\\ + & \ddots & \ddots & \\\\ + & & A & B \\\\ + & & 0 & A + \end{array} + \\right] \\\\ + \mathbf{A} = + \left[ + \\begin{array}{cc} + \\frac{1}{\delta t} \MfMui & \MfMui\dcurl \\\\ + \dcurl^\\top \MfMui & -\MeSig + \end{array} + \\right] \\\\ + \mathbf{B} = + \left[ + \\begin{array}{cc} + -\\frac{1}{\delta t} \MfMui & 0 \\\\ + 0 & 0 + \end{array} + \\right] \\\\ + """ + self.curModel = m + f = FieldsTDEM(self.mesh, self.survey) + for i in range(self.nT): + b = 1.0/self.timeSteps[i]*self.MfMui*vec[:,'b',i+1] + self.MfMui*(self.mesh.edgeCurl*vec[:,'e',i+1]) + if i < self.nT-1: + b = b - 1.0/self.timeSteps[i+1]*self.MfMui*vec[:,'b',i+2] + f[:,'b', i+1] = b + f[:,'e', i+1] = self.mesh.edgeCurl.T*(self.MfMui*vec[:,'b',i+1]) - self.MeSigma*vec[:,'e',i+1] + return f diff --git a/SimPEG/EM/TDEM/__init__.py b/SimPEG/EM/TDEM/__init__.py new file mode 100644 index 00000000..dd5a8bce --- /dev/null +++ b/SimPEG/EM/TDEM/__init__.py @@ -0,0 +1,3 @@ +from SurveyTDEM import * #SurveyTDEM, RxTDEM, SrcTDEM +from BaseTDEM import BaseTDEMProblem, FieldsTDEM +from TDEM_b import ProblemTDEM_b diff --git a/SimPEG/EM/Utils/EMUtils.py b/SimPEG/EM/Utils/EMUtils.py new file mode 100644 index 00000000..4a342acb --- /dev/null +++ b/SimPEG/EM/Utils/EMUtils.py @@ -0,0 +1,49 @@ +import numpy as np +from scipy.constants import mu_0, epsilon_0 + +# useful params +def omega(freq): + """Angular frequency, omega""" + return 2.*np.pi*freq + +def k(freq, sigma, mu=mu_0, eps=epsilon_0): + """ Eq 1.47 - 1.49 in Ward and Hohmann """ + w = omega(freq) + alp = w * np.sqrt( mu*eps/2 * ( np.sqrt(1. + (sigma / (eps*w))**2 ) + 1) ) + beta = w * np.sqrt( mu*eps/2 * ( np.sqrt(1. + (sigma / (eps*w))**2 ) - 1) ) + return alp - 1j*beta + +# Constitutive relations +def e_from_j(prob,j): + eqLocs = prob._eqLocs + if eqLocs is 'FE': + MSigmaI = prob.MeSigmaI + elif eqLocs is 'EF': + MSigmaI = prob.MfRho + return MSigmaI*j + +def j_from_e(prob,e): + eqLocs = prob._eqLocs + if eqLocs is 'FE': + MSigma = prob.MeSigma + elif eqLocs is 'EF': + MSigma = prob.MfRhoI + return MSigma*e + +def b_from_h(prob,h): + eqLocs = prob._eqLocs + if eqLocs is 'FE': + MMu = prob.MfMuiI + elif eqLocs is 'EF': + MMu = prob.MeMu + return MMu*h + +def h_from_b(prob,b): + eqLocs = prob._eqLocs + if eqLocs is 'FE': + MMuI = prob.MfMui + elif eqLocs is 'EF': + MMuI = prob.MeMuI + return MMuI*b + + diff --git a/SimPEG/EM/Utils/SrcUtils.py b/SimPEG/EM/Utils/SrcUtils.py new file mode 100644 index 00000000..1827f6b2 --- /dev/null +++ b/SimPEG/EM/Utils/SrcUtils.py @@ -0,0 +1,203 @@ +from SimPEG import * +from scipy.special import ellipk, ellipe +from scipy.constants import mu_0, pi + +def MagneticDipoleVectorPotential(srcLoc, obsLoc, component, moment=1., dipoleMoment=(0., 0., 1.), mu = mu_0): + """ + Calculate the vector potential of a set of magnetic dipoles + at given locations 'ref. ' + + :param numpy.ndarray srcLoc: Location of the source(s) (x, y, z) + :param numpy.ndarray,SimPEG.Mesh obsLoc: Where the potentials will be calculated (x, y, z) or a SimPEG Mesh + :param str,list component: The component to calculate - 'x', 'y', or 'z' if an array, or grid type if mesh, can be a list + :param numpy.ndarray dipoleMoment: The vector dipole moment + :rtype: numpy.ndarray + :return: The vector potential each dipole at each observation location + """ + #TODO: break this out! + + if type(component) in [list, tuple]: + out = range(len(component)) + for i, comp in enumerate(component): + out[i] = MagneticDipoleVectorPotential(srcLoc, obsLoc, comp, dipoleMoment=dipoleMoment) + return np.concatenate(out) + + if isinstance(obsLoc, Mesh.BaseMesh): + mesh = obsLoc + assert component in ['Ex','Ey','Ez','Fx','Fy','Fz'], "Components must be in: ['Ex','Ey','Ez','Fx','Fy','Fz']" + return MagneticDipoleVectorPotential(srcLoc, getattr(mesh,'grid'+component), component[1], dipoleMoment=dipoleMoment) + + if component == 'x': + dimInd = 0 + elif component == 'y': + dimInd = 1 + elif component == 'z': + dimInd = 2 + else: + raise ValueError('Invalid component') + + srcLoc = np.atleast_2d(srcLoc) + obsLoc = np.atleast_2d(obsLoc) + dipoleMoment = np.atleast_2d(dipoleMoment) + + nEdges = obsLoc.shape[0] + nSrc = srcLoc.shape[0] + + m = np.array(dipoleMoment).repeat(nEdges, axis=0) + A = np.empty((nEdges, nSrc)) + for i in range(nSrc): + dR = obsLoc - srcLoc[i, np.newaxis].repeat(nEdges, axis=0) + mCr = np.cross(m, dR) + r = np.sqrt((dR**2).sum(axis=1)) + A[:, i] = +(mu/(4*pi)) * mCr[:,dimInd]/(r**3) + if nSrc == 1: + return A.flatten() + return A + + +def MagneticDipoleFields(srcLoc, obsLoc, component, moment=1., mu = mu_0): + """ + Calculate the vector potential of a set of magnetic dipoles + at given locations 'ref. ' + + :param numpy.ndarray srcLoc: Location of the source(s) (x, y, z) + :param numpy.ndarray obsLoc: Where the potentials will be calculated (x, y, z) + :param str component: The component to calculate - 'x', 'y', or 'z' + :param numpy.ndarray moment: The vector dipole moment (vertical) + :rtype: numpy.ndarray + :return: The vector potential each dipole at each observation location + """ + + if component=='x': + dimInd = 0 + elif component=='y': + dimInd = 1 + elif component=='z': + dimInd = 2 + else: + raise ValueError('Invalid component') + + srcLoc = np.atleast_2d(srcLoc) + obsLoc = np.atleast_2d(obsLoc) + moment = np.atleast_2d(moment) + + nFaces = obsLoc.shape[0] + nSrc = srcLoc.shape[0] + + m = np.array(moment).repeat(nFaces, axis=0) + B = np.empty((nFaces, nSrc)) + for i in range(nSrc): + dR = obsLoc - srcLoc[i, np.newaxis].repeat(nFaces, axis=0) + r = np.sqrt((dR**2).sum(axis=1)) + if dimInd == 0: + B[:, i] = +(mu/(4*pi)) /(r**3) * (3*dR[:,2]*dR[:,0]/r**2) + elif dimInd == 1: + B[:, i] = +(mu/(4*pi)) /(r**3) * (3*dR[:,2]*dR[:,1]/r**2) + elif dimInd == 2: + B[:, i] = +(mu/(4*pi)) /(r**3) * (3*dR[:,2]**2/r**2-1) + else: + raise Exception("Not Implemented") + if nSrc == 1: + return B.flatten() + return B + + + +def MagneticLoopVectorPotential(srcLoc, obsLoc, component, radius, mu=mu_0): + """ + Calculate the vector potential of horizontal circular loop + at given locations + + :param numpy.ndarray srcLoc: Location of the source(s) (x, y, z) + :param numpy.ndarray,SimPEG.Mesh obsLoc: Where the potentials will be calculated (x, y, z) or a SimPEG Mesh + :param str,list component: The component to calculate - 'x', 'y', or 'z' if an array, or grid type if mesh, can be a list + :param numpy.ndarray I: Input current of the loop + :param numpy.ndarray radius: radius of the loop + :rtype: numpy.ndarray + :return: The vector potential each dipole at each observation location + """ + + if type(component) in [list, tuple]: + out = range(len(component)) + for i, comp in enumerate(component): + out[i] = MagneticLoopVectorPotential(srcLoc, obsLoc, comp, radius, mu) + return np.concatenate(out) + + if isinstance(obsLoc, Mesh.BaseMesh): + mesh = obsLoc + assert component in ['Ex','Ey','Ez','Fx','Fy','Fz'], "Components must be in: ['Ex','Ey','Ez','Fx','Fy','Fz']" + return MagneticLoopVectorPotential(srcLoc, getattr(mesh,'grid'+component), component[1], radius, mu) + + srcLoc = np.atleast_2d(srcLoc) + obsLoc = np.atleast_2d(obsLoc) + + n = obsLoc.shape[0] + nSrc = srcLoc.shape[0] + + if component=='z': + A = np.zeros((n, nSrc)) + if nSrc ==1: + return A.flatten() + return A + + else: + + A = np.zeros((n, nSrc)) + for i in range (nSrc): + x = obsLoc[:, 0] - srcLoc[i, 0] + y = obsLoc[:, 1] - srcLoc[i, 1] + z = obsLoc[:, 2] - srcLoc[i, 2] + r = np.sqrt(x**2 + y**2) + m = (4 * radius * r) / ((radius + r)**2 + z**2) + m[m > 1.] = 1. + # m might be slightly larger than 1 due to rounding errors + # but ellipke requires 0 <= m <= 1 + K = ellipk(m) + E = ellipe(m) + ind = (r > 0) & (m < 1) + # % 1/r singular at r = 0 and K(m) singular at m = 1 + Aphi = np.zeros(n) + # % Common factor is (mu * I) / pi with I = 1 and mu = 4e-7 * pi. + Aphi[ind] = 4e-7 / np.sqrt(m[ind]) * np.sqrt(radius / r[ind]) *((1. - m[ind] / 2.) * K[ind] - E[ind]) + if component == 'x': + A[ind, i] = Aphi[ind] * (-y[ind] / r[ind] ) + elif component == 'y': + A[ind, i] = Aphi[ind] * ( x[ind] / r[ind] ) + else: + raise ValueError('Invalid component') + + if nSrc == 1: + return A.flatten() + return A + +if __name__ == '__main__': + from SimPEG import Mesh + import matplotlib.pyplot as plt + cs = 20 + ncx, ncy, ncz = 41, 41, 40 + hx = np.ones(ncx)*cs + hy = np.ones(ncy)*cs + hz = np.ones(ncz)*cs + mesh = Mesh.TensorMesh([hx, hy, hz], 'CCC') + srcLoc = np.r_[0., 0., 0.] + Ax = MagneticLoopVectorPotential(srcLoc, mesh.gridEx, 'x', 200) + Ay = MagneticLoopVectorPotential(srcLoc, mesh.gridEy, 'y', 200) + Az = MagneticLoopVectorPotential(srcLoc, mesh.gridEz, 'z', 200) + A = np.r_[Ax, Ay, Az] + B0 = mesh.edgeCurl*A + J0 = mesh.edgeCurl.T*B0 + + # mesh.plotImage(A, vType = 'Ex') + # mesh.plotImage(A, vType = 'Ey') + + mesh.plotImage(B0, vType = 'Fx') + mesh.plotImage(B0, vType = 'Fy') + mesh.plotImage(B0, vType = 'Fz') + + # # mesh.plotImage(J0, vType = 'Ex') + # mesh.plotImage(J0, vType = 'Ey') + # mesh.plotImage(J0, vType = 'Ez') + + plt.show() + + diff --git a/SimPEG/EM/Utils/__init__.py b/SimPEG/EM/Utils/__init__.py new file mode 100644 index 00000000..6e430cf9 --- /dev/null +++ b/SimPEG/EM/Utils/__init__.py @@ -0,0 +1,5 @@ +# import Sources +# import Ana +# import Solver +import EMUtils +import SrcUtils \ No newline at end of file diff --git a/SimPEG/EM/__init__.py b/SimPEG/EM/__init__.py new file mode 100644 index 00000000..6a1ca774 --- /dev/null +++ b/SimPEG/EM/__init__.py @@ -0,0 +1,6 @@ +# from EM import * +import TDEM +import FDEM +import Base +import Analytics +import Utils diff --git a/docs/em/api_FDEM.rst b/docs/em/api_FDEM.rst new file mode 100644 index 00000000..bf5bdcb4 --- /dev/null +++ b/docs/em/api_FDEM.rst @@ -0,0 +1,159 @@ +.. _api_FDEM: + +.. math:: + \renewcommand{\div}{\nabla\cdot\,} + \newcommand{\grad}{\vec \nabla} + \newcommand{\curl}{{\vec \nabla}\times\,} + + +Frequency Domain Electromagnetics +********************************* + +Electromagnetic (EM) geophysical methods are used in a variety of applications from resource exploration, including for hydrocarbons and minerals, to environmental applications, such as groundwater monitoring. The primary physical property of interest in EM is electrical conductivity, which describes the ease with which electric current flows through a material. + + +Background +========== + +Electromagnetic phenomena are governed by Maxwell's equations. They describe the behavior of EM fields and fluxes. Electromagnetic theory for geophysical applications by Ward and Hohmann (1988) is a highly recommended resource on this topic. + +Fourier Transform Convention +---------------------------- +In order to examine Maxwell's equations in the frequency domain, we must first define our choice of harmonic time-dependence by choosing a Fourier transform convention. We use the \\(e^{i \\omega t} \\) convention, so we define our Fourier Transform pair as + +.. math :: + F(\omega) = \int_{-\infty}^{\infty} f(t) e^{- i \omega t} dt \\ + + f(t) = \frac{1}{2\pi}\int_{-\infty}^{\infty} F(\omega) e^{i \omega t} d \omega + +where \\(\\omega\\) is angular frequency, \\(t\\) is time, \\(F(\\omega)\\) is the function defined in the frequency domain and \\(f(t)\\) is the function defined in the time domain. + + +Maxwell's Equations +=================== +In the frequency domain, Maxwell's equations are given by + +.. math :: + \curl \vec{E} = - i \omega \vec{B} \\ + + \curl \vec{H} = \vec{J} + i \omega \vec{D} + \vec{S} \\ + + \div \vec{B} = 0 \\ + + \div \vec{D} = \rho_f + +where: + + - \\(\\vec{E}\\) : electric field (\\(V/m\\)) + - \\(\\vec{H}\\) : magnetic field (\\(A/m\\)) + - \\(\\vec{B}\\) : magnetic flux density (\\(Wb/m^2\\)) + - \\(\\vec{D}\\) : electric displacement / electric flux density (\\(C/m^2\\)) + - \\(\\vec{J}\\) : electric current density (\\(A/m^2\\)) + - \\(\\rho_f\\) : free charge density + +The source term is \\(\\vec{S}\\) + + +Constitutive Relations +---------------------- +The fields and fluxes are related through the constitutive relations. At each frequency, they are given by + +.. math :: + \vec{J} = \sigma \vec{E} \\ + + \vec{B} = \mu \vec{H} \\ + + \vec{D} = \varepsilon \vec{E} + +where: + +- \\(\\sigma\\) : electrical conductivity \\(S/m\\) +- \\(\\mu\\) : magnetic permeability \\(H/m\\) +- \\(\\varepsilon\\) : dielectric permittivity \\(F/m\\) + +\\(\\sigma\\), \\(\\mu\\), \\(\\varepsilon\\) are physical properties which depend on the material. \\(\\sigma\\) describes how easily electric current passes through a material, \\(\\mu\\) describes how easily a material is magnetized, and \\(\\varepsilon\\) describes how easily a material is electrically polarized. In most geophysical applications of EM, \\(\\sigma\\) is the the primary physical property of interest, and \\(\\mu\\), \\(\\varepsilon\\) are assumed to have their free-space values \\(\\mu_0 = 4\\pi \\times 10^{-7} H/m \\), \\(\\varepsilon_0 = 8.85 \\times 10^{-12} F/m\\) + + +Quasi-static Approximation +-------------------------- + +For the frequency range typical of most geophysical surveys, the contribution of the electric displacement is negligible compared to the electric current density. In this case, we use the Quasi-static approximation and assume that this term can be neglected, giving + +.. math :: + \nabla \times \vec{E} = -i \omega \vec{B} \\ + \nabla \times \vec{H} = \vec{J} + \vec{S} + + +Implementation in SimPEG.EM +=========================== + +We consider two formulations in SimPEG.EM, both first-order and both in terms of one field and one flux. We allow for the definition of magnetic and electric sources (see for example: Ward and Hohmann, starting on page 144). The E-B formulation is in terms of the electric field and the magnetic flux: + +.. math :: + \nabla \times \vec{E} + i \omega \vec{B} = \vec{S}_m \\ + \nabla \times \mu^{-1} \vec{B} - \sigma \vec{E} = \vec{S}_e + +The H-J formulation is in terms of the current density and the magnetic field: + +.. math :: + \nabla \times \sigma^{-1} \vec{J} + i \omega \mu \vec{H} = \vec{S}_m \\ + \nabla \times \vec{H} - \vec{J} = \vec{S}_e + + +Discretizing +------------ +For both formulations, we use a finite volume discretization +and discretize fields on cell edges, fluxes on cell faces and +physical properties in cell centers. This is particularly +important when using symmetry to reduce the dimensionality of a problem +(for instance on a 2D CylMesh, there are \\(r\\), \\(z\\) faces and \\(\\theta\\) edges) + +.. figure:: ../images/finitevolrealestate.png + :align: center + :scale: 60 % + +For the two formulations, the discretization of the physical properties, fields and fluxes are summarized below. + +.. figure:: ../images/ebjhdiscretizations.png + :align: center + :scale: 60 % + +Note that resistivity is the inverse of conductivity, \\(\\rho = \\sigma^{-1}\\). + + +E-B Formulation: +**************** + +.. math :: + \mathbf{C} \mathbf{e} + i \omega \mathbf{b} = \mathbf{s_m} \\ + \mathbf{C^T} \mathbf{M^f_{\mu^{-1}}} \mathbf{b} - \mathbf{M^e_\sigma} \mathbf{e} = \mathbf{M^e} \mathbf{s_e} + +H-J Formulation: +**************** + +.. math :: + \mathbf{C^T} \mathbf{M^f_\rho} \mathbf{j} + i \omega \mathbf{M^e_\mu} \mathbf{h} = \mathbf{M^e} \mathbf{s_m} \\ + \mathbf{C} \mathbf{h} - \mathbf{j} = \mathbf{s_e} + + +.. Forward Problem +.. =============== + +.. Inverse Problem +.. =============== + +API +=== +.. automodule:: SimPEG.EM.FDEM.FDEM + :show-inheritance: + :members: + :undoc-members: + + +FDEM Survey +----------- + +.. automodule:: SimPEG.EM.FDEM.SurveyFDEM + :show-inheritance: + :members: + :undoc-members: diff --git a/docs/em/api_TDEM.rst b/docs/em/api_TDEM.rst new file mode 100644 index 00000000..cbbc48b8 --- /dev/null +++ b/docs/em/api_TDEM.rst @@ -0,0 +1,88 @@ +.. _api_TDEM: + + +.. math:: + + \renewcommand{\div}{\nabla\cdot\,} + \newcommand{\grad}{\vec \nabla} + \newcommand{\curl}{{\vec \nabla}\times\,} + \newcommand {\J}{{\vec J}} + \renewcommand{\H}{{\vec H}} + \newcommand {\E}{{\vec E}} + \newcommand{\dcurl}{{\mathbf C}} + \newcommand{\dgrad}{{\mathbf G}} + \newcommand{\Acf}{{\mathbf A_c^f}} + \newcommand{\Ace}{{\mathbf A_c^e}} + \renewcommand{\S}{{\mathbf \Sigma}} + \newcommand{\St}{{\mathbf \Sigma_\tau}} + \newcommand{\T}{{\mathbf T}} + \newcommand{\Tt}{{\mathbf T_\tau}} + \newcommand{\diag}[1]{\,{\sf diag}\left( #1 \right)} + \newcommand{\M}{{\mathbf M}} + \newcommand{\MfMui}{{\M^f_{\mu^{-1}}}} + \newcommand{\MeSig}{{\M^e_\sigma}} + \newcommand{\MeSigInf}{{\M^e_{\sigma_\infty}}} + \newcommand{\MeSigO}{{\M^e_{\sigma_0}}} + \newcommand{\Me}{{\M^e}} + \newcommand{\Mes}[1]{{\M^e_{#1}}} + \newcommand{\Mee}{{\M^e_e}} + \newcommand{\Mej}{{\M^e_j}} + \newcommand{\BigO}[1]{\mathcal{O}\bigl(#1\bigr)} + \newcommand{\bE}{\mathbf{E}} + \newcommand{\bH}{\mathbf{H}} + \newcommand{\B}{\vec{B}} + \newcommand{\D}{\vec{D}} + \renewcommand{\H}{\vec{H}} + \newcommand{\s}{\vec{s}} + \newcommand{\bfJ}{\bf{J}} + \newcommand{\vecm}{\vec m} + \renewcommand{\Re}{\mathsf{Re}} + \renewcommand{\Im}{\mathsf{Im}} + \renewcommand {\j} { {\vec j} } + \newcommand {\h} { {\vec h} } + \renewcommand {\b} { {\vec b} } + \newcommand {\e} { {\vec e} } + \newcommand {\c} { {\vec c} } + \renewcommand {\d} { {\vec d} } + \renewcommand {\u} { {\vec u} } + \newcommand{\I}{\vec{I}} + + +TDEM - B formulation +==================== + +.. automodule:: SimPEG.EM.TDEM.TDEM_b + :show-inheritance: + :members: + :undoc-members: + + +Field Storage +============= + +.. autoclass:: SimPEG.EM.TDEM.SurveyTDEM.FieldsTDEM + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + + +TDEM Survey Classes +=================== + +.. autoclass:: SimPEG.EM.TDEM.SurveyTDEM.SurveyTDEM + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + + +Base Classes +============ + +.. automodule:: SimPEG.EM.TDEM.BaseTDEM + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + diff --git a/docs/em/api_TDEM_derivation.rst b/docs/em/api_TDEM_derivation.rst new file mode 100644 index 00000000..af3fc2fc --- /dev/null +++ b/docs/em/api_TDEM_derivation.rst @@ -0,0 +1,341 @@ +.. _api_TDEM_derivation: + + +.. math:: + + \renewcommand{\div}{\nabla\cdot\,} + \newcommand{\grad}{\vec \nabla} + \newcommand{\curl}{{\vec \nabla}\times\,} + \newcommand {\J}{{\vec J}} + \renewcommand{\H}{{\vec H}} + \newcommand {\E}{{\vec E}} + \newcommand{\dcurl}{{\mathbf C}} + \newcommand{\dgrad}{{\mathbf G}} + \newcommand{\Acf}{{\mathbf A_c^f}} + \newcommand{\Ace}{{\mathbf A_c^e}} + \renewcommand{\S}{{\mathbf \Sigma}} + \newcommand{\St}{{\mathbf \Sigma_\tau}} + \newcommand{\T}{{\mathbf T}} + \newcommand{\Tt}{{\mathbf T_\tau}} + \newcommand{\diag}[1]{\,{\sf diag}\left( #1 \right)} + \newcommand{\M}{{\mathbf M}} + \newcommand{\MfMui}{{\M^f_{\mu^{-1}}}} + \newcommand{\MeSig}{{\M^e_\sigma}} + \newcommand{\MeSigInf}{{\M^e_{\sigma_\infty}}} + \newcommand{\MeSigO}{{\M^e_{\sigma_0}}} + \newcommand{\Me}{{\M^e}} + \newcommand{\Mes}[1]{{\M^e_{#1}}} + \newcommand{\Mee}{{\M^e_e}} + \newcommand{\Mej}{{\M^e_j}} + \newcommand{\BigO}[1]{\mathcal{O}\bigl(#1\bigr)} + \newcommand{\bE}{\mathbf{E}} + \newcommand{\bH}{\mathbf{H}} + \newcommand{\B}{\vec{B}} + \newcommand{\D}{\vec{D}} + \renewcommand{\H}{\vec{H}} + \newcommand{\s}{\vec{s}} + \newcommand{\bfJ}{\bf{J}} + \newcommand{\vecm}{\vec m} + \renewcommand{\Re}{\mathsf{Re}} + \renewcommand{\Im}{\mathsf{Im}} + \renewcommand {\j} { {\vec j} } + \newcommand {\h} { {\vec h} } + \renewcommand {\b} { {\vec b} } + \newcommand {\e} { {\vec e} } + \newcommand {\c} { {\vec c} } + \renewcommand {\d} { {\vec d} } + \renewcommand {\u} { {\vec u} } + \newcommand{\I}{\vec{I}} + + +Time-Domain EM Derivation +************************* + +The following shows the derivation for the TDEM problem. We use the b-formulation below. +(More to come soon..!) + + +Sensitivity Calculation +======================= + +.. math:: + + \begin{align} + \dcurl \e^{(t+1)} + \frac{\b^{(t+1)} - \b^{(t)}}{\delta t} = 0 \\ + \dcurl^\top \MfMui \b^{(t+1)} - \MeSig \e^{(t+1)} = \Me \j_s^{(t+1)} + \end{align} + +Using Gauss-Newton to solve the inverse problem requires the ability to calculate the product of the +Jacobian and a vector, as well as the transpose of the Jacobian times a vector. +The above system can be rewritten as: + +.. math:: + + \begin{align} + \mathbf{A} \u^{(t+1)} + \mathbf{B} \u^{(t)}= \s^{(t+1)} + \end{align} + +where + +.. math:: + + \begin{align} + \mathbf{A} = + \left[ + \begin{array}{cc} + \frac{1}{\delta t} \MfMui & \MfMui\dcurl \\ + \dcurl^\top \MfMui & -\MeSig + \end{array} + \right] \\ + \mathbf{B} = + \left[ + \begin{array}{cc} + -\frac{1}{\delta t} \MfMui & 0 \\ + 0 & 0 + \end{array} + \right] \\ + \u^{(k)} = \left[ + \begin{array}{c} + \b^{(k)}\\ + \e^{(k)} + \end{array} + \right] \\ + \s^{(k)} = \left[ + \begin{array}{c} + 0\\ + \Me \j^{(k)}_s + \end{array} + \right] + \end{align} + +.. note:: + + Here we have multiplied through by \\(\\MfMui\\) to make A and B symmetric! + +The entire time dependent system can be written in a single matrix expression + +.. math:: + + \begin{align} + \hat{\mathbf{A}} \hat{u} = \hat{s} + \end{align} + +where + +.. math:: + + \begin{align} + \mathbf{\hat{A}} = \left[ + \begin{array}{cccc} + A & 0 & & \\ + B & A & & \\ + & \ddots & \ddots & \\ + & & B & A + \end{array} + \right] \\ + \hat{u} = \left[ + \begin{array}{c} + \u^{(1)} \\ + \u^{(2)} \\ + \vdots \\ + \u^{(N)} + \end{array} \right]\\ + \hat{s} = \left[ + \begin{array}{c} + \s^{(1)} - \mathbf{B} \u^{(0)} \\ + \s^{(2)} \\ + \vdots \\ + \s^{(N)} + \end{array} + \right] + \end{align} + +For the fields \\(\\u\\), the measured data is given by + +.. math:: + + \begin{align} + \vec{d} = \mathbf{Q} \u + \end{align} + +The sensitivity matrix **J** is then defined as + +.. math:: + + \begin{align} + \mathbf{J} = \mathbf{Q} \frac{\partial \u}{\partial \sigma} + \end{align} + + +Defining the function \\(\\c(m,\\u)\\) to be + +.. math:: + + \begin{align} + \vec{c}(m,\u) = \hat{\mathbf{A}} \vec{u} - \vec{q} = \vec{0} + \end{align} + +then + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial m} \partial m + + \frac{\partial \vec{c}}{\partial \u} \partial \vec{u} = 0 + \end{align} + +or + +.. math:: + + \begin{align} + \frac{\partial \vec{u}}{\partial m} = -\left(\frac{\partial \vec{c}}{\partial \u} \right)^{-1} \frac{\partial \vec{c}}{\partial m} + \end{align} + + +Differentiating, we find that + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial \hat{u}} = \hat{\mathbf{A}} + \end{align} + +and + +.. math:: + + \begin{align} + \frac{\partial \vec{c}}{\partial \sigma} = \mathbf{G}_\sigma = + \left[ + \begin{array}{c} + g_\sigma^{(1)}\\ + g_\sigma^{(2)}\\ + \vdots \\ + g_\sigma^{(N)} + \end{array} + \right] + \end{align} + +with + +.. math:: + + \begin{align} + g_\sigma^{(n)} = + \left[ + \begin{array}{c} + \mathbf{0} \\ + - \diag{\e^{(n)}} \Ace \diag{\vec{V}} + \end{array} + \right] + \end{align} + + +Implementing **J** times a vector +================================= + +Multiplying **J** onto a vector can be broken into three steps + + +* Compute \\(\\vec{p} = \\mathbf{G}m\\) +* Solve \\(\\hat{\\mathbf{A}} \\vec{y} = \\vec{p}\\) +* Compute \\(\\vec{w} = -\\mathbf{Q} \\vec{y}\\) + +.. math:: + + \begin{align} + \vec{p}^{(n)} = \left[ + \begin{array}{c} + \vec{p}_b^{(n)} \\ + \vec{p}_e^{(n)} + \end{array} + \right] \\ + \vec{p}_b^{(n)} = 0 \\ + \vec{p}_e^{(n)} = - \diag{\e^{(n)}} \Ace \diag{V} m + \end{align} + + +For all time steps: + +.. math:: + + \begin{align} + \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t+1)} + \MfMui\dcurl \vec{y}_{e}^{(t+1)} + - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t)} + = \vec{p}_b^{(t+1)} \\ + \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig \vec{y}_e^{(t+1)} = \vec{p}_e^{(t+1)} + \end{align} + +and + +.. math:: + + \begin{align} + \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t+1)} = + \frac{1}{\delta t} \MfMui \vec{y}_b^{(t)} + + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t+1)} + \vec{p}_b^{(t+1)} \\ + \vec{y}_e^{(t+1)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t+1)} - \MeSig^{-1} \vec{p}_e^{(t+1)} + \end{align} + +.. note:: + + For the first time step, \\\(t=0\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(0)}\\\) is zero. + + + + +Implementing **J** transpose times a vector +=========================================== + +Multiplying \\(\\mathbf{J}^\\top\\) onto a vector can be broken into three steps + + +* Compute \\(\\vec{p} = \\mathbf{Q}^\\top \\vec{v}\\) +* Solve \\(\\hat{\\mathbf{A}}^\\top \\vec{y} = \\vec{p}\\) +* Compute \\(\\vec{w} = -\\mathbf{G}^\\top y\\) + + +.. math:: + + \mathbf{\hat{A}}^\top = \left[ + \begin{array}{cccc} + A & B & & \\ + & \ddots & \ddots & \\ + & & A & B \\ + & & 0 & A + \end{array} + \right] + +For the all time-steps (going backwards in time): + + +.. math:: + + A \vec{y}^{(t)} + B \vec{y}^{(t+1)} = \vec{p}^{(t)} + + +.. math:: + + \begin{align} + \frac{1}{\delta t} \MfMui\vec{y}_{b}^{(t)} + \MfMui\dcurl \vec{y}_{e}^{(t)} + - \frac{1}{\delta t} \MfMui \vec{y}_{b}^{(t+1)} + = \vec{p}_b^{(t)} \\ + \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig \vec{y}_e^{(t)} = \vec{p}_e^{(t)} + \end{align} + +and + +.. math:: + + \begin{align} + \left( \MfMui \dcurl \MeSig^{-1} \dcurl^\top \MfMui + \frac{1}{\delta t} \MfMui \right) \vec{y}_{b}^{(t)} = + \frac{1}{\delta t} \MfMui \vec{y}_b^{(t+1)} + + \MfMui \dcurl \MeSig^{-1} \vec{p}_e^{(t)} + \vec{p}_b^{(t)} \\ + \vec{y}_e^{(t)} = \MeSig^{-1} \dcurl^\top \MfMui \vec{y}_b^{(t)} - \MeSig^{-1} \vec{p}_e^{(t)} + \end{align} + + +.. note:: + + For the last time step, \\\(t=N\\\), the term: \\\(\\frac{1}{\\delta t} \\MfMui \\vec{y}_b^{(N+1)}\\\) is zero. diff --git a/docs/em/api_Utils.rst b/docs/em/api_Utils.rst new file mode 100644 index 00000000..b7e9ea45 --- /dev/null +++ b/docs/em/api_Utils.rst @@ -0,0 +1,34 @@ +simpegEM Utilities +****************** + +SimPEG for EM provides a few EM specific utility codes, +sources, and analytic functions. + +Analytic Functions - Time +========================= + +.. automodule:: SimPEG.EM.Utils.Ana.TEM + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + + +Analytic Functions - Frequency +============================== + +.. automodule:: SimPEG.EM.Utils.Ana.FEM + :show-inheritance: + :members: + :undoc-members: + :inherited-members: + + +Sources +======= + +.. automodule:: SimPEG.EM.Utils.Sources.magneticDipole + :show-inheritance: + :members: + :undoc-members: + :inherited-members: diff --git a/docs/em/index.rst b/docs/em/index.rst new file mode 100644 index 00000000..8cbb8619 --- /dev/null +++ b/docs/em/index.rst @@ -0,0 +1,45 @@ + +Electromagnetics +================ + +`SimPEG.EM` uses SimPEG as the framework for the forward and inverse +electromagnetics geophysical problems. + +Time Domian Electromagnetics +---------------------------- + +.. toctree:: + :maxdepth: 2 + + api_TDEM_derivation + + +Code for Time Domian Electromagnetics +------------------------------------- + +.. toctree:: + :maxdepth: 2 + + api_TDEM + +Frequency Domian Electromagnetics +--------------------------------- + +.. toctree:: + :maxdepth: 2 + + api_ForwardProblem + api_FDEM + + +Utility Codes +------------- + +.. toctree:: + :maxdepth: 2 + + api_Utils + + + + diff --git a/docs/images/ebjhdiscretizations.png b/docs/images/ebjhdiscretizations.png new file mode 100644 index 00000000..85bfbd36 Binary files /dev/null and b/docs/images/ebjhdiscretizations.png differ diff --git a/docs/images/finitevolrealestate.png b/docs/images/finitevolrealestate.png new file mode 100644 index 00000000..38a53c71 Binary files /dev/null and b/docs/images/finitevolrealestate.png differ diff --git a/docs/index.rst b/docs/index.rst index c66b2741..d3ec4e8c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -85,6 +85,7 @@ Packages .. toctree:: :maxdepth: 3 + em/index flow/index Developer's Documentation diff --git a/tests/em/__init__.py b/tests/em/__init__.py new file mode 100644 index 00000000..38d84328 --- /dev/null +++ b/tests/em/__init__.py @@ -0,0 +1,11 @@ +if __name__ == '__main__': + import os + import glob + import unittest + test_file_strings = glob.glob('test_*.py') + module_strings = [str[0:len(str)-3] for str in test_file_strings] + suites = [unittest.defaultTestLoader.loadTestsFromName(str) for str + in module_strings] + testSuite = unittest.TestSuite(suites) + + unittest.TextTestRunner(verbosity=2).run(testSuite) diff --git a/tests/em/test_Examples.py b/tests/em/test_Examples.py new file mode 100644 index 00000000..5a601d3b --- /dev/null +++ b/tests/em/test_Examples.py @@ -0,0 +1,10 @@ +import unittest, os +from SimPEG.EM import Examples + +class EM_ExamplesRunning(unittest.TestCase): + + def test_CylInversion(self): + Examples.CylInversion.run(plotIt=False) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_FDEM.py b/tests/em/test_FDEM.py new file mode 100644 index 00000000..23bfa0d2 --- /dev/null +++ b/tests/em/test_FDEM.py @@ -0,0 +1,478 @@ +import unittest +from SimPEG import * +from SimPEG import EM +import sys +from scipy.constants import mu_0 + +testDerivs = True +testCrossCheck = True +testAdjoint = True +testEB = True +testHJ = True + +verbose = False + +TOL = 1e-5 +FLR = 1e-20 # "zero", so if residual below this --> pass regardless of order +CONDUCTIVITY = 1e1 +MU = mu_0 +freq = 1e-1 +addrandoms = True + +SrcType = 'RawVec' #or 'MAgDipole_Bfield', 'CircularLoop', 'RawVec' + + +def getProblem(fdemType, comp): + cs = 5. + ncx, ncy, ncz = 6, 6, 6 + npad = 3 + hx = [(cs,npad,-1.3), (cs,ncx), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,ncy), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,ncz), (cs,npad,1.3)] + mesh = Mesh.TensorMesh([hx,hy,hz],['C','C','C']) + + mapping = Maps.ExpMap(mesh) + + x = np.array([np.linspace(-30,-15,3),np.linspace(15,30,3)]) #don't sample right by the source + XYZ = Utils.ndgrid(x,x,np.r_[0.]) + Rx0 = EM.FDEM.RxFDEM(XYZ, comp) + + if SrcType is 'MagDipole': + Src = EM.FDEM.SrcFDEM_MagDipole([Rx0], freq=freq, loc=np.r_[0.,0.,0.]) + elif SrcType is 'MagDipole_Bfield': + Src = EM.FDEM.SrcFDEM_MagDipole_Bfield([Rx0], freq=freq, loc=np.r_[0.,0.,0.]) + elif SrcType is 'CircularLoop': + Src2 = EM.FDEM.SrcFDEM_CircularLoop([Rx0], freq=freq, loc=np.r_[0.,0.,0.]) + + if verbose: + print ' Fetching %s problem' % (fdemType) + + if fdemType == 'e': + if SrcType is 'RawVec': + S_m = np.zeros(mesh.nF) + S_e = np.zeros(mesh.nE) + S_m[Utils.closestPoints(mesh,[0.,0.,0.],'Fz') + np.sum(mesh.vnF[:1])] = 1. + S_e[Utils.closestPoints(mesh,[0.,0.,0.],'Ez') + np.sum(mesh.vnE[:1])] = 1. + Src = EM.FDEM.SrcFDEM_RawVec([Rx0], freq, S_m, S_e) + + survey = EM.FDEM.SurveyFDEM([Src]) + prb = EM.FDEM.ProblemFDEM_e(mesh, mapping=mapping) + + elif fdemType == 'b': + if SrcType is 'RawVec': + S_m = np.zeros(mesh.nF) + S_e = np.zeros(mesh.nE) + S_m[Utils.closestPoints(mesh,[0.,0.,0.],'Fz') + np.sum(mesh.vnF[:1])] = 1. + S_e[Utils.closestPoints(mesh,[0.,0.,0.],'Ez') + np.sum(mesh.vnE[:1])] = 1. + Src = EM.FDEM.SrcFDEM_RawVec([Rx0], freq, S_m, S_e) + + survey = EM.FDEM.SurveyFDEM([Src]) + prb = EM.FDEM.ProblemFDEM_b(mesh, mapping=mapping) + + elif fdemType == 'j': + if SrcType is 'RawVec': + S_m = np.zeros(mesh.nE) + S_e = np.zeros(mesh.nF) + S_m[Utils.closestPoints(mesh,[0.,0.,0.],'Ez') + np.sum(mesh.vnE[:1])] = 1. + S_e[Utils.closestPoints(mesh,[0.,0.,0.],'Fz') + np.sum(mesh.vnF[:1])] = 1. + Src = EM.FDEM.SrcFDEM_RawVec([Rx0], freq, S_m, S_e) + + survey = EM.FDEM.SurveyFDEM([Src]) + prb = EM.FDEM.ProblemFDEM_j(mesh, mapping=mapping) + + elif fdemType == 'h': + if SrcType is 'RawVec': + S_m = np.zeros(mesh.nE) + S_e = np.zeros(mesh.nF) + S_m[Utils.closestPoints(mesh,[0.,0.,0.],'Ez') + np.sum(mesh.vnE[:1])] = 1. + S_e[Utils.closestPoints(mesh,[0.,0.,0.],'Fz') + np.sum(mesh.vnF[:1])] = 1. + Src = EM.FDEM.SrcFDEM_RawVec([Rx0], freq, S_m, S_e) + + survey = EM.FDEM.SurveyFDEM([Src]) + prb = EM.FDEM.ProblemFDEM_h(mesh, mapping=mapping) + + else: + raise NotImplementedError() + prb.pair(survey) + + try: + from pymatsolver import MumpsSolver + prb.Solver = MumpsSolver + except ImportError, e: + pass + + return prb + +def adjointTest(fdemType, comp): + prb = getProblem(fdemType, comp) + print 'Adjoint %s formulation - %s' % (fdemType, comp) + + m = np.log(np.ones(prb.mapping.nP)*CONDUCTIVITY) + mu = np.ones(prb.mesh.nC)*MU + + if addrandoms is True: + m = m + np.random.randn(prb.mapping.nP)*np.log(CONDUCTIVITY)*1e-1 + mu = mu + np.random.randn(prb.mesh.nC)*MU*1e-1 + + survey = prb.survey + # prb.PropMap.PropModel.mu = mu + # prb.PropMap.PropModel.mui = 1./mu + u = prb.fields(m) + + v = np.random.rand(survey.nD) + w = np.random.rand(prb.mesh.nC) + + vJw = v.dot(prb.Jvec(m, w, u)) + wJtv = w.dot(prb.Jtvec(m, v, u)) + tol = np.max([TOL*(10**int(np.log10(np.abs(vJw)))),FLR]) + print vJw, wJtv, vJw - wJtv, tol, np.abs(vJw - wJtv) < tol + return np.abs(vJw - wJtv) < tol + + +def derivTest(fdemType, comp): + + prb = getProblem(fdemType, comp) + print '%s formulation - %s' % (fdemType, comp) + x0 = np.log(np.ones(prb.mapping.nP)*CONDUCTIVITY) + mu = np.log(np.ones(prb.mesh.nC)*MU) + + if addrandoms is True: + x0 = x0 + np.random.randn(prb.mapping.nP)*np.log(CONDUCTIVITY)*1e-1 + mu = mu + np.random.randn(prb.mapping.nP)*MU*1e-1 + + # prb.PropMap.PropModel.mu = mu + # prb.PropMap.PropModel.mui = 1./mu + + survey = prb.survey + def fun(x): + return survey.dpred(x), lambda x: prb.Jvec(x0, x) + return Tests.checkDerivative(fun, x0, num=3, plotIt=False, eps=FLR) + + +def crossCheckTest(fdemType, comp): + + l2norm = lambda r: np.sqrt(r.dot(r)) + + prb1 = getProblem(fdemType, comp) + mesh = prb1.mesh + print 'Cross Checking Forward: %s formulation - %s' % (fdemType, comp) + m = np.log(np.ones(mesh.nC)*CONDUCTIVITY) + mu = np.log(np.ones(mesh.nC)*MU) + + if addrandoms is True: + m = m + np.random.randn(mesh.nC)*np.log(CONDUCTIVITY)*1e-1 + mu = mu + np.random.randn(mesh.nC)*MU*1e-1 + + # prb1.PropMap.PropModel.mu = mu + # prb1.PropMap.PropModel.mui = 1./mu + survey1 = prb1.survey + d1 = survey1.dpred(m) + + if verbose: + print ' Problem 1 solved' + + if fdemType == 'e': + prb2 = getProblem('b', comp) + elif fdemType == 'b': + prb2 = getProblem('e', comp) + elif fdemType == 'j': + prb2 = getProblem('h', comp) + elif fdemType == 'h': + prb2 = getProblem('j', comp) + else: + raise NotImplementedError() + + # prb2.mu = mu + survey2 = prb2.survey + d2 = survey2.dpred(m) + + if verbose: + print ' Problem 2 solved' + + r = d2-d1 + l2r = l2norm(r) + + tol = np.max([TOL*(10**int(np.log10(l2norm(d1)))),FLR]) + print l2norm(d1), l2norm(d2), l2r , tol, l2r < tol + return l2r < tol + + + + + +class FDEM_DerivTests(unittest.TestCase): + + if testDerivs: + if testEB: + def test_Jvec_exr_Eform(self): + self.assertTrue(derivTest('e', 'exr')) + def test_Jvec_eyr_Eform(self): + self.assertTrue(derivTest('e', 'eyr')) + def test_Jvec_ezr_Eform(self): + self.assertTrue(derivTest('e', 'ezr')) + def test_Jvec_exi_Eform(self): + self.assertTrue(derivTest('e', 'exi')) + def test_Jvec_eyi_Eform(self): + self.assertTrue(derivTest('e', 'eyi')) + def test_Jvec_ezi_Eform(self): + self.assertTrue(derivTest('e', 'ezi')) + + def test_Jvec_bxr_Eform(self): + self.assertTrue(derivTest('e', 'bxr')) + def test_Jvec_byr_Eform(self): + self.assertTrue(derivTest('e', 'byr')) + def test_Jvec_bzr_Eform(self): + self.assertTrue(derivTest('e', 'bzr')) + def test_Jvec_bxi_Eform(self): + self.assertTrue(derivTest('e', 'bxi')) + def test_Jvec_byi_Eform(self): + self.assertTrue(derivTest('e', 'byi')) + def test_Jvec_bzi_Eform(self): + self.assertTrue(derivTest('e', 'bzi')) + + def test_Jvec_exr_Bform(self): + self.assertTrue(derivTest('b', 'exr')) + def test_Jvec_eyr_Bform(self): + self.assertTrue(derivTest('b', 'eyr')) + def test_Jvec_ezr_Bform(self): + self.assertTrue(derivTest('b', 'ezr')) + def test_Jvec_exi_Bform(self): + self.assertTrue(derivTest('b', 'exi')) + def test_Jvec_eyi_Bform(self): + self.assertTrue(derivTest('b', 'eyi')) + def test_Jvec_ezi_Bform(self): + self.assertTrue(derivTest('b', 'ezi')) + + def test_Jvec_bxr_Bform(self): + self.assertTrue(derivTest('b', 'bxr')) + def test_Jvec_byr_Bform(self): + self.assertTrue(derivTest('b', 'byr')) + def test_Jvec_bzr_Bform(self): + self.assertTrue(derivTest('b', 'bzr')) + def test_Jvec_bxi_Bform(self): + self.assertTrue(derivTest('b', 'bxi')) + def test_Jvec_byi_Bform(self): + self.assertTrue(derivTest('b', 'byi')) + def test_Jvec_bzi_Bform(self): + self.assertTrue(derivTest('b', 'bzi')) + + if testHJ: + def test_Jvec_jxr_Jform(self): + self.assertTrue(derivTest('j', 'jxr')) + def test_Jvec_jyr_Jform(self): + self.assertTrue(derivTest('j', 'jyr')) + def test_Jvec_jzr_Jform(self): + self.assertTrue(derivTest('j', 'jzr')) + def test_Jvec_jxi_Jform(self): + self.assertTrue(derivTest('j', 'jxi')) + def test_Jvec_jyi_Jform(self): + self.assertTrue(derivTest('j', 'jyi')) + def test_Jvec_jzi_Jform(self): + self.assertTrue(derivTest('j', 'jzi')) + + def test_Jvec_hxr_Jform(self): + self.assertTrue(derivTest('j', 'hxr')) + def test_Jvec_hyr_Jform(self): + self.assertTrue(derivTest('j', 'hyr')) + def test_Jvec_hzr_Jform(self): + self.assertTrue(derivTest('j', 'hzr')) + def test_Jvec_hxi_Jform(self): + self.assertTrue(derivTest('j', 'hxi')) + def test_Jvec_hyi_Jform(self): + self.assertTrue(derivTest('j', 'hyi')) + def test_Jvec_hzi_Jform(self): + self.assertTrue(derivTest('j', 'hzi')) + + def test_Jvec_hxr_Hform(self): + self.assertTrue(derivTest('h', 'hxr')) + def test_Jvec_hyr_Hform(self): + self.assertTrue(derivTest('h', 'hyr')) + def test_Jvec_hzr_Hform(self): + self.assertTrue(derivTest('h', 'hzr')) + def test_Jvec_hxi_Hform(self): + self.assertTrue(derivTest('h', 'hxi')) + def test_Jvec_hyi_Hform(self): + self.assertTrue(derivTest('h', 'hyi')) + def test_Jvec_hzi_Hform(self): + self.assertTrue(derivTest('h', 'hzi')) + + def test_Jvec_hxr_Hform(self): + self.assertTrue(derivTest('h', 'jxr')) + def test_Jvec_hyr_Hform(self): + self.assertTrue(derivTest('h', 'jyr')) + def test_Jvec_hzr_Hform(self): + self.assertTrue(derivTest('h', 'jzr')) + def test_Jvec_hxi_Hform(self): + self.assertTrue(derivTest('h', 'jxi')) + def test_Jvec_hyi_Hform(self): + self.assertTrue(derivTest('h', 'jyi')) + def test_Jvec_hzi_Hform(self): + self.assertTrue(derivTest('h', 'jzi')) + + + if testAdjoint: + if testEB: + def test_Jtvec_adjointTest_exr_Eform(self): + self.assertTrue(adjointTest('e', 'exr')) + def test_Jtvec_adjointTest_eyr_Eform(self): + self.assertTrue(adjointTest('e', 'eyr')) + def test_Jtvec_adjointTest_ezr_Eform(self): + self.assertTrue(adjointTest('e', 'ezr')) + def test_Jtvec_adjointTest_exi_Eform(self): + self.assertTrue(adjointTest('e', 'exi')) + def test_Jtvec_adjointTest_eyi_Eform(self): + self.assertTrue(adjointTest('e', 'eyi')) + def test_Jtvec_adjointTest_ezi_Eform(self): + self.assertTrue(adjointTest('e', 'ezi')) + + def test_Jtvec_adjointTest_bxr_Eform(self): + self.assertTrue(adjointTest('e', 'bxr')) + def test_Jtvec_adjointTest_byr_Eform(self): + self.assertTrue(adjointTest('e', 'byr')) + def test_Jtvec_adjointTest_bzr_Eform(self): + self.assertTrue(adjointTest('e', 'bzr')) + def test_Jtvec_adjointTest_bxi_Eform(self): + self.assertTrue(adjointTest('e', 'bxi')) + def test_Jtvec_adjointTest_byi_Eform(self): + self.assertTrue(adjointTest('e', 'byi')) + def test_Jtvec_adjointTest_bzi_Eform(self): + self.assertTrue(adjointTest('e', 'bzi')) + + def test_Jtvec_adjointTest_exr_Bform(self): + self.assertTrue(adjointTest('b', 'exr')) + def test_Jtvec_adjointTest_eyr_Bform(self): + self.assertTrue(adjointTest('b', 'eyr')) + def test_Jtvec_adjointTest_ezr_Bform(self): + self.assertTrue(adjointTest('b', 'ezr')) + def test_Jtvec_adjointTest_exi_Bform(self): + self.assertTrue(adjointTest('b', 'exi')) + def test_Jtvec_adjointTest_eyi_Bform(self): + self.assertTrue(adjointTest('b', 'eyi')) + def test_Jtvec_adjointTest_ezi_Bform(self): + self.assertTrue(adjointTest('b', 'ezi')) + def test_Jtvec_adjointTest_bxr_Bform(self): + self.assertTrue(adjointTest('b', 'bxr')) + def test_Jtvec_adjointTest_byr_Bform(self): + self.assertTrue(adjointTest('b', 'byr')) + def test_Jtvec_adjointTest_bzr_Bform(self): + self.assertTrue(adjointTest('b', 'bzr')) + def test_Jtvec_adjointTest_bxi_Bform(self): + self.assertTrue(adjointTest('b', 'bxi')) + def test_Jtvec_adjointTest_byi_Bform(self): + self.assertTrue(adjointTest('b', 'byi')) + def test_Jtvec_adjointTest_bzi_Bform(self): + self.assertTrue(adjointTest('b', 'bzi')) + + + if testHJ: + def test_Jtvec_adjointTest_jxr_Jform(self): + self.assertTrue(adjointTest('j', 'jxr')) + def test_Jtvec_adjointTest_jyr_Jform(self): + self.assertTrue(adjointTest('j', 'jyr')) + def test_Jtvec_adjointTest_jzr_Jform(self): + self.assertTrue(adjointTest('j', 'jzr')) + def test_Jtvec_adjointTest_jxi_Jform(self): + self.assertTrue(adjointTest('j', 'jxi')) + def test_Jtvec_adjointTest_jyi_Jform(self): + self.assertTrue(adjointTest('j', 'jyi')) + def test_Jtvec_adjointTest_jzi_Jform(self): + self.assertTrue(adjointTest('j', 'jzi')) + + def test_Jtvec_adjointTest_hxr_Jform(self): + self.assertTrue(adjointTest('j', 'hxr')) + def test_Jtvec_adjointTest_hyr_Jform(self): + self.assertTrue(adjointTest('j', 'hyr')) + def test_Jtvec_adjointTest_hzr_Jform(self): + self.assertTrue(adjointTest('j', 'hzr')) + def test_Jtvec_adjointTest_hxi_Jform(self): + self.assertTrue(adjointTest('j', 'hxi')) + def test_Jtvec_adjointTest_hyi_Jform(self): + self.assertTrue(adjointTest('j', 'hyi')) + def test_Jtvec_adjointTest_hzi_Jform(self): + self.assertTrue(adjointTest('j', 'hzi')) + + def test_Jtvec_adjointTest_hxr_Hform(self): + self.assertTrue(adjointTest('h', 'hxr')) + def test_Jtvec_adjointTest_hyr_Hform(self): + self.assertTrue(adjointTest('h', 'hyr')) + def test_Jtvec_adjointTest_hzr_Hform(self): + self.assertTrue(adjointTest('h', 'hzr')) + def test_Jtvec_adjointTest_hxi_Hform(self): + self.assertTrue(adjointTest('h', 'hxi')) + def test_Jtvec_adjointTest_hyi_Hform(self): + self.assertTrue(adjointTest('h', 'hyi')) + def test_Jtvec_adjointTest_hzi_Hform(self): + self.assertTrue(adjointTest('h', 'hzi')) + + def test_Jtvec_adjointTest_hxr_Hform(self): + self.assertTrue(adjointTest('h', 'jxr')) + def test_Jtvec_adjointTest_hyr_Hform(self): + self.assertTrue(adjointTest('h', 'jyr')) + def test_Jtvec_adjointTest_hzr_Hform(self): + self.assertTrue(adjointTest('h', 'jzr')) + def test_Jtvec_adjointTest_hxi_Hform(self): + self.assertTrue(adjointTest('h', 'jxi')) + def test_Jtvec_adjointTest_hyi_Hform(self): + self.assertTrue(adjointTest('h', 'jyi')) + def test_Jtvec_adjointTest_hzi_Hform(self): + self.assertTrue(adjointTest('h', 'jzi')) + + + if testCrossCheck: + if testEB: + def test_EB_CrossCheck_exr_Eform(self): + self.assertTrue(crossCheckTest('e', 'exr')) + def test_EB_CrossCheck_eyr_Eform(self): + self.assertTrue(crossCheckTest('e', 'eyr')) + def test_EB_CrossCheck_ezr_Eform(self): + self.assertTrue(crossCheckTest('e', 'ezr')) + def test_EB_CrossCheck_exi_Eform(self): + self.assertTrue(crossCheckTest('e', 'exi')) + def test_EB_CrossCheck_eyi_Eform(self): + self.assertTrue(crossCheckTest('e', 'eyi')) + def test_EB_CrossCheck_ezi_Eform(self): + self.assertTrue(crossCheckTest('e', 'ezi')) + + def test_EB_CrossCheck_bxr_Eform(self): + self.assertTrue(crossCheckTest('e', 'bxr')) + def test_EB_CrossCheck_byr_Eform(self): + self.assertTrue(crossCheckTest('e', 'byr')) + def test_EB_CrossCheck_bzr_Eform(self): + self.assertTrue(crossCheckTest('e', 'bzr')) + def test_EB_CrossCheck_bxi_Eform(self): + self.assertTrue(crossCheckTest('e', 'bxi')) + def test_EB_CrossCheck_byi_Eform(self): + self.assertTrue(crossCheckTest('e', 'byi')) + def test_EB_CrossCheck_bzi_Eform(self): + self.assertTrue(crossCheckTest('e', 'bzi')) + + if testHJ: + def test_HJ_CrossCheck_jxr_Jform(self): + self.assertTrue(crossCheckTest('j', 'jxr')) + def test_HJ_CrossCheck_jyr_Jform(self): + self.assertTrue(crossCheckTest('j', 'jyr')) + def test_HJ_CrossCheck_jzr_Jform(self): + self.assertTrue(crossCheckTest('j', 'jzr')) + def test_HJ_CrossCheck_jxi_Jform(self): + self.assertTrue(crossCheckTest('j', 'jxi')) + def test_HJ_CrossCheck_jyi_Jform(self): + self.assertTrue(crossCheckTest('j', 'jyi')) + def test_HJ_CrossCheck_jzi_Jform(self): + self.assertTrue(crossCheckTest('j', 'jzi')) + + def test_HJ_CrossCheck_hxr_Jform(self): + self.assertTrue(crossCheckTest('j', 'hxr')) + def test_HJ_CrossCheck_hyr_Jform(self): + self.assertTrue(crossCheckTest('j', 'hyr')) + def test_HJ_CrossCheck_hzr_Jform(self): + self.assertTrue(crossCheckTest('j', 'hzr')) + def test_HJ_CrossCheck_hxi_Jform(self): + self.assertTrue(crossCheckTest('j', 'hxi')) + def test_HJ_CrossCheck_hyi_Jform(self): + self.assertTrue(crossCheckTest('j', 'hyi')) + def test_HJ_CrossCheck_hzi_Jform(self): + self.assertTrue(crossCheckTest('j', 'hzi')) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_FDEMCasing.py b/tests/em/test_FDEMCasing.py new file mode 100644 index 00000000..c30e4ac8 --- /dev/null +++ b/tests/em/test_FDEMCasing.py @@ -0,0 +1,62 @@ +from SimPEG import Tests, Utils, np +import SimPEG.EM.Analytics.FDEMcasing as Casing +import unittest +from scipy.constants import mu_0 + + +n = 50. +freq = 1. +a = 5e-2 +b = a + 1e-2 +sigma = np.r_[10., 5.5e6, 1e-1] +mu = mu_0*np.r_[1.,100.,1.] +srcloc = np.r_[0., 0., 0.] +xobs = np.random.rand(n)+10. +yobs = np.zeros(n) +zobs = np.random.randn(n) +plotit = False + +def CasingMagDipoleDeriv_r(x): + obsloc = np.vstack([x, yobs, zobs]).T + + f = Casing._getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu) + g = Utils.sdiag(Casing._getCasingHertzMagDipoleDeriv_r(srcloc,obsloc,freq,sigma,a,b,mu)) + + return f,g + +def CasingMagDipoleDeriv_z(z): + obsloc = np.vstack([xobs, yobs, z]).T + + f = Casing._getCasingHertzMagDipole(srcloc,obsloc,freq,sigma,a,b,mu) + g = Utils.sdiag(Casing._getCasingHertzMagDipoleDeriv_z(srcloc,obsloc,freq,sigma,a,b,mu)) + + return f,g + +def CasingMagDipole2Deriv_z_r(x): + obsloc = np.vstack([x, yobs, zobs]).T + + f = Casing._getCasingHertzMagDipoleDeriv_z(srcloc,obsloc,freq,sigma,a,b,mu) + g = Utils.sdiag(Casing._getCasingHertzMagDipole2Deriv_z_r(srcloc,obsloc,freq,sigma,a,b,mu)) + + return f,g + +def CasingMagDipole2Deriv_z_z(z): + obsloc = np.vstack([xobs, yobs, z]).T + + f = Casing._getCasingHertzMagDipoleDeriv_z(srcloc,obsloc,freq,sigma,a,b,mu) + g = Utils.sdiag(Casing._getCasingHertzMagDipole2Deriv_z_z(srcloc,obsloc,freq,sigma,a,b,mu)) + + return f,g + + + +class Casing_DerivTest(unittest.TestCase): + def test_derivs(self): + Tests.checkDerivative(CasingMagDipoleDeriv_r,np.ones(n)*10+np.random.randn(n),plotIt=False) + Tests.checkDerivative(CasingMagDipoleDeriv_z,np.random.randn(n),plotIt=False) + Tests.checkDerivative(CasingMagDipole2Deriv_z_r,np.ones(n)*10+np.random.randn(n),plotIt=False) + Tests.checkDerivative(CasingMagDipole2Deriv_z_z,np.random.randn(n),plotIt=False) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_FDEM_analytics.py b/tests/em/test_FDEM_analytics.py new file mode 100644 index 00000000..cb906262 --- /dev/null +++ b/tests/em/test_FDEM_analytics.py @@ -0,0 +1,243 @@ +import unittest +from SimPEG import * +from SimPEG import EM +from scipy.constants import mu_0 + +plotIt = False +tol_EBdipole = 1e-2 + +if plotIt: + import matplotlib.pylab + + +class FDEM_analyticTests(unittest.TestCase): + + def setUp(self): + + cs = 10. + ncx, ncy, ncz = 10, 10, 10 + npad = 4 + freq = 1e2 + + hx = [(cs,npad,-1.3), (cs,ncx), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,ncy), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,ncz), (cs,npad,1.3)] + mesh = Mesh.TensorMesh([hx,hy,hz], 'CCC') + + mapping = Maps.ExpMap(mesh) + + x = np.linspace(-10,10,5) + XYZ = Utils.ndgrid(x,np.r_[0],np.r_[0]) + rxList = EM.FDEM.RxFDEM(XYZ, 'exi') + Src0 = EM.FDEM.SrcFDEM_MagDipole([rxList],loc=np.r_[0.,0.,0.], freq=freq) + + survey = EM.FDEM.SurveyFDEM([Src0]) + + prb = EM.FDEM.ProblemFDEM_b(mesh, mapping=mapping) + prb.pair(survey) + + try: + from pymatsolver import MumpsSolver + prb.Solver = MumpsSolver + except ImportError, e: + prb.Solver = SolverLU + + sig = 1e-1 + sigma = np.ones(mesh.nC)*sig + sigma[mesh.gridCC[:,2] > 0] = 1e-8 + m = np.log(sigma) + + self.prb = prb + self.mesh = mesh + self.m = m + self.Src0 = Src0 + self.sig = sig + + def test_Transect(self): + print 'Testing Transect for analytic' + + u = self.prb.fields(self.m) + + bfz = self.mesh.r(u[self.Src0, 'b'],'F','Fz','M') + x = np.linspace(-55,55,12) + XYZ = Utils.ndgrid(x,np.r_[0],np.r_[0]) + + P = self.mesh.getInterpolationMat(XYZ, 'Fz') + + an = EM.Analytics.FDEM.hzAnalyticDipoleF(x, self.Src0.freq, self.sig) + + diff = np.log10(np.abs(P*np.imag(u[self.Src0, 'b']) - mu_0*np.imag(an))) + + if plotIt: + import matplotlib.pyplot as plt + plt.plot(x,np.log10(np.abs(P*np.imag(u[self.Src0, 'b'])))) + plt.plot(x,np.log10(np.abs(mu_0*np.imag(an))), 'r') + plt.plot(x,diff,'g') + plt.show() + + # We want the difference to be an orderMag less + # than the analytic solution. Note that right at + # the source, both the analytic and the numerical + # solution will be poor. Use plotIt up top to see that... + orderMag = 1.6 + passed = np.abs(np.mean(diff - np.log10(np.abs(mu_0*np.imag(an))))) > orderMag + self.assertTrue(passed) + + + def test_CylMeshEBDipoles(self): + print 'Testing CylMesh Electric and Magnetic Dipoles in a wholespace- Analytic: J-formulation' + sigmaback = 1. + mur = 2. + freq = 1. + skdpth = 500./np.sqrt(sigmaback*freq) + + csx, ncx, npadx = 5, 50, 25 + csz, ncz, npadz = 5, 50, 25 + hx = Utils.meshTensor([(csx,ncx), (csx,npadx,1.3)]) + hz = Utils.meshTensor([(csz,npadz,-1.3), (csz,ncz), (csz,npadz,1.3)]) + mesh = Mesh.CylMesh([hx,1,hz], [0.,0.,-hz.sum()/2]) # define the cylindrical mesh + + if plotIt: + mesh.plotGrid() + + # make sure mesh is big enough + self.assertTrue(mesh.hz.sum() > skdpth*2.) + self.assertTrue(mesh.hx.sum() > skdpth*2.) + + SigmaBack = sigmaback*np.ones((mesh.nC)) + MuBack = mur*mu_0*np.ones((mesh.nC)) + + # set up source + # test electric dipole + src_loc = np.r_[0.,0.,0.] + s_ind = Utils.closestPoints(mesh,src_loc,'Fz') + mesh.nFx + + de = np.zeros(mesh.nF,dtype=complex) + de[s_ind] = 1./csz + de_p = [EM.FDEM.SrcFDEM_RawVec_e([],freq,de/mesh.area)] + + dm_p = [EM.FDEM.SrcFDEM_MagDipole([],freq,src_loc)] + + + # Pair the problem and survey + surveye = EM.FDEM.SurveyFDEM(de_p) + surveym = EM.FDEM.SurveyFDEM(dm_p) + + mapping = [('sigma', Maps.IdentityMap(mesh)),('mu', Maps.IdentityMap(mesh))] + + prbe = EM.FDEM.ProblemFDEM_h(mesh, mapping=mapping) + prbm = EM.FDEM.ProblemFDEM_e(mesh, mapping=mapping) + + prbe.pair(surveye) # pair problem and survey + prbm.pair(surveym) + + # solve + fieldsBackE = prbe.fields(np.r_[SigmaBack, MuBack]) # Done + fieldsBackM = prbm.fields(np.r_[SigmaBack, MuBack]) # Done + + + rlim = [20.,500.] + lookAtTx = de_p + r = mesh.vectorCCx[np.argmin(np.abs(mesh.vectorCCx-rlim[0])):np.argmin(np.abs(mesh.vectorCCx-rlim[1]))] + z = 100. + + # where we choose to measure + XYZ = Utils.ndgrid(r, np.r_[0.], np.r_[z]) + + Pf = mesh.getInterpolationMat(XYZ, 'CC') + Zero = sp.csr_matrix(Pf.shape) + Pfx,Pfz = sp.hstack([Pf,Zero]),sp.hstack([Zero,Pf]) + + jn = fieldsBackE[de_p,'j'] + bn = fieldsBackM[dm_p,'b'] + + Rho = Utils.sdiag(1./SigmaBack) + Rho = sp.block_diag([Rho,Rho]) + + en = Rho*mesh.aveF2CCV*jn + bn = mesh.aveF2CCV*bn + + ex,ez = Pfx*en, Pfz*en + bx,bz = Pfx*bn, Pfz*bn + + # get analytic solution + exa, eya, eza = EM.Analytics.FDEM.ElectricDipoleWholeSpace(XYZ, src_loc, sigmaback, freq,orientation='Z',mu= mur*mu_0) + exa, eya, eza = Utils.mkvc(exa,2), Utils.mkvc(eya,2), Utils.mkvc(eza,2) + + bxa, bya, bza = EM.Analytics.FDEM.MagneticDipoleWholeSpace(XYZ, src_loc, sigmaback, freq,orientation='Z',mu= mur*mu_0) + bxa, bya, bza = Utils.mkvc(bxa,2), Utils.mkvc(bya,2), Utils.mkvc(bza,2) + + print ' comp, anayltic, numeric, num - ana, (num - ana)/ana' + print ' ex:', np.linalg.norm(exa), np.linalg.norm(ex), np.linalg.norm(exa-ex), np.linalg.norm(exa-ex)/np.linalg.norm(exa) + print ' ez:', np.linalg.norm(eza), np.linalg.norm(ez), np.linalg.norm(eza-ez), np.linalg.norm(eza-ez)/np.linalg.norm(eza) + + print ' bx:', np.linalg.norm(bxa), np.linalg.norm(bx), np.linalg.norm(bxa-bx), np.linalg.norm(bxa-bx)/np.linalg.norm(bxa) + print ' bz:', np.linalg.norm(bza), np.linalg.norm(bz), np.linalg.norm(bza-bz), np.linalg.norm(bza-bz)/np.linalg.norm(bza) + + if plotIt: + # Edipole + plt.subplot(221) + plt.plot(r,ex.real,'o',r,exa.real,linewidth=2) + plt.grid(which='both') + plt.title('Ex Real') + plt.xlabel('r (m)') + + plt.subplot(222) + plt.plot(r,ex.imag,'o',r,exa.imag,linewidth=2) + plt.grid(which='both') + plt.title('Ex Imag') + plt.legend(['Num','Ana'],bbox_to_anchor=(1.5,0.5)) + plt.xlabel('r (m)') + + plt.subplot(223) + plt.plot(r,ez.real,'o',r,eza.real,linewidth=2) + plt.grid(which='both') + plt.title('Ez Real') + plt.xlabel('r (m)') + + plt.subplot(224) + plt.plot(r,ez.imag,'o',r,eza.imag,linewidth=2) + plt.grid(which='both') + plt.title('Ez Imag') + plt.xlabel('r (m)') + + plt.tight_layout() + + # Bdipole + plt.subplot(221) + plt.plot(r,bx.real,'o',r,bxa.real,linewidth=2) + plt.grid(which='both') + plt.title('Bx Real') + plt.xlabel('r (m)') + + plt.subplot(222) + plt.plot(r,bx.imag,'o',r,bxa.imag,linewidth=2) + plt.grid(which='both') + plt.title('Bx Imag') + plt.legend(['Num','Ana'],bbox_to_anchor=(1.5,0.5)) + plt.xlabel('r (m)') + + plt.subplot(223) + plt.plot(r,bz.real,'o',r,bza.real,linewidth=2) + plt.grid(which='both') + plt.title('Bz Real') + plt.xlabel('r (m)') + + plt.subplot(224) + plt.plot(r,bz.imag,'o',r,bza.imag,linewidth=2) + plt.grid(which='both') + plt.title('Bz Imag') + plt.xlabel('r (m)') + + plt.tight_layout() + + self.assertTrue(np.linalg.norm(exa-ex)/np.linalg.norm(exa) < tol_EBdipole) + self.assertTrue(np.linalg.norm(eza-ez)/np.linalg.norm(eza) < tol_EBdipole) + + self.assertTrue(np.linalg.norm(bxa-bx)/np.linalg.norm(bxa) < tol_EBdipole) + self.assertTrue(np.linalg.norm(bza-bz)/np.linalg.norm(bza) < tol_EBdipole) + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_TDEM_b_DerivAdjoint.py b/tests/em/test_TDEM_b_DerivAdjoint.py new file mode 100644 index 00000000..570c808b --- /dev/null +++ b/tests/em/test_TDEM_b_DerivAdjoint.py @@ -0,0 +1,314 @@ +import unittest +from SimPEG import * +from SimPEG import EM + +plotIt = False +tol = 1e-6 + +class TDEM_bDerivTests(unittest.TestCase): + + def setUp(self): + + cs = 5. + ncx = 20 + ncy = 6 + npad = 20 + hx = [(cs,ncx), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,ncy), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hy], '00C') + + active = mesh.vectorCCz<0. + activeMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * activeMap + + rxOffset = 40. + rx = EM.TDEM.RxTDEM(np.array([[rxOffset, 0., 0.]]), np.logspace(-4,-3, 20), 'bz') + src = EM.TDEM.SrcTDEM_VMD_MVP([rx], loc=np.array([0., 0., 0.])) + + survey = EM.TDEM.SurveyTDEM([src]) + + self.prb = EM.TDEM.ProblemTDEM_b(mesh, mapping=mapping) + # self.prb.timeSteps = [1e-5] + self.prb.timeSteps = [(1e-05, 10), (5e-05, 10), (2.5e-4, 10)] + # self.prb.timeSteps = [(1e-05, 100)] + + try: + from pymatsolver import MumpsSolver + self.prb.Solver = MumpsSolver + except ImportError, e: + self.prb.Solver = SolverLU + + self.sigma = np.ones(mesh.nCz)*1e-8 + self.sigma[mesh.vectorCCz<0] = 1e-1 + self.sigma = np.log(self.sigma[active]) + + self.prb.pair(survey) + self.mesh = mesh + + def test_AhVec(self): + """ + Test that fields and AhVec produce consistent results + """ + + prb = self.prb + sigma = self.sigma + + u = prb.fields(sigma) + Ahu = prb._AhVec(sigma, u) + + V1 = Ahu[:,'b',1] + V2 = 1./prb.timeSteps[0]*prb.MfMui*u[:,'b',0] + self.assertLess(np.linalg.norm(V1-V2)/np.linalg.norm(V2), 1.e-6) + + V1 = Ahu[:,'e',1] + return np.linalg.norm(V1) < 1.e-6 + + for i in range(2,prb.nT): + + dt = prb.timeSteps[i] + + V1 = Ahu[:,'b',i] + V2 = 1.0/dt*prb.MfMui*u[:,'b', i-1] + # print np.linalg.norm(V1), np.linalg.norm(V2) + self.assertLess(np.linalg.norm(V1)/np.linalg.norm(V2), 1.e-6) + + V1 = Ahu[:,'e',i] + V2 = prb.MeSigma*u[:,'e',i] + # print np.linalg.norm(V1), np.linalg.norm(V2) + return np.linalg.norm(V1)/np.linalg.norm(V2), 1.e-6 + + def test_AhVecVSMat_OneTS(self): + + prb = self.prb + prb.timeSteps = [1e-05] + sigma = self.sigma + prb.curModel = sigma + + dt = prb.timeSteps[0] + a11 = 1/dt*prb.MfMui*sp.identity(prb.mesh.nF) + a12 = prb.MfMui*prb.mesh.edgeCurl + a21 = prb.mesh.edgeCurl.T*prb.MfMui + a22 = -prb.MeSigma + A = sp.bmat([[a11,a12],[a21,a22]]) + + f = prb.fields(sigma) + u1 = A*f.tovec() + u2 = prb._AhVec(sigma,f).tovec() + + self.assertTrue(np.linalg.norm(u1-u2)/np.linalg.norm(u1)<1e-12) + + def test_solveAhVSMat_OneTS(self): + prb = self.prb + + prb.timeSteps = [1e-05] + + sigma = self.sigma + prb.curModel = sigma + + dt = prb.timeSteps[0] + a11 = 1.0/dt*prb.MfMui*sp.identity(prb.mesh.nF) + a12 = prb.MfMui*prb.mesh.edgeCurl + a21 = prb.mesh.edgeCurl.T*prb.MfMui + a22 = -prb.MeSigma + A = sp.bmat([[a11,a12],[a21,a22]]) + + f = prb.fields(sigma) + f[:,:,0] = {'b':0} + f[:,'b',1] = 0 + + self.assertTrue(np.all(np.r_[f[:,'b',1],f[:,'e',1]] == f.tovec())) + + u1 = prb.solveAh(sigma,f).tovec().flatten() + u2 = sp.linalg.spsolve(A.tocsr(),f.tovec()) + + self.assertTrue(np.linalg.norm(u1-u2)<1e-8) + + def test_solveAhVsAhVec(self): + + prb = self.prb + mesh = self.prb.mesh + sigma = self.sigma + self.prb.curModel = sigma + + f = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + f[:,'b',:] = 0.0 + for i in range(prb.nT): + f[:,'e', i] = np.random.rand(mesh.nE, 1) + + Ahf = prb._AhVec(sigma, f) + f_test = prb.solveAh(sigma, Ahf) + + u1 = f.tovec() + u2 = f_test.tovec() + self.assertTrue(np.linalg.norm(u1-u2)<1e-8) + + def test_DerivG(self): + """ + Test the derivative of c with respect to sigma + """ + + # Random model and perturbation + sigma = np.random.rand(self.prb.mapping.nP) + + f = self.prb.fields(sigma) + dm = 1000*np.random.rand(self.prb.mapping.nP) + h = 0.01 + + derChk = lambda m: [self.prb._AhVec(m, f).tovec(), lambda mx: self.prb.Gvec(sigma, mx, u=f).tovec()] + print '\ntest_DerivG' + passed = Tests.checkDerivative(derChk, sigma, plotIt=False, dx=dm, num=4, eps=1e-20) + return passed + + def test_Deriv_dUdM(self): + + prb = self.prb + prb.timeSteps = [(1e-05, 10), (0.0001, 10), (0.001, 10)] + mesh = self.mesh + sigma = self.sigma + + dm = 10*np.random.rand(prb.mapping.nP) + f = prb.fields(sigma) + + derChk = lambda m: [self.prb.fields(m).tovec(), lambda mx: -prb.solveAh(sigma, prb.Gvec(sigma, mx, u=f)).tovec()] + print '\n' + print 'test_Deriv_dUdM' + Tests.checkDerivative(derChk, sigma, plotIt=False, dx=dm, num=4, eps=1e-20) + + def test_Deriv_J(self): + + prb = self.prb + prb.timeSteps = [(1e-05, 10), (0.0001, 10), (0.001, 10)] + mesh = self.mesh + sigma = self.sigma + + # d_sig = 0.8*sigma #np.random.rand(mesh.nCz) + d_sig = 10*np.random.rand(prb.mapping.nP) + + + derChk = lambda m: [prb.survey.dpred(m), lambda mx: prb.Jvec(sigma, mx)] + print '\n' + print 'test_Deriv_J' + Tests.checkDerivative(derChk, sigma, plotIt=False, dx=d_sig, num=4, eps=1e-20) + + def test_projectAdjoint(self): + prb = self.prb + survey = prb.survey + mesh = self.mesh + + # Generate random fields and data + f = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(prb.nT): + f[:,'b',i] = np.random.rand(mesh.nF, 1) + f[:,'e',i] = np.random.rand(mesh.nE, 1) + d_vec = np.random.rand(survey.nD) + d = Survey.Data(survey,v=d_vec) + + # Check that d.T*Q*f = f.T*Q.T*d + V1 = d_vec.dot(survey.projectFieldsDeriv(None, v=f).tovec()) + V2 = f.tovec().dot(survey.projectFieldsDeriv(None, v=d, adjoint=True).tovec()) + + self.assertTrue((V1-V2)/np.abs(V1) < tol) + + def test_adjointAhVsAht(self): + prb = self.prb + mesh = self.mesh + sigma = self.sigma + + f1 = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + f1[:,'b',i] = np.random.rand(mesh.nF, 1) + f1[:,'e',i] = np.random.rand(mesh.nE, 1) + + f2 = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + f2[:,'b',i] = np.random.rand(mesh.nF, 1) + f2[:,'e',i] = np.random.rand(mesh.nE, 1) + + V1 = f2.tovec().dot(prb._AhVec(sigma, f1).tovec()) + V2 = f1.tovec().dot(prb._AhtVec(sigma, f2).tovec()) + self.assertTrue(np.abs(V1-V2)/np.abs(V1) < tol) + + # def test_solveAhtVsAhtVec(self): + # prb = self.prb + # mesh = self.mesh + # sigma = np.random.rand(prb.mapping.nP) + + # f1 = EM.TDEM.FieldsTDEM(mesh,prb.survey) + # for i in range(1,prb.nT+1): + # f1[:,'b',i] = np.random.rand(mesh.nF, 1) + # f1[:,'e',i] = np.random.rand(mesh.nE, 1) + + # f2 = prb.solveAht(sigma, f1) + # f3 = prb._AhtVec(sigma, f2) + + # if True: + # import matplotlib.pyplot as plt + # plt.plot(f3.tovec(),'b') + # plt.plot(f1.tovec(),'r') + # plt.show() + # V1 = np.linalg.norm(f3.tovec()-f1.tovec()) + # V2 = np.linalg.norm(f1.tovec()) + # print 'AhtVsAhtVec', V1, V2, f1.tovec() + # print 'I am gunna fail this one: boo. :(' + # self.assertLess(V1/V2, 1e-6) + + # def test_adjointsolveAhVssolveAht(self): + # prb = self.prb + # mesh = self.mesh + # sigma = self.sigma + + # f1 = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + # for i in range(1,prb.nT+1): + # f1[:,'b',i] = np.random.rand(mesh.nF, 1) + # f1[:,'e',i] = np.random.rand(mesh.nE, 1) + + # f2 = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + # for i in range(1,prb.nT+1): + # f2[:,'b',i] = np.random.rand(mesh.nF, 1) + # f2[:,'e',i] = np.random.rand(mesh.nE, 1) + + # V1 = f2.tovec().dot(prb.solveAh(sigma, f1).tovec()) + # V2 = f1.tovec().dot(prb.solveAht(sigma, f2).tovec()) + # print V1, V2 + # self.assertLess(np.abs(V1-V2)/np.abs(V1), 1e-6) + + def test_adjointGvecVsGtvec(self): + mesh = self.mesh + prb = self.prb + + m = np.random.rand(prb.mapping.nP) + sigma = np.random.rand(prb.mapping.nP) + + u = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + u[:,'b',i] = np.random.rand(mesh.nF, 1) + u[:,'e',i] = np.random.rand(mesh.nE, 1) + + v = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + v[:,'b',i] = np.random.rand(mesh.nF, 1) + v[:,'e',i] = np.random.rand(mesh.nE, 1) + + V1 = m.dot(prb.Gtvec(sigma, v, u)) + V2 = v.tovec().dot(prb.Gvec(sigma, m, u).tovec()) + self.assertTrue(np.abs(V1-V2)/np.abs(V1) < tol) + + def test_adjointJvecVsJtvec(self): + mesh = self.mesh + prb = self.prb + sigma = self.sigma + + m = np.random.rand(prb.mapping.nP) + d = np.random.rand(prb.survey.nD) + + V1 = d.dot(prb.Jvec(sigma, m)) + V2 = m.dot(prb.Jtvec(sigma, d)) + passed = np.abs(V1-V2)/np.abs(V1) < tol + print 'AdjointTest', V1, V2, passed + self.assertTrue(passed) + + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_TDEM_b_MultiSrc_DerivAdjoint.py b/tests/em/test_TDEM_b_MultiSrc_DerivAdjoint.py new file mode 100644 index 00000000..de261a10 --- /dev/null +++ b/tests/em/test_TDEM_b_MultiSrc_DerivAdjoint.py @@ -0,0 +1,153 @@ +import unittest +from SimPEG import * +from SimPEG import EM + +plotIt = False + +class TDEM_bDerivTests(unittest.TestCase): + + def setUp(self): + + cs = 5. + ncx = 20 + ncy = 6 + npad = 20 + hx = [(cs,ncx), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,ncy), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hy], '00C') + + active = mesh.vectorCCz<0. + activeMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * activeMap + + rxOffset = 40. + rx = EM.TDEM.RxTDEM(np.array([[rxOffset, 0., 0.]]), np.logspace(-4,-3, 20), 'bz') + src = EM.TDEM.SrcTDEM_VMD_MVP( [rx], loc=np.array([0., 0., 0.])) + rx2 = EM.TDEM.RxTDEM(np.array([[rxOffset-10, 0., 0.]]), np.logspace(-5,-4, 25), 'bz') + src2 = EM.TDEM.SrcTDEM_VMD_MVP( [rx2], loc=np.array([0., 0., 0.])) + + survey = EM.TDEM.SurveyTDEM([src,src2]) + + self.prb = EM.TDEM.ProblemTDEM_b(mesh, mapping=mapping) + # self.prb.timeSteps = [1e-5] + self.prb.timeSteps = [(1e-05, 10), (5e-05, 10), (2.5e-4, 10)] + # self.prb.timeSteps = [(1e-05, 100)] + + try: + from pymatsolver import MumpsSolver + self.prb.Solver = MumpsSolver + except ImportError, e: + self.prb.Solver = SolverLU + + self.sigma = np.ones(mesh.nCz)*1e-8 + self.sigma[mesh.vectorCCz<0] = 1e-1 + self.sigma = np.log(self.sigma[active]) + + self.prb.pair(survey) + self.mesh = mesh + + def test_DerivG(self): + """ + Test the derivative of c with respect to sigma + """ + + # Random model and perturbation + sigma = np.random.rand(self.prb.mapping.nP) + + f = self.prb.fields(sigma) + dm = 1000*np.random.rand(self.prb.mapping.nP) + h = 0.01 + + derChk = lambda m: [self.prb._AhVec(m, f).tovec(), lambda mx: self.prb.Gvec(sigma, mx, u=f).tovec()] + print '\ntest_DerivG' + Tests.checkDerivative(derChk, sigma, plotIt=False, dx=dm, num=4, eps=1e-20) + + def test_Deriv_dUdM(self): + + prb = self.prb + prb.timeSteps = [(1e-05, 10), (0.0001, 10), (0.001, 10)] + mesh = self.mesh + sigma = self.sigma + + dm = 10*np.random.rand(prb.mapping.nP) + f = prb.fields(sigma) + + derChk = lambda m: [self.prb.fields(m).tovec(), lambda mx: -prb.solveAh(sigma, prb.Gvec(sigma, mx, u=f)).tovec()] + print '\n' + print 'test_Deriv_dUdM' + Tests.checkDerivative(derChk, sigma, plotIt=False, dx=dm, num=4, eps=1e-20) + + def test_Deriv_J(self): + + prb = self.prb + prb.timeSteps = [(1e-05, 10), (0.0001, 10), (0.001, 10)] + mesh = self.mesh + sigma = self.sigma + + # d_sig = 0.8*sigma #np.random.rand(mesh.nCz) + d_sig = 10*np.random.rand(prb.mapping.nP) + + + derChk = lambda m: [prb.survey.dpred(m), lambda mx: prb.Jvec(sigma, mx)] + print '\n' + print 'test_Deriv_J' + Tests.checkDerivative(derChk, sigma, plotIt=False, dx=d_sig, num=4, eps=1e-20) + + def test_projectAdjoint(self): + prb = self.prb + survey = prb.survey + nSrc = survey.nSrc + mesh = self.mesh + + # Generate random fields and data + f = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(prb.nT): + f[:,'b',i] = np.random.rand(mesh.nF, nSrc) + f[:,'e',i] = np.random.rand(mesh.nE, nSrc) + d_vec = np.random.rand(survey.nD) + d = Survey.Data(survey,v=d_vec) + + # Check that d.T*Q*f = f.T*Q.T*d + V1 = d_vec.dot(survey.projectFieldsDeriv(None, v=f).tovec()) + V2 = np.sum((f.tovec())*(survey.projectFieldsDeriv(None, v=d, adjoint=True).tovec())) + + self.assertTrue((V1-V2)/np.abs(V1) < 1e-6) + + def test_adjointGvecVsGtvec(self): + mesh = self.mesh + prb = self.prb + + m = np.random.rand(prb.mapping.nP) + sigma = np.random.rand(prb.mapping.nP) + + u = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + u[:,'b',i] = np.random.rand(mesh.nF, 2) + u[:,'e',i] = np.random.rand(mesh.nE, 2) + + v = EM.TDEM.FieldsTDEM(prb.mesh, prb.survey) + for i in range(1,prb.nT+1): + v[:,'b',i] = np.random.rand(mesh.nF, 2) + v[:,'e',i] = np.random.rand(mesh.nE, 2) + + V1 = m.dot(prb.Gtvec(sigma, v, u)) + V2 = np.sum(v.tovec()*prb.Gvec(sigma, m, u).tovec()) + self.assertTrue(np.abs(V1-V2)/np.abs(V1) <1e-6) + + def test_adjointJvecVsJtvec(self): + mesh = self.mesh + prb = self.prb + sigma = self.sigma + + m = np.random.rand(prb.mapping.nP) + d = np.random.rand(prb.survey.nD) + + V1 = d.dot(prb.Jvec(sigma, m)) + V2 = m.dot(prb.Jtvec(sigma, d)) + print 'AdjointTest', V1, V2 + self.assertTrue(np.abs(V1-V2)/np.abs(V1) < 1e-6) + + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_TDEM_combos.py b/tests/em/test_TDEM_combos.py new file mode 100644 index 00000000..1f538c3c --- /dev/null +++ b/tests/em/test_TDEM_combos.py @@ -0,0 +1,94 @@ +import unittest +from SimPEG import * +from SimPEG import EM + +plotIt = False + +def getProb(meshType='CYL',rxTypes='bx,bz',nSrc=1): + cs = 5. + ncx = 20 + ncy = 6 + npad = 20 + hx = [(cs,ncx), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,ncy), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hy], '00C') + + active = mesh.vectorCCz<0. + activeMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * activeMap + + rxOffset = 40. + + srcs = [] + for ii in range(nSrc): + rxs = [EM.TDEM.RxTDEM(np.array([[rxOffset, 0., 0.]]), np.logspace(-4,-3, 20 + ii), rxType) for rxType in rxTypes.split(',')] + srcs += [EM.TDEM.SrcTDEM_VMD_MVP(rxs,np.array([0., 0., 0.]))] + + survey = EM.TDEM.SurveyTDEM(srcs) + + prb = EM.TDEM.ProblemTDEM_b(mesh, mapping=mapping) + # prb.timeSteps = [1e-5] + prb.timeSteps = [(1e-05, 10), (5e-05, 10), (2.5e-4, 10)] + # prb.timeSteps = [(1e-05, 100)] + + try: + from pymatsolver import MumpsSolver + prb.Solver = MumpsSolver + except ImportError, e: + prb.Solver = SolverLU + + sigma = np.ones(mesh.nCz)*1e-8 + sigma[mesh.vectorCCz<0] = 1e-1 + sigma = np.log(sigma[active]) + + prb.pair(survey) + return prb, mesh, sigma + +def dotestJvec(prb, mesh, sigma): + prb.timeSteps = [(1e-05, 10), (0.0001, 10), (0.001, 10)] + # d_sig = 0.8*sigma #np.random.rand(mesh.nCz) + d_sig = 10*np.random.rand(prb.mapping.nP) + derChk = lambda m: [prb.survey.dpred(m), lambda mx: prb.Jvec(sigma, mx)] + return Tests.checkDerivative(derChk, sigma, plotIt=False, dx=d_sig, num=2, eps=1e-20) + +def dotestAdjoint(prb, mesh, sigma): + m = np.random.rand(prb.mapping.nP) + d = np.random.rand(prb.survey.nD) + + V1 = d.dot(prb.Jvec(sigma, m)) + V2 = m.dot(prb.Jtvec(sigma, d)) + print 'AdjointTest', V1, V2 + return np.abs(V1-V2)/np.abs(V1), 1e-6 + +class TDEM_bDerivTests(unittest.TestCase): + + def test_Jvec_bx(self): self.assertTrue(dotestJvec(*getProb(rxTypes='bx'))) + def test_Adjoint_bx(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='bx'))) + + def test_Jvec_bxbz(self): self.assertTrue(dotestJvec(*getProb(rxTypes='bx,bz'))) + def test_Adjoint_bxbz(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='bx,bz'))) + + def test_Jvec_bxbz_2src(self): self.assertTrue(dotestJvec(*getProb(rxTypes='bx,bz',nSrc=2))) + def test_Adjoint_bxbz_2src(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='bx,bz',nSrc=2))) + + def test_Jvec_bxbzbz(self): self.assertTrue(dotestJvec(*getProb(rxTypes='bx,bz,bz'))) + def test_Adjoint_bxbzbz(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='bx,bz,bz'))) + + def test_Jvec_dbxdt(self): self.assertTrue(dotestJvec(*getProb(rxTypes='dbxdt'))) + def test_Adjoint_dbxdt(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='dbxdt'))) + + def test_Jvec_dbzdt(self): self.assertTrue(dotestJvec(*getProb(rxTypes='dbzdt'))) + def test_Adjoint_dbzdt(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='dbzdt'))) + + def test_Jvec_dbxdtbz(self): self.assertTrue(dotestJvec(*getProb(rxTypes='dbxdt,bz'))) + def test_Adjoint_dbxdtbz(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='dbxdt,bz'))) + + def test_Jvec_ey(self): self.assertTrue(dotestJvec(*getProb(rxTypes='ey'))) + def test_Adjoint_ey(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='ey'))) + + def test_Jvec_eybzdbxdt(self): self.assertTrue(dotestJvec(*getProb(rxTypes='ey,bz,dbxdt'))) + def test_Adjoint_eybzdbxdt(self): self.assertLess(*dotestAdjoint(*getProb(rxTypes='ey,bz,dbxdt'))) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/em/test_TDEM_forward_Analytic.py b/tests/em/test_TDEM_forward_Analytic.py new file mode 100644 index 00000000..06890541 --- /dev/null +++ b/tests/em/test_TDEM_forward_Analytic.py @@ -0,0 +1,89 @@ +import unittest +from SimPEG import * +from SimPEG import EM +from scipy.constants import mu_0 +import matplotlib.pyplot as plt + +try: + from pymatsolver import MumpsSolver +except ImportError, e: + MumpsSolver = SolverLU + + +def halfSpaceProblemAnaDiff(meshType, sig_half=1e-2, rxOffset=50., bounds=[1e-5,1e-3], showIt=False): + if meshType == 'CYL': + cs, ncx, ncz, npad = 5., 30, 10, 15 + hx = [(cs,ncx), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,ncz), (cs,npad,1.3)] + mesh = Mesh.CylMesh([hx,1,hz], '00C') + elif meshType == 'TENSOR': + cs, nc, npad = 20., 13, 5 + hx = [(cs,npad,-1.3), (cs,nc), (cs,npad,1.3)] + hy = [(cs,npad,-1.3), (cs,nc), (cs,npad,1.3)] + hz = [(cs,npad,-1.3), (cs,nc), (cs,npad,1.3)] + mesh = Mesh.TensorMesh([hx,hy,hz], 'CCC') + + active = mesh.vectorCCz<0. + actMap = Maps.ActiveCells(mesh, active, np.log(1e-8), nC=mesh.nCz) + mapping = Maps.ExpMap(mesh) * Maps.Vertical1DMap(mesh) * actMap + + rx = EM.TDEM.RxTDEM(np.array([[rxOffset, 0., 0.]]), np.logspace(-5,-4, 21), 'bz') + src = EM.TDEM.SrcTDEM_VMD_MVP([rx], loc=np.array([0., 0., 0.])) + # src = EM.TDEM.SrcTDEM([rx], loc=np.array([0., 0., 0.])) + + survey = EM.TDEM.SurveyTDEM([src]) + prb = EM.TDEM.ProblemTDEM_b(mesh, mapping=mapping) + prb.Solver = MumpsSolver + + prb.timeSteps = [(1e-06, 40), (5e-06, 40), (1e-05, 40), (5e-05, 40), (0.0001, 40), (0.0005, 40)] + + sigma = np.ones(mesh.nCz)*1e-8 + sigma[active] = sig_half + sigma = np.log(sigma[active]) + prb.pair(survey) + + bz_ana = mu_0*EM.Analytics.hzAnalyticDipoleT(rx.locs[0][0]+1e-3, rx.times, sig_half) + + bz_calc = survey.dpred(sigma) + + ind = np.logical_and(rx.times > bounds[0],rx.times < bounds[1]) + log10diff = np.linalg.norm(np.log10(np.abs(bz_calc[ind])) - np.log10(np.abs(bz_ana[ind])))/np.linalg.norm(np.log10(np.abs(bz_ana[ind]))) + print 'Difference: ', log10diff + + if showIt == True: + plt.loglog(rx.times[bz_calc>0], bz_calc[bz_calc>0], 'r', rx.times[bz_calc<0], -bz_calc[bz_calc<0], 'r--') + plt.loglog(rx.times, abs(bz_ana), 'b*') + plt.title('sig_half = %e'%sig_half) + plt.show() + + return log10diff + + +class TDEM_bTests(unittest.TestCase): + + def test_analytic_p2_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e+2) < 0.01) + def test_analytic_p1_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e+1) < 0.01) + def test_analytic_p0_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e+0) < 0.01) + def test_analytic_m1_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e-1) < 0.01) + def test_analytic_m2_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e-2) < 0.01) + def test_analytic_m3_CYL_50m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=50., sig_half=1e-3) < 0.02) + + def test_analytic_p0_CYL_1m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=1.0, sig_half=1e+0) < 0.01) + def test_analytic_m1_CYL_1m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=1.0, sig_half=1e-1) < 0.01) + def test_analytic_m2_CYL_1m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=1.0, sig_half=1e-2) < 0.01) + def test_analytic_m3_CYL_1m(self): + self.assertTrue(halfSpaceProblemAnaDiff('CYL', rxOffset=1.0, sig_half=1e-3) < 0.02) + + + +if __name__ == '__main__': + unittest.main()