Exemplo n.º 1
0
class ReciprocalPropExample(Props.HasModel):

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)
Exemplo n.º 2
0
class BaseSPProblem(BaseDCProblem):

    h, hMap, hDeriv = Props.Invertible("Hydraulic Head (m)")

    q, qMap, qDeriv = Props.Invertible("Streaming current source (A/m^3)")

    jsx, jsxMap, jsxDeriv = Props.Invertible(
        "Streaming current density in x-direction (A/m^2)")

    jsy, jsyMap, jsyDeriv = Props.Invertible(
        "Streaming current density in y-direction (A/m^2)")

    jsz, jszMap, jszDeriv = Props.Invertible(
        "Streaming current density in z-direction (A/m^2)")

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)

    modelType = None
    surveyPair = Survey
    fieldsPair = FieldsDC

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        return toDelete

    def evalq(self, Qv, vel):
        MfQviI = self.mesh.getFaceInnerProduct(1.0 / Qv, invMat=True)
        Mf = self.mesh.getFaceInnerProduct()
        return self.Div * (Mf * (MfQviI * vel))
Exemplo n.º 3
0
class ReciprocalPropExampleDefaults(Props.HasModel):

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)",
                                   default=np.r_[1., 2., 3.])

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)
Exemplo n.º 4
0
class ReciprocalExample(Props.HasModel):

    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)
Exemplo n.º 5
0
class SimpleExample(Props.HasModel):

    sigmaMap = Props.Mapping("Mapping to the inversion model.")

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)",
                                   mapping=sigmaMap)

    sigmaDeriv = Props.Derivative("Derivative of sigma wrt the model.",
                                  physical_property=sigma)
