from SimPEG import Survey, Problem, Utils, np, sp, Solver as SimpegSolver from scipy.constants import mu_0 from SurveyFDEM import SurveyFDEM, FieldsFDEM from simpegEM.Utils import Sources from simpegEM.Base import BaseEMProblem def omega(freq): """Change frequency to angular frequency, omega""" return 2.*np.pi*freq class BaseFDEMProblem(BaseEMProblem): """ We start by looking at Maxwell's equations in the electric field \\(\\vec{E}\\) and the magnetic flux density \\(\\vec{B}\\): .. math:: \\nabla \\times \\vec{E} + i \\omega \\vec{B} = 0 \\\\ \\nabla \\times \\mu^{-1} \\vec{B} - \\sigma \\vec{E} = \\vec{J_s} """ surveyPair = SurveyFDEM def forward(self, m, RHS, CalcFields): F = FieldsFDEM(self.mesh, self.survey) for freq in self.survey.freqs: A = self.getA(freq) rhs = RHS(freq) solver = self.Solver(A, **self.solverOpts) sol = solver.solve(rhs) for fieldType in self.storeTheseFields: Txs = self.survey.getTransmitters(freq) F[Txs, fieldType] = CalcFields(sol, freq, fieldType) return F def Jvec(self, m, v, u=None): if u is None: u = self.fields(m) self.curModel = m Jv = self.dataPair(self.survey) for freq in self.survey.freqs: A = self.getA(freq) solver = self.Solver(A, **self.solverOpts) for tx in self.survey.getTransmitters(freq): u_tx = u[tx, self.solType] w = self.getADeriv(freq, u_tx, v) Ainvw = solver.solve(w) for rx in tx.rxList: fAinvw = self.calcFields(Ainvw, freq, rx.projField) P = lambda v: rx.projectFieldsDeriv(tx, self.mesh, u, v) df_dm = self.calcFieldsDeriv(u_tx, freq, rx.projField, v) if df_dm is None: Jv[tx, rx] = - P(fAinvw) else: Jv[tx, rx] = - P(fAinvw) + P(df_dm) return Utils.mkvc(Jv) def Jtvec(self, m, v, u=None): if u is None: u = 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(self.mapping.nP) for freq in self.survey.freqs: AT = self.getA(freq).T solver = self.Solver(AT, **self.solverOpts) for tx in self.survey.getTransmitters(freq): u_tx = u[tx, self.solType] for rx in tx.rxList: PTv = rx.projectFieldsDeriv(tx, self.mesh, u, v[tx, rx], adjoint=True) fPTv = self.calcFields(PTv, freq, rx.projField, adjoint=True) w = solver.solve( fPTv ) Jtv_rx = - self.getADeriv(freq, u_tx, w, adjoint=True) df_dm = self.calcFieldsDeriv(u_tx, freq, rx.projField, PTv, adjoint=True) if df_dm is not None: Jtv_rx += df_dm real_or_imag = rx.projComp if real_or_imag == 'real': Jtv += Jtv_rx.real elif real_or_imag == 'imag': Jtv += - Jtv_rx.real else: raise Exception('Must be real or imag') return Jtv class ProblemFDEM_e(BaseFDEMProblem): """ By eliminating the magnetic flux density using .. math:: \\vec{B} = \\frac{-1}{i\\omega}\\nabla\\times\\vec{E}, we can write Maxwell's equations as a second order system in \\ \\vec{E} \\ only: .. math:: \\nabla \\times \\mu^{-1} \\nabla \\times \\vec{E} + i \\omega \\sigma \\vec{E} = \\vec{J_s} This is the definition of the Forward Problem using the E-formulation of Maxwell's equations. """ solType = 'e' def __init__(self, model, **kwargs): BaseFDEMProblem.__init__(self, model, **kwargs) def getA(self, freq): """ :param float freq: Frequency :rtype: scipy.sparse.csr_matrix :return: A """ mui = self.MfMui sig = self.MeSigma C = self.mesh.edgeCurl return C.T*mui*C + 1j*omega(freq)*sig def getADeriv(self, freq, u, v, adjoint=False): sig = self.curTModel dsig_dm = self.curTModelDeriv dMe_dsig = self.mesh.getEdgeInnerProductDeriv(sig, v=u) if adjoint: return 1j * omega(freq) * ( dsig_dm.T * ( dMe_dsig.T * v ) ) return 1j * omega(freq) * ( dMe_dsig * ( dsig_dm * v ) ) def getRHS(self, freq): """ :param float freq: Frequency :rtype: numpy.ndarray (nE, nTx) :return: RHS """ Txs = self.survey.getTransmitters(freq) rhs = range(len(Txs)) for i, tx in enumerate(Txs): if tx.txType == 'VMD': src = Sources.MagneticDipoleVectorPotential else: raise NotImplemented('%s txType is not implemented' % tx.txType) SRCx = src(tx.loc, self.mesh.gridEx, 'x') SRCy = src(tx.loc, self.mesh.gridEy, 'y') SRCz = src(tx.loc, self.mesh.gridEz, 'z') rhs[i] = np.concatenate((SRCx, SRCy, SRCz)) a = np.concatenate(rhs).reshape((self.mesh.nE, len(Txs)), order='F') mui = self.MfMui C = self.mesh.edgeCurl j_s = C.T*mui*C*a return -1j*omega(freq)*j_s def calcFields(self, sol, freq, fieldType, adjoint=False): e = sol if fieldType == 'e': return e elif fieldType == 'b': if not adjoint: b = -(1./(1j*omega(freq))) * ( self.mesh.edgeCurl * e ) else: b = -(1./(1j*omega(freq))) * ( self.mesh.edgeCurl.T * e ) return b raise NotImplementedError('fieldType "%s" is not implemented.' % fieldType) def calcFieldsDeriv(self, sol, freq, fieldType, v, adjoint=False): e = sol if fieldType == 'e': return None elif fieldType == 'b': return None raise NotImplementedError('fieldType "%s" is not implemented.' % fieldType) class ProblemFDEM_b(BaseFDEMProblem): """ Solving for b! """ solType = 'b' def __init__(self, model, **kwargs): BaseFDEMProblem.__init__(self, model, **kwargs) def getA(self, freq): """ :param float freq: Frequency :rtype: scipy.sparse.csr_matrix :return: A """ mui = self.MfMui sigI = self.MeSigmaI C = self.mesh.edgeCurl return mui*C*sigI*C.T*mui + 1j*omega(freq)*mui def getADeriv(self, freq, u, v, adjoint=False): mui = self.MfMui C = self.mesh.edgeCurl sig = self.curTModel dsig_dm = self.curTModelDeriv #TODO: This only works if diagonal (no tensors)... dMeSigmaI_dI = - self.MeSigmaI**2 vec = (C.T*(mui*u)) dMe_dsig = self.mesh.getEdgeInnerProductDeriv(sig, v=vec) if adjoint: return dsig_dm.T * ( dMe_dsig.T * ( dMeSigmaI_dI.T * ( C.T * ( mui.T * v ) ) ) ) return mui * ( C * ( dMeSigmaI_dI * ( dMe_dsig * ( dsig_dm * v ) ) ) ) def getRHS(self, freq): """ :param float freq: Frequency :rtype: numpy.ndarray (nE, nTx) :return: RHS """ Txs = self.survey.getTransmitters(freq) rhs = range(len(Txs)) for i, tx in enumerate(Txs): if tx.txType == 'VMD': src = Sources.MagneticDipoleVectorPotential else: raise NotImplemented('%s txType is not implemented' % tx.txType) SRCx = src(tx.loc, self.mesh.gridEx, 'x') SRCy = src(tx.loc, self.mesh.gridEy, 'y') SRCz = src(tx.loc, self.mesh.gridEz, 'z') rhs[i] = np.concatenate((SRCx, SRCy, SRCz)) a = np.concatenate(rhs).reshape((self.mesh.nE, len(Txs)), order='F') mui = self.MfMui C = self.mesh.edgeCurl b_0 = C*a return -1j*omega(freq)*mui*b_0 def calcFields(self, sol, freq, fieldType, adjoint=False): b = sol if fieldType == 'e': if not adjoint: e = self.MeSigmaI * ( self.mesh.edgeCurl.T * ( self.MfMui * b ) ) else: e = self.MfMui.T * ( self.mesh.edgeCurl * ( self.MeSigmaI.T * b ) ) return e elif fieldType == 'b': return b raise NotImplementedError('fieldType "%s" is not implemented.' % fieldType) def calcFieldsDeriv(self, sol, freq, fieldType, v, adjoint=False): b = sol if fieldType == 'e': sig = self.curTModel dsig_dm = self.curTModelDeriv C = self.mesh.edgeCurl mui = self.MfMui #TODO: This only works if diagonal (no tensors)... dMeSigmaI_dI = - self.MeSigmaI**2 vec = C.T * ( mui * b ) dMe_dsig = self.mesh.getEdgeInnerProductDeriv(sig, v=vec) if not adjoint: return dMeSigmaI_dI * ( dMe_dsig * ( dsig_dm * v ) ) else: return dsig_dm.T * ( dMe_dsig.T * ( dMeSigmaI_dI.T * v ) ) elif fieldType == 'b': return None raise NotImplementedError('fieldType "%s" is not implemented.' % fieldType)