Exemplo n.º 6
0
class Problem3D_e(Problem3DEM_e):

    _solutionType = 'eSolution'
    _formulation = 'EB'
    fieldsPair = Fields3D_e

    sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible(
        "Electrical conductivity at infinite frequency(S/m)")

    chi = Props.PhysicalProperty("Magnetic susceptibility", default=0.)

    eta, etaMap, etaDeriv = Props.Invertible(
        "Electrical chargeability (V/V), 0 <= eta < 1", default=0.)

    tau, tauMap, tauDeriv = Props.Invertible("Time constant (s)", default=1.)

    c, cMap, cDeriv = Props.Invertible("Frequency Dependency, 0 < c < 1",
                                       default=0.5)

    h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", )

    def __init__(self, mesh, **kwargs):
        Problem3DEM_e.__init__(self, mesh, **kwargs)

    def MeSigma(self, freq):
        """
            Edge inner product matrix for \\(\\sigma\\). Used in the E-B formulation
        """
        sigma = ColeColePelton(freq, self.sigmaInf, self.eta, self.tau, self.c)
        return self.mesh.getEdgeInnerProduct(sigma)

    def MeSigmaI(self, freq):
        """
            Inverse of the edge inner product matrix for \\(\\sigma\\).
        """
        sigma = ColeColePelton(freq, self.sigmaInf, self.eta, self.tau, self.c)
        return self.mesh.getEdgeInnerProduct(sigma, invMat=True)

    def getA(self, freq):
        """
        System matrix

        .. math ::
            \mathbf{A} = \mathbf{C}^{\\top} \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(freq)
        C = self.mesh.edgeCurl

        return C.T * MfMui * C + 1j * omega(freq) * MeSigma
Exemplo n.º 7
0
class BaseIPProblem(BaseEMProblem):

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)

    eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability")

    surveyPair = Survey
    fieldsPair = FieldsDC
    Ainv = None
    f = None
    Ainv = None

    def fields(self, m):
        if m is not None:
            self.model = m
        if self.f is None:
            self.f = self.fieldsPair(self.mesh, self.survey)
            if self.Ainv is None:
                A = self.getA()
                self.Ainv = self.Solver(A, **self.solverOpts)
            RHS = self.getRHS()
            u = self.Ainv * RHS
            Srcs = self.survey.srcList
            self.f[Srcs, self._solutionType] = u
        return self.f

    def Jvec(self, m, v, f=None):

        if f is None:
            f = self.fields(m)

        self.model = m

        Jv = []
        A = self.getA()

        for src in self.survey.srcList:
            u_src = f[src, self._solutionType]  # solution vector
            dA_dm_v = self.getADeriv(u_src, v)
            dRHS_dm_v = self.getRHSDeriv(src, v)
            du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v)

            for rx in src.rxList:
                df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField), None)
                df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False)
                # Jv[src, rx] = rx.evalDeriv(src, self.mesh, f, df_dm_v)
                Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v))
        # Conductivity (d u / d log sigma)
        if self._formulation == 'EB':
            # return -Utils.mkvc(Jv)
            return -np.hstack(Jv)
        # Conductivity (d u / d log rho)
        if self._formulation == 'HJ':
            # return Utils.mkvc(Jv)
            return np.hstack(Jv)

    def Jtvec(self, m, v, f=None):
        if f is None:
            f = self.fields(m)

        self.model = m

        # Ensure v is a data object.
        if not isinstance(v, self.dataPair):
            v = self.dataPair(self.survey, v)

        Jtv = np.zeros(m.size)
        AT = self.getA()

        for src in self.survey.srcList:
            u_src = f[src, self._solutionType]
            for rx in src.rxList:
                PTv = rx.evalDeriv(
                    src, self.mesh, f, v[src, rx],
                    adjoint=True)  # wrt f, need possibility wrt m
                df_duTFun = getattr(f, '_{0!s}Deriv'.format(rx.projField),
                                    None)
                df_duT, df_dmT = df_duTFun(src, None, PTv, adjoint=True)
                ATinvdf_duT = self.Ainv * df_duT
                dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True)
                dRHS_dmT = self.getRHSDeriv(src, ATinvdf_duT, adjoint=True)
                du_dmT = -dA_dmT + dRHS_dmT
                Jtv += (df_dmT + du_dmT).astype(float)
        # Conductivity ((d u / d log sigma).T)
        if self._formulation == 'EB':
            return -Utils.mkvc(Jtv)
        # Conductivity ((d u / d log rho).T)
        if self._formulation == 'HJ':
            return Utils.mkvc(Jtv)

    def getSourceTerm(self):
        """
        takes concept of source and turns it into a matrix
        """
        """
        Evaluates the sources, and puts them in matrix form

        :rtype: (numpy.ndarray, numpy.ndarray)
        :return: q (nC or nN, nSrc)
        """

        Srcs = self.survey.srcList

        if self._formulation == 'EB':
            n = self.mesh.nN
            # return NotImplementedError

        elif self._formulation == 'HJ':
            n = self.mesh.nC

        q = np.zeros((n, len(Srcs)))

        for i, src in enumerate(Srcs):
            q[:, i] = src.eval(self)
        return q

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        return toDelete

    # assume log rho or log cond
    @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.sigma)
        return self._MeSigma

    @property
    def MfRhoI(self):
        """
            Inverse of :code:`MfRho`
        """
        if getattr(self, '_MfRhoI', None) is None:
            self._MfRhoI = self.mesh.getFaceInnerProduct(self.rho, invMat=True)
        return self._MfRhoI

    def MfRhoIDeriv(self, u):
        """
            Derivative of :code:`MfRhoI` with respect to the model.
        """

        dMfRhoI_dI = -self.MfRhoI**2
        dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u)
        drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv
        return dMfRhoI_dI * (dMf_drho * drho_dlogrho)

    # TODO: This should take a vector
    def MeSigmaDeriv(self, u):
        """
            Derivative of MeSigma with respect to the model
        """
        dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv
        return self.mesh.getEdgeInnerProductDeriv(
            self.sigma)(u) * dsigma_dlogsigma
Exemplo n.º 8
0
class DebyeDecProblem(Problem.BaseProblem):

    sigmaInf, sigmaInfMap, sigmaInfDeriv = Props.Invertible(
        "Scalar, Conductivity at infinite frequency (S/m)"
    )
    eta, etaMap, etaDeriv = Props.Invertible(
        "Array, Chargeability (V/V)"
    )

    tau = Props.PhysicalProperty(
        "Array, Time constant (s)",
        default=0.1
    )

    nfreq = None
    ntau = None
    G = None
    frequency = None
    tau = None
    f = None
    InvertOnlyEta = False

    def __init__(self, mesh, **kwargs):
        Problem.BaseProblem.__init__(self, mesh, **kwargs)
        self.omega = 2*np.pi*self.frequency
        self.nfreq = self.frequency.size
        self.tau = self.mesh.gridN
        self.ntau = self.mesh.nN
        if self.sigmaInfMap == None:
            self.InvertOnlyEta = True
            print ("Assume sigmaInf is known")

    @property
    def X(self):
        """
        Denominator in Cole-Cole model
        """
        if getattr(self, '_X', None) is None:
            X = np.zeros((self.nfreq, self.ntau), dtype=complex)
            for itau in range(self.ntau):
                X[:,itau] = 1./(1.+(1.-self.eta[itau])*(1j*self.omega*self.tau[itau]))
            self._X = X
        return self._X


    def fields(self, m=None):
        if m is not None:
            self._X = None
            self.model = m
        f = self.sigmaInf - self.sigmaInf*(np.dot(self.X, self.eta))
        self.f = f
        return f

    def get_petaImpulse(self, time, m):
        etas = m
        taus = self.tau
        b = -1. / ((1.-etas)*taus)
        a = -etas*b
        t_temp = np.atleast_2d(time).T
        temp = np.exp(np.dot(t_temp, np.atleast_2d(b)))
        out = np.dot(temp, a)

        return out

    def get_petaStepon(self, time, m):
        etas = m
        taus = self.tau
        b = -1. / ((1.-etas)*taus)
        t_temp = np.atleast_2d(time).T
        temp = np.exp(np.dot(t_temp, np.atleast_2d(b)))
        out = np.dot(temp, etas)
        return out

    def get_Expb(self, time, m):
        etas = m
        taus = self.tau
        b = -1. / ((1.-etas)*taus)
        e = etas / b
        t_temp = np.atleast_2d(time).T
        temp = 1.-np.exp(np.dot(t_temp, np.atleast_2d(b)))
        out = np.dot(temp, e)
        return out

    def dsig_dm(self, v, adjoint=False):

        if not adjoint:

            deta_dm_v = self.etaDeriv*v
            if self.InvertOnlyEta:
                return self.dsig_deta(deta_dm_v)
            else:
                dsigmaInf_dm_v = self.sigmaInfDeriv*v
                return self.dsig_dsigmaInf(dsigmaInf_dm_v) + self.dsig_deta(deta_dm_v)

        elif adjoint:

            dsig_detaT_v = self.dsig_deta(v, adjoint=adjoint)
            dsig_dm_v = self.etaDeriv.T * dsig_detaT_v

            if not self.InvertOnlyEta:

                dsig_dsigmaInfT_v = self.dsig_dsigmaInf(v, adjoint=adjoint)
                dsig_dm_v += self.sigmaInfDeriv.T * dsig_dsigmaInfT_v

            return dsig_dm_v

    def dsig_deta(self, v, adjoint=False):
        """
            NxM matrix vec
            I*eta*omega*tau/(I*omega*tau*(-eta + 1) + 1)**2 + 1/(I*omega*tau*(-eta + 1) + 1)
        """
        if not adjoint:
            dsig_deta_v = -self.sigmaInf*np.dot(self.X, v)
            temp_v = Utils.sdiag(self.tau*self.eta) * v
            dsig_deta_v -= np.dot(Utils.sdiag(self.sigmaInf*1j*self.omega)*self.X**2, temp_v)
            return dsig_deta_v

        elif adjoint:
            dsig_detaT_v = -self.sigmaInf*np.dot(self.X.conj().T, v)
            tempa_v = Utils.sdiag(self.sigmaInf*1j*self.omega).conj() * v
            temp_v = np.dot((self.X**2).conj().T, tempa_v)
            dsig_detaT_v -= Utils.sdiag(self.tau*self.eta) * temp_v
            return dsig_detaT_v.real

    def dsig_dsigmaInf(self, v, adjoint=False):
        """
            Nx1 matrix vec
        """
        if not adjoint:
            dsig_dsigmaInf_v = (1.-np.dot(self.X, self.eta)) * v
            return dsig_dsigmaInf_v

        elif adjoint:
            e = np.ones_like(v)
            dsig_dsigmaInfT_v = np.dot(e, v) - np.dot(self.eta, np.dot(self.X.conj().T, v))
            return np.r_[dsig_dsigmaInfT_v.real]

    def Jvec(self, m, v, f=None):
        if f is None:
            f = self.fields(m=m)
        dsig_dm_v = self.dsig_dm(v)
        Jv = self.survey.evalDeriv(dsig_dm_v, f, adjoint=False)
        return Jv

    def Jtvec(self, m, v, f=None):
        if f is None:
            f = self.fields(m=m)
        dP_dsigT_v = self.survey.evalDeriv(v, f, adjoint=True)
        Jtv = self.dsig_dm(dP_dsigT_v, adjoint=True)
        return Jtv
Exemplo n.º 9
0
class BaseIPProblem(BaseEMProblem):

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)

    eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability")

    surveyPair = Survey
    fieldsPair = FieldsDC
    Ainv = None
    _f = None
    storeJ = False
    _Jmatrix = None
    sign = None

    def fields(self, m):
        if self.verbose is True:
            print(">> Compute fields")

        if self._f is None:
            self._f = self.fieldsPair(self.mesh, self.survey)
            if self.Ainv is None:
                A = self.getA()
                self.Ainv = self.Solver(A, **self.solverOpts)
            RHS = self.getRHS()
            u = self.Ainv * RHS
            Srcs = self.survey.srcList
            self._f[Srcs, self._solutionType] = u
        return self._f

    def getJ(self, m, f=None):
        """
            Generate Full sensitivity matrix
        """
        self.model = m

        if self.verbose:
            print("Calculating J and storing")

        if self._Jmatrix is not None:
            return self._Jmatrix
        else:

            if f is None:
                f = self.fields(m)
            self._Jmatrix = (self._Jtvec(m, v=None, f=f)).T

            # delete fields after computing sensitivity
            del f
            # Not sure why this is a problem
            # if self._f is not None:
            #     del self._f
            # clean all factorization
            if self.Ainv is not None:
                self.Ainv.clean()

        return self._Jmatrix

    def Jvec(self, m, v, f=None):

        self.model = m

        # When sensitivity matrix J is stored
        if self.storeJ:
            J = self.getJ(m, f=f)
            Jv = Utils.mkvc(np.dot(J, v))
            return self.sign * Jv

        else:

            if f is None:
                f = self.fields(m)

            Jv = []

            for src in self.survey.srcList:
                u_src = f[src, self._solutionType]  # solution vector
                dA_dm_v = self.getADeriv(u_src.flatten(), v, adjoint=False)
                dRHS_dm_v = self.getRHSDeriv(src, v)
                du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v)

                for rx in src.rxList:
                    df_dmFun = getattr(f, '_{0!s}Deriv'.format(rx.projField),
                                       None)
                    df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False)
                    Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v))

            # Conductivity (d u / d log sigma) - EB form
            # Resistivity (d u / d log rho) - HJ form
            return self.sign * np.hstack(Jv)

    def Jtvec(self, m, v, f=None):
        """
            Compute adjoint sensitivity matrix (J^T) and vector (v) product.

        """

        # When sensitivity matrix J is stored
        if self.storeJ:
            J = self.getJ(m, f=f)
            Jtv = Utils.mkvc(np.dot(J.T, v))
            return self.sign * Jtv

        else:
            self.model = m

            if f is None:
                f = self.fields(m)
            return self._Jtvec(m, v=v, f=f)

    def _Jtvec(self, m, v=None, f=None):
        """
            Compute adjoint sensitivity matrix (J^T) and vector (v) product.
            Full J matrix can be computed by inputing v=None
        """

        if v is not None:
            # Ensure v is a data object.
            if not isinstance(v, self.dataPair):
                v = self.dataPair(self.survey, v)
            Jtv = np.zeros(m.size)
        else:
            # This is for forming full sensitivity matrix
            Jtv = np.zeros((self.model.size, self.survey.nD), order='F')
            istrt = int(0)
            iend = int(0)

        for isrc, src in enumerate(self.survey.srcList):
            u_src = f[src, self._solutionType]
            if self.storeJ:
                # TODO: use logging package
                sys.stdout.write(("\r %d / %d") % (isrc + 1, self.survey.nSrc))
                sys.stdout.flush()

            for rx in src.rxList:
                if v is not None:
                    PTv = rx.evalDeriv(
                        src, self.mesh, f, v[src, rx],
                        adjoint=True)  # wrt f, need possibility wrt m
                    df_duTFun = getattr(f, '_{0!s}Deriv'.format(rx.projField),
                                        None)
                    df_duT, df_dmT = df_duTFun(src, None, PTv, adjoint=True)
                    ATinvdf_duT = self.Ainv * df_duT
                    dA_dmT = self.getADeriv(u_src.flatten(),
                                            ATinvdf_duT,
                                            adjoint=True)
                    dRHS_dmT = self.getRHSDeriv(src, ATinvdf_duT, adjoint=True)
                    du_dmT = -dA_dmT + dRHS_dmT
                    Jtv += (df_dmT + du_dmT).astype(float)
                else:
                    P = rx.getP(self.mesh, rx.projGLoc(f)).toarray()
                    ATinvdf_duT = self.Ainv * (P.T)
                    dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True)

                    iend = istrt + rx.nD
                    if rx.nD == 1:
                        Jtv[:, istrt] = dA_dmT
                    else:
                        Jtv[:, istrt:iend] = dA_dmT
                    istrt += rx.nD

        # Conductivity ((d u / d log sigma).T) - EB form
        # Resistivity ((d u / d log rho).T) - HJ form

        if v is not None:
            return self.sign * Utils.mkvc(Jtv)
        else:
            return Jtv
        return

    def getSourceTerm(self):
        """
        takes concept of source and turns it into a matrix
        """
        """
        Evaluates the sources, and puts them in matrix form

        :rtype: (numpy.ndarray, numpy.ndarray)
        :return: q (nC or nN, nSrc)
        """

        Srcs = self.survey.srcList

        if self._formulation == 'EB':
            n = self.mesh.nN
            # return NotImplementedError

        elif self._formulation == 'HJ':
            n = self.mesh.nC

        q = np.zeros((n, len(Srcs)))

        for i, src in enumerate(Srcs):
            q[:, i] = src.eval(self)
        return q

    def delete_these_for_sensitivity(self):
        del self._Jmatrix, self._MfRhoI, self._MeSigma

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        return toDelete

    @property
    def MfRhoDerivMat(self):
        """
        Derivative of MfRho with respect to the model
        """
        if getattr(self, '_MfRhoDerivMat', None) is None:
            drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv
            self._MfRhoDerivMat = self.mesh.getFaceInnerProductDeriv(
                np.ones(self.mesh.nC))(np.ones(self.mesh.nF)) * drho_dlogrho
        return self._MfRhoDerivMat

    def MfRhoIDeriv(self, u, v, adjoint=False):
        """
            Derivative of :code:`MfRhoI` with respect to the model.
        """
        dMfRhoI_dI = -self.MfRhoI**2
        if self.storeInnerProduct:
            if adjoint:
                return self.MfRhoDerivMat.T * (Utils.sdiag(u) *
                                               (dMfRhoI_dI.T * v))
            else:
                return dMfRhoI_dI * (Utils.sdiag(u) * (self.MfRhoDerivMat * v))
        else:
            dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u)
            drho_dlogrho = Utils.sdiag(self.rho) * self.etaDeriv
            if adjoint:
                return drho_dlogrho.T * (dMf_drho.T * (dMfRhoI_dI.T * v))
            else:
                return dMfRhoI_dI * (dMf_drho * (drho_dlogrho * v))

    @property
    def MeSigmaDerivMat(self):
        """
        Derivative of MeSigma with respect to the model
        """

        if getattr(self, '_MeSigmaDerivMat', None) is None:
            dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv
            self._MeSigmaDerivMat = self.mesh.getEdgeInnerProductDeriv(
                np.ones(self.mesh.nC))(np.ones(
                    self.mesh.nE)) * dsigma_dlogsigma
        return self._MeSigmaDerivMat

    # TODO: This should take a vector
    def MeSigmaDeriv(self, u, v, adjoint=False):
        """
        Derivative of MeSigma with respect to the model times a vector (u)
        """
        if self.storeInnerProduct:
            if adjoint:
                return self.MeSigmaDerivMat.T * (Utils.sdiag(u) * v)
            else:
                return Utils.sdiag(u) * (self.MeSigmaDerivMat * v)
        else:
            dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.etaDeriv
            if adjoint:
                return (
                    dsigma_dlogsigma.T *
                    (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u).T * v))
            else:
                return (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) *
                        (dsigma_dlogsigma * v))
Exemplo n.º 10
0
class GlobalEM1DProblem(Problem.BaseProblem):
    """
        The GlobalProblem allows you to run a whole bunch of SubProblems,
        potentially in parallel, potentially of different meshes.
        This is handy for working with lots of sources,
    """
    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity (S/m)")

    h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", )

    chi = Props.PhysicalProperty("Magnetic susceptibility (H/m)", )

    eta = Props.PhysicalProperty(
        "Electrical chargeability (V/V), 0 <= eta < 1")

    tau = Props.PhysicalProperty("Time constant (s)")

    c = Props.PhysicalProperty("Frequency Dependency, 0 < c < 1")

    _Jmatrix_sigma = None
    _Jmatrix_height = None
    run_simulation = None
    n_cpu = None
    hz = None
    parallel = False
    parallel_jvec_jtvec = False
    verbose = False
    fix_Jmatrix = False
    invert_height = None

    def __init__(self, mesh, **kwargs):
        Utils.setKwargs(self, **kwargs)
        self.mesh = mesh
        if PARALLEL:
            if self.parallel:
                print(">> Use multiprocessing for parallelization")
                if self.n_cpu is None:
                    self.n_cpu = multiprocessing.cpu_count()
                print((">> n_cpu: %i") % (self.n_cpu))
            else:
                print(">> Serial version is used")
        else:
            print(">> Serial version is used")
        if self.hz is None:
            raise Exception("Input vertical thickness hz !")
        if self.hMap is None:
            self.invert_height = False
        else:
            self.invert_height = True

    @property
    def n_layer(self):
        return self.hz.size

    @property
    def n_sounding(self):
        return self.survey.n_sounding

    @property
    def rx_locations(self):
        return self.survey.rx_locations

    @property
    def src_locations(self):
        return self.survey.src_locations

    @property
    def data_index(self):
        return self.survey.data_index

    @property
    def topo(self):
        return self.survey.topo

    @property
    def offset(self):
        return self.survey.offset

    @property
    def a(self):
        return self.survey.a

    @property
    def I(self):
        return self.survey.I

    @property
    def field_type(self):
        return self.survey.field_type

    @property
    def rx_type(self):
        return self.survey.rx_type

    @property
    def src_type(self):
        return self.survey.src_type

    @property
    def half_switch(self):
        return self.survey.half_switch

    @property
    def Sigma(self):
        if getattr(self, '_Sigma', None) is None:
            # Ordering: first z then x
            self._Sigma = self.sigma.reshape((self.n_sounding, self.n_layer))
        return self._Sigma

    @property
    def Chi(self):
        if getattr(self, '_Chi', None) is None:
            # Ordering: first z then x
            if self.chi is None:
                self._Chi = np.zeros((self.n_sounding, self.n_layer),
                                     dtype=float,
                                     order='C')
            else:
                self._Chi = self.chi.reshape((self.n_sounding, self.n_layer))
        return self._Chi

    @property
    def Eta(self):
        if getattr(self, '_Eta', None) is None:
            # Ordering: first z then x
            if self.eta is None:
                self._Eta = np.zeros((self.n_sounding, self.n_layer),
                                     dtype=float,
                                     order='C')
            else:
                self._Eta = self.eta.reshape((self.n_sounding, self.n_layer))
        return self._Eta

    @property
    def Tau(self):
        if getattr(self, '_Tau', None) is None:
            # Ordering: first z then x
            if self.tau is None:
                self._Tau = 1e-3 * np.ones(
                    (self.n_sounding, self.n_layer), dtype=float, order='C')
            else:
                self._Tau = self.tau.reshape((self.n_sounding, self.n_layer))
        return self._Tau

    @property
    def C(self):
        if getattr(self, '_C', None) is None:
            # Ordering: first z then x
            if self.c is None:
                self._C = np.ones((self.n_sounding, self.n_layer),
                                  dtype=float,
                                  order='C')
            else:
                self._C = self.c.reshape((self.n_sounding, self.n_layer))
        return self._C

    @property
    def H(self):
        if self.hMap is None:
            return np.ones(self.n_sounding)
        else:
            return self.h

    def fields(self, m):
        if self.verbose:
            print("Compute fields")
        self.survey._pred = self.forward(m)
        return []

    def forward(self, m):
        self.model = m

        if self.verbose:
            print(">> Compute response")

        if self.parallel:
            pool = Pool(self.n_cpu)
            # This assumes the same # of layer for each of soundings
            result = pool.map(self.run_simulation, [
                self.input_args(i, jac_switch='forward')
                for i in range(self.n_sounding)
            ])
            pool.close()
            pool.join()
        else:
            result = [
                self.run_simulation(self.input_args(i, jac_switch='forward'))
                for i in range(self.n_sounding)
            ]
        return np.hstack(result)

    def getJ_sigma(self, m):
        """
             Compute d F / d sigma
        """
        if self._Jmatrix_sigma is not None:
            return self._Jmatrix_sigma
        if self.verbose:
            print(">> Compute J sigma")
        self.model = m
        if self.parallel:
            pool = Pool(self.n_cpu)
            self._Jmatrix_sigma = pool.map(self.run_simulation, [
                self.input_args(i, jac_switch='sensitivity_sigma')
                for i in range(self.n_sounding)
            ])
            pool.close()
            pool.join()
            if self.parallel_jvec_jtvec is False:
                self._Jmatrix_sigma = sp.block_diag(
                    self._Jmatrix_sigma).tocsr()
        else:
            # _Jmatrix_sigma is block diagnoal matrix (sparse)
            self._Jmatrix_sigma = sp.block_diag([
                self.run_simulation(
                    self.input_args(i, jac_switch='sensitivity_sigma'))
                for i in range(self.n_sounding)
            ]).tocsr()
        return self._Jmatrix_sigma

    def getJ_height(self, m):
        """
             Compute d F / d height
        """
        if self.hMap is None:
            return Utils.Zero()

        if self._Jmatrix_height is not None:
            return self._Jmatrix_height
        if self.verbose:
            print(">> Compute J height")

        self.model = m

        if self.parallel:
            pool = Pool(self.n_cpu)
            self._Jmatrix_height = pool.map(self.run_simulation, [
                self.input_args(i, jac_switch="sensitivity_height")
                for i in range(self.n_sounding)
            ])
            pool.close()
            pool.join()
            if self.parallel_jvec_jtvec is False:
                self._Jmatrix_height = sp.block_diag(
                    self._Jmatrix_height).tocsr()
        else:
            self._Jmatrix_height = sp.block_diag([
                self.run_simulation(
                    self.input_args(i, jac_switch='sensitivity_height'))
                for i in range(self.n_sounding)
            ]).tocsr()
        return self._Jmatrix_height

    def Jvec(self, m, v, f=None):
        J_sigma = self.getJ_sigma(m)
        J_height = self.getJ_height(m)
        # This is deprecated at the moment
        # if self.parallel and self.parallel_jvec_jtvec:
        #     # Extra division of sigma is because:
        #     # J_sigma = dF/dlog(sigma)
        #     # And here sigmaMap also includes ExpMap
        #     v_sigma = Utils.sdiag(1./self.sigma) * self.sigmaMap.deriv(m, v)
        #     V_sigma = v_sigma.reshape((self.n_sounding, self.n_layer))

        #     pool = Pool(self.n_cpu)
        #     Jv = np.hstack(
        #         pool.map(
        #             dot,
        #             [(J_sigma[i], V_sigma[i, :]) for i in range(self.n_sounding)]
        #         )
        #     )
        #     if self.hMap is not None:
        #         v_height = self.hMap.deriv(m, v)
        #         V_height = v_height.reshape((self.n_sounding, self.n_layer))
        #         Jv += np.hstack(
        #             pool.map(
        #                 dot,
        #                 [(J_height[i], V_height[i, :]) for i in range(self.n_sounding)]
        #             )
        #         )
        #     pool.close()
        #     pool.join()
        # else:
        Jv = J_sigma * (Utils.sdiag(1. / self.sigma) * (self.sigmaDeriv * v))
        if self.hMap is not None:
            Jv += J_height * (self.hDeriv * v)
        return Jv

    def Jtvec(self, m, v, f=None):
        J_sigma = self.getJ_sigma(m)
        J_height = self.getJ_height(m)
        # This is deprecated at the moment
        # if self.parallel and self.parallel_jvec_jtvec:
        #     pool = Pool(self.n_cpu)
        #     Jtv = np.hstack(
        #         pool.map(
        #             dot,
        #             [(J_sigma[i].T, v[self.data_index[i]]) for i in range(self.n_sounding)]
        #         )
        #     )
        #     if self.hMap is not None:
        #         Jtv_height = np.hstack(
        #             pool.map(
        #                 dot,
        #                 [(J_sigma[i].T, v[self.data_index[i]]) for i in range(self.n_sounding)]
        #             )
        #         )
        #         # This assumes certain order for model, m = (sigma, height)
        #         Jtv = np.hstack((Jtv, Jtv_height))
        #     pool.close()
        #     pool.join()
        #     return Jtv
        # else:
        # Extra division of sigma is because:
        # J_sigma = dF/dlog(sigma)
        # And here sigmaMap also includes ExpMap
        Jtv = self.sigmaDeriv.T * (Utils.sdiag(1. / self.sigma) *
                                   (J_sigma.T * v))
        if self.hMap is not None:
            Jtv += self.hDeriv.T * (J_height.T * v)
        return Jtv

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        if self.sigmaMap is not None:
            toDelete += ['_Sigma']
        if self.fix_Jmatrix is False:
            if self._Jmatrix_sigma is not None:
                toDelete += ['_Jmatrix_sigma']
            if self._Jmatrix_height is not None:
                toDelete += ['_Jmatrix_height']
        return toDelete
Exemplo n.º 11
0
class BaseEMProblem(Problem.BaseProblem):

    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity (S/m)")

    rho, rhoMap, rhoDeriv = Props.Invertible("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)

    mu = Props.PhysicalProperty("Magnetic Permeability (H/m)", default=mu_0)
    mui = Props.PhysicalProperty("Inverse Magnetic Permeability (m/H)")

    Props.Reciprocal(mu, mui)

    surveyPair = Survey.BaseSurvey  #: The survey to pair with.
    dataPair = Survey.Data  #: The data to pair with.

    mapPair = Maps.IdentityMap  #: Type of mapping to pair with

    Solver = SimpegSolver  #: Type of solver to pair with
    solverOpts = {}  #: Solver options

    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 _clear_on_mu_update(self):
        return ['_MeMu', '_MeMuI', '_MfMui', '_MfMuiI']

    @property
    def _clear_on_sigma_update(self):
        return ['_MeSigma', '_MeSigmaI', '_MfRho', '_MfRhoI']

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        if self.sigmaMap is not None or self.rhoMap is not None:
            toDelete += self._clear_on_sigma_update

        if hasattr(self, 'muMap') or hasattr(self, 'muiMap'):
            if self.muMap is not None or self.muiMap is not None:
                toDelete += self._clear_on_mu_update
        return toDelete

    @properties.observer('mu')
    def _clear_mu_mats_on_mu_update(self, change):
        if change['previous'] is change['value']:
            return
        if (isinstance(change['previous'], np.ndarray)
                and isinstance(change['value'], np.ndarray)
                and np.allclose(change['previous'], change['value'])):
            return
        for mat in self._clear_on_mu_update:
            if hasattr(self, mat):
                delattr(self, mat)

    @properties.observer('mui')
    def _clear_mu_mats_on_mui_update(self, change):
        if change['previous'] is change['value']:
            return
        if (isinstance(change['previous'], np.ndarray)
                and isinstance(change['value'], np.ndarray)
                and np.allclose(change['previous'], change['value'])):
            return
        for mat in self._clear_on_mu_update:
            if hasattr(self, mat):
                delattr(self, mat)

    @properties.observer('sigma')
    def _clear_sigma_mats_on_sigma_update(self, change):
        if change['previous'] is change['value']:
            return
        if (isinstance(change['previous'], np.ndarray)
                and isinstance(change['value'], np.ndarray)
                and np.allclose(change['previous'], change['value'])):
            return
        for mat in self._clear_on_sigma_update:
            if hasattr(self, mat):
                delattr(self, mat)

    @properties.observer('rho')
    def _clear_sigma_mats_on_rho_update(self, change):
        if change['previous'] is change['value']:
            return
        if (isinstance(change['previous'], np.ndarray)
                and isinstance(change['value'], np.ndarray)
                and np.allclose(change['previous'], change['value'])):
            return
        for mat in self._clear_on_sigma_update:
            if hasattr(self, mat):
                delattr(self, mat)

    @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 MeI(self):
        """
            Edge inner product matrix
        """
        if getattr(self, '_MeI', None) is None:
            self._MeI = self.mesh.getEdgeInnerProduct(invMat=True)
        return self._MeI

    @property
    def Mf(self):
        """
            Face inner product matrix
        """
        if getattr(self, '_Mf', None) is None:
            self._Mf = self.mesh.getFaceInnerProduct()
        return self._Mf

    @property
    def MfI(self):
        """
            Face inner product matrix
        """
        if getattr(self, '_MfI', None) is None:
            self._MfI = self.mesh.getFaceInnerProduct(invMat=True)
        return self._MfI

    @property
    def Vol(self):
        if getattr(self, '_Vol', None) is None:
            self._Vol = Utils.sdiag(self.mesh.vol)
        return self._Vol

    ####################################################
    # 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.mui)
        return self._MfMui

    def MfMuiDeriv(self, u):
        """
        Derivative of :code:`MfMui` with respect to the model.
        """
        if self.muiMap is None:
            return Utils.Zero()

        return (self.mesh.getFaceInnerProductDeriv(self.mui)(u) *
                self.muiDeriv)

    @property
    def MfMuiI(self):
        """
        Inverse of :code:`MfMui`.
        """
        if getattr(self, '_MfMuiI', None) is None:
            self._MfMuiI = self.mesh.getFaceInnerProduct(self.mui, invMat=True)
        return self._MfMuiI

    # TODO: This should take a vector
    def MfMuiIDeriv(self, u):
        """
        Derivative of :code:`MfMui` with respect to the model
        """

        if self.muiMap is None:
            return Utils.Zero()

        if len(self.mui.shape) > 1:
            if self.mui.shape[1] > self.mesh.dim:
                raise NotImplementedError(
                    "Full anisotropy is not implemented for MfMuiIDeriv.")

        dMfMuiI_dI = -self.MfMuiI**2
        dMf_dmui = self.mesh.getEdgeInnerProductDeriv(self.mui)(u)
        return dMfMuiI_dI * (dMf_dmui * self.muiDeriv)

    @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.mu)
        return self._MeMu

    def MeMuDeriv(self, u):
        """
        Derivative of :code:`MeMu` with respect to the model.
        """
        if self.muMap is None:
            return Utils.Zero()

        return (self.mesh.getEdgeInnerProductDeriv(self.mu)(u) * self.muDeriv)

    @property
    def MeMuI(self):
        """
            Inverse of :code:`MeMu`
        """
        if getattr(self, '_MeMuI', None) is None:
            self._MeMuI = self.mesh.getEdgeInnerProduct(self.mu, invMat=True)
        return self._MeMuI

    # TODO: This should take a vector
    def MeMuIDeriv(self, u):
        """
        Derivative of :code:`MeMuI` with respect to the model
        """

        if self.muMap is None:
            return Utils.Zero()

        if len(self.mu.shape) > 1:
            if self.mu.shape[1] > self.mesh.dim:
                raise NotImplementedError(
                    "Full anisotropy is not implemented for MeMuIDeriv.")

        dMeMuI_dI = -self.MeMuI**2
        dMe_dmu = self.mesh.getEdgeInnerProductDeriv(self.mu)(u)
        return dMeMuI_dI * (dMe_dmu * self.muDeriv)

    ####################################################
    # Electrical Conductivity
    ####################################################
    @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.sigma)
        return self._MeSigma

    # TODO: This should take a vector
    def MeSigmaDeriv(self, u):
        """
        Derivative of MeSigma with respect to the model
        """
        if self.sigmaMap is None:
            return Utils.Zero()

        return (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) *
                self.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.sigma,
                                                           invMat=True)
        return self._MeSigmaI

    # TODO: This should take a vector
    def MeSigmaIDeriv(self, u):
        """
        Derivative of :code:`MeSigmaI` with respect to the model
        """
        if self.sigmaMap is None:
            return Utils.Zero()

        if len(self.sigma.shape) > 1:
            if self.sigma.shape[1] > self.mesh.dim:
                raise NotImplementedError(
                    "Full anisotropy is not implemented for MeSigmaIDeriv.")

        dMeSigmaI_dI = -self.MeSigmaI**2
        dMe_dsig = self.mesh.getEdgeInnerProductDeriv(self.sigma)(u)
        return dMeSigmaI_dI * (dMe_dsig * self.sigmaDeriv)

    @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.rho)
        return self._MfRho

    # TODO: This should take a vector
    def MfRhoDeriv(self, u):
        """
        Derivative of :code:`MfRho` with respect to the model.
        """
        if self.rhoMap is None:
            return Utils.Zero()

        return (self.mesh.getFaceInnerProductDeriv(self.rho)(u) *
                self.rhoDeriv)

    @property
    def MfRhoI(self):
        """
        Inverse of :code:`MfRho`
        """
        if getattr(self, '_MfRhoI', None) is None:
            self._MfRhoI = self.mesh.getFaceInnerProduct(self.rho, invMat=True)
        return self._MfRhoI

    # TODO: This should take a vector
    def MfRhoIDeriv(self, u):
        """
            Derivative of :code:`MfRhoI` with respect to the model.
        """
        if self.rhoMap is None:
            return Utils.Zero()

        if len(self.rho.shape) > 1:
            if self.rho.shape[1] > self.mesh.dim:
                raise NotImplementedError(
                    "Full anisotropy is not implemented for MfRhoIDeriv.")

        dMfRhoI_dI = -self.MfRhoI**2
        dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u)
        return dMfRhoI_dI * (dMf_drho * self.rhoDeriv)
Exemplo n.º 12
0
class BaseSIPProblem(BaseEMProblem):

    sigma = Props.PhysicalProperty("Electrical conductivity (S/m)")

    rho = Props.PhysicalProperty("Electrical resistivity (Ohm m)")

    Props.Reciprocal(sigma, rho)

    eta, etaMap, etaDeriv = Props.Invertible("Electrical Chargeability (V/V)")

    tau, tauMap, tauDeriv = Props.Invertible("Time constant (s)", default=0.1)

    taui, tauiMap, tauiDeriv = Props.Invertible(
        "Inverse of time constant (1/s)")

    c, cMap, cDeriv = Props.Invertible("Frequency dependency", default=0.5)

    Props.Reciprocal(tau, taui)

    surveyPair = Survey
    fieldsPair = FieldsDC
    dataPair = Data
    Ainv = None
    _f = None
    actinds = None
    storeJ = False
    _Jmatrix = None
    actMap = None

    def getPeta(self, t):
        peta = self.eta * np.exp(-(self.taui * t)**self.c)
        return peta

    def PetaEtaDeriv(self, t, v, adjoint=False):
        v = np.array(v, dtype=float)
        taui_t_c = (self.taui * t)**self.c
        dpetadeta = np.exp(-taui_t_c)
        if adjoint:
            return self.etaDeriv.T * (dpetadeta * v)
        else:
            return dpetadeta * (self.etaDeriv * v)

    def PetaTauiDeriv(self, t, v, adjoint=False):
        v = np.array(v, dtype=float)
        taui_t_c = (self.taui * t)**self.c
        dpetadtaui = (-self.c * self.eta / self.taui * taui_t_c *
                      np.exp(-taui_t_c))
        if adjoint:
            return self.tauiDeriv.T * (dpetadtaui * v)
        else:
            return dpetadtaui * (self.tauiDeriv * v)

    def PetaCDeriv(self, t, v, adjoint=False):
        v = np.array(v, dtype=float)
        taui_t_c = (self.taui * t)**self.c
        dpetadc = (-self.eta * (taui_t_c) * np.exp(-taui_t_c) *
                   np.log(self.taui * t))
        if adjoint:
            return self.cDeriv.T * (dpetadc * v)
        else:
            return dpetadc * (self.cDeriv * v)

    def fields(self, m):
        if self.verbose:
            print(">> Compute DC fields")
        if self._f is None:
            self._f = self.fieldsPair(self.mesh, self.survey)
            if self.Ainv is None:
                A = self.getA()
                self.Ainv = self.Solver(A, **self.solverOpts)
            RHS = self.getRHS()
            u = self.Ainv * RHS
            Srcs = self.survey.srcList
            self._f[Srcs, self._solutionType] = u
        return self._f

    def getJ(self, m, f=None):
        """
            Generate Full sensitivity matrix
        """

        if self.verbose:
            print("Calculating J and storing")

        if self._Jmatrix is not None:
            return self._Jmatrix
        else:

            if f is None:
                f = self.fields(m)

            Jt = np.zeros(
                (self.actMap.nP, int(self.survey.nD / self.survey.times.size)),
                order='F')
            istrt = int(0)
            iend = int(0)

            for isrc, src in enumerate(self.survey.srcList):
                if self.verbose:
                    sys.stdout.write(
                        ("\r %d / %d") % (isrc + 1, self.survey.nSrc))
                    sys.stdout.flush()
                u_src = f[src, self._solutionType]
                for rx in src.rxList:
                    P = rx.getP(self.mesh, rx.projGLoc(f)).toarray()
                    ATinvdf_duT = self.Ainv * (P.T)
                    dA_dmT = self.getADeriv(u_src, ATinvdf_duT, adjoint=True)
                    iend = istrt + rx.nD
                    if rx.nD == 1:
                        Jt[:, istrt] = dA_dmT
                    else:
                        Jt[:, istrt:iend] = dA_dmT
                    istrt += rx.nD

            self._Jmatrix = Jt.T
            if self.verbose:
                collected = gc.collect()
                print("Garbage collector: collected %d objects." % (collected))

            # Not sure why below has raise memory issue
            # only for problem_cc, test_dataObj
            # if self._f is not None:
            #     del self._f
            # clean all factorization
            if self.Ainv is not None:
                self.Ainv.clean()

            return self._Jmatrix

    def forward(self, m, f=None):

        self.model = m
        Jv = []

        # When sensitivity matrix is stored
        if self.storeJ:
            J = self.getJ(m, f=f)

            ntime = len(self.survey.times)

            self.model = m
            for tind in range(ntime):
                Jv.append(
                    J.dot(self.actMap.P.T *
                          self.getPeta(self.survey.times[tind])))
            return self.sign * np.hstack(Jv)

        # Do not store sensitivity matrix (memory-wise efficient)
        else:

            if f is None:
                f = self.fields(m)

            # A = self.getA()
            for tind in range(len(self.survey.times)):
                # Pseudo-chareability
                t = self.survey.times[tind]
                v = self.getPeta(t)
                for src in self.survey.srcList:
                    u_src = f[src, self._solutionType]  # solution vector
                    dA_dm_v = self.getADeriv(u_src, v)
                    dRHS_dm_v = self.getRHSDeriv(src, v)
                    du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v)
                    for rx in src.rxList:
                        timeindex = rx.getTimeP(self.survey.times)
                        if timeindex[tind]:
                            df_dmFun = getattr(
                                f, '_{0!s}Deriv'.format(rx.projField), None)
                            df_dm_v = df_dmFun(src, du_dm_v, v, adjoint=False)
                            Jv.append(rx.evalDeriv(src, self.mesh, f, df_dm_v))

            return self.sign * np.hstack(Jv)

    def Jvec(self, m, v, f=None):

        self.model = m

        Jv = []

        # When sensitivity matrix is stored
        if self.storeJ:
            J = self.getJ(m, f=f)
            ntime = len(self.survey.times)

            for tind in range(ntime):

                t = self.survey.times[tind]
                v0 = self.PetaEtaDeriv(t, v)
                v1 = self.PetaTauiDeriv(t, v)
                v2 = self.PetaCDeriv(t, v)
                PTv = self.actMap.P.T * (v0 + v1 + v2)
                Jv.append(J.dot(PTv))

            return self.sign * np.hstack(Jv)

        # Do not store sensitivity matrix (memory-wise efficient)
        else:

            if f is None:
                f = self.fields(m)

            for tind in range(len(self.survey.times)):

                t = self.survey.times[tind]
                v0 = self.PetaEtaDeriv(t, v)
                v1 = self.PetaTauiDeriv(t, v)
                v2 = self.PetaCDeriv(t, v)

                for src in self.survey.srcList:
                    u_src = f[src, self._solutionType]  # solution vector
                    dA_dm_v = self.getADeriv(u_src, v0 + v1 + v2)
                    dRHS_dm_v = self.getRHSDeriv(src, v0 + v1 + v2)
                    du_dm_v = self.Ainv * (-dA_dm_v + dRHS_dm_v)

                    for rx in src.rxList:
                        timeindex = rx.getTimeP(self.survey.times)
                        if timeindex[tind]:
                            Jv_temp = (rx.evalDeriv(src, self.mesh, f,
                                                    du_dm_v))
                            if rx.nD == 1:
                                Jv_temp = Jv_temp.reshape([-1, 1])

                            Jv.append(Jv_temp)

            return self.sign * np.hstack(Jv)

    def Jtvec(self, m, v, f=None):

        self.model = m

        # When sensitivity matrix is stored
        if self.storeJ:
            J = self.getJ(m, f=f)
            ntime = len(self.survey.times)
            Jtvec = np.zeros(m.size)
            v = v.reshape((int(self.survey.nD / ntime), ntime), order="F")

            for tind in range(ntime):
                t = self.survey.times[tind]
                Jtv = self.actMap.P * J.T.dot(v[:, tind])
                Jtvec += (self.PetaEtaDeriv(t, Jtv, adjoint=True) +
                          self.PetaTauiDeriv(t, Jtv, adjoint=True) +
                          self.PetaCDeriv(t, Jtv, adjoint=True))

            return self.sign * Jtvec

        # Do not store sensitivity matrix (memory-wise efficient)
        else:

            if f is None:
                f = self.fields(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 tind in range(len(self.survey.times)):
                t = self.survey.times[tind]
                for src in self.survey.srcList:
                    u_src = f[src, self._solutionType]
                    for rx in src.rxList:
                        timeindex = rx.getTimeP(self.survey.times)
                        if timeindex[tind]:
                            # wrt f, need possibility wrt m
                            PTv = rx.evalDeriv(src,
                                               self.mesh,
                                               f,
                                               v[src, rx, t],
                                               adjoint=True)
                            df_duTFun = getattr(
                                f, '_{0!s}Deriv'.format(rx.projField), None)
                            df_duT, _ = df_duTFun(src, None, PTv, adjoint=True)
                            ATinvdf_duT = self.Ainv * df_duT
                            dA_dmT = self.getADeriv(u_src,
                                                    ATinvdf_duT,
                                                    adjoint=True)
                            dRHS_dmT = self.getRHSDeriv(src,
                                                        ATinvdf_duT,
                                                        adjoint=True)
                            du_dmT = -dA_dmT + dRHS_dmT
                            Jtv += (self.PetaEtaDeriv(self.survey.times[tind],
                                                      du_dmT,
                                                      adjoint=True) +
                                    self.PetaTauiDeriv(self.survey.times[tind],
                                                       du_dmT,
                                                       adjoint=True) +
                                    self.PetaCDeriv(self.survey.times[tind],
                                                    du_dmT,
                                                    adjoint=True))

            return self.sign * Jtv

    def getSourceTerm(self):
        """
        takes concept of source and turns it into a matrix
        """
        """
        Evaluates the sources, and puts them in matrix form

        :rtype: (numpy.ndarray, numpy.ndarray)
        :return: q (nC or nN, nSrc)
        """

        Srcs = self.survey.srcList

        if self._formulation == 'EB':
            n = self.mesh.nN
            # return NotImplementedError

        elif self._formulation == 'HJ':
            n = self.mesh.nC

        q = np.zeros((n, len(Srcs)))

        for i, src in enumerate(Srcs):
            q[:, i] = src.eval(self)
        return q

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        return toDelete

    @property
    def MfRhoDerivMat(self):
        """
        Derivative of MfRho with respect to the model
        """
        if getattr(self, '_MfRhoDerivMat', None) is None:
            if self.storeJ:
                drho_dlogrho = Utils.sdiag(self.rho) * self.actMap.P
            else:
                drho_dlogrho = Utils.sdiag(self.rho)
            self._MfRhoDerivMat = self.mesh.getFaceInnerProductDeriv(
                np.ones(self.mesh.nC))(np.ones(self.mesh.nF)) * drho_dlogrho
        return self._MfRhoDerivMat

    def MfRhoIDeriv(self, u, v, adjoint=False):
        """
            Derivative of :code:`MfRhoI` with respect to the model.
        """
        dMfRhoI_dI = -self.MfRhoI**2

        if self.storeInnerProduct:
            if adjoint:
                return (self.MfRhoDerivMat.T * (Utils.sdiag(u) *
                                                (dMfRhoI_dI.T * v)))
            else:
                return dMfRhoI_dI * (Utils.sdiag(u) * (self.MfRhoDerivMat * v))
        else:
            if self.storeJ:
                drho_dlogrho = Utils.sdiag(self.rho) * self.actMap.P
            else:
                drho_dlogrho = Utils.sdiag(self.rho)
            dMf_drho = self.mesh.getFaceInnerProductDeriv(self.rho)(u)
            if adjoint:
                return drho_dlogrho.T * (dMf_drho.T * (dMfRhoI_dI.T * v))
            else:
                return dMfRhoI_dI * (dMf_drho * (drho_dlogrho * v))

    @property
    def MeSigmaDerivMat(self):
        """
        Derivative of MeSigma with respect to the model
        """
        if getattr(self, '_MeSigmaDerivMat', None) is None:
            if self.storeJ:
                dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.actMap.P
            else:
                dsigma_dlogsigma = Utils.sdiag(self.sigma)
            self._MeSigmaDerivMat = self.mesh.getEdgeInnerProductDeriv(
                np.ones(self.mesh.nC))(np.ones(
                    self.mesh.nE)) * dsigma_dlogsigma
        return self._MeSigmaDerivMat

    # TODO: This should take a vector
    def MeSigmaDeriv(self, u, v, adjoint=False):
        """
        Derivative of MeSigma with respect to the model times a vector (u)
        """
        if self.storeInnerProduct:
            if adjoint:
                return self.MeSigmaDerivMat.T * (Utils.sdiag(u) * v)
            else:
                return Utils.sdiag(u) * (self.MeSigmaDerivMat * v)
        else:
            if self.storeJ:
                dsigma_dlogsigma = Utils.sdiag(self.sigma) * self.actMap.P
            else:
                dsigma_dlogsigma = Utils.sdiag(self.sigma)
            if adjoint:
                return (
                    dsigma_dlogsigma.T *
                    (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u).T * v))
            else:
                return (self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) *
                        (dsigma_dlogsigma * v))
Exemplo n.º 13
0
class Problem_LogUniform(Problem_BaseVRM):
    """

    """

    _A = None
    _T = None
    _TisSet = False
    # _xiMap = None

    surveyPair = Survey.BaseSurvey

    chi0 = Props.PhysicalProperty("DC susceptibility")
    dchi = Props.PhysicalProperty("Frequency dependence")
    tau1 = Props.PhysicalProperty("Low bound time-relaxation constant")
    tau2 = Props.PhysicalProperty("Upper bound time-relaxation constant")

    def __init__(self, mesh, **kwargs):

        super(Problem_LogUniform, self).__init__(mesh, **kwargs)

    @property
    def A(self):
        """
        The geometric sensitivity matrix for the linear VRM problem. Accessing
        this property requires that the problem be paired with a survey object.

        """

        if self._AisSet is False:

            if self.ispaired is False:
                AssertionError(
                    "Problem must be paired with survey to generate A matrix")

            # Remove any previously stored A matrix
            if self._A is not None:
                self._A = None

            print('CREATING A MATRIX')

            # COLLAPSE ALL A MATRICIES INTO SINGLE OPERATOR
            self._A = self._getAMatricies()
            self._AisSet = True

            return self._A

        elif self._AisSet is True:

            return self._A

    def fields(self, m=None):
        """Computes the fields at every time d(t) = G*M(t)"""

        if self.ispaired is False:
            AssertionError(
                "Problem must be paired with survey to generate A matrix")

        # Fields from each source
        srcList = self.survey.srcList
        nSrc = len(srcList)
        f = []

        for pp in range(0, nSrc):

            rxList = srcList[pp].rxList
            nRx = len(rxList)
            waveObj = srcList[pp].waveform

            for qq in range(0, nRx):

                times = rxList[qq].times
                eta = waveObj.getLogUniformDecay(rxList[qq].fieldType, times,
                                                 self.chi0, self.dchi,
                                                 self.tau1, self.tau2)

                f.append(mkvc((self.A[qq] * np.matrix(eta)).T))

        return np.array(np.hstack(f))
Exemplo n.º 14
0
class EM1D(Problem.BaseProblem):
    """
    Pseudo analytic solutions for frequency and time domain EM problems
    assumingLayered earth (1D).
    """
    surveyPair = BaseEM1DSurvey
    mapPair = Maps.IdentityMap
    chi = None
    hankel_filter = 'key_101_2009'  # Default: Hankel filter
    hankel_pts_per_dec = None       # Default: Standard DLF
    verbose = False
    fix_Jmatrix = False
    _Jmatrix_sigma = None
    _Jmatrix_height = None
    _pred = None

    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity at infinite frequency(S/m)"
    )

    chi = Props.PhysicalProperty(
        "Magnetic susceptibility",
        default=0.
    )

    eta, etaMap, etaDeriv = Props.Invertible(
        "Electrical chargeability (V/V), 0 <= eta < 1",
        default=0.
    )

    tau, tauMap, tauDeriv = Props.Invertible(
        "Time constant (s)",
        default=1.
    )

    c, cMap, cDeriv = Props.Invertible(
        "Frequency Dependency, 0 < c < 1",
        default=0.5
    )

    h, hMap, hDeriv = Props.Invertible(
        "Receiver Height (m), h > 0",
    )

    def __init__(self, mesh, **kwargs):
        Problem.BaseProblem.__init__(self, mesh, **kwargs)

        # Check input arguments. If self.hankel_filter is not a valid filter,
        # it will set it to the default (key_201_2009).
        ht, htarg = check_hankel('fht', [self.hankel_filter,
                                         self.hankel_pts_per_dec], 1)

        self.fhtfilt = htarg[0]                 # Store filter
        self.hankel_filter = self.fhtfilt.name  # Store name
        self.hankel_pts_per_dec = htarg[1]      # Store pts_per_dec
        if self.verbose:
            print(">> Use "+self.hankel_filter+" filter for Hankel Transform")

        if self.hankel_pts_per_dec != 0:
            raise NotImplementedError()

    def hz_kernel_vertical_magnetic_dipole(
        self, lamda, f, n_layer, sig, chi, depth, h, z,
        flag, output_type='response'
    ):

        """
            Kernel for vertical magnetic component (Hz) due to
            vertical magnetic diopole (VMD) source in (kx,ky) domain

        """
        u0 = lamda
        coefficient_wavenumber = 1/(4*np.pi)*lamda**3/u0

        n_frequency = self.survey.n_frequency
        n_layer = self.survey.n_layer
        n_filter = self.n_filter

        if output_type == 'sensitivity_sigma':
            drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter)

            kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
        else:
            rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) 

            kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
            if output_type == 'sensitivity_height':
                kernel *= -2*u0

        return kernel

        # Note
        # Here only computes secondary field.
        # I am not sure why it does not work if we add primary term.
        # This term can be analytically evaluated, where h = 0.
        #     kernel = (
        #         1./(4*np.pi) *
        #         (np.exp(u0*(z-h))+rTE * np.exp(-u0*(z+h)))*lamda**3/u0
        #     )

    # TODO: make this to take a vector rather than a single frequency
    def hz_kernel_circular_loop(
        self, lamda, f, n_layer, sig, chi, depth, h, z, I, a,
        flag,  output_type='response'
    ):

        """

        Kernel for vertical magnetic component (Hz) at the center
        due to circular loop source in (kx,ky) domain

        .. math::

            H_z = \\frac{Ia}{2} \int_0^{\infty} [e^{-u_0|z+h|} +
            \\r_{TE}e^{u_0|z-h|}]
            \\frac{\lambda^2}{u_0} J_1(\lambda a)] d \lambda

        """

        n_frequency = self.survey.n_frequency
        n_layer = self.survey.n_layer
        n_filter = self.n_filter

        w = 2*np.pi*f
        u0 = lamda
        radius = np.empty([n_frequency, n_filter], order='F')
        radius[:, :] = np.tile(a.reshape([-1, 1]), (1, n_filter))

        coefficient_wavenumber = I*radius*0.5*lamda**2/u0

        if output_type == 'sensitivity_sigma':
            drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter)

            kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
        else:
            rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) 

            if flag == 'secondary':
                kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
            else:
                kernel = rTE * (
                    np.exp(-u0*(z+h)) + np.exp(u0*(z-h))
                ) * coefficient_wavenumber

            if output_type == 'sensitivity_height':
                kernel *= -2*u0

        return kernel

    def hz_kernel_horizontal_electric_dipole(
        self, lamda, f, n_layer, sig, chi, depth, h, z,
        flag, output_type='response'
    ):

        """
            Kernel for vertical magnetic field (Hz) due to
            horizontal electric diopole (HED) source in (kx,ky) domain

        """
        n_frequency = self.survey.n_frequency
        n_layer = self.survey.n_layer
        n_filter = self.n_filter

        u0 = lamda
        coefficient_wavenumber = 1/(4*np.pi)*lamda**2/u0

        if output_type == 'sensitivity_sigma':
            drTE = np.zeros([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_sensitivity(f, lamda, sig, chi, depth, self.survey.half_switch, drTE, n_layer, n_frequency, n_filter)

            kernel = drTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
        else:
            rTE = np.empty([n_frequency, n_filter], dtype=np.complex128, order='F')
            rte_fortran.rte_forward(f, lamda, sig, chi, depth, self.survey.half_switch, rTE, n_layer, n_frequency, n_filter) 

            kernel = rTE * np.exp(-u0*(z+h)) * coefficient_wavenumber
            if output_type == 'sensitivity_height':
                kernel *= -2*u0

        return kernel

    # make it as a property?

    def sigma_cole(self):
        """
        Computes Pelton's Cole-Cole conductivity model
        in frequency domain.

        Parameter
        ---------

        n_filter: int
            the number of filter values
        f: ndarray
            frequency (Hz)

        Return
        ------

        sigma_complex: ndarray (n_layer x n_frequency x n_filter)
            Cole-Cole conductivity values at given frequencies

        """
        n_layer = self.survey.n_layer
        n_frequency = self.survey.n_frequency
        n_filter = self.n_filter
        f = self.survey.frequency

        sigma = np.tile(self.sigma.reshape([-1, 1]), (1, n_frequency))
        if np.isscalar(self.eta):
            eta = self.eta
            tau = self.tau
            c = self.c
        else:
            eta = np.tile(self.eta.reshape([-1, 1]), (1, n_frequency))
            tau = np.tile(self.tau.reshape([-1, 1]), (1, n_frequency))
            c = np.tile(self.c.reshape([-1, 1]), (1, n_frequency))

        w = np.tile(
            2*np.pi*f,
            (n_layer, 1)
        )
        
        sigma_complex = np.empty([n_layer, n_frequency], dtype=np.complex128, order='F')
        sigma_complex[:, :] = (
            sigma -
            sigma*eta/(1+(1-eta)*(1j*w*tau)**c)
        )

        sigma_complex_tensor = np.empty([n_layer, n_frequency, n_filter], dtype=np.complex128, order='F')
        sigma_complex_tensor[:, :, :] = np.tile(sigma_complex.reshape(
            (n_layer, n_frequency, 1)), (1, 1, n_filter)
        )

        return sigma_complex_tensor

    @property
    def n_filter(self):
        """ Length of filter """
        return self.fhtfilt.base.size

    def forward(self, m, output_type='response'):
        """
            Return Bz or dBzdt
        """

        self.model = m

        n_frequency = self.survey.n_frequency
        flag = self.survey.field_type
        n_layer = self.survey.n_layer
        depth = self.survey.depth
        I = self.survey.I
        n_filter = self.n_filter

        # Get lambd and offset, will depend on pts_per_dec
        if self.survey.src_type == "VMD":
            r = self.survey.offset
        else:
            # a is the radius of the loop
            r = self.survey.a * np.ones(n_frequency)

        # Use function from empymod
        # size of lambd is (n_frequency x n_filter)
        lambd = np.empty([self.survey.frequency.size, n_filter], order='F')
        lambd[:, :], _ = get_spline_values(self.fhtfilt, r, self.hankel_pts_per_dec)

        # lambd, _ = get_spline_values(self.fhtfilt, r, self.hankel_pts_per_dec)
        
        # TODO: potentially store
        f = np.empty([self.survey.frequency.size, n_filter], order='F')
        f[:,:] = np.tile(self.survey.frequency.reshape([-1, 1]), (1, n_filter))
        # h is an inversion parameter
        if self.hMap is not None:
            h = self.h
        else:
            h = self.survey.h

        z = h + self.survey.dz

        chi = self.chi

        if np.isscalar(self.chi):
            chi = np.ones_like(self.sigma) * self.chi

        # TODO: potentially store
        sig = self.sigma_cole()

        if output_type == 'response':
            # for simulation
            if self.survey.src_type == 'VMD':
                hz = self.hz_kernel_vertical_magnetic_dipole(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z,
                    flag, output_type=output_type
                )

                # kernels for each bessel function
                # (j0, j1, j2)
                PJ = (hz, None, None)  # PJ0

            elif self.survey.src_type == 'CircularLoop':
                hz = self.hz_kernel_circular_loop(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z, I, r,
                    flag, output_type=output_type
                )

                # kernels for each bessel function
                # (j0, j1, j2)
                PJ = (None, hz, None)  # PJ1

            # TODO: This has not implemented yet!
            elif self.survey.src_type == "piecewise_line":
                # Need to compute y
                hz = self.hz_kernel_horizontal_electric_dipole(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z, I, r,
                    flag, output_type=output_type
                )
                # kernels for each bessel function
                # (j0, j1, j2)
                PJ = (None, hz, None)  # PJ1

            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

        elif output_type == 'sensitivity_sigma':

            # for simulation
            if self.survey.src_type == 'VMD':
                hz = self.hz_kernel_vertical_magnetic_dipole(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z,
                    flag, output_type=output_type
                )

                PJ = (hz, None, None)  # PJ0

            elif self.survey.src_type == 'CircularLoop':

                hz = self.hz_kernel_circular_loop(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z, I, r,
                    flag, output_type=output_type
                )

                PJ = (None, hz, None)  # PJ1

            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

            r = np.tile(r, (n_layer, 1))

        elif output_type == 'sensitivity_height':

            # for simulation
            if self.survey.src_type == 'VMD':
                hz = self.hz_kernel_vertical_magnetic_dipole(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z,
                    flag, output_type=output_type
                )

                PJ = (hz, None, None)  # PJ0

            elif self.survey.src_type == 'CircularLoop':

                hz = self.hz_kernel_circular_loop(
                    lambd, f, n_layer,
                    sig, chi, depth, h, z, I, r,
                    flag, output_type=output_type
                )

                PJ = (None, hz, None)  # PJ1

            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

        # Carry out Hankel DLF
        # ab=66 => 33 (vertical magnetic src and rec)
        # For response
        # HzFHT size = (n_frequency,)
        # For sensitivity
        # HzFHT size = (n_layer, n_frequency)

        HzFHT = dlf(PJ, lambd, r, self.fhtfilt, self.hankel_pts_per_dec,
                    factAng=None, ab=33)

        if output_type == "sensitivity_sigma":
            return HzFHT.T

        return HzFHT

    # @profile
    def fields(self, m):
        f = self.forward(m, output_type='response')
        self.survey._pred = Utils.mkvc(self.survey.projectFields(f))
        return f

    def getJ_height(self, m, f=None):
        """

        """
        if self.hMap is None:
            return Utils.Zero()

        if self._Jmatrix_height is not None:
            return self._Jmatrix_height
        else:

            if self.verbose:
                print (">> Compute J height ")

            dudz = self.forward(m, output_type="sensitivity_height")

            self._Jmatrix_height = (
                self.survey.projectFields(dudz)
            ).reshape([-1, 1])

            return self._Jmatrix_height

    # @profile
    def getJ_sigma(self, m, f=None):

        if self.sigmaMap is None:
            return Utils.Zero()

        if self._Jmatrix_sigma is not None:
            return self._Jmatrix_sigma
        else:

            if self.verbose:
                print (">> Compute J sigma")

            dudsig = self.forward(m, output_type="sensitivity_sigma")

            self._Jmatrix_sigma = self.survey.projectFields(dudsig)
            if self._Jmatrix_sigma.ndim == 1:
                self._Jmatrix_sigma = self._Jmatrix_sigma.reshape([-1, 1])
            return self._Jmatrix_sigma

    def getJ(self, m, f=None):
        return (
            self.getJ_sigma(m, f=f) * self.sigmaDeriv +
            self.getJ_height(m, f=f) * self.hDeriv
        )

    def Jvec(self, m, v, f=None):
        """
            Computing Jacobian^T multiplied by vector.
        """

        J_sigma = self.getJ_sigma(m, f=f)
        J_height = self.getJ_height(m, f=f)
        Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v))
        if self.hMap is not None:
            Jv += np.dot(J_height, self.hMap.deriv(m, v))
        return Jv

    def Jtvec(self, m, v, f=None):
        """
            Computing Jacobian^T multiplied by vector.
        """

        J_sigma = self.getJ_sigma(m, f=f)
        J_height = self.getJ_height(m, f=f)
        Jtv = self.sigmaDeriv.T*np.dot(J_sigma.T, v)
        if self.hMap is not None:
            Jtv += self.hDeriv.T*np.dot(J_height.T, v)
        return Jtv

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        if self.fix_Jmatrix is False:
            if self._Jmatrix_sigma is not None:
                toDelete += ['_Jmatrix_sigma']
            if self._Jmatrix_height is not None:
                toDelete += ['_Jmatrix_height']
        return toDelete

    def depth_of_investigation_christiansen_2012(self, std, thres_hold=0.8):
        pred = self.survey._pred.copy()
        delta_d = std * np.log(abs(self.survey.dobs))
        J = self.getJ(self.model)
        J_sum = abs(Utils.sdiag(1/delta_d/pred) * J).sum(axis=0)
        S = np.cumsum(J_sum[::-1])[::-1]
        active = S-thres_hold > 0.
        doi = abs(self.survey.depth[active]).max()
        return doi, active

    def get_threshold(self, uncert):
        _, active = self.depth_of_investigation(uncert)
        JtJdiag = self.get_JtJdiag(uncert)
        delta = JtJdiag[active].min()
        return delta

    def get_JtJdiag(self, uncert):
        J = self.getJ(self.model)
        JtJdiag = (np.power((Utils.sdiag(1./uncert)*J), 2)).sum(axis=0)
        return JtJdiag
Exemplo n.º 15
0
class BaseIPProblem_2D(BaseDCProblem_2D):

    sigma = Props.PhysicalProperty(
        "Electrical conductivity (S/m)"
    )

    rho = Props.PhysicalProperty(
        "Electrical resistivity (Ohm m)"
    )

    Props.Reciprocal(sigma, rho)

    eta, etaMap, etaDeriv = Props.Invertible(
        "Electrical Chargeability (V/V)"
    )

    surveyPair = Survey
    fieldsPair = Fields_ky
    _Jmatrix = None
    _f = None
    sign = None

    def fields(self, m):
        if self.verbose:
            print(">> Compute DC fields")

        if self._f is None:
            self._f = self.fieldsPair(self.mesh, self.survey)
            Srcs = self.survey.srcList
            for iky in range(self.nky):
                ky = self.kys[iky]
                A = self.getA(ky)
                self.Ainv[iky] = self.Solver(A, **self.solverOpts)
                RHS = self.getRHS(ky)
                u = self.Ainv[iky] * RHS
                self._f[Srcs, self._solutionType, iky] = u
        return self._f

    def Jvec(self, m, v, f=None):
        self.model = m
        J = self.getJ(m, f=f)
        Jv = J.dot(v)
        return self.sign * Jv

    def Jtvec(self, m, v, f=None):
        self.model = m
        J = self.getJ(m, f=f)
        Jtv = J.T.dot(v)
        return self.sign * Jtv

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        return toDelete

    @property
    def MeSigmaDerivMat(self):
        """
        Derivative of MeSigma with respect to the model
        """
        if getattr(self, '_MeSigmaDerivMat', None) is None:
            dsigma_dlogsigma = Utils.sdiag(self.sigma)*self.etaDeriv
            self._MeSigmaDerivMat = self.mesh.getEdgeInnerProductDeriv(
                np.ones(self.mesh.nC)
            )(np.ones(self.mesh.nE)) * dsigma_dlogsigma
        return self._MeSigmaDerivMat

    # TODO: This should take a vector
    def MeSigmaDeriv(self, u, v, adjoint=False):
        """
        Derivative of MeSigma with respect to the model times a vector (u)
        """
        if self.storeInnerProduct:
            if adjoint:
                return self.MeSigmaDerivMat.T * (Utils.sdiag(u)*v)
            else:
                return Utils.sdiag(u)*(self.MeSigmaDerivMat * v)
        else:
            dsigma_dlogsigma = Utils.sdiag(self.sigma)*self.etaDeriv
            if adjoint:
                return (
                    dsigma_dlogsigma.T * (
                        self.mesh.getEdgeInnerProductDeriv(self.sigma)(u).T * v
                    )
                )
            else:
                return (
                    self.mesh.getEdgeInnerProductDeriv(self.sigma)(u) *
                    (dsigma_dlogsigma * v)
                )

    @property
    def MccRhoiDerivMat(self):
        """
            Derivative of MccRho with respect to the model
        """
        if getattr(self, '_MccRhoiDerivMat', None) is None:
            rho = self.rho
            vol = self.mesh.vol
            drho_dlogrho = Utils.sdiag(rho)*self.etaDeriv
            self._MccRhoiDerivMat = (
                Utils.sdiag(vol*(-1./rho**2))*drho_dlogrho
            )
        return self._MccRhoiDerivMat

    def MccRhoiDeriv(self, u, v, adjoint=False):
        """
            Derivative of :code:`MccRhoi` with respect to the model.
        """
        if len(self.rho.shape) > 1:
            if self.rho.shape[1] > self.mesh.dim:
                raise NotImplementedError(
                    "Full anisotropy is not implemented for MccRhoiDeriv."
                )
        if self.storeInnerProduct:
            if adjoint:
                return self.MccRhoiDerivMat.T * (Utils.sdiag(u) * v)
            else:
                return Utils.sdiag(u) * (self.MccRhoiDerivMat * v)
        else:
            vol = self.mesh.vol
            rho = self.rho
            drho_dlogrho = Utils.sdiag(rho)*self.etaDeriv
            if adjoint:
                return drho_dlogrho.T * (Utils.sdia(u*vol*(-1./rho**2)) * v)
            else:
                return Utils.sdiag(u*vol*(-1./rho**2))*(drho_dlogrho * v)

    @property
    def MnSigmaDerivMat(self):
        """
            Derivative of MnSigma with respect to the model
        """
        if getattr(self, '_MnSigmaDerivMat', None) is None:
            sigma = self.sigma
            vol = self.mesh.vol
            dsigma_dlogsigma = Utils.sdiag(sigma)*self.etaDeriv
            self._MnSigmaDerivMat = (
                self.mesh.aveN2CC.T * Utils.sdiag(vol) * dsigma_dlogsigma
                )
        return self._MnSigmaDerivMat

    def MnSigmaDeriv(self, u, v, adjoint=False):
        """
            Derivative of MnSigma with respect to the model times a vector (u)
        """
        if self.storeInnerProduct:
            if adjoint:
                return self.MnSigmaDerivMat.T * (Utils.sdiag(u)*v)
            else:
                return Utils.sdiag(u)*(self.MnSigmaDerivMat * v)
        else:
            sigma = self.sigma
            vol = self.mesh.vol
            dsigma_dlogsigma = Utils.sdiag(sigma)*self.etaDeriv
            if adjoint:
                return dsigma_dlogsigma.T * (vol * (self.mesh.aveN2CC * (u*v)))
            else:
                dsig_dm_v = dsigma_dlogsigma * v
                return (
                    u * (self.mesh.aveN2CC.T * (vol * dsig_dm_v))
                )
Exemplo n.º 16
0
class EM1D(Problem.BaseProblem):
    """
    Pseudo analytic solutions for frequency and time domain EM problems
    assumingLayered earth (1D).
    """
    surveyPair = BaseEM1DSurvey
    mapPair = Maps.IdentityMap
    WT1 = None
    WT0 = None
    YBASE = None
    chi = None
    jacSwitch = True
    filter_type = 'key_101'
    verbose = False
    fix_Jmatrix = False
    _Jmatrix_sigma = None
    _Jmatrix_height = None
    _pred = None

    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity at infinite frequency(S/m)")

    chi = Props.PhysicalProperty("Magnetic susceptibility", default=0.)

    eta, etaMap, etaDeriv = Props.Invertible(
        "Electrical chargeability (V/V), 0 <= eta < 1", default=0.)

    tau, tauMap, tauDeriv = Props.Invertible("Time constant (s)", default=1.)

    c, cMap, cDeriv = Props.Invertible("Frequency Dependency, 0 < c < 1",
                                       default=0.5)

    h, hMap, hDeriv = Props.Invertible("Receiver Height (m), h > 0", )

    def __init__(self, mesh, **kwargs):
        Problem.BaseProblem.__init__(self, mesh, **kwargs)

        if self.filter_type == 'key_201':
            if self.verbose:
                print(">> Use Key 201 filter for Hankel Tranform")
            fht = filters.key_201_2009()
            self.WT0 = np.empty(201, complex)
            self.WT1 = np.empty(201, complex)
            self.YBASE = np.empty(201, complex)
            self.WT0 = fht.j0
            self.WT1 = fht.j1
            self.YBASE = fht.base
        elif self.filter_type == 'key_101':
            if self.verbose:
                print(">> Use Key 101 filter for Hankel Tranform")
            fht = filters.key_101_2009()
            self.WT0 = np.empty(101, complex)
            self.WT1 = np.empty(101, complex)
            self.YBASE = np.empty(101, complex)
            self.WT0 = fht.j0
            self.WT1 = fht.j1
            self.YBASE = fht.base
        elif self.filter_type == 'anderson_801':
            if self.verbose:
                print(">> Use Anderson 801 filter for Hankel Tranform")
            fht = filters.anderson_801_1982()
            self.WT0 = np.empty(801, complex)
            self.WT1 = np.empty(801, complex)
            self.YBASE = np.empty(801, complex)
            self.WT0 = fht.j0
            self.WT1 = fht.j1
            self.YBASE = fht.base
        else:
            raise NotImplementedError()

    def hz_kernel_vertical_magnetic_dipole(self,
                                           lamda,
                                           f,
                                           n_layer,
                                           sig,
                                           chi,
                                           depth,
                                           h,
                                           z,
                                           flag,
                                           output_type='response'):
        """
            Kernel for vertical magnetic component (Hz) due to
            vertical magnetic diopole (VMD) source in (kx,ky) domain

        """
        u0 = lamda
        rTE = np.zeros(lamda.size, dtype=complex)
        coefficient_wavenumber = 1 / (4 * np.pi) * lamda**3 / u0

        if output_type == 'sensitivity_sigma':
            drTE = np.zeros((n_layer, lamda.size), dtype=complex)
            drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth,
                             self.survey.half_switch)
            kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
        else:
            rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth,
                            self.survey.half_switch)
            kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
            if output_type == 'sensitivity_height':
                kernel *= -2 * u0

        return kernel

        # Note
        # Here only computes secondary field.
        # I am not sure why it does not work if we add primary term.
        # This term can be analytically evaluated, where h = 0.
        #     kernel = (
        #         1./(4*np.pi) *
        #         (np.exp(u0*(z-h))+rTE * np.exp(-u0*(z+h)))*lamda**3/u0
        #     )

    def hz_kernel_circular_loop(self,
                                lamda,
                                f,
                                n_layer,
                                sig,
                                chi,
                                depth,
                                h,
                                z,
                                I,
                                a,
                                flag,
                                output_type='response'):
        """

        Kernel for vertical magnetic component (Hz) at the center
        due to circular loop source in (kx,ky) domain

        .. math::

            H_z = \\frac{Ia}{2} \int_0^{\infty} [e^{-u_0|z+h|} + \\r_{TE}e^{u_0|z-h|}] \\frac{\lambda^2}{u_0} J_1(\lambda a)] d \lambda

        """

        w = 2 * np.pi * f
        rTE = np.empty(lamda.size, dtype=complex)
        u0 = lamda
        coefficient_wavenumber = I * a * 0.5 * lamda**2 / u0

        if output_type == 'sensitivity_sigma':
            drTE = np.empty((n_layer, lamda.size), dtype=complex)
            drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth,
                             self.survey.half_switch)
            kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
        else:
            rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth,
                            self.survey.half_switch)

            if flag == 'secondary':
                kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
            else:
                kernel = rTE * (np.exp(-u0 * (z + h)) +
                                np.exp(u0 * (z - h))) * coefficient_wavenumber

            if output_type == 'sensitivity_height':
                kernel *= -2 * u0

        return kernel

    def hz_kernel_horizontal_electric_dipole(self,
                                             lamda,
                                             f,
                                             n_layer,
                                             sig,
                                             chi,
                                             depth,
                                             h,
                                             z,
                                             flag,
                                             output_type='response'):
        """
            Kernel for vertical magnetic field (Hz) due to
            horizontal electric diopole (HED) source in (kx,ky) domain

        """
        u0 = lamda
        rTE = np.zeros(lamda.size, dtype=complex)
        coefficient_wavenumber = 1 / (4 * np.pi) * lamda**2 / u0

        if output_type == 'sensitivity_sigma':
            drTE = np.zeros((n_layer, lamda.size), dtype=complex)
            drTE = rTEfunjac(n_layer, f, lamda, sig, chi, depth,
                             self.survey.half_switch)
            kernel = drTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
        else:
            rTE = rTEfunfwd(n_layer, f, lamda, sig, chi, depth,
                            self.survey.half_switch)
            kernel = rTE * np.exp(-u0 * (z + h)) * coefficient_wavenumber
            if output_type == 'sensitivity_height':
                kernel *= -2 * u0

        return kernel

    def sigma_cole(self, f):
        """
        Computes Pelton's Cole-Cole conductivity model
        in frequency domain.

        Parameter
        ---------

        f: ndarray
            frequency (Hz)

        Return
        ------

        sigma_complex: ndarray
            Cole-Cole conductivity values at given frequencies.

        """
        w = 2 * np.pi * f
        sigma_complex = (self.sigma - self.sigma * self.eta /
                         (1 + (1 - self.eta) * (1j * w * self.tau)**self.c))
        return sigma_complex

    def forward(self, m, output_type='response'):
        """
            Return Bz or dBzdt
        """

        f = self.survey.frequency
        n_frequency = self.survey.n_frequency
        flag = self.survey.field_type
        r = self.survey.offset

        self.model = m

        n_layer = self.survey.n_layer
        depth = self.survey.depth
        nfilt = self.YBASE.size

        # h is an inversion parameter
        if self.hMap is not None:
            h = self.h
        else:
            h = self.survey.h
        z = h + self.survey.dz
        HzFHT = np.empty(n_frequency, dtype=complex)
        chi = self.chi

        if np.isscalar(self.chi):
            chi = np.ones_like(self.sigma) * self.chi

        if output_type == 'response':
            if self.verbose:
                print('>> Compute response')

            # for simulation
            hz = np.empty(nfilt, complex)
            if self.survey.src_type == 'VMD':
                r = self.survey.offset
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    hz = self.hz_kernel_vertical_magnetic_dipole(
                        self.YBASE / r[ifreq],
                        f[ifreq],
                        n_layer,
                        sig,
                        chi,
                        depth,
                        h,
                        z,
                        flag,
                        output_type=output_type)
                    HzFHT[ifreq] = np.dot(hz, self.WT0) / r[ifreq]

            elif self.survey.src_type == 'CircularLoop':
                I = self.survey.I
                a = self.survey.a
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    hz = self.hz_kernel_circular_loop(self.YBASE / a,
                                                      f[ifreq],
                                                      n_layer,
                                                      sig,
                                                      chi,
                                                      depth,
                                                      h,
                                                      z,
                                                      I,
                                                      a,
                                                      flag,
                                                      output_type=output_type)
                    HzFHT[ifreq] = np.dot(hz, self.WT1) / a

            elif self.survey.src_type == "piecewise_line":
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    # Need to compute y
                    hz = self.hz_kernel_horizontal_electric_dipole(
                        self.YBASE / r[ifreq] * y,
                        f[ifreq],
                        n_layer,
                        sig,
                        chi,
                        depth,
                        h,
                        z,
                        I,
                        a,
                        flag,
                        output_type=output_type)
                    HzFHT[ifreq] = np.dot(hz, self.WT1) / a
            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

            return HzFHT

        elif output_type == 'sensitivity_sigma':

            dHzFHT_dsig = np.empty((n_frequency, n_layer), dtype=complex)
            dhz = np.empty((nfilt, n_layer), complex)
            if self.survey.src_type == 'VMD':
                r = self.survey.offset
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    dhz = self.hz_kernel_vertical_magnetic_dipole(
                        self.YBASE / r[ifreq],
                        f[ifreq],
                        n_layer,
                        sig,
                        chi,
                        depth,
                        h,
                        z,
                        flag,
                        output_type=output_type)
                    dHzFHT_dsig[ifreq, :] = np.dot(dhz, self.WT0) / r[ifreq]
            elif self.survey.src_type == 'CircularLoop':
                I = self.survey.I
                a = self.survey.a
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    dhz = self.hz_kernel_circular_loop(self.YBASE / a,
                                                       f[ifreq],
                                                       n_layer,
                                                       sig,
                                                       chi,
                                                       depth,
                                                       h,
                                                       z,
                                                       I,
                                                       a,
                                                       flag,
                                                       output_type=output_type)
                    dHzFHT_dsig[ifreq, :] = np.dot(dhz, self.WT1) / a
            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

            return dHzFHT_dsig

        elif output_type == 'sensitivity_height':
            dHzFHT_dh = np.empty((n_frequency, 1), dtype=complex)
            dhz = np.empty(nfilt, complex)
            if self.survey.src_type == 'VMD':
                r = self.survey.offset
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    dhz = self.hz_kernel_vertical_magnetic_dipole(
                        self.YBASE / r[ifreq],
                        f[ifreq],
                        n_layer,
                        sig,
                        chi,
                        depth,
                        h,
                        z,
                        flag,
                        output_type=output_type)
                    dHzFHT_dh[ifreq] = np.dot(dhz, self.WT0) / r[ifreq]

            elif self.survey.src_type == 'CircularLoop':
                I = self.survey.I
                a = self.survey.a
                for ifreq in range(n_frequency):
                    sig = self.sigma_cole(f[ifreq])
                    dhz = self.hz_kernel_circular_loop(self.YBASE / a,
                                                       f[ifreq],
                                                       n_layer,
                                                       sig,
                                                       chi,
                                                       depth,
                                                       h,
                                                       z,
                                                       I,
                                                       a,
                                                       flag,
                                                       output_type=output_type)
                    dHzFHT_dh[ifreq] = np.dot(dhz, self.WT1) / a
            else:
                raise Exception("Src options are only VMD or CircularLoop!!")

            return dHzFHT_dh

    # @profile
    def fields(self, m):
        f = self.forward(m, output_type='response')
        self.survey._pred = Utils.mkvc(self.survey.projectFields(f))
        return f

    def getJ_height(self, m, f=None):
        """

        """
        if self.hMap is None:
            return Utils.Zero()

        if self._Jmatrix_height is not None:
            return self._Jmatrix_height
        else:

            if self.verbose:
                print(">> Compute J height ")

            dudz = self.forward(m, output_type="sensitivity_height")

            self._Jmatrix_height = (self.survey.projectFields(dudz)).reshape(
                [-1, 1])

            return self._Jmatrix_height

    # @profile
    def getJ_sigma(self, m, f=None):

        if self.sigmaMap is None:
            return Utils.Zero()

        if self._Jmatrix_sigma is not None:
            return self._Jmatrix_sigma
        else:

            if self.verbose:
                print(">> Compute J sigma")

            dudsig = self.forward(m, output_type="sensitivity_sigma")

            self._Jmatrix_sigma = self.survey.projectFields(dudsig)
            if self._Jmatrix_sigma.ndim == 1:
                self._Jmatrix_sigma = self._Jmatrix_sigma.reshape([-1, 1])
            return self._Jmatrix_sigma

    def getJ(self, m, f=None):
        return (self.getJ_sigma(m, f=f) * self.sigmaDeriv +
                self.getJ_height(m, f=f) * self.hDeriv)

    def Jvec(self, m, v, f=None):
        """
            Computing Jacobian^T multiplied by vector.
        """

        J_sigma = self.getJ_sigma(m, f=f)
        J_height = self.getJ_height(m, f=f)

        if self.hMap is None:
            Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v))
        else:
            Jv = np.dot(J_sigma, self.sigmaMap.deriv(m, v))
            Jv += np.dot(J_height, self.hMap.deriv(m, v))
        return Jv

    def Jtvec(self, m, v, f=None):
        """
            Computing Jacobian^T multiplied by vector.
        """

        J_sigma = self.getJ_sigma(m, f=f)
        J_height = self.getJ_height(m, f=f)
        if self.hMap is None:
            Jtv = self.sigmaMap.deriv(m, np.dot(J_sigma.T, v))
        else:
            Jtv = (self.sigmaDeriv.T * np.dot(J_sigma.T, v))
            Jtv += self.hDeriv.T * np.dot(J_height.T, v)
        return Jtv

    @property
    def deleteTheseOnModelUpdate(self):
        toDelete = []
        if self.fix_Jmatrix is False:
            if self._Jmatrix_sigma is not None:
                toDelete += ['_Jmatrix_sigma']
            if self._Jmatrix_height is not None:
                toDelete += ['_Jmatrix_height']
        return toDelete

    def depth_of_investigation(self, uncert, thres_hold=0.8):
        thres_hold = 0.8
        J = self.getJ(self.model)
        S = np.cumsum(abs(np.dot(J.T, 1. / uncert))[::-1])[::-1]
        active = S - 0.8 > 0.
        doi = abs(self.survey.depth[active]).max()
        return doi, active

    def get_threshold(self, uncert):
        _, active = self.depth_of_investigation(uncert)
        JtJdiag = self.get_JtJdiag(uncert)
        delta = JtJdiag[active].min()
        return delta

    def get_JtJdiag(self, uncert):
        J = self.getJ(self.model)
        JtJdiag = ((Utils.sdiag(1. / uncert) * J)**2).sum(axis=0)
        return JtJdiag
Exemplo n.º 17
0
class MT1DProblem(Problem.BaseProblem):
    """
    1D Magnetotelluric problem under quasi-static approximation

    """

    sigma, sigmaMap, sigmaDeriv = Props.Invertible(
        "Electrical conductivity (S/m)")

    rho, rhoMap, rhoDeriv = Props.Invertible("Electrical resistivity (Ohm-m)")

    Props.Reciprocal(sigma, rho)

    mu = Props.PhysicalProperty("Magnetic Permeability (H/m)", default=mu_0)

    surveyPair = Survey.BaseSurvey  #: The survey to pair with.
    dataPair = Survey.Data  #: The data to pair with.

    mapPair = Maps.IdentityMap  #: Type of mapping to pair with

    Solver = SimpegSolver  #: Type of solver to pair with
    solverOpts = {}  #: Solver options

    verbose = False
    f = None

    def __init__(self, mesh, **kwargs):
        Problem.BaseProblem.__init__(self, mesh, **kwargs)
        # Setup boundary conditions
        mesh.setCellGradBC([['dirichlet', 'dirichlet']])

    @property
    def deleteTheseOnModelUpdate(self):
        if self.verbose:
            print("Delete Matrices")
        toDelete = []
        if self.sigmaMap is not None or self.rhoMap is not None:
            toDelete += ['_MccSigma', '_Ainv', '_ATinv']
        return toDelete

    @property
    def Exbc(self):
        """
            Boundary value for Ex
        """
        if getattr(self, '_Exbc', None) is None:
            self._Exbc = np.r_[0., 1.]
        return self._Exbc

    ####################################################
    # Mass Matrices
    ####################################################

    @property
    def MccSigma(self):
        """
        Diagonal matrix for \\(\\sigma\\).
        """
        if getattr(self, '_MccSigma', None) is None:
            self._MccSigma = Utils.sdiag(self.sigma)
        return self._MccSigma

    def MccSigmaDeriv(self, u):
        """
        Derivative of MccSigma with respect to the model
        """
        if self.sigmaMap is None:
            return Utils.Zero()

        return (Utils.sdiag(u) * self.sigmaDeriv)

    @property
    def MccEpsilon(self):
        """
        Diagonal matrix for \\(\\epsilon\\).
        """
        if getattr(self, '_MccEpsilon', None) is None:
            self._MccEpsilon = Utils.sdiag(self.epsilon)
        return self._MccEpsilon

    @property
    def MfMu(self):
        """
        Edge inner product matrix for \\(\\mu\\).
        """
        if getattr(self, '_MMfMu', None) is None:
            self._MMfMu = Utils.sdiag(self.mesh.aveCC2F * self.mu *
                                      np.ones(self.mesh.nC))
        return self._MMfMu

    ####################################################
    # Physics?
    ####################################################

    def getA(self, freq):
        """
            .. math::

                \mathbf{A} =
                \begin{bmatrix}
                    \mathbf{Grad} & \imath \omega \mathbf{M}^{f2cc}_{\mu} \\[0.3em]
                   \mathbf{M}^{cc}_{\hat{\sigma}} & \mathbf{Div}           \\[0.3em]
                \end{bmatrix}

        """

        Div = self.mesh.faceDiv
        Grad = self.mesh.cellGrad
        omega = 2 * np.pi * freq
        A = sp.vstack((sp.hstack(
            (Grad, 1j * omega * self.MfMu)), sp.hstack((self.MccSigma, Div))))
        return A

    @property
    def Ainv(self):
        if getattr(self, '_Ainv', None) is None:
            if self.verbose:
                print("Factorize A matrix")
            self._Ainv = []
            for freq in self.survey.frequency:
                self._Ainv.append(self.Solver(self.getA(freq)))
        return self._Ainv

    @property
    def ATinv(self):
        if getattr(self, '_ATinv', None) is None:
            if self.verbose:
                print("Factorize AT matrix")
            self._ATinv = []
            for freq in self.survey.frequency:
                self._ATinv.append(self.Solver(self.getA(freq).T))
        return self._ATinv

    def getADeriv_sigma(self, freq, f, v, adjoint=False):
        Ex = f[:self.mesh.nC]
        dMcc_dsig = self.MccSigmaDeriv(Ex)
        if adjoint:
            return sp.hstack(
                (Utils.spzeros(self.mesh.nC, self.mesh.nN), dMcc_dsig.T)) * v
        else:
            return np.r_[np.zeros(self.mesh.nC + 1), dMcc_dsig * v]

    def getRHS(self, freq):
        """
            .. math::

                \mathbf{rhs} =
                \begin{bmatrix}
                     - \mathbf{B}\mathbf{E}_x^{bc} \\ [0.3em]
                    \boldsymbol{0} \\[0.3em]
                \end{bmatrix}$

        """
        B = self.mesh.cellGradBC
        RHS = np.r_[-B * self.Exbc, np.zeros(self.mesh.nC)]
        return RHS

    def fields(self, m=None):
        if self.verbose:
            print(">> Compute fields")

        if m is not None:
            self.model = m

        f = np.zeros((int(self.mesh.nC * 2 + 1), self.survey.nFreq),
                     dtype="complex")

        for ifreq, freq in enumerate(self.survey.frequency):
            f[:, ifreq] = self.Ainv[ifreq] * self.getRHS(freq)
        return f

    def Jvec(self, m, v, f=None):

        if f is None:
            f = self.fields(m)

        Jv = []

        for src in self.survey.srcList:
            for rx in src.rxList:
                for ifreq, freq in enumerate(self.survey.frequency):
                    dA_dm_f_v = self.getADeriv_sigma(freq, f[:, ifreq], v)
                    df_dm_v = -(self.Ainv[ifreq] * dA_dm_f_v)
                    Jv.append(
                        rx.evalDeriv(f[:, ifreq],
                                     freq,
                                     self.survey.P0,
                                     df_dm_v=df_dm_v))
        return np.hstack(Jv)

    def Jtvec(self, m, v, f=None):

        if f is None:
            f = self.fields(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 src in self.survey.srcList:
            for rx in src.rxList:
                for ifreq, freq in enumerate(self.survey.frequency):
                    if rx.component == "both":
                        v_temp = v[src, rx].reshape(
                            (self.survey.nFreq, 2))[ifreq, :]
                    else:
                        v_temp = v[src, rx][ifreq]

                    dZ_dfT_v = rx.evalDeriv(f[:, ifreq],
                                            freq,
                                            self.survey.P0,
                                            v=v_temp,
                                            adjoint=True)

                    ATinvdZ_dfT = self.ATinv[ifreq] * dZ_dfT_v
                    Jtv += -self.getADeriv_sigma(
                        freq, f[:, ifreq], ATinvdZ_dfT, adjoint=True).real

        return Jtv